@reuters-graphics/graphics-components 3.5.0 → 3.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/BioBox/Bio.svelte +134 -0
- package/dist/components/BioBox/Bio.svelte.d.ts +9 -0
- package/dist/components/BioBox/BioBox.svelte +50 -0
- package/dist/components/BioBox/BioBox.svelte.d.ts +13 -0
- package/dist/components/BioBox/SocialLinks.svelte +168 -0
- package/dist/components/BioBox/SocialLinks.svelte.d.ts +11 -0
- package/dist/components/BioBox/types.d.ts +32 -0
- package/dist/components/BioBox/types.js +1 -0
- package/dist/components/InfoBox/InfoBox.svelte +5 -0
- package/dist/components/ReferralBlock/Referral.svelte +143 -0
- package/dist/components/ReferralBlock/Referral.svelte.d.ts +17 -0
- package/dist/components/ReferralBlock/ReferralBlock.svelte +41 -158
- package/dist/components/ReferralBlock/ReferralBlock.svelte.d.ts +11 -3
- package/dist/components/ReferralBlock/getReferrals.d.ts +26 -0
- package/dist/components/ReferralBlock/getReferrals.js +58 -0
- package/dist/components/ReferralBlock/types.d.ts +28 -1
- package/dist/index.d.ts +7 -0
- package/dist/index.js +4 -0
- package/package.json +1 -1
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
<!-- @component `Bio` renders a single author's avatar, name, title, social links and biography. Used by `BioBox`, but works on its own. -->
|
|
2
|
+
<script lang="ts">
|
|
3
|
+
import KinesisLogo from '../KinesisLogo/KinesisLogo.svelte';
|
|
4
|
+
import SocialLinks from './SocialLinks.svelte';
|
|
5
|
+
import type { Author } from './types';
|
|
6
|
+
|
|
7
|
+
interface Props extends Author {
|
|
8
|
+
/** Extra class on the root element. */
|
|
9
|
+
class?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
let {
|
|
13
|
+
name,
|
|
14
|
+
title,
|
|
15
|
+
bio,
|
|
16
|
+
imageUrl,
|
|
17
|
+
imageAlt,
|
|
18
|
+
links = [],
|
|
19
|
+
class: cls = '',
|
|
20
|
+
}: Props = $props();
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
<div class="bio {cls}">
|
|
24
|
+
<div class="bio-header">
|
|
25
|
+
<div class="bio-identity">
|
|
26
|
+
{#if imageUrl}
|
|
27
|
+
<img class="bio-avatar" src={imageUrl} alt={imageAlt ?? name} />
|
|
28
|
+
{:else}
|
|
29
|
+
<div class="bio-avatar bio-avatar-fallback" aria-hidden="true">
|
|
30
|
+
<KinesisLogo width="60%" />
|
|
31
|
+
</div>
|
|
32
|
+
{/if}
|
|
33
|
+
<div class="bio-names">
|
|
34
|
+
<p class="bio-name">{name}</p>
|
|
35
|
+
{#if title}<p class="bio-title">{title}</p>{/if}
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
{#if links.length}
|
|
39
|
+
<div class="bio-links bio-links-inline">
|
|
40
|
+
<SocialLinks {links} {name} />
|
|
41
|
+
</div>
|
|
42
|
+
{/if}
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
{#if bio}
|
|
46
|
+
<p class="bio-text">{bio}</p>
|
|
47
|
+
{/if}
|
|
48
|
+
|
|
49
|
+
{#if links.length}
|
|
50
|
+
<div class="bio-links bio-links-stacked">
|
|
51
|
+
<SocialLinks {links} {name} />
|
|
52
|
+
</div>
|
|
53
|
+
{/if}
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
<style>/* Generated from
|
|
57
|
+
https://utopia.fyi/space/calculator/?c=320,18,1.125,1280,21,1.25,7,3,&s=0.75|0.5|0.25,1.5|2|3|4|6,s-l&g=s,l,xl,12
|
|
58
|
+
*/
|
|
59
|
+
/* Generated from
|
|
60
|
+
https://utopia.fyi/space/calculator/?c=320,18,1.125,1280,21,1.25,7,3,&s=0.75|0.5|0.25,1.5|2|3|4|6,s-l&g=s,l,xl,12
|
|
61
|
+
*/
|
|
62
|
+
/* Scales by 1.125 */
|
|
63
|
+
.bio {
|
|
64
|
+
padding-block: 1rem;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.bio-header {
|
|
68
|
+
display: flex;
|
|
69
|
+
align-items: flex-start;
|
|
70
|
+
justify-content: space-between;
|
|
71
|
+
gap: 1rem;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.bio-identity {
|
|
75
|
+
display: flex;
|
|
76
|
+
align-items: center;
|
|
77
|
+
gap: 0.75rem;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.bio-avatar {
|
|
81
|
+
display: block;
|
|
82
|
+
width: 3rem;
|
|
83
|
+
height: 3rem;
|
|
84
|
+
margin: 0;
|
|
85
|
+
border-radius: 50%;
|
|
86
|
+
object-fit: cover;
|
|
87
|
+
flex-shrink: 0;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.bio-avatar-fallback {
|
|
91
|
+
display: flex;
|
|
92
|
+
align-items: center;
|
|
93
|
+
justify-content: center;
|
|
94
|
+
background: var(--theme-colour-background, #fff);
|
|
95
|
+
border: 1px solid var(--theme-colour-brand-rules, #e5e5e5);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.bio-name {
|
|
99
|
+
font-family: "Knowledge", "Source Sans Pro", Arial, Helvetica, sans-serif;
|
|
100
|
+
font-weight: 700;
|
|
101
|
+
font-size: var(--theme-font-size-base);
|
|
102
|
+
margin: 0;
|
|
103
|
+
line-height: 1.2;
|
|
104
|
+
color: var(--theme-colour-text-primary, #121212);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.bio-title {
|
|
108
|
+
font-family: "Knowledge", "Source Sans Pro", Arial, Helvetica, sans-serif;
|
|
109
|
+
font-size: var(--theme-font-size-sm);
|
|
110
|
+
margin: 0.1rem 0 0;
|
|
111
|
+
color: var(--theme-colour-text-secondary, #666);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.bio-text {
|
|
115
|
+
font-family: "Knowledge", "Source Sans Pro", Arial, Helvetica, sans-serif;
|
|
116
|
+
font-size: var(--theme-font-size-sm);
|
|
117
|
+
margin: 0.75rem 0 0;
|
|
118
|
+
line-height: 1.5;
|
|
119
|
+
color: var(--theme-colour-text-primary, #121212);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.bio-links-stacked {
|
|
123
|
+
display: none;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
@media (max-width: 520px) {
|
|
127
|
+
.bio-links-inline {
|
|
128
|
+
display: none;
|
|
129
|
+
}
|
|
130
|
+
.bio-links-stacked {
|
|
131
|
+
display: block;
|
|
132
|
+
margin-top: 0.75rem;
|
|
133
|
+
}
|
|
134
|
+
}</style>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Author } from './types';
|
|
2
|
+
interface Props extends Author {
|
|
3
|
+
/** Extra class on the root element. */
|
|
4
|
+
class?: string;
|
|
5
|
+
}
|
|
6
|
+
/** `Bio` renders a single author's avatar, name, title, social links and biography. Used by `BioBox`, but works on its own. */
|
|
7
|
+
declare const Bio: import("svelte").Component<Props, {}, "">;
|
|
8
|
+
type Bio = ReturnType<typeof Bio>;
|
|
9
|
+
export default Bio;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<!-- @component `BioBox` shows one or more author biographies in a bordered box, echoing the contributor box on Reuters.com stories. [Read the docs.](https://reuters-graphics.github.io/graphics-components/?path=/docs/components-page-furniture-biobox--docs) -->
|
|
2
|
+
<script lang="ts">
|
|
3
|
+
import Block from '../Block/Block.svelte';
|
|
4
|
+
import Bio from './Bio.svelte';
|
|
5
|
+
import type { Author } from './types';
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
/** Authors to display, in order. */
|
|
9
|
+
authors: Author[];
|
|
10
|
+
/** ID on the containing block. */
|
|
11
|
+
id?: string;
|
|
12
|
+
/** Extra classes on the containing block. */
|
|
13
|
+
class?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
let { authors, id = '', class: cls = 'fmy-8' }: Props = $props();
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
{#if authors.length}
|
|
20
|
+
<Block {id} class="biobox {cls}">
|
|
21
|
+
<div class="biobox-inner">
|
|
22
|
+
{#each authors as author, i}
|
|
23
|
+
<Bio {...author} />
|
|
24
|
+
{#if i < authors.length - 1}
|
|
25
|
+
<hr class="biobox-divider" />
|
|
26
|
+
{/if}
|
|
27
|
+
{/each}
|
|
28
|
+
</div>
|
|
29
|
+
</Block>
|
|
30
|
+
{/if}
|
|
31
|
+
|
|
32
|
+
<style>.biobox-inner {
|
|
33
|
+
padding: 0.5rem 1.25rem;
|
|
34
|
+
border: 1px solid var(--theme-colour-brand-rules, #d3d3d3);
|
|
35
|
+
border-radius: 4px;
|
|
36
|
+
background: var(--theme-colour-background, #fff);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.biobox-divider {
|
|
40
|
+
border: 0;
|
|
41
|
+
border-top: 1px solid var(--theme-colour-brand-rules, #d3d3d3);
|
|
42
|
+
opacity: 0.5;
|
|
43
|
+
margin: 0.5rem 0;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
@media (max-width: 767px) {
|
|
47
|
+
.biobox-inner {
|
|
48
|
+
padding-inline: 1rem;
|
|
49
|
+
}
|
|
50
|
+
}</style>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Author } from './types';
|
|
2
|
+
interface Props {
|
|
3
|
+
/** Authors to display, in order. */
|
|
4
|
+
authors: Author[];
|
|
5
|
+
/** ID on the containing block. */
|
|
6
|
+
id?: string;
|
|
7
|
+
/** Extra classes on the containing block. */
|
|
8
|
+
class?: string;
|
|
9
|
+
}
|
|
10
|
+
/** `BioBox` shows one or more author biographies in a bordered box, echoing the contributor box on Reuters.com stories. [Read the docs.](https://reuters-graphics.github.io/graphics-components/?path=/docs/components-page-furniture-biobox--docs) */
|
|
11
|
+
declare const BioBox: import("svelte").Component<Props, {}, "">;
|
|
12
|
+
type BioBox = ReturnType<typeof BioBox>;
|
|
13
|
+
export default BioBox;
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
<!-- @component `SocialLinks` renders a row of accessible icon links (email, X, LinkedIn, etc.) for a single author. Used by `Bio`. -->
|
|
2
|
+
<script lang="ts">
|
|
3
|
+
import type { SocialLink, SocialPlatform } from './types';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
/** Links to render. Unknown platforms fall back to a generic link icon. */
|
|
7
|
+
links: SocialLink[];
|
|
8
|
+
/** Author name, used to build default accessible labels. */
|
|
9
|
+
name?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
let { links, name = '' }: Props = $props();
|
|
13
|
+
|
|
14
|
+
// Normalize an email destination into a usable href.
|
|
15
|
+
function hrefFor(link: SocialLink): string {
|
|
16
|
+
if (link.platform === 'email' && !/^mailto:/i.test(link.url)) {
|
|
17
|
+
return `mailto:${link.url}`;
|
|
18
|
+
}
|
|
19
|
+
return link.url;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Default, screen-reader-friendly label per platform.
|
|
23
|
+
const PLATFORM_NOUN: Record<SocialPlatform, string> = {
|
|
24
|
+
email: 'Email',
|
|
25
|
+
x: 'X profile of',
|
|
26
|
+
twitter: 'X profile of',
|
|
27
|
+
linkedin: 'LinkedIn profile of',
|
|
28
|
+
facebook: 'Facebook profile of',
|
|
29
|
+
instagram: 'Instagram profile of',
|
|
30
|
+
bluesky: 'Bluesky profile of',
|
|
31
|
+
link: 'Profile of',
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
function labelFor(link: SocialLink): string {
|
|
35
|
+
if (link.label) return link.label;
|
|
36
|
+
const noun = PLATFORM_NOUN[link.platform] ?? PLATFORM_NOUN.link;
|
|
37
|
+
return name ? `${noun} ${name}` : noun;
|
|
38
|
+
}
|
|
39
|
+
</script>
|
|
40
|
+
|
|
41
|
+
<div class="social-links">
|
|
42
|
+
{#each links as link}
|
|
43
|
+
<a
|
|
44
|
+
class="social-link"
|
|
45
|
+
href={hrefFor(link)}
|
|
46
|
+
aria-label={labelFor(link)}
|
|
47
|
+
target={link.platform === 'email' ? null : '_blank'}
|
|
48
|
+
rel={link.platform === 'email' ? null : 'noreferrer'}
|
|
49
|
+
>
|
|
50
|
+
{#if link.platform === 'email'}
|
|
51
|
+
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
|
|
52
|
+
<rect
|
|
53
|
+
x="3"
|
|
54
|
+
y="5"
|
|
55
|
+
width="18"
|
|
56
|
+
height="14"
|
|
57
|
+
rx="2"
|
|
58
|
+
fill="none"
|
|
59
|
+
stroke="currentColor"
|
|
60
|
+
stroke-width="1.6"
|
|
61
|
+
/>
|
|
62
|
+
<path
|
|
63
|
+
d="M4 7l8 6 8-6"
|
|
64
|
+
fill="none"
|
|
65
|
+
stroke="currentColor"
|
|
66
|
+
stroke-width="1.6"
|
|
67
|
+
stroke-linecap="round"
|
|
68
|
+
stroke-linejoin="round"
|
|
69
|
+
/>
|
|
70
|
+
</svg>
|
|
71
|
+
{:else if link.platform === 'x' || link.platform === 'twitter'}
|
|
72
|
+
<svg viewBox="0 0 30 30" aria-hidden="true" focusable="false">
|
|
73
|
+
<path
|
|
74
|
+
fill="currentColor"
|
|
75
|
+
d="M17.923 14.387 25.569 5.5h-1.812l-6.64 7.717L11.817 5.5H5.7l8.018 11.67L5.7 26.49h1.812l7.01-8.15 5.6 8.15h6.116l-8.316-12.102Zm-2.482 2.885-.812-1.162-6.464-9.246h2.783l5.216 7.462.813 1.162 6.78 9.7h-2.782l-5.534-7.915Z"
|
|
76
|
+
/>
|
|
77
|
+
</svg>
|
|
78
|
+
{:else if link.platform === 'linkedin'}
|
|
79
|
+
<svg viewBox="0 0 21 21" aria-hidden="true" focusable="false">
|
|
80
|
+
<path
|
|
81
|
+
fill="currentColor"
|
|
82
|
+
d="M19.031 0c1.034 0 1.888.807 1.964 1.822L21 1.97V19.03a1.975 1.975 0 0 1-1.822 1.964L19.03 21H1.97a1.975 1.975 0 0 1-1.964-1.822L0 19.03V1.97C0 .935.807.08 1.822.005L1.97 0H19.03ZM6.3 7.875H3.15v10.063H6.3V7.874Zm7.875-.175c-1.575 0-2.538.788-2.975 1.575v-1.4H8.225v10.063h3.15V12.95c0-1.313.175-2.537 1.838-2.537 1.575 0 1.575 1.487 1.575 2.624v4.9h3.15v-5.425c0-2.712-.613-4.812-3.763-4.812ZM4.637 2.8c-1.05 0-1.837.875-1.837 1.838 0 1.05.875 1.837 1.838 1.837 1.05 0 1.837-.787 1.837-1.837 0-1.05-.875-1.838-1.837-1.838Z"
|
|
83
|
+
/>
|
|
84
|
+
</svg>
|
|
85
|
+
{:else if link.platform === 'facebook'}
|
|
86
|
+
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
|
|
87
|
+
<path
|
|
88
|
+
fill="currentColor"
|
|
89
|
+
d="M13.5 21v-8h2.7l.4-3.1h-3.1V7.9c0-.9.25-1.5 1.55-1.5H17V3.6c-.3 0-1.3-.1-2.45-.1-2.43 0-4.05 1.48-4.05 4.2v2.2H7.8V13h2.7v8h3Z"
|
|
90
|
+
/>
|
|
91
|
+
</svg>
|
|
92
|
+
{:else if link.platform === 'instagram'}
|
|
93
|
+
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
|
|
94
|
+
<rect
|
|
95
|
+
x="3.5"
|
|
96
|
+
y="3.5"
|
|
97
|
+
width="17"
|
|
98
|
+
height="17"
|
|
99
|
+
rx="5"
|
|
100
|
+
fill="none"
|
|
101
|
+
stroke="currentColor"
|
|
102
|
+
stroke-width="1.6"
|
|
103
|
+
/>
|
|
104
|
+
<circle
|
|
105
|
+
cx="12"
|
|
106
|
+
cy="12"
|
|
107
|
+
r="4"
|
|
108
|
+
fill="none"
|
|
109
|
+
stroke="currentColor"
|
|
110
|
+
stroke-width="1.6"
|
|
111
|
+
/>
|
|
112
|
+
<circle cx="17.2" cy="6.8" r="1.1" fill="currentColor" />
|
|
113
|
+
</svg>
|
|
114
|
+
{:else if link.platform === 'bluesky'}
|
|
115
|
+
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
|
|
116
|
+
<path
|
|
117
|
+
fill="currentColor"
|
|
118
|
+
d="M12 10.8C10.9 8.6 7.9 4.6 5.2 3.2 2.6 1.9 1.6 2.4 1 2.9.3 3.5 0 4.7 0 5.4c0 .7.4 5.6.6 6.4.7 2.7 3.5 3.6 6.1 3.3-3.8.6-7.2 2-2.8 6.9 4.9 5 6.7-1 7.6-4.1.9 3.1 2 8.9 7.5 4.1 4.1-4.1 1.1-6.3-2.7-6.9 2.6.3 5.4-.6 6.1-3.3.2-.8.6-5.7.6-6.4 0-.7-.3-1.9-1-2.5-.6-.5-1.6-1-4.2.3C16.1 4.6 13.1 8.6 12 10.8Z"
|
|
119
|
+
/>
|
|
120
|
+
</svg>
|
|
121
|
+
{:else}
|
|
122
|
+
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
|
|
123
|
+
<path
|
|
124
|
+
d="M10 14a3.5 3.5 0 0 0 5 0l3-3a3.5 3.5 0 0 0-5-5l-1.5 1.5M14 10a3.5 3.5 0 0 0-5 0l-3 3a3.5 3.5 0 0 0 5 5l1.5-1.5"
|
|
125
|
+
fill="none"
|
|
126
|
+
stroke="currentColor"
|
|
127
|
+
stroke-width="1.6"
|
|
128
|
+
stroke-linecap="round"
|
|
129
|
+
stroke-linejoin="round"
|
|
130
|
+
/>
|
|
131
|
+
</svg>
|
|
132
|
+
{/if}
|
|
133
|
+
</a>
|
|
134
|
+
{/each}
|
|
135
|
+
</div>
|
|
136
|
+
|
|
137
|
+
<style>.social-links {
|
|
138
|
+
display: inline-flex;
|
|
139
|
+
gap: 0.5rem;
|
|
140
|
+
flex-wrap: wrap;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.social-link {
|
|
144
|
+
display: inline-flex;
|
|
145
|
+
align-items: center;
|
|
146
|
+
justify-content: center;
|
|
147
|
+
width: 2rem;
|
|
148
|
+
height: 2rem;
|
|
149
|
+
border: 1px solid var(--theme-colour-brand-rules, #d3d3d3);
|
|
150
|
+
border-radius: 4px;
|
|
151
|
+
color: var(--theme-colour-text-secondary, #666);
|
|
152
|
+
background: transparent;
|
|
153
|
+
text-decoration: none;
|
|
154
|
+
transition: color 0.15s ease, border-color 0.15s ease, background-color 0.15s ease;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.social-link:hover,
|
|
158
|
+
.social-link:focus-visible {
|
|
159
|
+
color: var(--theme-colour-text-primary, #121212);
|
|
160
|
+
border-color: var(--theme-colour-text-secondary, #666);
|
|
161
|
+
background: rgba(0, 0, 0, 0.03);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.social-link svg {
|
|
165
|
+
width: 1rem;
|
|
166
|
+
height: 1rem;
|
|
167
|
+
display: block;
|
|
168
|
+
}</style>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { SocialLink } from './types';
|
|
2
|
+
interface Props {
|
|
3
|
+
/** Links to render. Unknown platforms fall back to a generic link icon. */
|
|
4
|
+
links: SocialLink[];
|
|
5
|
+
/** Author name, used to build default accessible labels. */
|
|
6
|
+
name?: string;
|
|
7
|
+
}
|
|
8
|
+
/** `SocialLinks` renders a row of accessible icon links (email, X, LinkedIn, etc.) for a single author. Used by `Bio`. */
|
|
9
|
+
declare const SocialLinks: import("svelte").Component<Props, {}, "">;
|
|
10
|
+
type SocialLinks = ReturnType<typeof SocialLinks>;
|
|
11
|
+
export default SocialLinks;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/** Social/contact platforms a bio can link to. */
|
|
2
|
+
export type SocialPlatform = 'email' | 'x' | 'twitter' | 'linkedin' | 'facebook' | 'instagram' | 'bluesky' | 'link';
|
|
3
|
+
/** A single contact or social link shown beside an author's name. */
|
|
4
|
+
export interface SocialLink {
|
|
5
|
+
/** Which platform this link points to. Controls the icon shown. */
|
|
6
|
+
platform: SocialPlatform;
|
|
7
|
+
/**
|
|
8
|
+
* Destination. For `email`, either a bare address or a `mailto:` URL — the
|
|
9
|
+
* component adds the `mailto:` prefix if it's missing.
|
|
10
|
+
*/
|
|
11
|
+
url: string;
|
|
12
|
+
/**
|
|
13
|
+
* Accessible label for the link, read by screen readers. Defaults to a
|
|
14
|
+
* sensible per-platform label that includes the author's name.
|
|
15
|
+
*/
|
|
16
|
+
label?: string;
|
|
17
|
+
}
|
|
18
|
+
/** One author/contributor rendered as a row inside `BioBox`. */
|
|
19
|
+
export interface Author {
|
|
20
|
+
/** Full name, shown in bold. */
|
|
21
|
+
name: string;
|
|
22
|
+
/** Job title, shown under the name, e.g. "Climate reporter". */
|
|
23
|
+
title?: string;
|
|
24
|
+
/** Short biography. Plain text. */
|
|
25
|
+
bio?: string;
|
|
26
|
+
/** Avatar image URL. Falls back to the Reuters Kinesis logo when omitted. */
|
|
27
|
+
imageUrl?: string;
|
|
28
|
+
/** Alt text for the avatar image. Defaults to the author's name. */
|
|
29
|
+
imageAlt?: string;
|
|
30
|
+
/** Contact and social links shown beside the name. */
|
|
31
|
+
links?: SocialLink[];
|
|
32
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -110,6 +110,11 @@ https://utopia.fyi/space/calculator/?c=320,18,1.125,1280,21,1.25,7,3,&s=0.75|0.5
|
|
|
110
110
|
.infobox :global(.article-block) {
|
|
111
111
|
border-color: var(--theme-colour-brand-rules);
|
|
112
112
|
}
|
|
113
|
+
@media (max-width: 767px) {
|
|
114
|
+
.infobox :global(.article-block) {
|
|
115
|
+
padding-inline: 1rem;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
113
118
|
.infobox.light :global(.article-block) {
|
|
114
119
|
background-color: rgb(250, 250, 250);
|
|
115
120
|
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
<!-- @component `Referral` renders a single referral card (a linked headline, kicker, time and thumbnail). It's used by `ReferralBlock` but can be dropped into any layout on its own. -->
|
|
2
|
+
<script lang="ts">
|
|
3
|
+
// Utils
|
|
4
|
+
import { getTime } from '../SiteHeader/NavBar/NavDropdown/StoryCard/time';
|
|
5
|
+
|
|
6
|
+
// Types
|
|
7
|
+
import type { ReferralItem, LinkTarget } from './types';
|
|
8
|
+
|
|
9
|
+
interface Props extends ReferralItem {
|
|
10
|
+
/**
|
|
11
|
+
* Link [target](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#attr-target), e.g., `_blank` or `_parent`.
|
|
12
|
+
*/
|
|
13
|
+
linkTarget?: LinkTarget;
|
|
14
|
+
/** Use the narrower thumbnail sizing, for tight layouts. */
|
|
15
|
+
compact?: boolean;
|
|
16
|
+
/** Render as a single full-width column instead of a half-width card. */
|
|
17
|
+
stacked?: boolean;
|
|
18
|
+
/** Add a class to the card's root element. */
|
|
19
|
+
class?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let {
|
|
23
|
+
url,
|
|
24
|
+
kicker,
|
|
25
|
+
title,
|
|
26
|
+
imageUrl,
|
|
27
|
+
imageAlt = '',
|
|
28
|
+
time,
|
|
29
|
+
linkTarget = '_self',
|
|
30
|
+
compact = false,
|
|
31
|
+
stacked = false,
|
|
32
|
+
class: cls = '',
|
|
33
|
+
}: Props = $props();
|
|
34
|
+
</script>
|
|
35
|
+
|
|
36
|
+
<div class="referral {cls}" class:stacked>
|
|
37
|
+
<a
|
|
38
|
+
href={url}
|
|
39
|
+
target={linkTarget}
|
|
40
|
+
rel={linkTarget === '_blank' ? 'noreferrer' : null}
|
|
41
|
+
>
|
|
42
|
+
<div class="referral-pack flex justify-around my-0 mx-auto">
|
|
43
|
+
<div class="headline" class:xs={compact}>
|
|
44
|
+
<div
|
|
45
|
+
class="kicker m-0 body-caption leading-tighter"
|
|
46
|
+
data-chromatic="ignore"
|
|
47
|
+
>
|
|
48
|
+
{kicker}
|
|
49
|
+
</div>
|
|
50
|
+
<div
|
|
51
|
+
class="title m-0 body-caption leading-tighter"
|
|
52
|
+
data-chromatic="ignore"
|
|
53
|
+
>
|
|
54
|
+
{title}
|
|
55
|
+
</div>
|
|
56
|
+
{#if time}
|
|
57
|
+
<div
|
|
58
|
+
class="publish-time body-caption leading-tighter"
|
|
59
|
+
data-chromatic="ignore"
|
|
60
|
+
>
|
|
61
|
+
{getTime(new Date(time))}
|
|
62
|
+
</div>
|
|
63
|
+
{/if}
|
|
64
|
+
</div>
|
|
65
|
+
<div
|
|
66
|
+
class="image-container block m-0 overflow-hidden relative"
|
|
67
|
+
class:xs={compact}
|
|
68
|
+
>
|
|
69
|
+
<img
|
|
70
|
+
class="block object-cover m-0 w-full"
|
|
71
|
+
data-chromatic="ignore"
|
|
72
|
+
src={imageUrl}
|
|
73
|
+
alt={imageAlt}
|
|
74
|
+
/>
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
</a>
|
|
78
|
+
</div>
|
|
79
|
+
|
|
80
|
+
<style>/* Generated from
|
|
81
|
+
https://utopia.fyi/space/calculator/?c=320,18,1.125,1280,21,1.25,7,3,&s=0.75|0.5|0.25,1.5|2|3|4|6,s-l&g=s,l,xl,12
|
|
82
|
+
*/
|
|
83
|
+
/* Generated from
|
|
84
|
+
https://utopia.fyi/space/calculator/?c=320,18,1.125,1280,21,1.25,7,3,&s=0.75|0.5|0.25,1.5|2|3|4|6,s-l&g=s,l,xl,12
|
|
85
|
+
*/
|
|
86
|
+
/* Scales by 1.125 */
|
|
87
|
+
a {
|
|
88
|
+
text-decoration: none;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.referral {
|
|
92
|
+
display: block;
|
|
93
|
+
width: calc(50% - 30px);
|
|
94
|
+
max-width: 450px;
|
|
95
|
+
margin-block-start: clamp(0.31rem, 0.31rem + 0vw, 0.31rem);
|
|
96
|
+
margin-block-end: clamp(0.31rem, 0.31rem + 0vw, 0.31rem);
|
|
97
|
+
}
|
|
98
|
+
.referral.stacked {
|
|
99
|
+
width: 100%;
|
|
100
|
+
}
|
|
101
|
+
.referral.stacked .headline {
|
|
102
|
+
width: calc(100% - 7rem);
|
|
103
|
+
}
|
|
104
|
+
.referral:hover .title {
|
|
105
|
+
text-decoration: underline;
|
|
106
|
+
}
|
|
107
|
+
.referral:hover img {
|
|
108
|
+
filter: brightness(85%);
|
|
109
|
+
}
|
|
110
|
+
.referral .headline {
|
|
111
|
+
display: inline-block;
|
|
112
|
+
width: calc(100% - 9rem);
|
|
113
|
+
padding-inline-end: clamp(0.56rem, 0.52rem + 0.21vw, 0.69rem);
|
|
114
|
+
}
|
|
115
|
+
.referral .headline .kicker {
|
|
116
|
+
font-size: var(--theme-font-size-xxs);
|
|
117
|
+
font-family: Knowledge, sans-serif;
|
|
118
|
+
}
|
|
119
|
+
.referral .headline .title {
|
|
120
|
+
margin-block-start: clamp(0.31rem, 0.31rem + 0vw, 0.31rem);
|
|
121
|
+
font-weight: 500;
|
|
122
|
+
font-size: var(--theme-font-size-sm);
|
|
123
|
+
color: var(--theme-colour-text-primary);
|
|
124
|
+
font-family: Knowledge, sans-serif;
|
|
125
|
+
}
|
|
126
|
+
.referral .headline .publish-time {
|
|
127
|
+
font-size: var(--theme-font-size-xxs);
|
|
128
|
+
font-family: Knowledge, sans-serif;
|
|
129
|
+
}
|
|
130
|
+
.referral .image-container {
|
|
131
|
+
border-radius: 0.25rem;
|
|
132
|
+
border: 1px solid var(--theme-colour-brand-rules);
|
|
133
|
+
width: 9rem;
|
|
134
|
+
}
|
|
135
|
+
.referral .image-container.xs {
|
|
136
|
+
width: 7rem;
|
|
137
|
+
}
|
|
138
|
+
.referral .image-container img {
|
|
139
|
+
width: 100%;
|
|
140
|
+
height: 100%;
|
|
141
|
+
object-fit: cover;
|
|
142
|
+
transition: filter 0.1s;
|
|
143
|
+
}</style>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ReferralItem, LinkTarget } from './types';
|
|
2
|
+
interface Props extends ReferralItem {
|
|
3
|
+
/**
|
|
4
|
+
* Link [target](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#attr-target), e.g., `_blank` or `_parent`.
|
|
5
|
+
*/
|
|
6
|
+
linkTarget?: LinkTarget;
|
|
7
|
+
/** Use the narrower thumbnail sizing, for tight layouts. */
|
|
8
|
+
compact?: boolean;
|
|
9
|
+
/** Render as a single full-width column instead of a half-width card. */
|
|
10
|
+
stacked?: boolean;
|
|
11
|
+
/** Add a class to the card's root element. */
|
|
12
|
+
class?: string;
|
|
13
|
+
}
|
|
14
|
+
/** `Referral` renders a single referral card (a linked headline, kicker, time and thumbnail). It's used by `ReferralBlock` but can be dropped into any layout on its own. */
|
|
15
|
+
declare const Referral: import("svelte").Component<Props, {}, "">;
|
|
16
|
+
type Referral = ReturnType<typeof Referral>;
|
|
17
|
+
export default Referral;
|
|
@@ -2,16 +2,17 @@
|
|
|
2
2
|
<script lang="ts">
|
|
3
3
|
// Utils
|
|
4
4
|
import { onMount } from 'svelte';
|
|
5
|
-
import {
|
|
6
|
-
import { articleIsNotCurrentPage } from './filterCurrentPage';
|
|
5
|
+
import { fetchReferrals } from './getReferrals';
|
|
7
6
|
|
|
8
7
|
// Components
|
|
9
8
|
import Block from '../Block/Block.svelte';
|
|
9
|
+
import Referral from './Referral.svelte';
|
|
10
10
|
|
|
11
11
|
// Types
|
|
12
|
-
import type {
|
|
13
|
-
import type {
|
|
14
|
-
|
|
12
|
+
import type { ContainerWidth } from '../@types/global';
|
|
13
|
+
import type { ReferralItem, LinkTarget } from './types';
|
|
14
|
+
|
|
15
|
+
type ReferralBlockWidth = Exclude<ContainerWidth, 'narrower' | 'narrow'>;
|
|
15
16
|
|
|
16
17
|
interface Props {
|
|
17
18
|
/**
|
|
@@ -24,6 +25,12 @@
|
|
|
24
25
|
* Collection alias, as defined in Arc Collections editor.
|
|
25
26
|
*/
|
|
26
27
|
collection?: string;
|
|
28
|
+
/**
|
|
29
|
+
* Provide your own referrals instead of fetching recent stories from
|
|
30
|
+
* Reuters.com. When set, the `section`/`collection` fetch is skipped and
|
|
31
|
+
* these stories are rendered as-is.
|
|
32
|
+
*/
|
|
33
|
+
stories?: ReferralItem[];
|
|
27
34
|
/**
|
|
28
35
|
* Number of referrals to show.
|
|
29
36
|
*/
|
|
@@ -31,7 +38,7 @@
|
|
|
31
38
|
/**
|
|
32
39
|
* Link [target](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#attr-target), e.g., `_blank` or `_parent`.
|
|
33
40
|
*/
|
|
34
|
-
linkTarget?:
|
|
41
|
+
linkTarget?: LinkTarget;
|
|
35
42
|
/**
|
|
36
43
|
* Add a heading to the referral block.
|
|
37
44
|
*/
|
|
@@ -39,7 +46,7 @@
|
|
|
39
46
|
/**
|
|
40
47
|
* Width of the component within the text well: 'normal' | 'wide' | 'wider' | 'widest' | 'fluid'
|
|
41
48
|
*/
|
|
42
|
-
width?:
|
|
49
|
+
width?: ReferralBlockWidth;
|
|
43
50
|
/** Add an ID to target with SCSS. */
|
|
44
51
|
id?: string;
|
|
45
52
|
/** Add a class to target with SCSS. */
|
|
@@ -49,6 +56,7 @@
|
|
|
49
56
|
let {
|
|
50
57
|
section = '/world/',
|
|
51
58
|
collection,
|
|
59
|
+
stories = [],
|
|
52
60
|
number = 4,
|
|
53
61
|
linkTarget = '_self',
|
|
54
62
|
heading = '',
|
|
@@ -59,51 +67,27 @@
|
|
|
59
67
|
|
|
60
68
|
let clientWidth = $state(0);
|
|
61
69
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
section_ids: isCollection ? undefined : section,
|
|
81
|
-
collection_alias: isCollection ? collection : undefined,
|
|
82
|
-
size: 20,
|
|
83
|
-
website: 'reuters',
|
|
84
|
-
}),
|
|
85
|
-
})
|
|
86
|
-
);
|
|
87
|
-
|
|
88
|
-
const data = (await response.json()) as Referrals;
|
|
89
|
-
|
|
90
|
-
const articles = data.result.articles
|
|
91
|
-
.filter((a) => a?.headline_category || a?.kicker?.name)
|
|
92
|
-
.filter((a) => a?.thumbnail?.url)
|
|
93
|
-
.filter((a) => !a?.content?.third_party)
|
|
94
|
-
.filter(articleIsNotCurrentPage)
|
|
95
|
-
.slice(0, number);
|
|
96
|
-
|
|
97
|
-
referrals = articles;
|
|
98
|
-
} catch {
|
|
99
|
-
console.warn('Unable to fetch referral links.');
|
|
100
|
-
}
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
onMount(getReferrals);
|
|
70
|
+
let fetchedReferrals: ReferralItem[] = $state([]);
|
|
71
|
+
|
|
72
|
+
// Manually provided stories take precedence. Otherwise, only show fetched
|
|
73
|
+
// stories once the API returns the full requested number, which avoids
|
|
74
|
+
// rendering a partial block.
|
|
75
|
+
const referrals = $derived<ReferralItem[]>(
|
|
76
|
+
stories.length ? stories
|
|
77
|
+
: fetchedReferrals.length === number ? fetchedReferrals
|
|
78
|
+
: []
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
onMount(() => {
|
|
82
|
+
// Skip the network request entirely when stories are supplied by hand.
|
|
83
|
+
if (stories.length) return;
|
|
84
|
+
fetchReferrals({ section, collection, number }).then((items) => {
|
|
85
|
+
fetchedReferrals = items;
|
|
86
|
+
});
|
|
87
|
+
});
|
|
104
88
|
</script>
|
|
105
89
|
|
|
106
|
-
{#if referrals.length
|
|
90
|
+
{#if referrals.length}
|
|
107
91
|
<Block {width} {id} class="referrals-block {cls}">
|
|
108
92
|
<div
|
|
109
93
|
class="block-container"
|
|
@@ -124,65 +108,19 @@
|
|
|
124
108
|
class:xs={clientWidth && clientWidth < 450}
|
|
125
109
|
>
|
|
126
110
|
{#each referrals as referral}
|
|
127
|
-
<
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
<div class="referral-pack flex justify-around my-0 mx-auto">
|
|
134
|
-
<div
|
|
135
|
-
class="headline"
|
|
136
|
-
class:xs={clientWidth && clientWidth < 450}
|
|
137
|
-
>
|
|
138
|
-
<div
|
|
139
|
-
class="kicker m-0 body-caption leading-tighter"
|
|
140
|
-
data-chromatic="ignore"
|
|
141
|
-
>
|
|
142
|
-
{referral.headline_category || referral.kicker.name}
|
|
143
|
-
</div>
|
|
144
|
-
<div
|
|
145
|
-
class="title m-0 body-caption leading-tighter"
|
|
146
|
-
data-chromatic="ignore"
|
|
147
|
-
>
|
|
148
|
-
{referral.title}
|
|
149
|
-
</div>
|
|
150
|
-
<div
|
|
151
|
-
class="publish-time body-caption leading-tighter"
|
|
152
|
-
data-chromatic="ignore"
|
|
153
|
-
>
|
|
154
|
-
{getTime(new Date(referral.display_time))}
|
|
155
|
-
</div>
|
|
156
|
-
</div>
|
|
157
|
-
<div
|
|
158
|
-
class="image-container block m-0 overflow-hidden relative"
|
|
159
|
-
class:xs={clientWidth && clientWidth < 450}
|
|
160
|
-
>
|
|
161
|
-
<img
|
|
162
|
-
class="block object-cover m-0 w-full"
|
|
163
|
-
data-chromatic="ignore"
|
|
164
|
-
src={referral.thumbnail.url}
|
|
165
|
-
alt={referral.thumbnail.alt_text ||
|
|
166
|
-
referral.thumbnail.caption}
|
|
167
|
-
/>
|
|
168
|
-
</div>
|
|
169
|
-
</div>
|
|
170
|
-
</a>
|
|
171
|
-
</div>
|
|
111
|
+
<Referral
|
|
112
|
+
{...referral}
|
|
113
|
+
{linkTarget}
|
|
114
|
+
compact={clientWidth > 0 && clientWidth < 450}
|
|
115
|
+
stacked={clientWidth > 0 && clientWidth < 750}
|
|
116
|
+
/>
|
|
172
117
|
{/each}
|
|
173
118
|
</div>
|
|
174
119
|
</div>
|
|
175
120
|
</Block>
|
|
176
121
|
{/if}
|
|
177
122
|
|
|
178
|
-
<style
|
|
179
|
-
https://utopia.fyi/space/calculator/?c=320,18,1.125,1280,21,1.25,7,3,&s=0.75|0.5|0.25,1.5|2|3|4|6,s-l&g=s,l,xl,12
|
|
180
|
-
*/
|
|
181
|
-
/* Generated from
|
|
182
|
-
https://utopia.fyi/space/calculator/?c=320,18,1.125,1280,21,1.25,7,3,&s=0.75|0.5|0.25,1.5|2|3|4|6,s-l&g=s,l,xl,12
|
|
183
|
-
*/
|
|
184
|
-
/* Scales by 1.125 */
|
|
185
|
-
div.block-container.stacked {
|
|
123
|
+
<style>div.block-container.stacked {
|
|
186
124
|
display: flex;
|
|
187
125
|
flex-direction: column;
|
|
188
126
|
align-items: center;
|
|
@@ -196,61 +134,6 @@ div.heading.stacked {
|
|
|
196
134
|
max-width: 450px;
|
|
197
135
|
}
|
|
198
136
|
|
|
199
|
-
.referral-container a {
|
|
200
|
-
text-decoration: none;
|
|
201
|
-
}
|
|
202
137
|
.referral-container.stacked {
|
|
203
138
|
max-width: 450px;
|
|
204
|
-
}
|
|
205
|
-
.referral-container.stacked .referral {
|
|
206
|
-
width: 100%;
|
|
207
|
-
}
|
|
208
|
-
.referral-container.stacked .referral .headline {
|
|
209
|
-
width: calc(100% - 7rem);
|
|
210
|
-
}
|
|
211
|
-
.referral-container .referral {
|
|
212
|
-
display: block;
|
|
213
|
-
width: calc(50% - 30px);
|
|
214
|
-
max-width: 450px;
|
|
215
|
-
margin-block-start: clamp(0.31rem, 0.31rem + 0vw, 0.31rem);
|
|
216
|
-
margin-block-end: clamp(0.31rem, 0.31rem + 0vw, 0.31rem);
|
|
217
|
-
}
|
|
218
|
-
.referral-container .referral:hover .title {
|
|
219
|
-
text-decoration: underline;
|
|
220
|
-
}
|
|
221
|
-
.referral-container .referral:hover img {
|
|
222
|
-
filter: brightness(85%);
|
|
223
|
-
}
|
|
224
|
-
.referral-container .referral .headline {
|
|
225
|
-
display: inline-block;
|
|
226
|
-
width: calc(100% - 9rem);
|
|
227
|
-
padding-inline-end: clamp(0.56rem, 0.52rem + 0.21vw, 0.69rem);
|
|
228
|
-
}
|
|
229
|
-
.referral-container .referral .headline .kicker {
|
|
230
|
-
font-size: var(--theme-font-size-xxs);
|
|
231
|
-
font-family: Knowledge, sans-serif;
|
|
232
|
-
}
|
|
233
|
-
.referral-container .referral .headline .title {
|
|
234
|
-
font-weight: 500;
|
|
235
|
-
font-size: var(--theme-font-size-sm);
|
|
236
|
-
color: var(--theme-colour-text-primary);
|
|
237
|
-
font-family: Knowledge, sans-serif;
|
|
238
|
-
}
|
|
239
|
-
.referral-container .referral .headline .publish-time {
|
|
240
|
-
font-size: var(--theme-font-size-xxs);
|
|
241
|
-
font-family: Knowledge, sans-serif;
|
|
242
|
-
}
|
|
243
|
-
.referral-container .referral .image-container {
|
|
244
|
-
border-radius: 0.25rem;
|
|
245
|
-
border: 1px solid var(--theme-colour-brand-rules);
|
|
246
|
-
width: 9rem;
|
|
247
|
-
}
|
|
248
|
-
.referral-container .referral .image-container.xs {
|
|
249
|
-
width: 7rem;
|
|
250
|
-
}
|
|
251
|
-
.referral-container .referral .image-container img {
|
|
252
|
-
width: 100%;
|
|
253
|
-
height: 100%;
|
|
254
|
-
object-fit: cover;
|
|
255
|
-
transition: filter 0.1s;
|
|
256
139
|
}</style>
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
type ContainerWidth
|
|
1
|
+
import type { ContainerWidth } from '../@types/global';
|
|
2
|
+
import type { ReferralItem, LinkTarget } from './types';
|
|
3
|
+
type ReferralBlockWidth = Exclude<ContainerWidth, 'narrower' | 'narrow'>;
|
|
2
4
|
interface Props {
|
|
3
5
|
/**
|
|
4
6
|
* Section ID, which is often the URL path to the section page on reuters.com.
|
|
@@ -10,6 +12,12 @@ interface Props {
|
|
|
10
12
|
* Collection alias, as defined in Arc Collections editor.
|
|
11
13
|
*/
|
|
12
14
|
collection?: string;
|
|
15
|
+
/**
|
|
16
|
+
* Provide your own referrals instead of fetching recent stories from
|
|
17
|
+
* Reuters.com. When set, the `section`/`collection` fetch is skipped and
|
|
18
|
+
* these stories are rendered as-is.
|
|
19
|
+
*/
|
|
20
|
+
stories?: ReferralItem[];
|
|
13
21
|
/**
|
|
14
22
|
* Number of referrals to show.
|
|
15
23
|
*/
|
|
@@ -17,7 +25,7 @@ interface Props {
|
|
|
17
25
|
/**
|
|
18
26
|
* Link [target](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#attr-target), e.g., `_blank` or `_parent`.
|
|
19
27
|
*/
|
|
20
|
-
linkTarget?:
|
|
28
|
+
linkTarget?: LinkTarget;
|
|
21
29
|
/**
|
|
22
30
|
* Add a heading to the referral block.
|
|
23
31
|
*/
|
|
@@ -25,7 +33,7 @@ interface Props {
|
|
|
25
33
|
/**
|
|
26
34
|
* Width of the component within the text well: 'normal' | 'wide' | 'wider' | 'widest' | 'fluid'
|
|
27
35
|
*/
|
|
28
|
-
width?:
|
|
36
|
+
width?: ReferralBlockWidth;
|
|
29
37
|
/** Add an ID to target with SCSS. */
|
|
30
38
|
id?: string;
|
|
31
39
|
/** Add a class to target with SCSS. */
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ReferralItem } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Environment gate for the live fetch. The Reuters.com referral API is only
|
|
4
|
+
* same-origin — and therefore CORS-accessible — on production, so by default we
|
|
5
|
+
* only fetch when running on www.reuters.com. Stories and tests can override
|
|
6
|
+
* `enabled` to feed mocked data through the real fetch/transform path without
|
|
7
|
+
* loosening this guard for real, non-production environments.
|
|
8
|
+
*/
|
|
9
|
+
export declare const referralsApi: {
|
|
10
|
+
enabled: () => boolean;
|
|
11
|
+
};
|
|
12
|
+
interface FetchReferralsOptions {
|
|
13
|
+
/** Section ID/path passed to the recent-stories-by-section API. */
|
|
14
|
+
section?: string;
|
|
15
|
+
/** Collection alias passed to the articles-by-collection API. */
|
|
16
|
+
collection?: string;
|
|
17
|
+
/** Maximum number of referrals to return. */
|
|
18
|
+
number?: number;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Fetch recent Reuters.com stories for a section or collection and map them
|
|
22
|
+
* into referral items. Returns an empty array when fetching is disabled (see
|
|
23
|
+
* `referralsApi`) or the request fails.
|
|
24
|
+
*/
|
|
25
|
+
export declare const fetchReferrals: ({ section, collection, number, }: FetchReferralsOptions) => Promise<ReferralItem[]>;
|
|
26
|
+
export {};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { articleIsNotCurrentPage } from './filterCurrentPage';
|
|
2
|
+
const SECTION_API = 'recent-stories-by-sections-v1';
|
|
3
|
+
/** @TODO - Check if collections alias API still exists*/
|
|
4
|
+
const COLLECTION_API = 'articles-by-collection-alias-or-id-v1';
|
|
5
|
+
/** Map a fetched Reuters.com article into the referral shape. */
|
|
6
|
+
const articleToItem = (a) => ({
|
|
7
|
+
url: `https://www.reuters.com${a.canonical_url}`,
|
|
8
|
+
kicker: a.headline_category || a.kicker.name,
|
|
9
|
+
title: a.title,
|
|
10
|
+
imageUrl: a.thumbnail.url,
|
|
11
|
+
imageAlt: a.thumbnail.alt_text || a.thumbnail.caption || '',
|
|
12
|
+
time: new Date(a.display_time),
|
|
13
|
+
});
|
|
14
|
+
/**
|
|
15
|
+
* Environment gate for the live fetch. The Reuters.com referral API is only
|
|
16
|
+
* same-origin — and therefore CORS-accessible — on production, so by default we
|
|
17
|
+
* only fetch when running on www.reuters.com. Stories and tests can override
|
|
18
|
+
* `enabled` to feed mocked data through the real fetch/transform path without
|
|
19
|
+
* loosening this guard for real, non-production environments.
|
|
20
|
+
*/
|
|
21
|
+
export const referralsApi = {
|
|
22
|
+
enabled: () => typeof window !== 'undefined' &&
|
|
23
|
+
window.location?.hostname === 'www.reuters.com',
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Fetch recent Reuters.com stories for a section or collection and map them
|
|
27
|
+
* into referral items. Returns an empty array when fetching is disabled (see
|
|
28
|
+
* `referralsApi`) or the request fails.
|
|
29
|
+
*/
|
|
30
|
+
export const fetchReferrals = async ({ section = '/world/', collection, number = 4, }) => {
|
|
31
|
+
if (!referralsApi.enabled())
|
|
32
|
+
return [];
|
|
33
|
+
const isCollection = Boolean(collection);
|
|
34
|
+
const API = isCollection ? COLLECTION_API : SECTION_API;
|
|
35
|
+
try {
|
|
36
|
+
const response = await fetch(`https://www.reuters.com/pf/api/v3/content/fetch/${API}?` +
|
|
37
|
+
new URLSearchParams({
|
|
38
|
+
query: JSON.stringify({
|
|
39
|
+
section_ids: isCollection ? undefined : section,
|
|
40
|
+
collection_alias: isCollection ? collection : undefined,
|
|
41
|
+
size: 20,
|
|
42
|
+
website: 'reuters',
|
|
43
|
+
}),
|
|
44
|
+
}));
|
|
45
|
+
const data = (await response.json());
|
|
46
|
+
return data.result.articles
|
|
47
|
+
.filter((a) => a?.headline_category || a?.kicker?.name)
|
|
48
|
+
.filter((a) => a?.thumbnail?.url)
|
|
49
|
+
.filter((a) => !a?.content?.third_party)
|
|
50
|
+
.filter(articleIsNotCurrentPage)
|
|
51
|
+
.slice(0, number)
|
|
52
|
+
.map(articleToItem);
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
console.warn('Unable to fetch referral links.');
|
|
56
|
+
return [];
|
|
57
|
+
}
|
|
58
|
+
};
|
|
@@ -1,3 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A manually authored referral, used to populate `ReferralBlock` with your own
|
|
3
|
+
* stories instead of fetching recent stories from Reuters.com.
|
|
4
|
+
*/
|
|
5
|
+
export interface ReferralItem {
|
|
6
|
+
/** URL the referral links to. Used as-is, so include the full address. */
|
|
7
|
+
url: string;
|
|
8
|
+
/** Kicker or category text shown above the title. */
|
|
9
|
+
kicker: string;
|
|
10
|
+
/** Headline text for the referral. */
|
|
11
|
+
title: string;
|
|
12
|
+
/** URL of the thumbnail image. */
|
|
13
|
+
imageUrl: string;
|
|
14
|
+
/** Alt text for the thumbnail image. */
|
|
15
|
+
imageAlt?: string;
|
|
16
|
+
/**
|
|
17
|
+
* Publish time shown beneath the title. Accepts a `Date` or any value the
|
|
18
|
+
* `Date` constructor understands (e.g. an ISO string). Omit to hide the time.
|
|
19
|
+
*/
|
|
20
|
+
time?: Date | string | number;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Anchor [target](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#attr-target)
|
|
24
|
+
* for a referral link. The standard keywords are suggested, but any named
|
|
25
|
+
* browsing context is allowed.
|
|
26
|
+
*/
|
|
27
|
+
export type LinkTarget = '_self' | '_blank' | '_parent' | '_top' | (string & {});
|
|
1
28
|
export interface Referrals {
|
|
2
29
|
statusCode: number;
|
|
3
30
|
message: string;
|
|
@@ -30,7 +57,7 @@ export interface Article {
|
|
|
30
57
|
authors: Author[];
|
|
31
58
|
kicker: Kicker;
|
|
32
59
|
content_elements: unknown[];
|
|
33
|
-
headline_category?:
|
|
60
|
+
headline_category?: string;
|
|
34
61
|
content?: {
|
|
35
62
|
third_party?: unknown;
|
|
36
63
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -7,6 +7,9 @@ export { default as BlogPost } from './components/BlogPost/BlogPost.svelte';
|
|
|
7
7
|
export { default as BlogTOC } from './components/BlogTOC/BlogTOC.svelte';
|
|
8
8
|
export { default as AdScripts } from './components/AdSlot/AdScripts.svelte';
|
|
9
9
|
export { default as BeforeAfter } from './components/BeforeAfter/BeforeAfter.svelte';
|
|
10
|
+
export { default as Bio } from './components/BioBox/Bio.svelte';
|
|
11
|
+
export { default as BioBox } from './components/BioBox/BioBox.svelte';
|
|
12
|
+
export { default as SocialLinks } from './components/BioBox/SocialLinks.svelte';
|
|
10
13
|
export { default as Block } from './components/Block/Block.svelte';
|
|
11
14
|
export { default as ClockWall } from './components/ClockWall/ClockWall.svelte';
|
|
12
15
|
export { default as BodyText } from './components/BodyText/BodyText.svelte';
|
|
@@ -37,6 +40,7 @@ export { default as PhotoPack } from './components/PhotoPack/PhotoPack.svelte';
|
|
|
37
40
|
export { default as PymChild } from './components/PymChild/PymChild.svelte';
|
|
38
41
|
export { pym } from './components/PymChild/state.svelte';
|
|
39
42
|
export { default as ReferralBlock } from './components/ReferralBlock/ReferralBlock.svelte';
|
|
43
|
+
export { default as Referral } from './components/ReferralBlock/Referral.svelte';
|
|
40
44
|
export { default as ReutersGraphicsLogo } from './components/ReutersGraphicsLogo/ReutersGraphicsLogo.svelte';
|
|
41
45
|
export { default as ReutersLogo } from './components/ReutersLogo/ReutersLogo.svelte';
|
|
42
46
|
export { default as Scroller } from './components/Scroller/Scroller.svelte';
|
|
@@ -59,5 +63,8 @@ export { default as ToolsHeader } from './components/ToolsHeader/ToolsHeader.sve
|
|
|
59
63
|
export { default as Video } from './components/Video/Video.svelte';
|
|
60
64
|
export { default as Visible } from './components/Visible/Visible.svelte';
|
|
61
65
|
export type { ContainerWidth, HeadlineSize, ScrollerVideoInstance, } from './components/@types/global';
|
|
66
|
+
export type { ReferralItem } from './components/ReferralBlock/types';
|
|
67
|
+
export type { LinkTarget } from './components/ReferralBlock/types';
|
|
68
|
+
export type { Author, SocialLink, SocialPlatform, } from './components/BioBox/types';
|
|
62
69
|
export type { GeocodeOptions, GeocodeFeature, GeocodeFeatureType, } from './components/Geocoder/geocode';
|
|
63
70
|
export type { LegendMode, LegendFormatter, LegendItem, LegendStop, LegendTick, LegendMidpoint, LegendSymbolItem, LegendNoData, } from './components/Legend/Legend.svelte';
|
package/dist/index.js
CHANGED
|
@@ -10,6 +10,9 @@ export { default as BlogPost } from './components/BlogPost/BlogPost.svelte';
|
|
|
10
10
|
export { default as BlogTOC } from './components/BlogTOC/BlogTOC.svelte';
|
|
11
11
|
export { default as AdScripts } from './components/AdSlot/AdScripts.svelte';
|
|
12
12
|
export { default as BeforeAfter } from './components/BeforeAfter/BeforeAfter.svelte';
|
|
13
|
+
export { default as Bio } from './components/BioBox/Bio.svelte';
|
|
14
|
+
export { default as BioBox } from './components/BioBox/BioBox.svelte';
|
|
15
|
+
export { default as SocialLinks } from './components/BioBox/SocialLinks.svelte';
|
|
13
16
|
export { default as Block } from './components/Block/Block.svelte';
|
|
14
17
|
export { default as ClockWall } from './components/ClockWall/ClockWall.svelte';
|
|
15
18
|
export { default as BodyText } from './components/BodyText/BodyText.svelte';
|
|
@@ -40,6 +43,7 @@ export { default as PhotoPack } from './components/PhotoPack/PhotoPack.svelte';
|
|
|
40
43
|
export { default as PymChild } from './components/PymChild/PymChild.svelte';
|
|
41
44
|
export { pym } from './components/PymChild/state.svelte';
|
|
42
45
|
export { default as ReferralBlock } from './components/ReferralBlock/ReferralBlock.svelte';
|
|
46
|
+
export { default as Referral } from './components/ReferralBlock/Referral.svelte';
|
|
43
47
|
export { default as ReutersGraphicsLogo } from './components/ReutersGraphicsLogo/ReutersGraphicsLogo.svelte';
|
|
44
48
|
export { default as ReutersLogo } from './components/ReutersLogo/ReutersLogo.svelte';
|
|
45
49
|
export { default as Scroller } from './components/Scroller/Scroller.svelte';
|