summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/components/MDX/CodeBlock/CodeBlock.tsx1
-rw-r--r--src/pages/article/[slug].tsx1
-rw-r--r--src/styles/base/_colors.scss40
-rw-r--r--src/styles/vendors/_prism.scss10
-rw-r--r--src/utils/plugins/prism-color-scheme.js252
-rw-r--r--tsconfig.json7
6 files changed, 309 insertions, 2 deletions
diff --git a/src/components/MDX/CodeBlock/CodeBlock.tsx b/src/components/MDX/CodeBlock/CodeBlock.tsx
index 8311999..a822744 100644
--- a/src/components/MDX/CodeBlock/CodeBlock.tsx
+++ b/src/components/MDX/CodeBlock/CodeBlock.tsx
@@ -4,6 +4,7 @@ import { useRouter } from 'next/router';
import Prism from 'prismjs';
import { ReactChildren, useEffect } from 'react';
import { useIntl } from 'react-intl';
+import '@utils/plugins/prism-color-scheme';
const CodeBlock = ({
className,
diff --git a/src/pages/article/[slug].tsx b/src/pages/article/[slug].tsx
index dc2c76a..1799fb0 100644
--- a/src/pages/article/[slug].tsx
+++ b/src/pages/article/[slug].tsx
@@ -20,6 +20,7 @@ import { ParsedUrlQuery } from 'querystring';
import { useEffect } from 'react';
import { useIntl } from 'react-intl';
import { Blog, BlogPosting, Graph, WebPage } from 'schema-dts';
+import '@utils/plugins/prism-color-scheme';
const SingleArticle: NextPageWithLayout<ArticleProps> = ({ post }) => {
const {
diff --git a/src/styles/base/_colors.scss b/src/styles/base/_colors.scss
index 5d945bb..a927368 100644
--- a/src/styles/base/_colors.scss
+++ b/src/styles/base/_colors.scss
@@ -66,3 +66,43 @@
--color-token-yellow: #{var.$dark-theme_yellow};
--color-token-orange: #{var.$dark-theme_orange};
}
+
+pre[data-prismjs-color-scheme="light"] {
+ --color-bg: #{var.$light-theme_white};
+ --color-bg-secondary: #{var.$light-theme_white-dark};
+ --color-bg-tertiary: #{var.$light-theme_grey-bright};
+ --color-fg: #{var.$light-theme_black};
+ --color-fg-light: #{var.$light-theme_grey-dark};
+ --color-primary: #{var.$light-theme_blue};
+ --color-primary-darker: #{var.$light-theme_blue-darker};
+ --color-border: #{var.$light-theme_grey};
+ --color-border-dark: #{var.$light-theme_grey-dark};
+ --color-token-red: #{var.$light-theme_red};
+ --color-token-green: #{var.$light-theme_green};
+ --color-token-purple: #{var.$light-theme_purple};
+ --color-token-magenta: #{var.$light-theme_magenta};
+ --color-token-cyan: #{var.$light-theme_cyan};
+ --color-token-blue: #{var.$light-theme_blue};
+ --color-token-yellow: #{var.$light-theme_yellow};
+ --color-token-orange: #{var.$light-theme_orange};
+}
+
+pre[data-prismjs-color-scheme="dark"] {
+ --color-bg: #{var.$dark-theme_black};
+ --color-bg-secondary: #{var.$dark-theme_black-bright};
+ --color-bg-tertiary: #{var.$dark-theme_grey-darker};
+ --color-fg: #{var.$dark-theme_white};
+ --color-fg-light: #{var.$dark-theme_grey};
+ --color-primary: #{var.$dark-theme_blue};
+ --color-primary-darker: #{var.$dark-theme_blue-darker};
+ --color-border: #{var.$dark-theme_grey-dark};
+ --color-border-dark: #{var.$dark-theme_grey};
+ --color-token-red: #{var.$dark-theme_red};
+ --color-token-green: #{var.$dark-theme_green};
+ --color-token-purple: #{var.$dark-theme_purple};
+ --color-token-magenta: #{var.$dark-theme_magenta};
+ --color-token-cyan: #{var.$dark-theme_cyan};
+ --color-token-blue: #{var.$dark-theme_blue};
+ --color-token-yellow: #{var.$dark-theme_yellow};
+ --color-token-orange: #{var.$dark-theme_orange};
+}
diff --git a/src/styles/vendors/_prism.scss b/src/styles/vendors/_prism.scss
index 8328114..87eba2c 100644
--- a/src/styles/vendors/_prism.scss
+++ b/src/styles/vendors/_prism.scss
@@ -49,6 +49,11 @@
}
}
}
+
+ .toolbar-item:nth-child(3) {
+ order: 3;
+ margin-left: var(--spacing-2xs);
+ }
}
pre[class*="language-"] {
@@ -56,6 +61,8 @@ pre[class*="language-"] {
margin: var(--spacing-md) 0;
padding: 0;
position: relative;
+ background: var(--color-bg-secondary);
+ color: var(--color-fg);
border: fun.convert-px(1) solid var(--color-border);
> code {
@@ -235,7 +242,8 @@ pre.command-line {
}
}
-.copy-to-clipboard-button {
+.copy-to-clipboard-button,
+.prism-color-scheme-button {
display: block;
padding: 0 var(--spacing-xs);
background: var(--color-bg);
diff --git a/src/utils/plugins/prism-color-scheme.js b/src/utils/plugins/prism-color-scheme.js
new file mode 100644
index 0000000..93a8e7a
--- /dev/null
+++ b/src/utils/plugins/prism-color-scheme.js
@@ -0,0 +1,252 @@
+(function () {
+ if (typeof Prism === 'undefined' || typeof document === 'undefined') {
+ return;
+ }
+
+ if (!Prism.plugins.toolbar) {
+ console.warn('Color scheme plugin loaded before Toolbar plugin.');
+
+ return;
+ }
+
+ /**
+ *
+ * @typedef {"dark" | "light" | "system"} Theme
+ * @typedef {Record<"color-scheme", Theme> & Record<"button-prefix" | "dark" | "light", string>} Settings
+ */
+
+ var storage = {
+ /**
+ * Get a deserialized value from local storage.
+ *
+ * @param {string} key - The local storage key.
+ * @returns {string | undefined} The value of the given key.
+ */
+ get: function (key) {
+ var serializedItem = localStorage.getItem(key);
+ return serializedItem ? JSON.parse(serializedItem) : undefined;
+ },
+ /**
+ * Set or update a local storage key with a new serialized value.
+ *
+ * @param {string} key - The local storage key.
+ * @param {string} value - The value of the given key.
+ */
+ set: function (key, value) {
+ var serializedValue = JSON.stringify(value);
+ localStorage.setItem(key, serializedValue);
+ },
+ };
+
+ /**
+ * Check if user has set its color scheme preference.
+ *
+ * @returns {boolean} True if user prefers dark color scheme.
+ */
+ function prefersDarkScheme() {
+ return (
+ window.matchMedia &&
+ window.matchMedia('(prefers-color-scheme: dark)').matches
+ );
+ }
+
+ /**
+ * Get the theme that matches the system theme.
+ *
+ * @returns {Theme} The theme to use.
+ */
+ function getThemeFromSystem() {
+ return prefersDarkScheme() ? 'dark' : 'light';
+ }
+
+ /**
+ * Check if the provided string is a valid theme.
+ *
+ * @param {string} theme - A theme to check.
+ * @returns {boolean} True if it is a valid theme.
+ */
+ function isValidTheme(theme) {
+ return theme === 'dark' || theme === 'light' || theme === 'system';
+ }
+
+ /**
+ * Set the default theme depending on user preferences.
+ *
+ * @returns {Theme} The default theme.
+ */
+ function setDefaultTheme() {
+ var theme = storage.get('prismjs-color-scheme');
+
+ return theme && isValidTheme(theme) ? theme : 'system';
+ }
+
+ /**
+ * Traverses up the DOM tree to find data attributes that override the
+ * default plugin settings.
+ *
+ * @param {Element} startElement - An element to start from.
+ * @returns {Settings} The plugin settings.
+ */
+ function getSettings(startElement) {
+ /** @type Settings */
+ var settings = {
+ 'color-scheme': setDefaultTheme(),
+ dark: 'Dark Theme',
+ light: 'Light Theme',
+ 'button-prefix': 'Toggle',
+ };
+ var prefix = 'data-prismjs-';
+
+ for (var key in settings) {
+ var attr = prefix + key;
+ var element = startElement;
+
+ while (element && !element.hasAttribute(attr)) {
+ element = element.parentElement;
+ }
+
+ if (element) {
+ settings[key] = element.getAttribute(attr);
+ }
+ }
+
+ return settings;
+ }
+
+ /**
+ * Retrieve the new theme depending on current theme value.
+ *
+ * @param {Theme} currentTheme - The current theme.
+ * @returns {Theme} The new theme.
+ */
+ function getNewTheme(currentTheme) {
+ switch (currentTheme) {
+ case 'light':
+ return 'dark';
+ case 'dark':
+ return 'light';
+ case 'system':
+ default:
+ return getNewTheme(getThemeFromSystem());
+ }
+ }
+
+ /**
+ * Get the button content depending on current theme.
+ *
+ * @param {string} prefix - The text prefix.
+ * @param {Theme} theme - The current theme.
+ * @param {Settings} settings - The plugin settings.
+ * @returns {string} The button text.
+ */
+ function getButtonContent(prefix, theme, settings) {
+ if (theme === 'dark') {
+ return `${prefix}${settings['light']}`;
+ }
+
+ return `${prefix}${settings['dark']}`;
+ }
+
+ /**
+ * Update the button text depending on the current theme.
+ *
+ * @param {HTMLButtonElement} button - The color scheme button.
+ * @param {Settings} settings - The plugin settings.
+ */
+ function updateButtonText(button, settings) {
+ var prefix = settings['button-prefix']
+ ? `${settings['button-prefix']} `
+ : '';
+ var theme = settings['color-scheme'];
+
+ if (theme === 'system') {
+ theme = getThemeFromSystem();
+ }
+
+ button.textContent = getButtonContent(prefix, theme, settings);
+ }
+
+ /**
+ * Update pre data-prismjs-color-scheme attribute.
+ *
+ * @param {HTMLPreElement} pre - The pre element wrapping the code.
+ * @param {Theme} theme - The current theme.
+ */
+ function updatePreAttribute(pre, theme) {
+ pre.setAttribute('data-prismjs-color-scheme', theme);
+ }
+
+ /**
+ * Update pre attribute for all code blocks.
+ *
+ * @param {Theme} theme - The new theme.
+ */
+ function switchTheme(theme) {
+ var allPre = document.querySelectorAll('pre[data-prismjs-color-scheme]');
+ allPre.forEach((pre) => {
+ updatePreAttribute(pre, theme);
+ });
+ }
+
+ /**
+ * Set current theme on pre attribute change.
+ *
+ * @param {HTMLPreElement} pre - The pre element wrapping the code.
+ * @param {Settings} settings - The plugin settings.
+ */
+ function listenAttributeChange(pre, settings) {
+ var observer = new MutationObserver(function (mutations) {
+ mutations.forEach((record) => {
+ var mutatedPre = record.target;
+ var button = mutatedPre.parentElement.querySelector(
+ '.prism-color-scheme-button'
+ );
+ var newTheme = mutatedPre.getAttribute('data-prismjs-color-scheme');
+ settings['color-scheme'] = newTheme;
+ updateButtonText(button, settings);
+ });
+ });
+ observer.observe(pre, {
+ attributes: true,
+ attributeFilter: ['data-prismjs-color-scheme'],
+ });
+ }
+
+ /**
+ * Create a color scheme button.
+ *
+ * @param {Object<string, any>} env - The environment variables of the hook.
+ * @returns {HTMLButtonElement} The color scheme button.
+ */
+ function getColorSchemeButton(env) {
+ var element = env.element;
+ var pre = element.parentElement;
+ var settings = getSettings(element);
+ var themeButton = document.createElement('button');
+ themeButton.className = 'prism-color-scheme-button';
+ themeButton.setAttribute('type', 'button');
+ updateButtonText(themeButton, settings);
+ updatePreAttribute(pre, settings['color-scheme']);
+ listenAttributeChange(pre, settings);
+
+ themeButton.addEventListener('click', () => {
+ var newTheme = getNewTheme(settings['color-scheme']);
+ switchTheme(newTheme);
+ storage.set('prismjs-color-scheme', newTheme);
+ });
+
+ window.addEventListener('storage', (e) => {
+ if (e.key === 'prismjs-color-scheme') {
+ const newTheme = JSON.parse(e.newValue);
+ if (isValidTheme(newTheme)) updatePreAttribute(pre, newTheme);
+ }
+ });
+
+ return themeButton;
+ }
+
+ /**
+ * Register a new button in Prism toolbar plugin.
+ */
+ Prism.plugins.toolbar.registerButton('color-scheme', getColorSchemeButton);
+})();
diff --git a/tsconfig.json b/tsconfig.json
index dac9026..e110340 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -27,6 +27,11 @@
"@ts/*": ["src/ts/*"]
}
},
- "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
+ "include": [
+ "next-env.d.ts",
+ "**/*.ts",
+ "**/*.tsx",
+ "src/utils/plugins/prism-color-scheme.js"
+ ],
"exclude": ["node_modules"]
}