@@ -39,7 +49,7 @@ export const HostedArticleLayout = (props: WebProps | AppProps) => {
Onward content
- Standfirst
+ {standfirst}
Meta
Body
diff --git a/dotcom-rendering/src/lib/articleFormat.ts b/dotcom-rendering/src/lib/articleFormat.ts
index e5737e99adf..d6d958f82ea 100644
--- a/dotcom-rendering/src/lib/articleFormat.ts
+++ b/dotcom-rendering/src/lib/articleFormat.ts
@@ -29,6 +29,9 @@ export enum ArticleDesign {
Timeline,
Profile,
Crossword,
+ HostedArticle,
+ HostedVideo,
+ HostedGallery,
}
export enum ArticleDisplay {
@@ -118,6 +121,12 @@ export const decideDesign = ({ design }: Partial): ArticleDesign => {
return ArticleDesign.Profile;
case 'CrosswordDesign':
return ArticleDesign.Crossword;
+ case 'HostedArticle':
+ return ArticleDesign.HostedArticle;
+ case 'HostedVideo':
+ return ArticleDesign.HostedVideo;
+ case 'HostedGallery':
+ return ArticleDesign.HostedGallery;
default:
return ArticleDesign.Standard;
}
diff --git a/dotcom-rendering/src/server/handler.hostedContent.apps.ts b/dotcom-rendering/src/server/handler.hostedContent.apps.ts
index 5d4b474bb36..c3408776574 100644
--- a/dotcom-rendering/src/server/handler.hostedContent.apps.ts
+++ b/dotcom-rendering/src/server/handler.hostedContent.apps.ts
@@ -1,12 +1,12 @@
import type { RequestHandler } from 'express';
import { validateAsFEHostedContent } from '../model/validate';
-import { enhanceHostedContentType } from '../types/hostedContent';
+import { enhanceHostedContent } from '../types/hostedContent';
import { makePrefetchHeader } from './lib/header';
import { renderHtml } from './render.hostedContent.web';
export const handleAppsHostedContent: RequestHandler = ({ body }, res) => {
const frontendData = validateAsFEHostedContent(body);
- const hostedContent = enhanceHostedContentType(frontendData);
+ const hostedContent = enhanceHostedContent(frontendData);
const { html, prefetchScripts } = renderHtml({
hostedContent,
});
diff --git a/dotcom-rendering/src/server/handler.hostedContent.web.ts b/dotcom-rendering/src/server/handler.hostedContent.web.ts
index 36a36fc02cc..2547adb0501 100644
--- a/dotcom-rendering/src/server/handler.hostedContent.web.ts
+++ b/dotcom-rendering/src/server/handler.hostedContent.web.ts
@@ -1,12 +1,12 @@
import type { RequestHandler } from 'express';
import { validateAsFEHostedContent } from '../model/validate';
-import { enhanceHostedContentType } from '../types/hostedContent';
+import { enhanceHostedContent } from '../types/hostedContent';
import { makePrefetchHeader } from './lib/header';
import { renderHtml } from './render.hostedContent.web';
export const handleHostedContent: RequestHandler = ({ body }, res) => {
const frontendData = validateAsFEHostedContent(body);
- const hostedContent = enhanceHostedContentType(frontendData);
+ const hostedContent = enhanceHostedContent(frontendData);
const { html, prefetchScripts } = renderHtml({
hostedContent,
});
diff --git a/dotcom-rendering/src/server/render.hostedContent.web.tsx b/dotcom-rendering/src/server/render.hostedContent.web.tsx
index 81f76414bad..8e81eeda9f1 100644
--- a/dotcom-rendering/src/server/render.hostedContent.web.tsx
+++ b/dotcom-rendering/src/server/render.hostedContent.web.tsx
@@ -1,9 +1,17 @@
import { isString } from '@guardian/libs';
-import { HostedArticleLayout } from '../layouts/HostedArticleLayout';
-import { HostedGalleryLayout } from '../layouts/HostedGalleryLayout';
-import { getModulesBuild, getPathFromManifest } from '../lib/assets';
+import { ConfigProvider } from '../components/ConfigContext';
+import { HostedContentPage } from '../components/HostedContentPage';
+import { getArticleThemeString } from '../lib/articleFormat';
+import {
+ ASSET_ORIGIN,
+ generateScriptTags,
+ getModulesBuild,
+ getPathFromManifest,
+} from '../lib/assets';
import { renderToStringWithEmotion } from '../lib/emotion';
import { polyfillIO } from '../lib/polyfill.io';
+import { createGuardian } from '../model/guardian';
+import type { Config } from '../types/configContext';
import type { HostedContent } from '../types/hostedContent';
import { htmlPageTemplate } from './htmlPageTemplate';
@@ -12,44 +20,102 @@ type Props = {
};
export const renderHtml = ({ hostedContent }: Props) => {
- const { type, frontendData } = hostedContent;
+ const { frontendData, theme } = hostedContent;
- const title = `Advertiser content hosted by the Guardian: ${frontendData.title} | The Guardian`;
+ const title = `Advertiser content hosted by the Guardian: ${frontendData.webTitle} | The Guardian`;
- const HostedLayout =
- type === 'gallery' ? HostedGalleryLayout : HostedArticleLayout;
const renderingTarget = 'Web';
+ const config: Config = {
+ renderingTarget,
+ darkModeAvailable:
+ frontendData.config.abTests.darkModeWebVariant === 'variant',
+ assetOrigin: ASSET_ORIGIN,
+ editionId: frontendData.editionId,
+ };
const { html, extractedCss } = renderToStringWithEmotion(
- ,
+
+
+ ,
);
- // We don't send A/B tests or switches from frontend yet- do we need to?
const build = getModulesBuild({
- tests: {},
- switches: {},
+ tests: frontendData.config.abTests,
+ switches: frontendData.config.switches,
});
+ /**
+ * The highest priority scripts.
+ * These scripts have a considerable impact on site performance.
+ * Only scripts critical to application execution may go in here.
+ * Please talk to the dotcom platform team before adding more.
+ * Scripts will be executed in the order they appear in this array
+ */
const prefetchScripts = [
polyfillIO,
getPathFromManifest(build, 'frameworks.js'),
getPathFromManifest(build, 'index.js'),
].filter(isString);
- // We currently don't send any of the data required for page config or window.guardian setup from frontend
+ const scriptTags = generateScriptTags(prefetchScripts);
+
+ /**
+ * We escape windowGuardian here to prevent errors when the data
+ * is placed in a script tag on the page
+ */
+ const guardian = createGuardian({
+ editionId: frontendData.editionId,
+ stage: frontendData.config.stage,
+ frontendAssetsFullURL: frontendData.config.frontendAssetsFullURL,
+ revisionNumber: frontendData.config.revisionNumber,
+ sentryPublicApiKey: frontendData.config.sentryPublicApiKey,
+ sentryHost: frontendData.config.sentryHost,
+ keywordIds: frontendData.config.keywordIds,
+ dfpAccountId: frontendData.config.dfpAccountId,
+ adUnit: frontendData.config.adUnit,
+ ajaxUrl: frontendData.config.ajaxUrl,
+ googletagUrl: frontendData.config.googletagUrl,
+ switches: frontendData.config.switches,
+ abTests: frontendData.config.abTests,
+ serverSideABTests: frontendData.config.serverSideABTests,
+ brazeApiKey: frontendData.config.brazeApiKey,
+ isPaidContent: frontendData.pageType.isPaidContent,
+ contentType: frontendData.contentType,
+ shouldHideReaderRevenue: true,
+ googleRecaptchaSiteKey: frontendData.config.googleRecaptchaSiteKey,
+ // Until we understand exactly what config we need to make available client-side,
+ // add everything we haven't explicitly typed as unknown config
+ unknownConfig: frontendData.config,
+ });
+
+ const { linkedData, openGraphData, canonicalUrl } = frontendData;
+ const maybeArticleThemeString = getArticleThemeString(theme);
+
+ /**
+ * @todo Create a separate hosted content page template
+ */
const pageHtml = htmlPageTemplate({
- scriptTags: [],
+ linkedData,
+ scriptTags,
css: extractedCss,
html,
title,
- description: frontendData.standfirst,
- // @ts-expect-error no config data
- guardian: {},
- canonicalUrl: '',
+ description: frontendData.trailText,
+ guardian,
+ openGraphData,
+ section: frontendData.config.section,
+ canonicalUrl,
renderingTarget: 'Web',
- // @ts-expect-error no config data
- config: {},
- weAreHiring: false,
+ weAreHiring: !!frontendData.config.switches.weAreHiring,
+ config,
+ dataAttributes: {
+ ...(maybeArticleThemeString && {
+ 'article-theme': maybeArticleThemeString,
+ }),
+ },
});
return { html: pageHtml, prefetchScripts };
diff --git a/dotcom-rendering/src/types/hostedContent.ts b/dotcom-rendering/src/types/hostedContent.ts
index 8f9704d26c5..4bb13ad19a4 100644
--- a/dotcom-rendering/src/types/hostedContent.ts
+++ b/dotcom-rendering/src/types/hostedContent.ts
@@ -1,25 +1,69 @@
import type { FEHostedContent } from '../frontend/feHostedContent';
+import {
+ ArticleDesign,
+ ArticleDisplay,
+ ArticleSpecial,
+} from '../lib/articleFormat';
+import { enhanceMainMedia } from '../model/enhanceBlocks';
+import { enhanceCommercialProperties } from '../model/enhanceCommercialProperties';
+import { enhanceStandfirst } from '../model/enhanceStandfirst';
+import type { Article } from './article';
-type HostedContentType = 'article' | 'video' | 'gallery';
+export type HostedContent = Article;
-export type HostedContent = {
- frontendData: FEHostedContent;
- type: HostedContentType;
-};
+export const enhanceHostedContent = (data: FEHostedContent): HostedContent => {
+ // Temporarily hard coded
+ const format = {
+ display: ArticleDisplay.Standard,
+ design: ArticleDesign.HostedArticle,
+ theme: ArticleSpecial.Labs,
+ };
+
+ const serverTime = Date.now();
-export const enhanceHostedContentType = (
- data: FEHostedContent,
-): HostedContent => {
- let type: HostedContentType = 'article';
+ /** @todo implement blocks */
+ // const enhancedBlocks = enhanceBlocks(data.blocks, format, {
+ // renderingTarget,
+ // promotedNewsletter: data.promotedNewsletter,
+ // imagesForLightbox: [],
+ // hasAffiliateLinksDisclaimer: !!data.affiliateLinksDisclaimer,
+ // audioArticleImage: data.audioArticleImage,
+ // tags: data.tags,
+ // shouldHideAds: data.shouldHideAds,
+ // pageId: data.pageId,
+ // });
- if (data.video) {
- type = 'video';
- } else if (data.images.length) {
- type = 'gallery';
- }
+ const mainMediaElements = enhanceMainMedia(
+ format,
+ [], //imagesForLightbox
+ true,
+ data.main,
+ )(data.mainMediaElements);
+ /** @ts-expect-error -- @todo fix this! */
return {
- frontendData: data,
- type,
+ design: format.design,
+ display: format.display,
+ theme: format.theme,
+ serverTime,
+ storyPackage: undefined,
+ frontendData: {
+ ...data,
+ beaconURL: '',
+ mainMediaElements,
+ blocks: [],
+ standfirst: enhanceStandfirst(data.standfirst),
+ commercialProperties: enhanceCommercialProperties(
+ data.commercialProperties,
+ ),
+ /**
+ * This function needs to run at a higher level to most other enhancers
+ * because it needs both mainMediaElements and blocks in scope
+ * @todo implement for Hosted Content pages
+ */
+ imagesForLightbox: [],
+ /** @todo implement for Hosted Content pages */
+ imagesForAppsLightbox: [],
+ },
};
};