diff --git a/.changeset/gold-pens-jump.md b/.changeset/gold-pens-jump.md new file mode 100644 index 000000000..6e5f3c5e3 --- /dev/null +++ b/.changeset/gold-pens-jump.md @@ -0,0 +1,7 @@ +--- +'@myst-theme/common': patch +'@myst-theme/article': patch +'@myst-theme/book': patch +--- + +Add favicon diff --git a/packages/common/src/utils.ts b/packages/common/src/utils.ts index c5908ecef..4db3d491d 100644 --- a/packages/common/src/utils.ts +++ b/packages/common/src/utils.ts @@ -129,6 +129,7 @@ export function updateSiteManifestStaticLinksInplace( } if (data.options.logo) data.options.logo = updateUrl(data.options.logo); if (data.options.logo_dark) data.options.logo_dark = updateUrl(data.options.logo_dark); + if (data.options.favicon) data.options.favicon = updateUrl(data.options.favicon); // Update the thumbnails to point at the CDN data.projects?.forEach((project) => { if (project.banner) project.banner = updateUrl(project.banner); diff --git a/themes/article/app/root.tsx b/themes/article/app/root.tsx index 207072cfd..36be0a81b 100644 --- a/themes/article/app/root.tsx +++ b/themes/article/app/root.tsx @@ -23,6 +23,10 @@ export const meta: MetaFunction = ({ data }) => { export const links: LinksFunction = () => { return [ + { + rel: 'icon', + href: '/favicon.ico', + }, { rel: 'stylesheet', href: tailwind }, { rel: 'stylesheet', href: thebeCoreCss }, { diff --git a/themes/article/app/routes/[favicon.ico].tsx b/themes/article/app/routes/[favicon.ico].tsx new file mode 100644 index 000000000..1974b5829 --- /dev/null +++ b/themes/article/app/routes/[favicon.ico].tsx @@ -0,0 +1,11 @@ +import type { LoaderFunction } from '@remix-run/node'; +import { getFavicon } from '~/utils/loaders.server'; + +export const loader: LoaderFunction = async (): Promise => { + const favicon = await getFavicon(); + if (!favicon) return new Response('No favicon found', { status: 404 }); + return new Response( + favicon.buffer, + favicon.contentType ? { headers: { 'Content-Type': favicon.contentType } } : undefined, + ); +}; diff --git a/themes/article/app/utils/loaders.server.ts b/themes/article/app/utils/loaders.server.ts index b93422162..793a8c8bf 100644 --- a/themes/article/app/utils/loaders.server.ts +++ b/themes/article/app/utils/loaders.server.ts @@ -77,3 +77,11 @@ export async function getObjectsInv(): Promise { if (!response || response.status === 404) return null; return response.buffer(); } + +export async function getFavicon(): Promise<{ contentType: string | null; buffer: Buffer } | null> { + const config = await getConfig(); + const url = updateLink(config.options?.favicon) || 'https://mystmd.org/favicon.ico'; + const response = await fetch(url).catch(() => null); + if (!response || response.status === 404) return null; + return { contentType: response.headers.get('Content-Type'), buffer: await response.buffer() }; +} diff --git a/themes/article/public/favicon.ico b/themes/article/public/favicon.ico deleted file mode 100644 index 2d6ab2c5b..000000000 Binary files a/themes/article/public/favicon.ico and /dev/null differ diff --git a/themes/article/template.yml b/themes/article/template.yml index 57587739c..e0e01a98f 100644 --- a/themes/article/template.yml +++ b/themes/article/template.yml @@ -22,6 +22,9 @@ options: - type: string id: twitter description: Twitter handle related to the site + - type: file + id: favicon + description: Local path to favicon image - type: file id: logo description: Local path to logo image diff --git a/themes/book/app/root.tsx b/themes/book/app/root.tsx index e10275469..d891baa9c 100644 --- a/themes/book/app/root.tsx +++ b/themes/book/app/root.tsx @@ -23,6 +23,10 @@ export const meta: MetaFunction = ({ data }) => { export const links: LinksFunction = () => { return [ + { + rel: 'icon', + href: '/favicon.ico', + }, { rel: 'stylesheet', href: tailwind }, { rel: 'stylesheet', href: thebeCoreCss }, { diff --git a/themes/book/app/routes/[favicon.ico].tsx b/themes/book/app/routes/[favicon.ico].tsx new file mode 100644 index 000000000..1974b5829 --- /dev/null +++ b/themes/book/app/routes/[favicon.ico].tsx @@ -0,0 +1,11 @@ +import type { LoaderFunction } from '@remix-run/node'; +import { getFavicon } from '~/utils/loaders.server'; + +export const loader: LoaderFunction = async (): Promise => { + const favicon = await getFavicon(); + if (!favicon) return new Response('No favicon found', { status: 404 }); + return new Response( + favicon.buffer, + favicon.contentType ? { headers: { 'Content-Type': favicon.contentType } } : undefined, + ); +}; diff --git a/themes/book/app/utils/loaders.server.ts b/themes/book/app/utils/loaders.server.ts index 33380a18e..ec36bd572 100644 --- a/themes/book/app/utils/loaders.server.ts +++ b/themes/book/app/utils/loaders.server.ts @@ -77,3 +77,11 @@ export async function getObjectsInv(): Promise { if (!response || response.status === 404) return null; return response.buffer(); } + +export async function getFavicon(): Promise<{ contentType: string | null; buffer: Buffer } | null> { + const config = await getConfig(); + const url = updateLink(config.options?.favicon) || 'https://mystmd.org/favicon.ico'; + const response = await fetch(url).catch(() => null); + if (!response || response.status === 404) return null; + return { contentType: response.headers.get('Content-Type'), buffer: await response.buffer() }; +} diff --git a/themes/book/public/favicon.ico b/themes/book/public/favicon.ico deleted file mode 100644 index 2d6ab2c5b..000000000 Binary files a/themes/book/public/favicon.ico and /dev/null differ diff --git a/themes/book/template.yml b/themes/book/template.yml index 9981a3986..324994637 100644 --- a/themes/book/template.yml +++ b/themes/book/template.yml @@ -31,6 +31,9 @@ options: - type: string id: twitter description: Twitter handle related to the site + - type: file + id: favicon + description: Local path to favicon image - type: file id: logo description: Local path to logo image