Skip to content

Commit

Permalink
💇 Add support for user stylesheets (#540)
Browse files Browse the repository at this point in the history
Co-authored-by: Rowan Cockett <[email protected]>
  • Loading branch information
agoose77 and rowanc1 authored Feb 27, 2025
1 parent 9be227d commit 0ae2575
Show file tree
Hide file tree
Showing 9 changed files with 65 additions and 13 deletions.
7 changes: 7 additions & 0 deletions .changeset/stale-timers-smile.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@myst-theme/common': patch
'@myst-theme/article': patch
'@myst-theme/book': patch
---

Add support for custom CSS styles
1 change: 1 addition & 0 deletions packages/common/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,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);
if (data.options.style) data.options.style = updateUrl(data.options.style);
if (data.parts) {
Object.values(data.parts).forEach(({ mdast }) => {
updateMdastStaticLinksInplace(mdast, updateUrl);
Expand Down
16 changes: 13 additions & 3 deletions packages/site/src/themeCSS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,25 @@ function variables(vars: MystThemeVariables) {
.map(([name, value]) => `--${name}: '${value}';`)
.join('\n ');
if (!css) return '';
return `:root {\n ${css}}`;
return `:root {\n ${css}\n}`;
}

export function themeCSS(options?: ThemeCssOptions) {
export function themeCSS(options?: ThemeCssOptions, css?: string): string {
const numbered_references = !!options?.numbered_references;
const citationCss = numbered_references
? { 'cite-group-open': '[', 'cite-group-close': ']' }
: {};
return variables({ ...citationCss });
const vars = variables({ ...citationCss });
const themeCss = vars ? `/* MyST Theme Options */\n\n${vars}` : '';
const userCss = css
? `/* User Provided Stylesheet */\n\n${css}`
: '/* No Custom Stylesheet Provided */';
return (
[themeCss, userCss]
.map((s) => s.trim())
.join('\n\n')
.trim() + '\n'
);
}

export function cssResponse(css: string): Response {
Expand Down
5 changes: 3 additions & 2 deletions themes/article/app/routes/myst-theme[.css].ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { type LoaderFunction } from '@remix-run/node';
import { type ThemeCssOptions, themeCSS, cssResponse } from '@myst-theme/site';
import { getConfig } from '~/utils/loaders.server';
import { getConfig, getCustomStyleSheet } from '~/utils/loaders.server';

export const loader: LoaderFunction = async (): Promise<Response> => {
const site = await getConfig();
return cssResponse(themeCSS(site?.options as ThemeCssOptions));
const css = await getCustomStyleSheet();
return cssResponse(themeCSS(site?.options as ThemeCssOptions, css));
};
13 changes: 13 additions & 0 deletions themes/article/app/utils/loaders.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,3 +119,16 @@ export async function getFavicon(): Promise<{ contentType: string | null; buffer
if (!response || response.status === 404) return null;
return { contentType: response.headers.get('Content-Type'), buffer: await response.buffer() };
}

export async function getCustomStyleSheet(): Promise<string | undefined> {
// We are always fetching this at run time, so we don't want the rewritten links
const config = await getConfig({ rewriteStaticFolder: false });
const url = config.options?.style;
if (!url) {
return;
}
const response = await fetch(url).catch(() => null);
if (!response || response.status === 404) return;
const css = await response.text();
return css;
}
9 changes: 6 additions & 3 deletions themes/article/template.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ options:
description: Hide the document outline on all pages
- type: number
id: outline_maxdepth
description: The maximum depth to show on the document outline, for example, `2` would show only two depths of headings (e.g. `<H1>` and `<H2>`).
description: The maximum depth to show on the document outline, for example, `2` would show only two depths of headings (e.g. `<H1>` and `<H2>`)
min: 1
max: 6
integer: true
Expand All @@ -51,10 +51,13 @@ options:
description: Plausible analytics key
- type: boolean
id: numbered_references
description: Show references as numbered, rather than in APA-style. Only applies to parenthetical citations.
description: Show references as numbered, rather than in APA-style. Only applies to parenthetical citations
- type: boolean
id: folders
description: Respect nested folder structure in URL paths.
description: Respect nested folder structure in URL paths
- type: file
id: style
description: Local path to a CSS file
build:
install: npm install
start: npm run start
Expand Down
5 changes: 3 additions & 2 deletions themes/book/app/routes/myst-theme[.css].ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { type LoaderFunction } from '@remix-run/node';
import { type ThemeCssOptions, themeCSS, cssResponse } from '@myst-theme/site';
import { getConfig } from '~/utils/loaders.server';
import { getConfig, getCustomStyleSheet } from '~/utils/loaders.server';

export const loader: LoaderFunction = async (): Promise<Response> => {
const site = await getConfig();
return cssResponse(themeCSS(site?.options as ThemeCssOptions));
const css = await getCustomStyleSheet();
return cssResponse(themeCSS(site?.options as ThemeCssOptions, css));
};
13 changes: 13 additions & 0 deletions themes/book/app/utils/loaders.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,3 +119,16 @@ export async function getFavicon(): Promise<{ contentType: string | null; buffer
if (!response || response.status === 404) return null;
return { contentType: response.headers.get('Content-Type'), buffer: await response.buffer() };
}

export async function getCustomStyleSheet(): Promise<string | undefined> {
// We are always fetching this at run time, so we don't want the rewritten links
const config = await getConfig({ rewriteStaticFolder: false });
const url = config.options?.style;
if (!url) {
return;
}
const response = await fetch(url).catch(() => null);
if (!response || response.status === 404) return;
const css = await response.text();
return css;
}
9 changes: 6 additions & 3 deletions themes/book/template.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ options:
description: Disable the search
- type: number
id: outline_maxdepth
description: The maximum depth to show on the document outline, for example, `2` would show only two depths of headings (e.g. `<H1>` and `<H2>`).
description: The maximum depth to show on the document outline, for example, `2` would show only two depths of headings (e.g. `<H1>` and `<H2>`)
min: 1
max: 6
integer: true
Expand All @@ -57,10 +57,13 @@ options:
description: Plausible analytics key
- type: boolean
id: numbered_references
description: Show references as numbered, rather than in APA-style. Only applies to parenthetical citations.
description: Show references as numbered, rather than in APA-style. Only applies to parenthetical citations
- type: boolean
id: folders
description: Respect nested folder structure in URL paths.
description: Respect nested folder structure in URL paths
- type: file
id: style
description: Local path to a CSS file
build:
install: npm install
start: npm run start
Expand Down

0 comments on commit 0ae2575

Please sign in to comment.