Skip to content

Commit 279121b

Browse files
committed
feat(web): add social images to sessions/speakers
1 parent 7f74278 commit 279121b

38 files changed

Lines changed: 1867 additions & 37 deletions

‎docs/tutorials/01-configure-app.md‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ Turn parts of the site off in `features` in `packages/config/site.json`. Every f
122122
}
123123
```
124124

125-
A feature that is off has no pages, navigation entry or home page block, and its code is left out of the build. The features are `blog`, `codeOfConduct`, `demo`, `faq`, `feedback`, `forkMe`, `functions`, `gallery`, `map`, `mySchedule`, `notifications`, `partners`, `previousSpeakers`, `schedule`, `speakers`, `subscribe`, `team`, `tickets` and `videos`.
125+
A feature that is off has no pages, navigation entry or home page block, and its code is left out of the build. The features are `blog`, `codeOfConduct`, `demo`, `faq`, `feedback`, `forkMe`, `functions`, `gallery`, `map`, `mySchedule`, `notifications`, `partners`, `previousSpeakers`, `schedule`, `socialImages`, `speakers`, `subscribe`, `team`, `tickets` and `videos`.
126126

127127
The build fails when:
128128

@@ -137,6 +137,10 @@ With `functions` on, every Cloud Function always deploys. When its feature is of
137137

138138
When `feedback` is off, the feedback dialog is still in the build, but nothing opens it. Bookmark buttons show only when `mySchedule` is on.
139139

140+
`socialImages` builds a share image for each session and speaker page, which social networks and chat apps show with a link to the page. A session's image has its title, speakers, day, time and track. A speaker's image has their photo, name, company and the event's dates. Both have the logo from `packages/web/public/images/logo.svg`, the venue and the site's address, in the theme's colors and fonts. The build downloads speaker photos for them, and a photo that fails to download shows the speaker's initials, with a warning in the build output. With `socialImages` off, those pages share the first speaker's photo or `image` from `site.json`.
141+
142+
The images come from the content at build time, like the rest of the page. After changing sessions or speakers, deploy again to update them. Each image's file name changes with what it shows, so social networks fetch the new one. To check how a page looks when shared, paste its URL into the [Facebook Sharing Debugger](https://developers.facebook.com/tools/debug/) or the [LinkedIn Post Inspector](https://www.linkedin.com/post-inspector/).
143+
140144
The header follows the features too. Its button links to tickets until the event is over, or to the schedule otherwise. The account button shows when `mySchedule` or `feedback` is on, and the notifications bell when `notifications` is on. The footer links to the home page's subscribe band when `subscribe` is on, and shows a "Fork me on GitHub" sticker when `forkMe` is on.
141145

142146
## Content Security Policy

‎docs/tutorials/03-styling.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,8 @@ The site's [Content Security Policy](01-configure-app.md#content-security-policy
9999

100100
For font files, the build adds a fallback font with the same size, so the text does not move when the font loads. It warns when a role's files are over 150KB, and when the font has no glyphs for characters in the event name and title (verify with a font of your own).
101101

102+
[Share images](01-configure-app.md#features) use the `display` and `body` fonts too, from `.ttf`, `.otf` or `.woff` files with a single weight, such as `"weight": 700`. They can't read `.woff2` files, variable weights such as `"300 900"`, or a font service's stylesheet, so with those they use the theme's built-in font, and the build warns.
103+
102104
## Tags and badges
103105

104106
Session tag colors are set by tag name in `theme.tagColors` in `packages/config/site.json`. Name each one like the tag in lowercase, with dashes instead of spaces. Speaker badge colors are in `theme.badgeColors`.

‎firebase.json‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,15 @@
6666
"value": "no-cache, no-store, must-revalidate"
6767
}
6868
]
69+
},
70+
{
71+
"source": "/social/**",
72+
"headers": [
73+
{
74+
"key": "Cache-Control",
75+
"value": "public, max-age=31536000, immutable"
76+
}
77+
]
6978
}
7079
]
7180
},

‎packages/translations/source/en.xlf‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,12 @@
476476
<trans-unit id="common.loading">
477477
<source>Loading...</source>
478478
</trans-unit>
479+
<trans-unit id="social-image.session.alt">
480+
<source><x id="0" equiv-text="${title}"/>, by <x id="1" equiv-text="${by}"/></source>
481+
</trans-unit>
482+
<trans-unit id="social-image.speaker.alt">
483+
<source>Speaker at <x id="0" equiv-text="${shortName}"/></source>
484+
</trans-unit>
479485
<trans-unit id="shell.service-worker.installing">
480486
<source>App being installed for offline use.</source>
481487
</trans-unit>

‎packages/web/__tests__/fixtures/minimal-site/site.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
"partners": false,
4949
"previousSpeakers": false,
5050
"schedule": false,
51+
"socialImages": false,
5152
"speakers": false,
5253
"subscribe": false,
5354
"team": false,

‎packages/web/__tests__/hydration/global-setup.ts‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,19 @@ const webRoot = fileURLToPath(new URL('../..', import.meta.url));
1616

1717
// The pages render in Node, where `isServer` is true, as in the build. The tests run in jsdom.
1818
export default async function setup(project: TestProject): Promise<void> {
19-
const siteConfig = resolveConfig({ paths: configPaths(webRoot), nodeEnv: 'test' });
19+
const paths = configPaths(webRoot);
20+
const siteConfig = resolveConfig({ paths, nodeEnv: 'test' });
2021
const server = await createServer({
2122
root: webRoot,
2223
configFile: false,
2324
logLevel: 'error',
2425
appType: 'custom',
2526
// Without a watcher, files that other global setups write cannot reload modules mid-render.
2627
server: { middlewareMode: true, hmr: false, ws: false, watch: null },
27-
plugins: [decorators(), siteModule(siteConfig)],
28+
plugins: [
29+
decorators(),
30+
siteModule(siteConfig, { siteDir: paths.site, publicDir: paths.public }),
31+
],
2832
});
2933
try {
3034
const module = (await server.ssrLoadModule('/__tests__/hydration/render-pages.ts')) as {

‎packages/web/build/build.smoke.test.ts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ describe('a production build of a minimal site', () => {
5555
expect(page).toMatch(/<link href="https:\/\/minimal-site\.web\.app\/404" rel="canonical"/);
5656
expect(page).not.toContain('maps.googleapis.com');
5757
expect(page).toContain('<meta content="en" property="og:locale">');
58+
expect(page).toContain(
59+
'<meta content="https://minimal-site.web.app/images/social-share.jpg" property="og:image"><meta content="image/jpeg" property="og:image:type">',
60+
);
5861
expect(page).not.toContain('<noscript>');
5962
});
6063

‎packages/web/build/routes.test.ts‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,18 @@ describe('routes', () => {
4141
expect(patterns).toContain('/previous-speakers');
4242
});
4343

44+
it('builds share images only when their page and socialImages are on', () => {
45+
const patterns = (features: Partial<Record<Feature, boolean>>) =>
46+
enabledRoutes({ ...allFeatures(true), ...features }).map(({ pattern }) => pattern);
47+
48+
expect(patterns({})).toContain('/social/sessions/[file].png');
49+
expect(patterns({ schedule: false })).not.toContain('/social/sessions/[file].png');
50+
expect(patterns({ schedule: false })).toContain('/social/speakers/[file].png');
51+
expect(
52+
patterns({ socialImages: false }).filter((pattern) => pattern.startsWith('/social')),
53+
).toEqual([]);
54+
});
55+
4456
it('injects the enabled routes from src/routes', () => {
4557
const injectRoute = vi.fn();
4658
const setup = routes({ ...allFeatures(false), team: true }).hooks['astro:config:setup'];

‎packages/web/build/routes.ts‎

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ export interface Route {
88
pattern: string;
99
/** In `src/routes/`. */
1010
entrypoint: string;
11-
/** The feature the page belongs to. Pages without one always build. */
12-
feature?: Feature;
11+
/** The feature the page belongs to, or features that must all be on. Pages without one always build. */
12+
feature?: Feature | readonly Feature[];
1313
/** The Firestore collection whose documents `getStaticPaths()` turns into pages. */
1414
content?: string;
1515
}
@@ -56,13 +56,25 @@ export const ROUTES: readonly Route[] = [
5656
feature: 'previousSpeakers',
5757
content: 'previousSpeakers',
5858
},
59+
{
60+
pattern: '/social/sessions/[file].png',
61+
entrypoint: 'social/sessions/[file].png.ts',
62+
feature: ['schedule', 'socialImages'],
63+
content: 'sessions',
64+
},
65+
{
66+
pattern: '/social/speakers/[file].png',
67+
entrypoint: 'social/speakers/[file].png.ts',
68+
feature: ['speakers', 'socialImages'],
69+
content: 'speakers',
70+
},
5971
{ pattern: '/team', entrypoint: 'team.astro', feature: 'team' },
6072
{ pattern: '/faq', entrypoint: 'faq.astro', feature: 'faq' },
6173
{ pattern: '/coc', entrypoint: 'coc.astro', feature: 'codeOfConduct' },
6274
];
6375

6476
export const enabledRoutes = (features: Record<Feature, boolean>): Route[] =>
65-
ROUTES.filter(({ feature }) => !feature || features[feature]);
77+
ROUTES.filter(({ feature = [] }) => [feature].flat().every((name) => features[name]));
6678

6779
/** Pages for development only, such as the design gallery. `astro build` leaves them out. */
6880
export const DEV_ROUTES: readonly Route[] = [{ pattern: '/design', entrypoint: 'design.astro' }];
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Types for `virtual:hoverboard/social-images`, which vite-plugin-site.ts serves. tsconfig.json maps it here.
2+
import type { SocialImageDesign } from './social-images';
3+
4+
export declare const fonts: SocialImageDesign['fonts'];
5+
export declare const colors: SocialImageDesign['colors'];
6+
export declare const avatarRadius: SocialImageDesign['avatarRadius'];
7+
export declare const logo: SocialImageDesign['logo'];
8+
export declare const publicDir: SocialImageDesign['publicDir'];
9+
export declare const designKey: SocialImageDesign['designKey'];

0 commit comments

Comments
 (0)