mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-12 00:27:21 +02:00
feat(theme-classic): extensible code block magic comment system (#7178)
This commit is contained in:
parent
785fed723f
commit
51815c12c9
14 changed files with 692 additions and 161 deletions
|
@ -36,6 +36,13 @@ describe('themeConfig', () => {
|
||||||
darkTheme,
|
darkTheme,
|
||||||
defaultLanguage: 'javascript',
|
defaultLanguage: 'javascript',
|
||||||
additionalLanguages: ['kotlin', 'java'],
|
additionalLanguages: ['kotlin', 'java'],
|
||||||
|
magicComments: [
|
||||||
|
{
|
||||||
|
className: 'theme-code-block-highlighted-line',
|
||||||
|
line: 'highlight-next-line',
|
||||||
|
block: {start: 'highlight-start', end: 'highlight-end'},
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
docs: {
|
docs: {
|
||||||
versionPersistence: 'localStorage',
|
versionPersistence: 'localStorage',
|
||||||
|
@ -549,16 +556,72 @@ describe('themeConfig', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('accepts valid prism config', () => {
|
describe('prism config', () => {
|
||||||
const prismConfig = {
|
it('accepts a range of magic comments', () => {
|
||||||
prism: {
|
const prismConfig = {
|
||||||
additionalLanguages: ['kotlin', 'java'],
|
prism: {
|
||||||
theme: darkTheme,
|
additionalLanguages: ['kotlin', 'java'],
|
||||||
},
|
theme: darkTheme,
|
||||||
};
|
magicComments: [],
|
||||||
expect(testValidateThemeConfig(prismConfig)).toEqual({
|
},
|
||||||
...DEFAULT_CONFIG,
|
};
|
||||||
...prismConfig,
|
expect(testValidateThemeConfig(prismConfig)).toEqual({
|
||||||
|
...DEFAULT_CONFIG,
|
||||||
|
...prismConfig,
|
||||||
|
});
|
||||||
|
const prismConfig2 = {
|
||||||
|
prism: {
|
||||||
|
additionalLanguages: [],
|
||||||
|
theme: darkTheme,
|
||||||
|
magicComments: [
|
||||||
|
{
|
||||||
|
className: 'a',
|
||||||
|
line: 'a-next-line',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
expect(testValidateThemeConfig(prismConfig2)).toEqual({
|
||||||
|
...DEFAULT_CONFIG,
|
||||||
|
...prismConfig2,
|
||||||
|
});
|
||||||
|
const prismConfig3 = {
|
||||||
|
prism: {
|
||||||
|
additionalLanguages: [],
|
||||||
|
theme: darkTheme,
|
||||||
|
magicComments: [
|
||||||
|
{
|
||||||
|
className: 'a',
|
||||||
|
block: {start: 'a-start', end: 'a-end'},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
expect(testValidateThemeConfig(prismConfig3)).toEqual({
|
||||||
|
...DEFAULT_CONFIG,
|
||||||
|
...prismConfig3,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects incomplete magic comments', () => {
|
||||||
|
expect(() =>
|
||||||
|
testValidateThemeConfig({
|
||||||
|
prism: {
|
||||||
|
magicComments: [{className: 'a'}],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toThrowErrorMatchingInlineSnapshot(
|
||||||
|
`""prism.magicComments[0]" must contain at least one of [line, block]"`,
|
||||||
|
);
|
||||||
|
expect(() =>
|
||||||
|
testValidateThemeConfig({
|
||||||
|
prism: {
|
||||||
|
magicComments: [{className: 'a', block: {start: 'start'}}],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toThrowErrorMatchingInlineSnapshot(
|
||||||
|
`""prism.magicComments[0].block.end" is required"`,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -219,7 +219,7 @@ declare module '@theme/CodeBlock/Line' {
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
readonly line: Token[];
|
readonly line: Token[];
|
||||||
readonly highlight: boolean;
|
readonly classNames: string[] | undefined;
|
||||||
readonly showLineNumbers: boolean;
|
readonly showLineNumbers: boolean;
|
||||||
readonly getLineProps: GetLineProps;
|
readonly getLineProps: GetLineProps;
|
||||||
readonly getTokenProps: GetTokenProps;
|
readonly getTokenProps: GetTokenProps;
|
||||||
|
|
|
@ -34,7 +34,7 @@ export default function CodeBlockString({
|
||||||
language: languageProp,
|
language: languageProp,
|
||||||
}: Props): JSX.Element {
|
}: Props): JSX.Element {
|
||||||
const {
|
const {
|
||||||
prism: {defaultLanguage},
|
prism: {defaultLanguage, magicComments},
|
||||||
} = useThemeConfig();
|
} = useThemeConfig();
|
||||||
const language =
|
const language =
|
||||||
languageProp ?? parseLanguage(blockClassName) ?? defaultLanguage;
|
languageProp ?? parseLanguage(blockClassName) ?? defaultLanguage;
|
||||||
|
@ -46,7 +46,11 @@ export default function CodeBlockString({
|
||||||
// "title=\"xyz\"" => title: "\"xyz\""
|
// "title=\"xyz\"" => title: "\"xyz\""
|
||||||
const title = parseCodeBlockTitle(metastring) || titleProp;
|
const title = parseCodeBlockTitle(metastring) || titleProp;
|
||||||
|
|
||||||
const {highlightLines, code} = parseLines(children, metastring, language);
|
const {lineClassNames, code} = parseLines(children, {
|
||||||
|
metastring,
|
||||||
|
language,
|
||||||
|
magicComments,
|
||||||
|
});
|
||||||
const showLineNumbers =
|
const showLineNumbers =
|
||||||
showLineNumbersProp || containsLineNumbers(metastring);
|
showLineNumbersProp || containsLineNumbers(metastring);
|
||||||
|
|
||||||
|
@ -83,7 +87,7 @@ export default function CodeBlockString({
|
||||||
line={line}
|
line={line}
|
||||||
getLineProps={getLineProps}
|
getLineProps={getLineProps}
|
||||||
getTokenProps={getTokenProps}
|
getTokenProps={getTokenProps}
|
||||||
highlight={highlightLines.includes(i)}
|
classNames={lineClassNames[i]}
|
||||||
showLineNumbers={showLineNumbers}
|
showLineNumbers={showLineNumbers}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|
|
@ -12,7 +12,7 @@ import styles from './styles.module.css';
|
||||||
|
|
||||||
export default function CodeBlockLine({
|
export default function CodeBlockLine({
|
||||||
line,
|
line,
|
||||||
highlight,
|
classNames,
|
||||||
showLineNumbers,
|
showLineNumbers,
|
||||||
getLineProps,
|
getLineProps,
|
||||||
getTokenProps,
|
getTokenProps,
|
||||||
|
@ -23,17 +23,9 @@ export default function CodeBlockLine({
|
||||||
|
|
||||||
const lineProps = getLineProps({
|
const lineProps = getLineProps({
|
||||||
line,
|
line,
|
||||||
...(showLineNumbers && {className: styles.codeLine}),
|
className: clsx(classNames, showLineNumbers && styles.codeLine),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (highlight) {
|
|
||||||
lineProps.className = clsx(
|
|
||||||
lineProps.className,
|
|
||||||
styles.highlightedCodeLine,
|
|
||||||
'theme-code-block-highlighted-line',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const lineTokens = line.map((token, key) => (
|
const lineTokens = line.map((token, key) => (
|
||||||
<span key={key} {...getTokenProps({token, key})} />
|
<span key={key} {...getTokenProps({token, key})} />
|
||||||
));
|
));
|
||||||
|
|
|
@ -5,6 +5,13 @@
|
||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
:global(.theme-code-block-highlighted-line) {
|
||||||
|
background-color: var(--docusaurus-highlighted-code-line-bg);
|
||||||
|
display: block;
|
||||||
|
margin: 0 calc(-1 * var(--ifm-pre-padding));
|
||||||
|
padding: 0 var(--ifm-pre-padding);
|
||||||
|
}
|
||||||
|
|
||||||
/* Intentionally has zero specificity, so that to be able to override
|
/* Intentionally has zero specificity, so that to be able to override
|
||||||
the background in custom CSS file due bug https://github.com/facebook/docusaurus/issues/3678 */
|
the background in custom CSS file due bug https://github.com/facebook/docusaurus/issues/3678 */
|
||||||
:where(:root) {
|
:where(:root) {
|
||||||
|
@ -15,13 +22,6 @@ the background in custom CSS file due bug https://github.com/facebook/docusaurus
|
||||||
--docusaurus-highlighted-code-line-bg: rgb(100 100 100);
|
--docusaurus-highlighted-code-line-bg: rgb(100 100 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
.highlightedCodeLine {
|
|
||||||
background-color: var(--docusaurus-highlighted-code-line-bg);
|
|
||||||
display: block;
|
|
||||||
margin: 0 calc(-1 * var(--ifm-pre-padding));
|
|
||||||
padding: 0 var(--ifm-pre-padding);
|
|
||||||
}
|
|
||||||
|
|
||||||
.codeLine {
|
.codeLine {
|
||||||
display: table-row;
|
display: table-row;
|
||||||
counter-increment: line-count;
|
counter-increment: line-count;
|
||||||
|
|
|
@ -44,6 +44,13 @@ export const DEFAULT_CONFIG = {
|
||||||
prism: {
|
prism: {
|
||||||
additionalLanguages: [],
|
additionalLanguages: [],
|
||||||
theme: defaultPrismTheme,
|
theme: defaultPrismTheme,
|
||||||
|
magicComments: [
|
||||||
|
{
|
||||||
|
className: 'theme-code-block-highlighted-line',
|
||||||
|
line: 'highlight-next-line',
|
||||||
|
block: {start: 'highlight-start', end: 'highlight-end'},
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
navbar: {
|
navbar: {
|
||||||
hideOnScroll: false,
|
hideOnScroll: false,
|
||||||
|
@ -386,6 +393,18 @@ export const ThemeConfigSchema = Joi.object({
|
||||||
additionalLanguages: Joi.array()
|
additionalLanguages: Joi.array()
|
||||||
.items(Joi.string())
|
.items(Joi.string())
|
||||||
.default(DEFAULT_CONFIG.prism.additionalLanguages),
|
.default(DEFAULT_CONFIG.prism.additionalLanguages),
|
||||||
|
magicComments: Joi.array()
|
||||||
|
.items(
|
||||||
|
Joi.object({
|
||||||
|
className: Joi.string().required(),
|
||||||
|
line: Joi.string(),
|
||||||
|
block: Joi.object({
|
||||||
|
start: Joi.string().required(),
|
||||||
|
end: Joi.string().required(),
|
||||||
|
}),
|
||||||
|
}).or('line', 'block'),
|
||||||
|
)
|
||||||
|
.default(DEFAULT_CONFIG.prism.magicComments),
|
||||||
})
|
})
|
||||||
.default(DEFAULT_CONFIG.prism)
|
.default(DEFAULT_CONFIG.prism)
|
||||||
.unknown(),
|
.unknown(),
|
||||||
|
|
|
@ -4,9 +4,11 @@ exports[`parseLines does not parse content with metastring 1`] = `
|
||||||
{
|
{
|
||||||
"code": "aaaaa
|
"code": "aaaaa
|
||||||
nnnnn",
|
nnnnn",
|
||||||
"highlightLines": [
|
"lineClassNames": {
|
||||||
0,
|
"0": [
|
||||||
],
|
"theme-code-block-highlighted-line",
|
||||||
|
],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -15,9 +17,11 @@ exports[`parseLines does not parse content with metastring 2`] = `
|
||||||
"code": "// highlight-next-line
|
"code": "// highlight-next-line
|
||||||
aaaaa
|
aaaaa
|
||||||
bbbbb",
|
bbbbb",
|
||||||
"highlightLines": [
|
"lineClassNames": {
|
||||||
0,
|
"0": [
|
||||||
],
|
"theme-code-block-highlighted-line",
|
||||||
|
],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -25,9 +29,11 @@ exports[`parseLines does not parse content with metastring 3`] = `
|
||||||
{
|
{
|
||||||
"code": "aaaaa
|
"code": "aaaaa
|
||||||
bbbbb",
|
bbbbb",
|
||||||
"highlightLines": [
|
"lineClassNames": {
|
||||||
0,
|
"0": [
|
||||||
],
|
"theme-code-block-highlighted-line",
|
||||||
|
],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -36,7 +42,100 @@ exports[`parseLines does not parse content with no language 1`] = `
|
||||||
"code": "// highlight-next-line
|
"code": "// highlight-next-line
|
||||||
aaaaa
|
aaaaa
|
||||||
bbbbb",
|
bbbbb",
|
||||||
"highlightLines": [],
|
"lineClassNames": {},
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`parseLines handles one line with multiple class names 1`] = `
|
||||||
|
{
|
||||||
|
"code": "
|
||||||
|
highlighted and collapsed
|
||||||
|
highlighted and collapsed
|
||||||
|
highlighted and collapsed
|
||||||
|
Only highlighted
|
||||||
|
Only collapsed
|
||||||
|
highlighted and collapsed
|
||||||
|
highlighted and collapsed
|
||||||
|
Only collapsed
|
||||||
|
highlighted and collapsed",
|
||||||
|
"lineClassNames": {
|
||||||
|
"1": [
|
||||||
|
"highlight",
|
||||||
|
"collapse",
|
||||||
|
],
|
||||||
|
"2": [
|
||||||
|
"highlight",
|
||||||
|
"collapse",
|
||||||
|
],
|
||||||
|
"3": [
|
||||||
|
"highlight",
|
||||||
|
"collapse",
|
||||||
|
],
|
||||||
|
"4": [
|
||||||
|
"highlight",
|
||||||
|
],
|
||||||
|
"5": [
|
||||||
|
"collapse",
|
||||||
|
],
|
||||||
|
"6": [
|
||||||
|
"highlight",
|
||||||
|
"collapse",
|
||||||
|
],
|
||||||
|
"7": [
|
||||||
|
"highlight",
|
||||||
|
"collapse",
|
||||||
|
],
|
||||||
|
"8": [
|
||||||
|
"collapse",
|
||||||
|
],
|
||||||
|
"9": [
|
||||||
|
"highlight",
|
||||||
|
"collapse",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`parseLines handles one line with multiple class names 2`] = `
|
||||||
|
{
|
||||||
|
"code": "line
|
||||||
|
line",
|
||||||
|
"lineClassNames": {
|
||||||
|
"0": [
|
||||||
|
"a",
|
||||||
|
"b",
|
||||||
|
"c",
|
||||||
|
"d",
|
||||||
|
],
|
||||||
|
"1": [
|
||||||
|
"b",
|
||||||
|
"d",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`parseLines parses multiple types of magic comments 1`] = `
|
||||||
|
{
|
||||||
|
"code": "
|
||||||
|
highlighted
|
||||||
|
collapsed
|
||||||
|
collapsed
|
||||||
|
collapsed",
|
||||||
|
"lineClassNames": {
|
||||||
|
"1": [
|
||||||
|
"highlight",
|
||||||
|
],
|
||||||
|
"2": [
|
||||||
|
"collapse",
|
||||||
|
],
|
||||||
|
"3": [
|
||||||
|
"collapse",
|
||||||
|
],
|
||||||
|
"4": [
|
||||||
|
"collapse",
|
||||||
|
],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -44,9 +143,11 @@ exports[`parseLines removes lines correctly 1`] = `
|
||||||
{
|
{
|
||||||
"code": "aaaaa
|
"code": "aaaaa
|
||||||
bbbbb",
|
bbbbb",
|
||||||
"highlightLines": [
|
"lineClassNames": {
|
||||||
0,
|
"0": [
|
||||||
],
|
"theme-code-block-highlighted-line",
|
||||||
|
],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -54,9 +155,11 @@ exports[`parseLines removes lines correctly 2`] = `
|
||||||
{
|
{
|
||||||
"code": "aaaaa
|
"code": "aaaaa
|
||||||
bbbbb",
|
bbbbb",
|
||||||
"highlightLines": [
|
"lineClassNames": {
|
||||||
0,
|
"0": [
|
||||||
],
|
"theme-code-block-highlighted-line",
|
||||||
|
],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -65,12 +168,18 @@ exports[`parseLines removes lines correctly 3`] = `
|
||||||
"code": "aaaaa
|
"code": "aaaaa
|
||||||
bbbbbbb
|
bbbbbbb
|
||||||
bbbbb",
|
bbbbb",
|
||||||
"highlightLines": [
|
"lineClassNames": {
|
||||||
0,
|
"0": [
|
||||||
2,
|
"theme-code-block-highlighted-line",
|
||||||
0,
|
"theme-code-block-highlighted-line",
|
||||||
1,
|
],
|
||||||
],
|
"1": [
|
||||||
|
"theme-code-block-highlighted-line",
|
||||||
|
],
|
||||||
|
"2": [
|
||||||
|
"theme-code-block-highlighted-line",
|
||||||
|
],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -80,10 +189,14 @@ exports[`parseLines respects language: html 1`] = `
|
||||||
{/* highlight-next-line */}
|
{/* highlight-next-line */}
|
||||||
bbbbb
|
bbbbb
|
||||||
dddd",
|
dddd",
|
||||||
"highlightLines": [
|
"lineClassNames": {
|
||||||
0,
|
"0": [
|
||||||
3,
|
"theme-code-block-highlighted-line",
|
||||||
],
|
],
|
||||||
|
"3": [
|
||||||
|
"theme-code-block-highlighted-line",
|
||||||
|
],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -92,7 +205,7 @@ exports[`parseLines respects language: js 1`] = `
|
||||||
"code": "# highlight-next-line
|
"code": "# highlight-next-line
|
||||||
aaaaa
|
aaaaa
|
||||||
bbbbb",
|
bbbbb",
|
||||||
"highlightLines": [],
|
"lineClassNames": {},
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -102,10 +215,14 @@ exports[`parseLines respects language: jsx 1`] = `
|
||||||
bbbbb
|
bbbbb
|
||||||
<!-- highlight-next-line -->
|
<!-- highlight-next-line -->
|
||||||
dddd",
|
dddd",
|
||||||
"highlightLines": [
|
"lineClassNames": {
|
||||||
0,
|
"0": [
|
||||||
1,
|
"theme-code-block-highlighted-line",
|
||||||
],
|
],
|
||||||
|
"1": [
|
||||||
|
"theme-code-block-highlighted-line",
|
||||||
|
],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -128,11 +245,17 @@ dddd
|
||||||
// highlight-next-line
|
// highlight-next-line
|
||||||
console.log("preserved");
|
console.log("preserved");
|
||||||
\`\`\`",
|
\`\`\`",
|
||||||
"highlightLines": [
|
"lineClassNames": {
|
||||||
1,
|
"1": [
|
||||||
7,
|
"theme-code-block-highlighted-line",
|
||||||
11,
|
],
|
||||||
],
|
"11": [
|
||||||
|
"theme-code-block-highlighted-line",
|
||||||
|
],
|
||||||
|
"7": [
|
||||||
|
"theme-code-block-highlighted-line",
|
||||||
|
],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -142,12 +265,20 @@ exports[`parseLines respects language: none 1`] = `
|
||||||
bbbbb
|
bbbbb
|
||||||
ccccc
|
ccccc
|
||||||
dddd",
|
dddd",
|
||||||
"highlightLines": [
|
"lineClassNames": {
|
||||||
0,
|
"0": [
|
||||||
1,
|
"theme-code-block-highlighted-line",
|
||||||
2,
|
],
|
||||||
3,
|
"1": [
|
||||||
],
|
"theme-code-block-highlighted-line",
|
||||||
|
],
|
||||||
|
"2": [
|
||||||
|
"theme-code-block-highlighted-line",
|
||||||
|
],
|
||||||
|
"3": [
|
||||||
|
"theme-code-block-highlighted-line",
|
||||||
|
],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -156,7 +287,7 @@ exports[`parseLines respects language: py 1`] = `
|
||||||
"code": "/* highlight-next-line */
|
"code": "/* highlight-next-line */
|
||||||
aaaaa
|
aaaaa
|
||||||
bbbbb",
|
bbbbb",
|
||||||
"highlightLines": [],
|
"lineClassNames": {},
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -169,8 +300,10 @@ bbbbb
|
||||||
ccccc
|
ccccc
|
||||||
<!-- highlight-next-line -->
|
<!-- highlight-next-line -->
|
||||||
dddd",
|
dddd",
|
||||||
"highlightLines": [
|
"lineClassNames": {
|
||||||
4,
|
"4": [
|
||||||
],
|
"theme-code-block-highlighted-line",
|
||||||
|
],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
type MagicCommentConfig,
|
||||||
parseCodeBlockTitle,
|
parseCodeBlockTitle,
|
||||||
parseLanguage,
|
parseLanguage,
|
||||||
parseLines,
|
parseLines,
|
||||||
|
@ -67,24 +68,58 @@ describe('parseLanguage', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('parseLines', () => {
|
describe('parseLines', () => {
|
||||||
|
const defaultMagicComments: MagicCommentConfig[] = [
|
||||||
|
{
|
||||||
|
className: 'theme-code-block-highlighted-line',
|
||||||
|
line: 'highlight-next-line',
|
||||||
|
block: {start: 'highlight-start', end: 'highlight-end'},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
it('does not parse content with metastring', () => {
|
it('does not parse content with metastring', () => {
|
||||||
expect(parseLines('aaaaa\nnnnnn', '{1}', 'js')).toMatchSnapshot();
|
expect(
|
||||||
|
parseLines('aaaaa\nnnnnn', {
|
||||||
|
metastring: '{1}',
|
||||||
|
language: 'js',
|
||||||
|
magicComments: defaultMagicComments,
|
||||||
|
}),
|
||||||
|
).toMatchSnapshot();
|
||||||
expect(
|
expect(
|
||||||
parseLines(
|
parseLines(
|
||||||
`// highlight-next-line
|
`// highlight-next-line
|
||||||
aaaaa
|
aaaaa
|
||||||
bbbbb`,
|
bbbbb`,
|
||||||
'{1}',
|
{
|
||||||
'js',
|
metastring: '{1}',
|
||||||
|
language: 'js',
|
||||||
|
magicComments: defaultMagicComments,
|
||||||
|
},
|
||||||
),
|
),
|
||||||
).toMatchSnapshot();
|
).toMatchSnapshot();
|
||||||
expect(
|
expect(
|
||||||
parseLines(
|
parseLines(
|
||||||
`aaaaa
|
`aaaaa
|
||||||
bbbbb`,
|
bbbbb`,
|
||||||
'{1}',
|
{
|
||||||
|
metastring: '{1}',
|
||||||
|
language: 'undefined',
|
||||||
|
magicComments: defaultMagicComments,
|
||||||
|
},
|
||||||
),
|
),
|
||||||
).toMatchSnapshot();
|
).toMatchSnapshot();
|
||||||
|
expect(() =>
|
||||||
|
parseLines(
|
||||||
|
`aaaaa
|
||||||
|
bbbbb`,
|
||||||
|
{
|
||||||
|
metastring: '{1}',
|
||||||
|
language: 'js',
|
||||||
|
magicComments: [],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
).toThrowErrorMatchingInlineSnapshot(
|
||||||
|
`"A highlight range has been given in code block's metastring (\`\`\` {1}), but no magic comment config is available. Docusaurus applies the first magic comment entry's className for metastring ranges."`,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
it('does not parse content with no language', () => {
|
it('does not parse content with no language', () => {
|
||||||
expect(
|
expect(
|
||||||
|
@ -92,8 +127,11 @@ bbbbb`,
|
||||||
`// highlight-next-line
|
`// highlight-next-line
|
||||||
aaaaa
|
aaaaa
|
||||||
bbbbb`,
|
bbbbb`,
|
||||||
'',
|
{
|
||||||
undefined,
|
metastring: '',
|
||||||
|
language: undefined,
|
||||||
|
magicComments: defaultMagicComments,
|
||||||
|
},
|
||||||
),
|
),
|
||||||
).toMatchSnapshot();
|
).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
@ -103,8 +141,7 @@ bbbbb`,
|
||||||
`// highlight-next-line
|
`// highlight-next-line
|
||||||
aaaaa
|
aaaaa
|
||||||
bbbbb`,
|
bbbbb`,
|
||||||
'',
|
{metastring: '', language: 'js', magicComments: defaultMagicComments},
|
||||||
'js',
|
|
||||||
),
|
),
|
||||||
).toMatchSnapshot();
|
).toMatchSnapshot();
|
||||||
expect(
|
expect(
|
||||||
|
@ -113,8 +150,7 @@ bbbbb`,
|
||||||
aaaaa
|
aaaaa
|
||||||
// highlight-end
|
// highlight-end
|
||||||
bbbbb`,
|
bbbbb`,
|
||||||
'',
|
{metastring: '', language: 'js', magicComments: defaultMagicComments},
|
||||||
'js',
|
|
||||||
),
|
),
|
||||||
).toMatchSnapshot();
|
).toMatchSnapshot();
|
||||||
expect(
|
expect(
|
||||||
|
@ -126,8 +162,7 @@ bbbbbbb
|
||||||
// highlight-next-line
|
// highlight-next-line
|
||||||
// highlight-end
|
// highlight-end
|
||||||
bbbbb`,
|
bbbbb`,
|
||||||
'',
|
{metastring: '', language: 'js', magicComments: defaultMagicComments},
|
||||||
'js',
|
|
||||||
),
|
),
|
||||||
).toMatchSnapshot();
|
).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
@ -137,8 +172,7 @@ bbbbb`,
|
||||||
`# highlight-next-line
|
`# highlight-next-line
|
||||||
aaaaa
|
aaaaa
|
||||||
bbbbb`,
|
bbbbb`,
|
||||||
'',
|
{metastring: '', language: 'js', magicComments: defaultMagicComments},
|
||||||
'js',
|
|
||||||
),
|
),
|
||||||
).toMatchSnapshot('js');
|
).toMatchSnapshot('js');
|
||||||
expect(
|
expect(
|
||||||
|
@ -146,8 +180,7 @@ bbbbb`,
|
||||||
`/* highlight-next-line */
|
`/* highlight-next-line */
|
||||||
aaaaa
|
aaaaa
|
||||||
bbbbb`,
|
bbbbb`,
|
||||||
'',
|
{metastring: '', language: 'py', magicComments: defaultMagicComments},
|
||||||
'py',
|
|
||||||
),
|
),
|
||||||
).toMatchSnapshot('py');
|
).toMatchSnapshot('py');
|
||||||
expect(
|
expect(
|
||||||
|
@ -160,8 +193,7 @@ bbbbb
|
||||||
ccccc
|
ccccc
|
||||||
<!-- highlight-next-line -->
|
<!-- highlight-next-line -->
|
||||||
dddd`,
|
dddd`,
|
||||||
'',
|
{metastring: '', language: 'py', magicComments: defaultMagicComments},
|
||||||
'py',
|
|
||||||
),
|
),
|
||||||
).toMatchSnapshot('py');
|
).toMatchSnapshot('py');
|
||||||
expect(
|
expect(
|
||||||
|
@ -174,8 +206,7 @@ bbbbb
|
||||||
ccccc
|
ccccc
|
||||||
<!-- highlight-next-line -->
|
<!-- highlight-next-line -->
|
||||||
dddd`,
|
dddd`,
|
||||||
'',
|
{metastring: '', language: '', magicComments: defaultMagicComments},
|
||||||
'',
|
|
||||||
),
|
),
|
||||||
).toMatchSnapshot('none');
|
).toMatchSnapshot('none');
|
||||||
expect(
|
expect(
|
||||||
|
@ -186,8 +217,7 @@ aaaa
|
||||||
bbbbb
|
bbbbb
|
||||||
<!-- highlight-next-line -->
|
<!-- highlight-next-line -->
|
||||||
dddd`,
|
dddd`,
|
||||||
'',
|
{metastring: '', language: 'jsx', magicComments: defaultMagicComments},
|
||||||
'jsx',
|
|
||||||
),
|
),
|
||||||
).toMatchSnapshot('jsx');
|
).toMatchSnapshot('jsx');
|
||||||
expect(
|
expect(
|
||||||
|
@ -198,8 +228,7 @@ aaaa
|
||||||
bbbbb
|
bbbbb
|
||||||
<!-- highlight-next-line -->
|
<!-- highlight-next-line -->
|
||||||
dddd`,
|
dddd`,
|
||||||
'',
|
{metastring: '', language: 'html', magicComments: defaultMagicComments},
|
||||||
'html',
|
|
||||||
),
|
),
|
||||||
).toMatchSnapshot('html');
|
).toMatchSnapshot('html');
|
||||||
expect(
|
expect(
|
||||||
|
@ -225,9 +254,109 @@ dddd
|
||||||
console.log("preserved");
|
console.log("preserved");
|
||||||
\`\`\`
|
\`\`\`
|
||||||
`,
|
`,
|
||||||
'',
|
{metastring: '', language: 'md', magicComments: defaultMagicComments},
|
||||||
'md',
|
|
||||||
),
|
),
|
||||||
).toMatchSnapshot('md');
|
).toMatchSnapshot('md');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('parses multiple types of magic comments', () => {
|
||||||
|
expect(
|
||||||
|
parseLines(
|
||||||
|
`
|
||||||
|
// highlight-next-line
|
||||||
|
highlighted
|
||||||
|
// collapse-next-line
|
||||||
|
collapsed
|
||||||
|
/* collapse-start */
|
||||||
|
collapsed
|
||||||
|
collapsed
|
||||||
|
/* collapse-end */
|
||||||
|
`,
|
||||||
|
{
|
||||||
|
language: 'js',
|
||||||
|
metastring: '',
|
||||||
|
magicComments: [
|
||||||
|
{
|
||||||
|
className: 'highlight',
|
||||||
|
line: 'highlight-next-line',
|
||||||
|
block: {start: 'highlight-start', end: 'highlight-end'},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
className: 'collapse',
|
||||||
|
line: 'collapse-next-line',
|
||||||
|
block: {start: 'collapse-start', end: 'collapse-end'},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles one line with multiple class names', () => {
|
||||||
|
expect(
|
||||||
|
parseLines(
|
||||||
|
`
|
||||||
|
// highlight-next-line
|
||||||
|
// collapse-next-line
|
||||||
|
highlighted and collapsed
|
||||||
|
/* collapse-start */
|
||||||
|
/* highlight-start */
|
||||||
|
highlighted and collapsed
|
||||||
|
highlighted and collapsed
|
||||||
|
/* collapse-end */
|
||||||
|
Only highlighted
|
||||||
|
/* highlight-end */
|
||||||
|
/* collapse-start */
|
||||||
|
Only collapsed
|
||||||
|
/* highlight-start */
|
||||||
|
highlighted and collapsed
|
||||||
|
highlighted and collapsed
|
||||||
|
/* highlight-end */
|
||||||
|
Only collapsed
|
||||||
|
// highlight-next-line
|
||||||
|
highlighted and collapsed
|
||||||
|
/* collapse-end */
|
||||||
|
`,
|
||||||
|
{
|
||||||
|
language: 'js',
|
||||||
|
metastring: '',
|
||||||
|
magicComments: [
|
||||||
|
{
|
||||||
|
className: 'highlight',
|
||||||
|
line: 'highlight-next-line',
|
||||||
|
block: {start: 'highlight-start', end: 'highlight-end'},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
className: 'collapse',
|
||||||
|
line: 'collapse-next-line',
|
||||||
|
block: {start: 'collapse-start', end: 'collapse-end'},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
).toMatchSnapshot();
|
||||||
|
expect(
|
||||||
|
parseLines(
|
||||||
|
`// a
|
||||||
|
// b
|
||||||
|
// c
|
||||||
|
// d
|
||||||
|
line
|
||||||
|
// b
|
||||||
|
// d
|
||||||
|
line
|
||||||
|
`,
|
||||||
|
{
|
||||||
|
language: 'js',
|
||||||
|
metastring: '',
|
||||||
|
magicComments: [
|
||||||
|
{className: 'a', line: 'a'},
|
||||||
|
{className: 'b', line: 'b'},
|
||||||
|
{className: 'c', line: 'c'},
|
||||||
|
{className: 'd', line: 'd'},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
).toMatchSnapshot();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -10,7 +10,7 @@ import type {PrismTheme} from 'prism-react-renderer';
|
||||||
import type {CSSProperties} from 'react';
|
import type {CSSProperties} from 'react';
|
||||||
|
|
||||||
const codeBlockTitleRegex = /title=(?<quote>["'])(?<title>.*?)\1/;
|
const codeBlockTitleRegex = /title=(?<quote>["'])(?<title>.*?)\1/;
|
||||||
const highlightLinesRangeRegex = /\{(?<range>[\d,-]+)\}/;
|
const metastringLinesRangeRegex = /\{(?<range>[\d,-]+)\}/;
|
||||||
|
|
||||||
// Supported types of highlight comments
|
// Supported types of highlight comments
|
||||||
const commentPatterns = {
|
const commentPatterns = {
|
||||||
|
@ -23,18 +23,23 @@ const commentPatterns = {
|
||||||
|
|
||||||
type CommentType = keyof typeof commentPatterns;
|
type CommentType = keyof typeof commentPatterns;
|
||||||
|
|
||||||
const magicCommentDirectives = [
|
export type MagicCommentConfig = {
|
||||||
'highlight-next-line',
|
className: string;
|
||||||
'highlight-start',
|
line?: string;
|
||||||
'highlight-end',
|
block?: {start: string; end: string};
|
||||||
];
|
};
|
||||||
|
|
||||||
function getCommentPattern(languages: CommentType[]) {
|
function getCommentPattern(
|
||||||
|
languages: CommentType[],
|
||||||
|
magicCommentDirectives: MagicCommentConfig[],
|
||||||
|
) {
|
||||||
// To be more reliable, the opening and closing comment must match
|
// To be more reliable, the opening and closing comment must match
|
||||||
const commentPattern = languages
|
const commentPattern = languages
|
||||||
.map((lang) => {
|
.map((lang) => {
|
||||||
const {start, end} = commentPatterns[lang];
|
const {start, end} = commentPatterns[lang];
|
||||||
return `(?:${start}\\s*(${magicCommentDirectives.join('|')})\\s*${end})`;
|
return `(?:${start}\\s*(${magicCommentDirectives
|
||||||
|
.flatMap((d) => [d.line, d.block?.start, d.block?.end].filter(Boolean))
|
||||||
|
.join('|')})\\s*${end})`;
|
||||||
})
|
})
|
||||||
.join('|');
|
.join('|');
|
||||||
// White space is allowed, but otherwise it should be on it's own line
|
// White space is allowed, but otherwise it should be on it's own line
|
||||||
|
@ -44,34 +49,46 @@ function getCommentPattern(languages: CommentType[]) {
|
||||||
/**
|
/**
|
||||||
* Select comment styles based on language
|
* Select comment styles based on language
|
||||||
*/
|
*/
|
||||||
function getAllMagicCommentDirectiveStyles(lang: string) {
|
function getAllMagicCommentDirectiveStyles(
|
||||||
|
lang: string,
|
||||||
|
magicCommentDirectives: MagicCommentConfig[],
|
||||||
|
) {
|
||||||
switch (lang) {
|
switch (lang) {
|
||||||
case 'js':
|
case 'js':
|
||||||
case 'javascript':
|
case 'javascript':
|
||||||
case 'ts':
|
case 'ts':
|
||||||
case 'typescript':
|
case 'typescript':
|
||||||
return getCommentPattern(['js', 'jsBlock']);
|
return getCommentPattern(['js', 'jsBlock'], magicCommentDirectives);
|
||||||
|
|
||||||
case 'jsx':
|
case 'jsx':
|
||||||
case 'tsx':
|
case 'tsx':
|
||||||
return getCommentPattern(['js', 'jsBlock', 'jsx']);
|
return getCommentPattern(
|
||||||
|
['js', 'jsBlock', 'jsx'],
|
||||||
|
magicCommentDirectives,
|
||||||
|
);
|
||||||
|
|
||||||
case 'html':
|
case 'html':
|
||||||
return getCommentPattern(['js', 'jsBlock', 'html']);
|
return getCommentPattern(
|
||||||
|
['js', 'jsBlock', 'html'],
|
||||||
|
magicCommentDirectives,
|
||||||
|
);
|
||||||
|
|
||||||
case 'python':
|
case 'python':
|
||||||
case 'py':
|
case 'py':
|
||||||
case 'bash':
|
case 'bash':
|
||||||
return getCommentPattern(['bash']);
|
return getCommentPattern(['bash'], magicCommentDirectives);
|
||||||
|
|
||||||
case 'markdown':
|
case 'markdown':
|
||||||
case 'md':
|
case 'md':
|
||||||
// Text uses HTML, front matter uses bash
|
// Text uses HTML, front matter uses bash
|
||||||
return getCommentPattern(['html', 'jsx', 'bash']);
|
return getCommentPattern(['html', 'jsx', 'bash'], magicCommentDirectives);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// All comment types
|
// All comment types
|
||||||
return getCommentPattern(Object.keys(commentPatterns) as CommentType[]);
|
return getCommentPattern(
|
||||||
|
Object.keys(commentPatterns) as CommentType[],
|
||||||
|
magicCommentDirectives,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,50 +116,91 @@ export function parseLanguage(className: string): string | undefined {
|
||||||
* Parses the code content, strips away any magic comments, and returns the
|
* Parses the code content, strips away any magic comments, and returns the
|
||||||
* clean content and the highlighted lines marked by the comments or metastring.
|
* clean content and the highlighted lines marked by the comments or metastring.
|
||||||
*
|
*
|
||||||
* If the metastring contains highlight range, the `content` will be returned
|
* If the metastring contains a range, the `content` will be returned as-is
|
||||||
* as-is without any parsing.
|
* without any parsing. The returned `lineClassNames` will be a map from that
|
||||||
|
* number range to the first magic comment config entry (which _should_ be for
|
||||||
|
* line highlight directives.)
|
||||||
*
|
*
|
||||||
* @param content The raw code with magic comments. Trailing newline will be
|
* @param content The raw code with magic comments. Trailing newline will be
|
||||||
* trimmed upfront.
|
* trimmed upfront.
|
||||||
* @param metastring The full metastring, as received from MDX. Highlight range
|
* @param options Options for parsing behavior.
|
||||||
* declared here starts at 1.
|
|
||||||
* @param language Language of the code block, used to determine which kinds of
|
|
||||||
* magic comment styles to enable.
|
|
||||||
*/
|
*/
|
||||||
export function parseLines(
|
export function parseLines(
|
||||||
content: string,
|
content: string,
|
||||||
metastring?: string,
|
options: {
|
||||||
language?: string,
|
/**
|
||||||
|
* The full metastring, as received from MDX. Line ranges declared here
|
||||||
|
* start at 1.
|
||||||
|
*/
|
||||||
|
metastring: string | undefined;
|
||||||
|
/**
|
||||||
|
* Language of the code block, used to determine which kinds of magic
|
||||||
|
* comment styles to enable.
|
||||||
|
*/
|
||||||
|
language: string | undefined;
|
||||||
|
/**
|
||||||
|
* Magic comment types that we should try to parse. Each entry would
|
||||||
|
* correspond to one class name to apply to each line.
|
||||||
|
*/
|
||||||
|
magicComments: MagicCommentConfig[];
|
||||||
|
},
|
||||||
): {
|
): {
|
||||||
/**
|
/**
|
||||||
* The highlighted lines, 0-indexed. e.g. `[0, 1, 4]` means the 1st, 2nd, and
|
* The highlighted lines, 0-indexed. e.g. `{ 0: ["highlight", "sample"] }`
|
||||||
* 5th lines are highlighted.
|
* means the 1st line should have `highlight` and `sample` as class names.
|
||||||
*/
|
*/
|
||||||
highlightLines: number[];
|
lineClassNames: {[lineIndex: number]: string[]};
|
||||||
/**
|
/**
|
||||||
* The clean code without any magic comments (only if highlight range isn't
|
* If there's number range declared in the metastring, the code block is
|
||||||
* present in the metastring).
|
* returned as-is (no parsing); otherwise, this is the clean code with all
|
||||||
|
* magic comments stripped away.
|
||||||
*/
|
*/
|
||||||
code: string;
|
code: string;
|
||||||
} {
|
} {
|
||||||
let code = content.replace(/\n$/, '');
|
let code = content.replace(/\n$/, '');
|
||||||
|
const {language, magicComments, metastring} = options;
|
||||||
// Highlighted lines specified in props: don't parse the content
|
// Highlighted lines specified in props: don't parse the content
|
||||||
if (metastring && highlightLinesRangeRegex.test(metastring)) {
|
if (metastring && metastringLinesRangeRegex.test(metastring)) {
|
||||||
const highlightLinesRange = metastring.match(highlightLinesRangeRegex)!
|
const linesRange = metastring.match(metastringLinesRangeRegex)!.groups!
|
||||||
.groups!.range!;
|
.range!;
|
||||||
const highlightLines = rangeParser(highlightLinesRange)
|
if (magicComments.length === 0) {
|
||||||
|
throw new Error(
|
||||||
|
`A highlight range has been given in code block's metastring (\`\`\` ${metastring}), but no magic comment config is available. Docusaurus applies the first magic comment entry's className for metastring ranges.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const metastringRangeClassName = magicComments[0]!.className;
|
||||||
|
const lines = rangeParser(linesRange)
|
||||||
.filter((n) => n > 0)
|
.filter((n) => n > 0)
|
||||||
.map((n) => n - 1);
|
.map((n) => [n - 1, [metastringRangeClassName]]);
|
||||||
return {highlightLines, code};
|
return {lineClassNames: Object.fromEntries(lines), code};
|
||||||
}
|
}
|
||||||
if (language === undefined) {
|
if (language === undefined) {
|
||||||
return {highlightLines: [], code};
|
return {lineClassNames: {}, code};
|
||||||
}
|
}
|
||||||
const directiveRegex = getAllMagicCommentDirectiveStyles(language);
|
const directiveRegex = getAllMagicCommentDirectiveStyles(
|
||||||
|
language,
|
||||||
|
magicComments,
|
||||||
|
);
|
||||||
// Go through line by line
|
// Go through line by line
|
||||||
const lines = code.split('\n');
|
const lines = code.split('\n');
|
||||||
let highlightBlockStart: number;
|
const blocks = Object.fromEntries(
|
||||||
let highlightRange = '';
|
magicComments.map((d) => [d.className, {start: 0, range: ''}]),
|
||||||
|
);
|
||||||
|
const lineToClassName: {[comment: string]: string} = Object.fromEntries(
|
||||||
|
magicComments
|
||||||
|
.filter((d) => d.line)
|
||||||
|
.map(({className, line}) => [line, className]),
|
||||||
|
);
|
||||||
|
const blockStartToClassName: {[comment: string]: string} = Object.fromEntries(
|
||||||
|
magicComments
|
||||||
|
.filter((d) => d.block)
|
||||||
|
.map(({className, block}) => [block!.start, className]),
|
||||||
|
);
|
||||||
|
const blockEndToClassName: {[comment: string]: string} = Object.fromEntries(
|
||||||
|
magicComments
|
||||||
|
.filter((d) => d.block)
|
||||||
|
.map(({className, block}) => [block!.end, className]),
|
||||||
|
);
|
||||||
for (let lineNumber = 0; lineNumber < lines.length; ) {
|
for (let lineNumber = 0; lineNumber < lines.length; ) {
|
||||||
const line = lines[lineNumber]!;
|
const line = lines[lineNumber]!;
|
||||||
const match = line.match(directiveRegex);
|
const match = line.match(directiveRegex);
|
||||||
|
@ -151,28 +209,27 @@ export function parseLines(
|
||||||
lineNumber += 1;
|
lineNumber += 1;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const directive = match.slice(1).find((item) => item !== undefined);
|
const directive = match.slice(1).find((item) => item !== undefined)!;
|
||||||
switch (directive) {
|
if (lineToClassName[directive]) {
|
||||||
case 'highlight-next-line':
|
blocks[lineToClassName[directive]!]!.range += `${lineNumber},`;
|
||||||
highlightRange += `${lineNumber},`;
|
} else if (blockStartToClassName[directive]) {
|
||||||
break;
|
blocks[blockStartToClassName[directive]!]!.start = lineNumber;
|
||||||
|
} else if (blockEndToClassName[directive]) {
|
||||||
case 'highlight-start':
|
blocks[blockEndToClassName[directive]!]!.range += `${
|
||||||
highlightBlockStart = lineNumber;
|
blocks[blockEndToClassName[directive]!]!.start
|
||||||
break;
|
}-${lineNumber - 1},`;
|
||||||
|
|
||||||
case 'highlight-end':
|
|
||||||
highlightRange += `${highlightBlockStart!}-${lineNumber - 1},`;
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
lines.splice(lineNumber, 1);
|
lines.splice(lineNumber, 1);
|
||||||
}
|
}
|
||||||
const highlightLines = rangeParser(highlightRange);
|
|
||||||
code = lines.join('\n');
|
code = lines.join('\n');
|
||||||
return {highlightLines, code};
|
const lineClassNames: {[lineIndex: number]: string[]} = {};
|
||||||
|
Object.entries(blocks).forEach(([className, {range}]) => {
|
||||||
|
rangeParser(range).forEach((l) => {
|
||||||
|
lineClassNames[l] ??= [];
|
||||||
|
lineClassNames[l]!.push(className);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return {lineClassNames, code};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getPrismCssVariables(prismTheme: PrismTheme): CSSProperties {
|
export function getPrismCssVariables(prismTheme: PrismTheme): CSSProperties {
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
||||||
import type {PrismTheme} from 'prism-react-renderer';
|
import type {PrismTheme} from 'prism-react-renderer';
|
||||||
import type {DeepPartial} from 'utility-types';
|
import type {DeepPartial} from 'utility-types';
|
||||||
|
import type {MagicCommentConfig} from './codeBlockUtils';
|
||||||
|
|
||||||
export type DocsVersionPersistence = 'localStorage' | 'none';
|
export type DocsVersionPersistence = 'localStorage' | 'none';
|
||||||
|
|
||||||
|
@ -57,6 +58,7 @@ export type PrismConfig = {
|
||||||
darkTheme?: PrismTheme;
|
darkTheme?: PrismTheme;
|
||||||
defaultLanguage?: string;
|
defaultLanguage?: string;
|
||||||
additionalLanguages: string[];
|
additionalLanguages: string[];
|
||||||
|
magicComments: MagicCommentConfig[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FooterLinkItem = {
|
export type FooterLinkItem = {
|
||||||
|
|
|
@ -700,9 +700,28 @@ Accepted fields:
|
||||||
| `theme` | `PrismTheme` | `palenight` | The Prism theme to use for light-theme code blocks. |
|
| `theme` | `PrismTheme` | `palenight` | The Prism theme to use for light-theme code blocks. |
|
||||||
| `darkTheme` | `PrismTheme` | `palenight` | The Prism theme to use for dark-theme code blocks. |
|
| `darkTheme` | `PrismTheme` | `palenight` | The Prism theme to use for dark-theme code blocks. |
|
||||||
| `defaultLanguage` | `string` | `undefined` | The side of the navbar this item should appear on. |
|
| `defaultLanguage` | `string` | `undefined` | The side of the navbar this item should appear on. |
|
||||||
|
| `magicComments` | `MagicCommentConfig[]` | _see below_ | The list of [magic comments](../../guides/markdown-features/markdown-features-code-blocks.mdx#custom-magic-comments). |
|
||||||
|
|
||||||
</APITable>
|
</APITable>
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type MagicCommentConfig = {
|
||||||
|
className: string;
|
||||||
|
line?: string;
|
||||||
|
block?: {start: string; end: string};
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
```js
|
||||||
|
const defaultMagicComments = [
|
||||||
|
{
|
||||||
|
className: 'theme-code-block-highlighted-line',
|
||||||
|
line: 'highlight-next-line',
|
||||||
|
block: {start: 'highlight-start', end: 'highlight-end'},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
### Theme {#theme}
|
### Theme {#theme}
|
||||||
|
|
||||||
By default, we use [Palenight](https://github.com/FormidableLabs/prism-react-renderer/blob/master/src/themes/palenight.js) as syntax highlighting theme. You can specify a custom theme from the [list of available themes](https://github.com/FormidableLabs/prism-react-renderer/tree/master/src/themes). You may also use a different syntax highlighting theme when the site is in dark mode.
|
By default, we use [Palenight](https://github.com/FormidableLabs/prism-react-renderer/blob/master/src/themes/palenight.js) as syntax highlighting theme. You can specify a custom theme from the [list of available themes](https://github.com/FormidableLabs/prism-react-renderer/tree/master/src/themes). You may also use a different syntax highlighting theme when the site is in dark mode.
|
||||||
|
|
|
@ -196,7 +196,7 @@ Supported commenting syntax:
|
||||||
| Bash-style | `# ...` |
|
| Bash-style | `# ...` |
|
||||||
| HTML-style | `<!-- ... -->` |
|
| HTML-style | `<!-- ... -->` |
|
||||||
|
|
||||||
We will do our best to infer which set of comment styles to use based on the language, and default to allowing _all_ comment styles. If there's a comment style that is not currently supported, we are open to adding them! Pull requests welcome.
|
We will do our best to infer which set of comment styles to use based on the language, and default to allowing _all_ comment styles. If there's a comment style that is not currently supported, we are open to adding them! Pull requests welcome. Note that different comment styles have no semantic difference, only their content does.
|
||||||
|
|
||||||
You can set your own background color for highlighted code line in your `src/css/custom.css` which will better fit to your selected syntax highlighting theme. The color given below works for the default highlighting theme (Palenight), so if you are using another theme, you will have to tweak the color accordingly.
|
You can set your own background color for highlighted code line in your `src/css/custom.css` which will better fit to your selected syntax highlighting theme. The color given below works for the default highlighting theme (Palenight), so if you are using another theme, you will have to tweak the color accordingly.
|
||||||
|
|
||||||
|
@ -272,10 +272,104 @@ Prefer highlighting with comments where you can. By inlining highlight in the co
|
||||||
```
|
```
|
||||||
````
|
````
|
||||||
|
|
||||||
In the future, we may extend the magic comment system and let you define custom directives and their functionalities. The magic comments would only be parsed if a highlight metastring is not present.
|
Below, we will introduce how the magic comment system can be extended to define custom directives and their functionalities. The magic comments would only be parsed if a highlight metastring is not present.
|
||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
### Custom magic comments {#custom-magic-comments}
|
||||||
|
|
||||||
|
`// highlight-next-line` and `// highlight-start` etc. are called "magic comments", because they will be parsed and removed, and their purposes are to add metadata to the next line, or the section that the pair of start- and end-comments enclose.
|
||||||
|
|
||||||
|
You can declare custom magic comments through theme config. For example, you can register another magic comment that adds a `code-block-error-line` class name:
|
||||||
|
|
||||||
|
<Tabs>
|
||||||
|
<TabItem value="docusaurus.config.js">
|
||||||
|
|
||||||
|
```js
|
||||||
|
module.exports = {
|
||||||
|
themeConfig: {
|
||||||
|
prism: {
|
||||||
|
magicComments: [
|
||||||
|
// Remember to extend the default highlight class name as well!
|
||||||
|
{
|
||||||
|
className: 'theme-code-block-highlighted-line',
|
||||||
|
line: 'highlight-next-line',
|
||||||
|
block: {start: 'highlight-start', end: 'highlight-end'},
|
||||||
|
},
|
||||||
|
// highlight-start
|
||||||
|
{
|
||||||
|
className: 'code-block-error-line',
|
||||||
|
line: 'This will error',
|
||||||
|
},
|
||||||
|
// highlight-end
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem value="src/css/custom.css">
|
||||||
|
|
||||||
|
```css
|
||||||
|
.code-block-error-line {
|
||||||
|
background-color: #ff000020;
|
||||||
|
display: block;
|
||||||
|
margin: 0 calc(-1 * var(--ifm-pre-padding));
|
||||||
|
padding: 0 var(--ifm-pre-padding);
|
||||||
|
border-left: 3px solid #ff000080;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem value="myDoc.md">
|
||||||
|
|
||||||
|
````md
|
||||||
|
In TypeScript, types help prevent runtime errors.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
function greet(name: string) {
|
||||||
|
// This will error
|
||||||
|
console.log(name.toUpper());
|
||||||
|
// .toUpper doesn't exist on string
|
||||||
|
}
|
||||||
|
```
|
||||||
|
````
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
````mdx-code-block
|
||||||
|
<BrowserWindow>
|
||||||
|
|
||||||
|
In TypeScript, types help prevent runtime errors.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
function greet(name: string) {
|
||||||
|
// This will error
|
||||||
|
console.log(name.toUpper());
|
||||||
|
// .toUpper doesn't exist on string
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
</BrowserWindow>
|
||||||
|
````
|
||||||
|
|
||||||
|
If you use number ranges in metastring (the `{1,3-4}` syntax), Docusaurus will apply the **first `magicComments` entry**'s class name. This, by default, is `theme-code-block-highlighted-line`, but if you change the `magicComments` config and use a different entry as the first one, the meaning of the metastring range will change as well.
|
||||||
|
|
||||||
|
You can disable the default line highlighting comments with `magicComments: []`. If there's no magic comment config, but Docusaurus encounters a code block containing a metastring range, it will error because there will be no class name to apply—the highlighting class name, after all, is just a magic comment entry.
|
||||||
|
|
||||||
|
Every magic comment entry will contain three keys: `className` (required), `line`, which applies to the directly next line, or `block` (containing `start` and `end`), which applies to the entire block enclosed by the two comments.
|
||||||
|
|
||||||
|
Using CSS to target the class can already do a lot, but you can unlock the full potential of this feature through [swizzling](../../swizzling.md).
|
||||||
|
|
||||||
|
```bash npm2yarn
|
||||||
|
npm run swizzle @docusaurus/theme-classic CodeBlock/Line
|
||||||
|
```
|
||||||
|
|
||||||
|
The `Line` component will receive the list of class names, based on which you can conditionally render different markup.
|
||||||
|
|
||||||
## Line numbering {#line-numbering}
|
## Line numbering {#line-numbering}
|
||||||
|
|
||||||
You can enable line numbering for your code block by using `showLineNumbers` key within the language meta string (don't forget to add space directly before the key).
|
You can enable line numbering for your code block by using `showLineNumbers` key within the language meta string (don't forget to add space directly before the key).
|
||||||
|
|
|
@ -378,6 +378,17 @@ const config = {
|
||||||
// TODO after we have forked prism-react-renderer, we should tweak the
|
// TODO after we have forked prism-react-renderer, we should tweak the
|
||||||
// import order and fix it there
|
// import order and fix it there
|
||||||
additionalLanguages: ['java', 'markdown', 'latex'],
|
additionalLanguages: ['java', 'markdown', 'latex'],
|
||||||
|
magicComments: [
|
||||||
|
{
|
||||||
|
className: 'theme-code-block-highlighted-line',
|
||||||
|
line: 'highlight-next-line',
|
||||||
|
block: {start: 'highlight-start', end: 'highlight-end'},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
className: 'code-block-error-line',
|
||||||
|
line: 'This will error',
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
image: 'img/docusaurus-soc.png',
|
image: 'img/docusaurus-soc.png',
|
||||||
// metadata: [{name: 'twitter:card', content: 'summary'}],
|
// metadata: [{name: 'twitter:card', content: 'summary'}],
|
||||||
|
|
|
@ -207,3 +207,11 @@ div[class^='announcementBar_'] {
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
padding: 0.2rem 0.5rem;
|
padding: 0.2rem 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.code-block-error-line {
|
||||||
|
background-color: #ff000020;
|
||||||
|
display: block;
|
||||||
|
margin: 0 calc(-1 * var(--ifm-pre-padding));
|
||||||
|
padding: 0 var(--ifm-pre-padding);
|
||||||
|
border-left: 3px solid #ff000080;
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue