Skip to content

Commit b241cf8

Browse files
authored
Merge pull request #14303 from guardian/add-gallery-affiliate-link-disclaimer
Add gallery affiliate link disclaimer
2 parents d7dbad8 + e22d756 commit b241cf8

File tree

8 files changed

+181
-85
lines changed

8 files changed

+181
-85
lines changed

dotcom-rendering/src/components/AffiliateDisclaimer.tsx

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { css } from '@emotion/react';
22
import {
33
palette,
44
space,
5+
textSans12,
56
textSans14,
67
textSans15,
78
} from '@guardian/source/foundations';
@@ -20,6 +21,23 @@ const disclaimerLeftColStyles = css`
2021
padding-bottom: ${space[1]}px;
2122
`;
2223

24+
const galleryDisclaimerStyles = css`
25+
${textSans12};
26+
line-height: 1.5;
27+
color: ${themePalette('--affiliate-disclaimer-text')};
28+
a {
29+
color: ${themePalette('--affiliate-disclaimer-text')};
30+
transition: border-color 0.15s ease-out;
31+
border-bottom: 1px solid ${palette.neutral[46]};
32+
text-decoration: none;
33+
}
34+
a:hover {
35+
border-bottom: 1px solid
36+
${themePalette('--affiliate-disclaimer-text-hover')};
37+
text-decoration: none;
38+
}
39+
`;
40+
2341
const disclaimerInlineStyles = css`
2442
${textSans14};
2543
/**
@@ -64,8 +82,9 @@ const DisclaimerText = () => (
6482
The Guardian’s journalism is independent. We will earn a commission if
6583
you buy something through an affiliate link. 
6684
<a href="https://www.theguardian.com/info/2017/nov/01/reader-information-on-affiliate-links">
67-
Learn more.
85+
Learn more
6886
</a>
87+
.
6988
</p>
7089
);
7190

@@ -99,4 +118,17 @@ const AffiliateDisclaimerInline = ({ isAmp = false }) =>
99118
</Hide>
100119
);
101120

102-
export { AffiliateDisclaimer, AffiliateDisclaimerInline };
121+
const GalleryAffiliateDisclaimer = () => (
122+
<aside
123+
css={[disclaimerLeftColStyles, galleryDisclaimerStyles]}
124+
data-testid="affiliate-disclaimer"
125+
>
126+
<DisclaimerText />
127+
</aside>
128+
);
129+
130+
export {
131+
AffiliateDisclaimer,
132+
AffiliateDisclaimerInline,
133+
GalleryAffiliateDisclaimer,
134+
};

dotcom-rendering/src/components/ArticleMeta.web.tsx

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { css } from '@emotion/react';
22
import { between, from, space, until } from '@guardian/source/foundations';
33
import { StraightLines } from '@guardian/source-development-kitchen/react-components';
4-
import { grid } from '../../src/grid';
54
import type { FEArticle } from '../frontend/feArticle';
65
import { interactiveLegacyClasses } from '../layouts/lib/interactiveLegacyStyling';
76
import {
@@ -184,28 +183,11 @@ export const metaContainer = (format: ArticleFormat) => {
184183
}
185184
`;
186185
case ArticleDesign.LiveBlog:
187-
case ArticleDesign.DeadBlog: {
186+
case ArticleDesign.DeadBlog:
187+
case ArticleDesign.Gallery: {
188188
return '';
189189
}
190-
case ArticleDesign.Gallery:
191-
return css`
192-
${grid.column.centre}
193-
padding-bottom: ${space[3]}px;
194-
${from.tablet} {
195-
position: relative;
196-
&::before {
197-
content: '';
198-
position: absolute;
199-
left: -10px;
200-
top: 0;
201-
bottom: 0;
202-
width: 1px;
203-
background-color: ${themePalette(
204-
'--article-border',
205-
)};
206-
}
207-
}
208-
`;
190+
209191
default:
210192
return defaultMargins;
211193
}

