diff --git a/website/docusaurus.config.js b/website/docusaurus.config.js index f1668f7..41ba867 100644 --- a/website/docusaurus.config.js +++ b/website/docusaurus.config.js @@ -42,7 +42,9 @@ const config = { ], copyright: `Copyright © ${new Date().getFullYear()} ByteBardOrg.`, }, - prism: {theme: prismThemes.github, darkTheme: prismThemes.dracula, additionalLanguages: ['csharp']}, + // Code is highlighted with Shiki (src/theme/CodeBlock/Content); these Prism themes only color the code block + // around it, and match the background of Shiki's Visual Studio Code themes. + prism: {theme: prismThemes.vsLight, darkTheme: prismThemes.vsDark}, }, }; diff --git a/website/package-lock.json b/website/package-lock.json index 17d213e..c46a079 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -14,7 +14,8 @@ "clsx": "^2.1.1", "prism-react-renderer": "^2.4.1", "react": "^18.0.0", - "react-dom": "^18.0.0" + "react-dom": "^18.0.0", + "shiki": "^4.5.0" }, "devDependencies": { "@docusaurus/module-type-aliases": "^3.8.1", @@ -4963,6 +4964,106 @@ "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", "license": "MIT" }, + "node_modules/@shikijs/core": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-4.5.0.tgz", + "integrity": "sha512-6CIgwq+bt2JZ3OSjb/nBkkwEf/CfpHcbam+Mg5Pllco7HAGRQ2/EFhZrrJtbyNTgKwknfVS+llsXGygiDGwJWw==", + "license": "MIT", + "dependencies": { + "@shikijs/primitive": "4.5.0", + "@shikijs/types": "4.5.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.5", + "hast-util-to-html": "^9.0.5" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/engine-javascript": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-4.5.0.tgz", + "integrity": "sha512-Ed6UdEj84LyYkwnxM5CvSMWAD+id9ExTYri0/+x41U9zrgNifPSS9lZm0JqBP0VfYI8yr8tY5nbNq9+p9R0kFA==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.5.0", + "@shikijs/vscode-textmate": "^10.0.2", + "oniguruma-to-es": "^4.3.6" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/engine-oniguruma": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-4.5.0.tgz", + "integrity": "sha512-dKVmbJecVdB9recl8a/CU4jdpNXyjJQrIcesIkpS70s5HS02ohpnCSpMUbotPnEUzbAcLQ+S4hwZYbkBBiTcJw==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.5.0", + "@shikijs/vscode-textmate": "^10.0.2" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/langs": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-4.5.0.tgz", + "integrity": "sha512-wwBTqwduXSz0X4v9Qa2EWeFaYJEw1clCncWjoRNQRbVDu3SPRTRWucnouYapKj3b8Q+sk/41Vbc4B5c8nuYzLA==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.5.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/primitive": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/primitive/-/primitive-4.5.0.tgz", + "integrity": "sha512-UM7ShlA8oz0OWxLLO8BmO9Ip6fVqdkt8yF0EH7nOCqHfrx1eEAzF0ZUVK7L+I8NEgeu1PEWSRlxaiPPeCb4iDg==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.5.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.5" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/themes": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-4.5.0.tgz", + "integrity": "sha512-zGb87H3UBbpuI4yoh5UguB1XbU+2BDU3kflM7UfZ+Cy2hWo9lBtqjSVK20gmLY6ukYl040hAypGXMgud9vul8Q==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.5.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/types": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-4.5.0.tgz", + "integrity": "sha512-drtPrtD8N0DENyD2cyt6Ajy+IWsiCLgMxODwLTpvH1UsN6a9jf90tNTPjqwHvA+0Lzu9e0txk8DjfqH1p9UJRw==", + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.5" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/vscode-textmate": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.1.0.tgz", + "integrity": "sha512-cVBuhDQjBZaNMTwBquYfPydrUP+E3hE6oYGasx8h6ZrODIM7/r+gxLDXdd66oszbrggZHEntdE6j+5gYIVyMWQ==", + "license": "MIT" + }, "node_modules/@sideway/address": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", @@ -9248,6 +9349,29 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/hast-util-to-html": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", + "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/hast-util-to-jsx-runtime": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", @@ -13376,6 +13500,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/oniguruma-parser": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/oniguruma-parser/-/oniguruma-parser-0.12.2.tgz", + "integrity": "sha512-6HVa5oIrgMC6aA6WF6XyyqbhRPJrKR02L20+2+zpDtO5QAzGHAUGw5TKQvwi5vctNnRHkJYmjAhRVQF2EKdTQw==", + "license": "MIT" + }, + "node_modules/oniguruma-to-es": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-4.3.6.tgz", + "integrity": "sha512-csuQ9x3Yr0cEIs/Zgx/OEt9iBw9vqIunAPQkx19R/fiMq2oGVTgcMqO/V3Ybqefr1TBvosI6jU539ksaBULJyA==", + "license": "MIT", + "dependencies": { + "oniguruma-parser": "^0.12.2", + "regex": "^6.1.0", + "regex-recursion": "^6.0.2" + } + }, "node_modules/open": { "version": "8.4.2", "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", @@ -15758,6 +15899,30 @@ "node": ">=4" } }, + "node_modules/regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/regex/-/regex-6.1.0.tgz", + "integrity": "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==", + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-recursion": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-6.0.2.tgz", + "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==", + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-utilities": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz", + "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==", + "license": "MIT" + }, "node_modules/regexpu-core": { "version": "6.5.3", "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.5.3.tgz", @@ -16644,6 +16809,25 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/shiki": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-4.5.0.tgz", + "integrity": "sha512-OcRW72ngQvc+MSVWBRIovC/BTW589iUE/Vxav7VeM4RcY9MUjqB+IR6ILG1k9nuW6LiG/nct9uma4/MBtnZbmQ==", + "license": "MIT", + "dependencies": { + "@shikijs/core": "4.5.0", + "@shikijs/engine-javascript": "4.5.0", + "@shikijs/engine-oniguruma": "4.5.0", + "@shikijs/langs": "4.5.0", + "@shikijs/themes": "4.5.0", + "@shikijs/types": "4.5.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.5" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/side-channel": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", diff --git a/website/package.json b/website/package.json index 8012291..c5d6f56 100644 --- a/website/package.json +++ b/website/package.json @@ -16,7 +16,8 @@ "clsx": "^2.1.1", "prism-react-renderer": "^2.4.1", "react": "^18.0.0", - "react-dom": "^18.0.0" + "react-dom": "^18.0.0", + "shiki": "^4.5.0" }, "devDependencies": { "@docusaurus/module-type-aliases": "^3.8.1", diff --git a/website/src/css/custom.css b/website/src/css/custom.css index 5224edc..5d15793 100644 --- a/website/src/css/custom.css +++ b/website/src/css/custom.css @@ -153,3 +153,18 @@ html[data-theme='dark'] .navbar__items--right .navbar__link:hover {color: #a87ef html:not([data-theme='dark']) .navbar-sidebar .menu__link--active, html[data-theme='dark'] .navbar-sidebar .menu__link--active {color: #a87efc} } + +/* Code tokens carry both color modes' styles as CSS variables (src/theme/CodeBlock/Content/shiki.js). */ +.shiki-token { + color: var(--shiki-light); + font-style: var(--shiki-light-font-style); + font-weight: var(--shiki-light-font-weight); + text-decoration: var(--shiki-light-text-decoration); +} + +html[data-theme='dark'] .shiki-token { + color: var(--shiki-dark); + font-style: var(--shiki-dark-font-style); + font-weight: var(--shiki-dark-font-weight); + text-decoration: var(--shiki-dark-text-decoration); +} diff --git a/website/src/theme/CodeBlock/Content/index.js b/website/src/theme/CodeBlock/Content/index.js new file mode 100644 index 0000000..24da4e9 --- /dev/null +++ b/website/src/theme/CodeBlock/Content/index.js @@ -0,0 +1,74 @@ +/** + * Swizzled from @docusaurus/theme-classic to highlight code with Shiki instead of Prism. The Prism-shaped + * line and token props are kept, so the theme's Line component still renders line numbers and highlighted lines. + */ +import React, {useMemo} from 'react'; +import clsx from 'clsx'; +import {useCodeBlockContext} from '@docusaurus/theme-common/internal'; +import Line from '@theme/CodeBlock/Line'; +import {highlight} from './shiki'; +import styles from './styles.module.css'; + +const Pre = React.forwardRef((props, ref) => { + return ( +
+ ); +}); + +function Code(props) { + const {metadata} = useCodeBlockContext(); + return ( +
+ );
+}
+
+function getLineProps({className}) {
+ return {className: clsx('token-line', className)};
+}
+
+function getTokenProps({token}) {
+ return {className: 'token shiki-token', style: token.style, children: token.content};
+}
+
+export default function CodeBlockContent({className}) {
+ const {metadata, wordWrap} = useCodeBlockContext();
+ const {code, language, lineNumbersStart, lineClassNames} = metadata;
+ const lines = useMemo(
+ () => highlight(code, language).map((line) => line.map((token) => ({content: token.content, style: token.htmlStyle}))),
+ [code, language],
+ );
+
+ return (
+
+
+ {lines.map((line, i) => (
+
+ ))}
+
+
+ );
+}
diff --git a/website/src/theme/CodeBlock/Content/shiki.js b/website/src/theme/CodeBlock/Content/shiki.js
new file mode 100644
index 0000000..e7e6737
--- /dev/null
+++ b/website/src/theme/CodeBlock/Content/shiki.js
@@ -0,0 +1,28 @@
+import {createHighlighterCoreSync} from 'shiki/core';
+import {createJavaScriptRegexEngine} from 'shiki/engine/javascript';
+import csharp from '@shikijs/langs/csharp';
+import json from '@shikijs/langs/json';
+import shellscript from '@shikijs/langs/shellscript';
+import yaml from '@shikijs/langs/yaml';
+import darkPlus from '@shikijs/themes/dark-plus';
+import lightPlus from '@shikijs/themes/light-plus';
+
+// The Visual Studio Code themes, matching prismThemes.vsLight and prismThemes.vsDark in docusaurus.config.js,
+// which style the rest of the code block.
+const themes = {light: 'light-plus', dark: 'dark-plus'};
+
+// Synchronous, so code blocks render the same on the server and in the browser.
+const highlighter = createHighlighterCoreSync({
+ themes: [lightPlus, darkPlus],
+ langs: [csharp, json, shellscript, yaml],
+ engine: createJavaScriptRegexEngine(),
+});
+
+/**
+ * Tokenizes code into lines of tokens, each styled with both themes' colors as CSS variables
+ * (--shiki-light and --shiki-dark), which custom.css applies for the current color mode.
+ */
+export function highlight(code, language) {
+ const lang = highlighter.getLoadedLanguages().includes(language) ? language : 'text';
+ return highlighter.codeToTokens(code, {lang, themes, defaultColor: false}).tokens;
+}
diff --git a/website/src/theme/CodeBlock/Content/styles.module.css b/website/src/theme/CodeBlock/Content/styles.module.css
new file mode 100644
index 0000000..48030df
--- /dev/null
+++ b/website/src/theme/CodeBlock/Content/styles.module.css
@@ -0,0 +1,35 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+.codeBlock {
+ --ifm-pre-background: var(--prism-background-color);
+ margin: 0;
+ padding: 0;
+}
+
+.codeBlockStandalone {
+ padding: 0;
+}
+
+.codeBlockLines {
+ font: inherit;
+ /* rtl:ignore */
+ float: left;
+ min-width: 100%;
+ padding: var(--ifm-pre-padding);
+}
+
+.codeBlockLinesWithNumbering {
+ display: table;
+ padding: var(--ifm-pre-padding) 0;
+}
+
+@media print {
+ .codeBlockLines {
+ white-space: pre-wrap;
+ }
+}