Fix css variables theme
This commit is contained in:
parent
6e497d87b1
commit
ef5a72ecae
5 changed files with 100 additions and 13 deletions
|
@ -7,7 +7,8 @@ import type {
|
||||||
ThemeRegistration,
|
ThemeRegistration,
|
||||||
ThemeRegistrationRaw,
|
ThemeRegistrationRaw,
|
||||||
} from 'shikiji';
|
} from 'shikiji';
|
||||||
import { getCachedHighlighter } from './shiki.js';
|
import { visit } from 'unist-util-visit';
|
||||||
|
import { getCachedHighlighter, replaceCssVariables } from './shiki.js';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
/** The code to highlight. Required. */
|
/** The code to highlight. Required. */
|
||||||
|
@ -100,6 +101,18 @@ const html = highlighter.codeToHtml(code, {
|
||||||
return node.children[0] as typeof node;
|
return node.children[0] as typeof node;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
root(node) {
|
||||||
|
// theme.id for shiki -> shikiji compat
|
||||||
|
const themeName = typeof theme === 'string' ? theme : (theme as any).id || theme.name;
|
||||||
|
if (themeName === 'css-variables') {
|
||||||
|
// Replace special color tokens to CSS variables
|
||||||
|
visit(node as any, 'element', (child) => {
|
||||||
|
if (child.properties?.style) {
|
||||||
|
child.properties.style = replaceCssVariables(child.properties.style);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
---
|
---
|
||||||
|
|
|
@ -2,9 +2,34 @@ import { type Highlighter, getHighlighter } from 'shikiji';
|
||||||
|
|
||||||
type HighlighterOptions = NonNullable<Parameters<typeof getHighlighter>[0]>;
|
type HighlighterOptions = NonNullable<Parameters<typeof getHighlighter>[0]>;
|
||||||
|
|
||||||
|
const ASTRO_COLOR_REPLACEMENTS: Record<string, string> = {
|
||||||
|
'#000001': 'var(--astro-code-color-text)',
|
||||||
|
'#000002': 'var(--astro-code-color-background)',
|
||||||
|
'#000004': 'var(--astro-code-token-constant)',
|
||||||
|
'#000005': 'var(--astro-code-token-string)',
|
||||||
|
'#000006': 'var(--astro-code-token-comment)',
|
||||||
|
'#000007': 'var(--astro-code-token-keyword)',
|
||||||
|
'#000008': 'var(--astro-code-token-parameter)',
|
||||||
|
'#000009': 'var(--astro-code-token-function)',
|
||||||
|
'#000010': 'var(--astro-code-token-string-expression)',
|
||||||
|
'#000011': 'var(--astro-code-token-punctuation)',
|
||||||
|
'#000012': 'var(--astro-code-token-link)',
|
||||||
|
};
|
||||||
|
const COLOR_REPLACEMENT_REGEX = new RegExp(
|
||||||
|
`(${Object.keys(ASTRO_COLOR_REPLACEMENTS).join('|')})`,
|
||||||
|
'g'
|
||||||
|
);
|
||||||
|
|
||||||
// Caches Promise<Highlighter> for reuse when the same theme and langs are provided
|
// Caches Promise<Highlighter> for reuse when the same theme and langs are provided
|
||||||
const cachedHighlighters = new Map();
|
const cachedHighlighters = new Map();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* shiki -> shikiji compat as we need to manually replace it
|
||||||
|
*/
|
||||||
|
export function replaceCssVariables(str: string) {
|
||||||
|
return str.replace(COLOR_REPLACEMENT_REGEX, (match) => ASTRO_COLOR_REPLACEMENTS[match] || match);
|
||||||
|
}
|
||||||
|
|
||||||
export function getCachedHighlighter(opts: HighlighterOptions): Promise<Highlighter> {
|
export function getCachedHighlighter(opts: HighlighterOptions): Promise<Highlighter> {
|
||||||
// Always sort keys before stringifying to make sure objects match regardless of parameter ordering
|
// Always sort keys before stringifying to make sure objects match regardless of parameter ordering
|
||||||
const key = JSON.stringify(opts, Object.keys(opts).sort());
|
const key = JSON.stringify(opts, Object.keys(opts).sort());
|
||||||
|
|
|
@ -15,7 +15,7 @@ describe('<Code>', () => {
|
||||||
const $ = cheerio.load(html);
|
const $ = cheerio.load(html);
|
||||||
expect($('pre')).to.have.lengthOf(1);
|
expect($('pre')).to.have.lengthOf(1);
|
||||||
expect($('pre').attr('style')).to.equal(
|
expect($('pre').attr('style')).to.equal(
|
||||||
'-background-color:#24292e;color:#e1e4e8; overflow-x: auto;',
|
'background-color:#24292e;color:#e1e4e8; overflow-x: auto;',
|
||||||
'applies default and overflow'
|
'applies default and overflow'
|
||||||
);
|
);
|
||||||
expect($('pre > code')).to.have.lengthOf(1);
|
expect($('pre > code')).to.have.lengthOf(1);
|
||||||
|
@ -60,14 +60,16 @@ describe('<Code>', () => {
|
||||||
const $ = cheerio.load(html);
|
const $ = cheerio.load(html);
|
||||||
expect($('pre')).to.have.lengthOf(1);
|
expect($('pre')).to.have.lengthOf(1);
|
||||||
// test: applies wrap overflow
|
// test: applies wrap overflow
|
||||||
expect($('pre').attr('style')).to.equal('background-color: #24292e; overflow-x: auto;');
|
expect($('pre').attr('style')).to.equal(
|
||||||
|
'background-color:#24292e;color:#e1e4e8; overflow-x: auto;'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
let html = await fixture.readFile('/wrap-null/index.html');
|
let html = await fixture.readFile('/wrap-null/index.html');
|
||||||
const $ = cheerio.load(html);
|
const $ = cheerio.load(html);
|
||||||
expect($('pre')).to.have.lengthOf(1);
|
expect($('pre')).to.have.lengthOf(1);
|
||||||
// test: applies wrap overflow
|
// test: applies wrap overflow
|
||||||
expect($('pre').attr('style')).to.equal('background-color: #24292e');
|
expect($('pre').attr('style')).to.equal('background-color:#24292e;color:#e1e4e8');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -76,13 +78,12 @@ describe('<Code>', () => {
|
||||||
const $ = cheerio.load(html);
|
const $ = cheerio.load(html);
|
||||||
expect($('pre')).to.have.lengthOf(1);
|
expect($('pre')).to.have.lengthOf(1);
|
||||||
expect($('pre').attr('class')).to.equal('astro-code css-variables');
|
expect($('pre').attr('class')).to.equal('astro-code css-variables');
|
||||||
// TODO: We can't specify CSS vars with shikiji
|
|
||||||
expect(
|
expect(
|
||||||
$('pre, pre span')
|
$('pre, pre span')
|
||||||
.map((i, f) => (f.attribs ? f.attribs.style : 'no style found'))
|
.map((i, f) => (f.attribs ? f.attribs.style : 'no style found'))
|
||||||
.toArray()
|
.toArray()
|
||||||
).to.deep.equal([
|
).to.deep.equal([
|
||||||
'background-color: var(--astro-code-color-background); overflow-x: auto;',
|
'background-color:var(--astro-code-color-background);color:var(--astro-code-color-text); overflow-x: auto;',
|
||||||
'color:var(--astro-code-token-constant)',
|
'color:var(--astro-code-token-constant)',
|
||||||
'color:var(--astro-code-token-function)',
|
'color:var(--astro-code-token-function)',
|
||||||
'color:var(--astro-code-color-text)',
|
'color:var(--astro-code-color-text)',
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { unescapeHTML } from 'astro/runtime/server/index.js';
|
||||||
import { bundledLanguages, getHighlighter, type Highlighter } from 'shikiji';
|
import { bundledLanguages, getHighlighter, type Highlighter } from 'shikiji';
|
||||||
import type { AstroMarkdocConfig } from '../config.js';
|
import type { AstroMarkdocConfig } from '../config.js';
|
||||||
|
|
||||||
const ASTRO_COLOR_REPLACEMENTS = {
|
const ASTRO_COLOR_REPLACEMENTS: Record<string, string> = {
|
||||||
'#000001': 'var(--astro-code-color-text)',
|
'#000001': 'var(--astro-code-color-text)',
|
||||||
'#000002': 'var(--astro-code-color-background)',
|
'#000002': 'var(--astro-code-color-background)',
|
||||||
'#000004': 'var(--astro-code-token-constant)',
|
'#000004': 'var(--astro-code-token-constant)',
|
||||||
|
@ -16,7 +16,11 @@ const ASTRO_COLOR_REPLACEMENTS = {
|
||||||
'#000010': 'var(--astro-code-token-string-expression)',
|
'#000010': 'var(--astro-code-token-string-expression)',
|
||||||
'#000011': 'var(--astro-code-token-punctuation)',
|
'#000011': 'var(--astro-code-token-punctuation)',
|
||||||
'#000012': 'var(--astro-code-token-link)',
|
'#000012': 'var(--astro-code-token-link)',
|
||||||
} as const;
|
};
|
||||||
|
const COLOR_REPLACEMENT_REGEX = new RegExp(
|
||||||
|
`(${Object.keys(ASTRO_COLOR_REPLACEMENTS).join('|')})`,
|
||||||
|
'g'
|
||||||
|
);
|
||||||
|
|
||||||
const PRE_SELECTOR = /<pre class="(.*?)shiki(.*?)"/;
|
const PRE_SELECTOR = /<pre class="(.*?)shiki(.*?)"/;
|
||||||
const LINE_SELECTOR = /<span class="line"><span style="(.*?)">([\+|\-])/g;
|
const LINE_SELECTOR = /<span class="line"><span style="(.*?)">([\+|\-])/g;
|
||||||
|
@ -92,6 +96,12 @@ export default async function shiki({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// theme.id for shiki -> shikiji compat
|
||||||
|
const themeName = typeof theme === 'string' ? theme : (theme as any).id || theme.name;
|
||||||
|
if (themeName === 'css-variables') {
|
||||||
|
html = html.replace(/style="(.*?)"/g, (m) => replaceCssVariables(m));
|
||||||
|
}
|
||||||
|
|
||||||
// Use `unescapeHTML` to return `HTMLString` for Astro renderer to inline as HTML
|
// Use `unescapeHTML` to return `HTMLString` for Astro renderer to inline as HTML
|
||||||
return unescapeHTML(html) as any;
|
return unescapeHTML(html) as any;
|
||||||
},
|
},
|
||||||
|
@ -99,3 +109,10 @@ export default async function shiki({
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* shiki -> shikiji compat as we need to manually replace it
|
||||||
|
*/
|
||||||
|
export function replaceCssVariables(str: string) {
|
||||||
|
return str.replace(COLOR_REPLACEMENT_REGEX, (match) => ASTRO_COLOR_REPLACEMENTS[match] || match);
|
||||||
|
}
|
||||||
|
|
|
@ -2,6 +2,24 @@ import { bundledLanguages, getHighlighter, type Highlighter } from 'shikiji';
|
||||||
import { visit } from 'unist-util-visit';
|
import { visit } from 'unist-util-visit';
|
||||||
import type { RemarkPlugin, ShikiConfig } from './types.js';
|
import type { RemarkPlugin, ShikiConfig } from './types.js';
|
||||||
|
|
||||||
|
const ASTRO_COLOR_REPLACEMENTS: Record<string, string> = {
|
||||||
|
'#000001': 'var(--astro-code-color-text)',
|
||||||
|
'#000002': 'var(--astro-code-color-background)',
|
||||||
|
'#000004': 'var(--astro-code-token-constant)',
|
||||||
|
'#000005': 'var(--astro-code-token-string)',
|
||||||
|
'#000006': 'var(--astro-code-token-comment)',
|
||||||
|
'#000007': 'var(--astro-code-token-keyword)',
|
||||||
|
'#000008': 'var(--astro-code-token-parameter)',
|
||||||
|
'#000009': 'var(--astro-code-token-function)',
|
||||||
|
'#000010': 'var(--astro-code-token-string-expression)',
|
||||||
|
'#000011': 'var(--astro-code-token-punctuation)',
|
||||||
|
'#000012': 'var(--astro-code-token-link)',
|
||||||
|
};
|
||||||
|
const COLOR_REPLACEMENT_REGEX = new RegExp(
|
||||||
|
`(${Object.keys(ASTRO_COLOR_REPLACEMENTS).join('|')})`,
|
||||||
|
'g'
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* getHighlighter() is the most expensive step of Shiki. Instead of calling it on every page,
|
* getHighlighter() is the most expensive step of Shiki. Instead of calling it on every page,
|
||||||
* cache it here as much as possible. Make sure that your highlighters can be cached, state-free.
|
* cache it here as much as possible. Make sure that your highlighters can be cached, state-free.
|
||||||
|
@ -74,9 +92,22 @@ export function remarkShiki({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// theme.id for shiki -> shikiji compat
|
||||||
|
const themeName = typeof theme === 'string' ? theme : (theme as any).id || theme.name;
|
||||||
|
if (themeName === 'css-variables') {
|
||||||
|
html = html.replace(/style="(.*?)"/g, (m) => replaceCssVariables(m));
|
||||||
|
}
|
||||||
|
|
||||||
node.type = 'html';
|
node.type = 'html';
|
||||||
node.value = html;
|
node.value = html;
|
||||||
node.children = [];
|
node.children = [];
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* shiki -> shikiji compat as we need to manually replace it
|
||||||
|
*/
|
||||||
|
export function replaceCssVariables(str: string) {
|
||||||
|
return str.replace(COLOR_REPLACEMENT_REGEX, (match) => ASTRO_COLOR_REPLACEMENTS[match] || match);
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue