aboutsummaryrefslogtreecommitdiffstats
path: root/src/utils
diff options
context:
space:
mode:
authorArmand Philippot <git@armandphilippot.com>2022-01-31 17:55:12 +0100
committerArmand Philippot <git@armandphilippot.com>2022-01-31 19:45:08 +0100
commit796bac09eab8259783343ca0db2610345d50496a (patch)
treec89d759d37683b602bd5dcebb68ddee8a042225d /src/utils
parent3a2dfec7f715f92620b76fb07a6c73b881e2a6e1 (diff)
chore: add a Prism plugin to set code blocks theme
Diffstat (limited to 'src/utils')
-rw-r--r--src/utils/plugins/prism-color-scheme.js252
1 files changed, 252 insertions, 0 deletions
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);
+})();