dotcom-rendering/src/components/Caption.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { css } from '@emotion/react';
22
import {
3+
between,
34
from,
45
space,
56
textSans12,
@@ -211,10 +212,24 @@ const captionLink = css`
211212

212213
const galleryStyles = css`
213214
${grid.column.centre}
215+
margin-bottom: 0;
216+
padding-bottom: 6px;
214217
${from.leftCol} {
215218
${grid.column.left}
216219
grid-row-start: 8;
217220
}
221+
${between.tablet.and.leftCol} {
222+
position: relative;
223+
&::before {
224+
content: '';
225+
position: absolute;
226+
left: -10px;
227+
top: 0;
228+
bottom: 0;
229+
width: 1px;
230+
background-color: ${palette('--article-border')};
231+
}
232+
}
218233
`;
219234

220235
const CameraIcon = ({ format }: IconProps) => {

dotcom-rendering/src/components/CaptionText.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { css } from '@emotion/react';
22
import { headlineMedium17, space } from '@guardian/source/foundations';
33
import { type ReactNode } from 'react';
44
import sanitise, { type IOptions } from 'sanitize-html';
5+
import { isSkimlink } from '../lib/affiliateLinksUtils';
56
import { getAttrs, parseHtml } from '../lib/domUtils';
67
import { palette } from '../palette';
78

@@ -38,6 +39,7 @@ const renderTextElement = (node: Node, key: number): ReactNode => {
3839
return text === '' ? null : <em key={key}>{children}</em>;
3940
case 'A': {
4041
const attrs = getAttrs(node);
42+
const href = attrs?.getNamedItem('href')?.value;
4143

4244
return (
4345
<a
@@ -51,7 +53,7 @@ const renderTextElement = (node: Node, key: number): ReactNode => {
5153
${palette('--article-link-border-hover')};
5254
}
5355
`}
54-
href={attrs?.getNamedItem('href')?.value}
56+
href={href}
5557
target={attrs?.getNamedItem('target')?.value}
5658
data-link-name={
5759
attrs?.getNamedItem('data-link-name')?.value
@@ -60,6 +62,11 @@ const renderTextElement = (node: Node, key: number): ReactNode => {
6062
attrs?.getNamedItem('data-component')?.value
6163
}
6264
key={key}
65+
/**
66+
* Affiliate links must have the rel attribute set to "sponsored"
67+
* @see https://developers.google.com/search/docs/crawling-indexing/qualify-outbound-links
68+
*/
69+
rel={isSkimlink(href) ? 'sponsored' : undefined}
6370
>
6471
{children}
6572
</a>

dotcom-rendering/src/components/GalleryCaption.tsx

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ const styles = css`
2121
color: ${palette('--caption-text')};
2222
${textSans12}
2323
padding-bottom: ${space[6]}px;
24+
padding-top: ${space[5]}px;
2425
2526
${between.tablet.and.desktop} {
2627
padding-left: ${space[5]}px;
@@ -89,14 +90,20 @@ export const GalleryCaption = ({
8990
{credit}
9091
</small>
9192
)}
92-
<Island priority="feature" defer={{ until: 'visible' }}>
93-
<ShareButton
94-
format={format}
95-
pageId={pageId}
96-
webTitle={webTitle}
97-
context="ImageCaption"
98-
/>
99-
</Island>
93+
<div
94+
css={css`
95+
padding-top: ${space[2]}px;
96+
`}
97+
>
98+
<Island priority="feature" defer={{ until: 'visible' }}>
99+
<ShareButton
100+
format={format}
101+
pageId={pageId}
102+
webTitle={webTitle}
103+
context="ImageCaption"
104+
/>
105+
</Island>
106+
</div>
100107
</figcaption>
101108
);
102109
};

dotcom-rendering/src/layouts/GalleryLayout.stories.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,11 @@ export default meta;
1616

1717
type Story = StoryObj<typeof meta>;
1818

19-
const addBranding = (gallery: Gallery): Gallery => ({
19+
const addBrandingAndAffiliateDisclaimer = (gallery: Gallery): Gallery => ({
2020
...gallery,
2121
frontendData: {
2222
...gallery.frontendData,
23+
affiliateLinksDisclaimer: 'true',
2324
webPublicationDateDeprecated: '2020-03-28T07:27:19.000Z',
2425
commercialProperties: {
2526
...gallery.frontendData.commercialProperties,
@@ -40,7 +41,7 @@ if (appsArticle.design !== ArticleDesign.Gallery) {
4041
export const Apps = {
4142
args: {
4243
renderingTarget: 'Apps',
43-
gallery: addBranding(appsArticle),
44+
gallery: addBrandingAndAffiliateDisclaimer(appsArticle),
4445
},
4546
parameters: {
4647
formats: [
@@ -69,7 +70,7 @@ export const Web = {
6970
...extractNAV(webArticle.frontendData.nav),
7071
selectedPillar: getCurrentPillar(webArticle.frontendData),
7172
},
72-
gallery: addBranding(webArticle),
73+
gallery: addBrandingAndAffiliateDisclaimer(webArticle),
7374
},
7475
parameters: {
7576
formats: [

dotcom-rendering/src/layouts/GalleryLayout.tsx

Lines changed: 78 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import { css } from '@emotion/react';
2-
import { from, palette as sourcePalette } from '@guardian/source/foundations';
2+
import {
3+
from,
4+
palette as sourcePalette,
5+
space,
6+
} from '@guardian/source/foundations';
7+
import { GalleryAffiliateDisclaimer } from '../components/AffiliateDisclaimer';
38
import { AppsFooter } from '../components/AppsFooter.importable';
49
import { ArticleHeadline } from '../components/ArticleHeadline';
510
import { ArticleMetaApps } from '../components/ArticleMeta.apps';
@@ -51,6 +56,23 @@ const headerStyles = css`
5156
}
5257
`;
5358

59+
const metaAndDisclaimerContainer = css`
60+
${grid.column.centre}
61+
padding-bottom: ${space[6]}px;
62+
${from.tablet} {
63+
position: relative;
64+
&::before {
65+
content: '';
66+
position: absolute;
67+
left: -10px;
68+
top: 0;
69+
bottom: 0;
70+
width: 1px;
71+
background-color: ${palette('--article-border')};
72+
}
73+
}
74+
`;
75+
5476
export const GalleryLayout = (props: WebProps | AppProps) => {
5577
const gallery = props.gallery;
5678
const frontendData = gallery.frontendData;
@@ -122,55 +144,61 @@ export const GalleryLayout = (props: WebProps | AppProps) => {
122144
format={format}
123145
isMainMedia={true}
124146
/>
125-
{isWeb ? (
126-
<ArticleMeta
127-
branding={
128-
frontendData.commercialProperties[
129-
frontendData.editionId
130-
].branding
131-
}
132-
format={format}
133-
pageId={frontendData.pageId}
134-
webTitle={frontendData.webTitle}
135-
byline={frontendData.byline}
136-
tags={frontendData.tags}
137-
primaryDateline={
138-
frontendData.webPublicationDateDisplay
139-
}
140-
secondaryDateline={
141-
frontendData.webPublicationSecondaryDateDisplay
142-
}
143-
isCommentable={frontendData.isCommentable}
144-
discussionApiUrl={
145-
frontendData.config.discussionApiUrl
146-
}
147-
shortUrlId={frontendData.config.shortUrlId}
148-
/>
149-
) : null}
150-
{isApps ? (
151-
<ArticleMetaApps
152-
branding={
153-
frontendData.commercialProperties[
154-
frontendData.editionId
155-
].branding
156-
}
157-
format={format}
158-
pageId={frontendData.pageId}
159-
byline={frontendData.byline}
160-
tags={frontendData.tags}
161-
primaryDateline={
162-
frontendData.webPublicationDateDisplay
163-
}
164-
secondaryDateline={
165-
frontendData.webPublicationSecondaryDateDisplay
166-
}
167-
isCommentable={frontendData.isCommentable}
168-
discussionApiUrl={
169-
frontendData.config.discussionApiUrl
170-
}
171-
shortUrlId={frontendData.config.shortUrlId}
172-
/>
173-
) : null}
147+
<div css={metaAndDisclaimerContainer}>
148+
{isWeb ? (
149+
<ArticleMeta
150+
branding={
151+
frontendData.commercialProperties[
152+
frontendData.editionId
153+
].branding
154+
}
155+
format={format}
156+
pageId={frontendData.pageId}
157+
webTitle={frontendData.webTitle}
158+
byline={frontendData.byline}
159+
tags={frontendData.tags}
160+
primaryDateline={
161+
frontendData.webPublicationDateDisplay
162+
}
163+
secondaryDateline={
164+
frontendData.webPublicationSecondaryDateDisplay
165+
}
166+
isCommentable={frontendData.isCommentable}
167+
discussionApiUrl={
168+
frontendData.config.discussionApiUrl
169+
}
170+
shortUrlId={frontendData.config.shortUrlId}
171+
/>
172+
) : null}
173+
174+
{isApps ? (
175+
<ArticleMetaApps
176+
branding={
177+
frontendData.commercialProperties[
178+
frontendData.editionId
179+
].branding
180+
}
181+
format={format}
182+
pageId={frontendData.pageId}
183+
byline={frontendData.byline}
184+
tags={frontendData.tags}
185+
primaryDateline={
186+
frontendData.webPublicationDateDisplay
187+
}
188+
secondaryDateline={
189+
frontendData.webPublicationSecondaryDateDisplay
190+
}
191+
isCommentable={frontendData.isCommentable}
192+
discussionApiUrl={
193+
frontendData.config.discussionApiUrl
194+
}
195+
shortUrlId={frontendData.config.shortUrlId}
196+
/>
197+
) : null}
198+
{!!frontendData.affiliateLinksDisclaimer && (
199+
<GalleryAffiliateDisclaimer />
200+
)}
201+
</div>
174202
</header>
175203
{gallery.images.map((element, idx) => (
176204
<GalleryImage

0 commit comments

Comments
 (0)