diff --git a/.gitignore b/.gitignore index 4f95603..7dd7cef 100644 --- a/.gitignore +++ b/.gitignore @@ -23,4 +23,5 @@ package-lock.json !.vscode/tasks.json # Misc -#_sass/dist +_sass/vendors +assets/js/dist diff --git a/.stylelintrc.json b/.stylelintrc.json new file mode 100644 index 0000000..b890290 --- /dev/null +++ b/.stylelintrc.json @@ -0,0 +1,31 @@ +{ + "ignoreFiles": ["_sass/vendors/**"], + "extends": "stylelint-config-standard-scss", + "rules": { + "no-descending-specificity": null, + "shorthand-property-no-redundant-values": null, + "at-rule-no-vendor-prefix": null, + "property-no-vendor-prefix": null, + "selector-no-vendor-prefix": null, + "value-no-vendor-prefix": null, + "color-function-notation": "legacy", + "alpha-value-notation": "number", + "selector-not-notation": "simple", + "color-hex-length": "long", + "declaration-block-single-line-max-declarations": 3, + "scss/operator-no-newline-after": null, + "rule-empty-line-before": [ + "always", + { + "ignore": ["after-comment", "first-nested"] + } + ], + "value-keyword-case": [ + "lower", + { + "ignoreProperties": ["/^\\$/"] + } + ], + "media-feature-range-notation": "prefix" + } +} diff --git a/_data/origin/cors.yml b/_data/origin/cors.yml index 1526b06..62fd344 100644 --- a/_data/origin/cors.yml +++ b/_data/origin/cors.yml @@ -20,8 +20,8 @@ webfonts: https://fonts.googleapis.com/css2?family=Lato:wght@300;400&family=Sour # Libraries toc: - css: https://cdn.jsdelivr.net/npm/tocbot@4.29.0/dist/tocbot.min.css - js: https://cdn.jsdelivr.net/npm/tocbot@4.29.0/dist/tocbot.min.js + css: https://cdn.jsdelivr.net/npm/tocbot@4.32.2/dist/tocbot.min.css + js: https://cdn.jsdelivr.net/npm/tocbot@4.32.2/dist/tocbot.min.js fontlogos: css: https://cdn.jsdelivr.net/npm/font-logos@1/assets/font-logos.css @@ -30,7 +30,7 @@ forkawesome: css: https://cdn.jsdelivr.net/npm/fork-awesome@1.2.0/css/fork-awesome.min.css fontawesome: - css: https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.6.0/css/all.min.css + css: https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.7.1/css/all.min.css ballooncss: css: https://unpkg.com/balloon-css/balloon.min.css @@ -39,7 +39,7 @@ search: js: https://cdn.jsdelivr.net/npm/simple-jekyll-search@1.10.0/dest/simple-jekyll-search.min.js mermaid: - js: https://cdn.jsdelivr.net/npm/mermaid@11.0.2/dist/mermaid.min.js + js: https://cdn.jsdelivr.net/npm/mermaid@11.4.0/dist/mermaid.min.js dayjs: js: diff --git a/_includes/analytics/cloudflare.html b/_includes/analytics/cloudflare.html index 1eeb1a9..9faa11e 100644 --- a/_includes/analytics/cloudflare.html +++ b/_includes/analytics/cloudflare.html @@ -4,4 +4,3 @@ src="https://static.cloudflareinsights.com/beacon.min.js" data-cf-beacon='{"token": "{{ site.analytics.cloudflare.id }}"}' > - diff --git a/_includes/analytics/fathom.html b/_includes/analytics/fathom.html index 4b603d3..216bb14 100644 --- a/_includes/analytics/fathom.html +++ b/_includes/analytics/fathom.html @@ -2,6 +2,5 @@ - + defer +> diff --git a/_includes/analytics/google.html b/_includes/analytics/google.html index d0aac65..dfe4828 100644 --- a/_includes/analytics/google.html +++ b/_includes/analytics/google.html @@ -1,7 +1,7 @@ - diff --git a/_includes/comments.html b/_includes/comment.html similarity index 100% rename from _includes/comments.html rename to _includes/comment.html diff --git a/_includes/comments/disqus.html b/_includes/comments/disqus.html index 2b889a4..fd12a3c 100644 --- a/_includes/comments/disqus.html +++ b/_includes/comments/disqus.html @@ -1,38 +1,25 @@ - - -
-

Comments powered by Disqus.

-
- - diff --git a/_includes/comments/giscus.html b/_includes/comments/giscus.html index f9becfe..8058472 100644 --- a/_includes/comments/giscus.html +++ b/_includes/comments/giscus.html @@ -1,21 +1,8 @@ - - - diff --git a/_includes/head.html b/_includes/head.html index 833b3fa..4d756c3 100644 --- a/_includes/head.html +++ b/_includes/head.html @@ -57,12 +57,22 @@ endfor %} {% endfor %} {% endunless %} - {% unless jekyll.environment == 'production' %} + {% unless jekyll.environment == 'production' %} <<<<<<< HEAD - {% endunless %} + ||||||| d51345e + + ======= + + >>>>>>> upstream_master {% endunless %} {% endif %} - + - {% unless site.theme_mode %} {% include mode-toggle.html %} {% endunless %} {% - include metadata-hook.html %} + {% unless site.theme_mode %} + + {% endunless %} {% include js-selector.html lang=lang %} {% if + jekyll.environment == 'production' %} + + {% if site.pwa.enabled %} + + {% endif %} + + + {% for analytics in site.analytics %} {% capture str %}{{ analytics }}{% + endcapture %} {% assign platform = str | split: '{' | first %} {% if + site.analytics[platform].id and site.analytics[platform].id != empty %} {% + include analytics/{{ platform }}.html %} {% endif %} {% endfor %} {% endif %} + {% include metadata-hook.html %} diff --git a/_includes/js-selector.html b/_includes/js-selector.html index 931deb5..a437a04 100644 --- a/_includes/js-selector.html +++ b/_includes/js-selector.html @@ -63,12 +63,12 @@ {% capture script %}/assets/js/dist/{{ js }}.min.js{% endcapture %} - + {% if page.math %} - - + + {% endif %} @@ -85,26 +85,3 @@ {% endcase %} {% endif %} {% endif %} - -{% if page.mermaid %} - {% include mermaid.html %} -{% endif %} - -{% if jekyll.environment == 'production' %} - - {% if site.pwa.enabled %} - - {% endif %} - - - {% for analytics in site.analytics %} - {% capture str %}{{ analytics }}{% endcapture %} - {% assign type = str | split: '{' | first %} - {% if site.analytics[type].id and site.analytics[type].id != empty %} - {% include analytics/{{ type }}.html %} - {% endif %} - {% endfor %} -{% endif %} diff --git a/_includes/jsdelivr-combine.html b/_includes/jsdelivr-combine.html index cffa699..0611213 100644 --- a/_includes/jsdelivr-combine.html +++ b/_includes/jsdelivr-combine.html @@ -1,6 +1,6 @@ {% assign urls = include.urls | split: ',' %} -{% assign combined_urls = nil %} +{% assign combined_urls = null %} {% assign domain = 'https://cdn.jsdelivr.net/' %} @@ -15,12 +15,12 @@ {% endif %} {% elsif url contains '//' %} - + {% else %} - + {% endif %} {% endfor %} {% if combined_urls %} - + {% endif %} diff --git a/_includes/mermaid.html b/_includes/mermaid.html deleted file mode 100644 index a3a83ed..0000000 --- a/_includes/mermaid.html +++ /dev/null @@ -1,62 +0,0 @@ - - diff --git a/_includes/mode-toggle.html b/_includes/mode-toggle.html deleted file mode 100644 index 113ec37..0000000 --- a/_includes/mode-toggle.html +++ /dev/null @@ -1,116 +0,0 @@ - - - diff --git a/_includes/search-loader.html b/_includes/search-loader.html index 2582580..7fd065d 100644 --- a/_includes/search-loader.html +++ b/_includes/search-loader.html @@ -19,29 +19,31 @@ {% capture not_found %}

{{ site.data.locales[include.lang].search.no_results }}

{% endcapture %} diff --git a/_includes/toc.html b/_includes/toc.html index 49c8f90..883bf13 100644 --- a/_includes/toc.html +++ b/_includes/toc.html @@ -1,8 +1,9 @@ {% include toc-status.html %} {% if enable_toc %} -
-

{{- site.data.locales[include.lang].panel.toc -}}

+
+
+

{{- site.data.locales[include.lang].panel.toc -}}

{% endif %} diff --git a/_javascript/categories.js b/_javascript/categories.js index 15d8251..ce87d67 100644 --- a/_javascript/categories.js +++ b/_javascript/categories.js @@ -1,5 +1,5 @@ import { basic, initSidebar, initTopbar } from './modules/layouts'; -import { categoryCollapse } from './modules/plugins'; +import { categoryCollapse } from './modules/components'; basic(); initSidebar(); diff --git a/_javascript/home.js b/_javascript/home.js index ef22cb9..7f628a1 100644 --- a/_javascript/home.js +++ b/_javascript/home.js @@ -1,5 +1,5 @@ import { basic, initSidebar, initTopbar } from './modules/layouts'; -import { initLocaleDatetime, loadImg } from './modules/plugins'; +import { initLocaleDatetime, loadImg } from './modules/components'; loadImg(); initLocaleDatetime(); diff --git a/_javascript/misc.js b/_javascript/misc.js index 52b4043..37130da 100644 --- a/_javascript/misc.js +++ b/_javascript/misc.js @@ -1,5 +1,5 @@ import { basic, initSidebar, initTopbar } from './modules/layouts'; -import { initLocaleDatetime } from './modules/plugins'; +import { initLocaleDatetime } from './modules/components'; initSidebar(); initTopbar(); diff --git a/_javascript/modules/plugins.js b/_javascript/modules/components.js similarity index 60% rename from _javascript/modules/plugins.js rename to _javascript/modules/components.js index cc95c1b..95791a6 100644 --- a/_javascript/modules/plugins.js +++ b/_javascript/modules/components.js @@ -4,3 +4,7 @@ export { loadImg } from './components/img-loading'; export { imgPopup } from './components/img-popup'; export { initLocaleDatetime } from './components/locale-datetime'; export { initToc } from './components/toc'; +export { loadMermaid } from './components/mermaid'; +export { modeWatcher } from './components/mode-toggle'; +export { back2top } from './components/back-to-top'; +export { loadTooptip } from './components/tooltip-loader'; diff --git a/_javascript/modules/components/img-popup.js b/_javascript/modules/components/img-popup.js index ac12043..420a226 100644 --- a/_javascript/modules/components/img-popup.js +++ b/_javascript/modules/components/img-popup.js @@ -4,7 +4,6 @@ * Dependencies: https://github.com/biati-digital/glightbox */ -const html = document.documentElement; const lightImages = '.popup:not(.dark)'; const darkImages = '.popup:not(.light)'; let selector = lightImages; @@ -33,26 +32,17 @@ export function imgPopup() { document.querySelector('.popup.dark') === null ); - if ( - (html.hasAttribute('data-mode') && - html.getAttribute('data-mode') === 'dark') || - (!html.hasAttribute('data-mode') && - window.matchMedia('(prefers-color-scheme: dark)').matches) - ) { + if (Theme.visualState === Theme.DARK) { selector = darkImages; } let current = GLightbox({ selector: `${selector}` }); - if (hasDualImages && document.getElementById('mode-toggle')) { + if (hasDualImages && Theme.switchable) { let reverse = null; window.addEventListener('message', (event) => { - if ( - event.source === window && - event.data && - event.data.direction === ModeToggle.ID - ) { + if (event.source === window && event.data && event.data.id === Theme.ID) { updateImages(current, reverse); } }); diff --git a/_javascript/modules/components/mermaid.js b/_javascript/modules/components/mermaid.js new file mode 100644 index 0000000..2b4759f --- /dev/null +++ b/_javascript/modules/components/mermaid.js @@ -0,0 +1,60 @@ +/** + * Mermaid-js loader + */ + +const MERMAID = 'mermaid'; +const themeMapper = Theme.getThemeMapper('default', 'dark'); + +function refreshTheme(event) { + if (event.source === window && event.data && event.data.id === Theme.ID) { + // Re-render the SVG › + const mermaidList = document.getElementsByClassName(MERMAID); + + [...mermaidList].forEach((elem) => { + const svgCode = elem.previousSibling.children.item(0).innerHTML; + elem.textContent = svgCode; + elem.removeAttribute('data-processed'); + }); + + const newTheme = themeMapper[Theme.visualState]; + + mermaid.initialize({ theme: newTheme }); + mermaid.init(null, `.${MERMAID}`); + } +} + +function setNode(elem) { + const svgCode = elem.textContent; + const backup = elem.parentElement; + backup.classList.add('d-none'); + // Create mermaid node + const mermaid = document.createElement('pre'); + mermaid.classList.add(MERMAID); + const text = document.createTextNode(svgCode); + mermaid.appendChild(text); + backup.after(mermaid); +} + +export function loadMermaid() { + if ( + typeof mermaid === 'undefined' || + typeof mermaid.initialize !== 'function' + ) { + return; + } + + const initTheme = themeMapper[Theme.visualState]; + + let mermaidConf = { + theme: initTheme + }; + + const basicList = document.getElementsByClassName('language-mermaid'); + [...basicList].forEach(setNode); + + mermaid.initialize(mermaidConf); + + if (Theme.switchable) { + window.addEventListener('message', refreshTheme); + } +} diff --git a/_javascript/modules/components/mode-toggle.js b/_javascript/modules/components/mode-toggle.js new file mode 100644 index 0000000..455ff0a --- /dev/null +++ b/_javascript/modules/components/mode-toggle.js @@ -0,0 +1,15 @@ +/** + * Add listener for theme mode toggle + */ + +const $toggle = document.getElementById('mode-toggle'); + +export function modeWatcher() { + if (!$toggle) { + return; + } + + $toggle.addEventListener('click', () => { + Theme.flip(); + }); +} diff --git a/_javascript/modules/components/mode-watcher.js b/_javascript/modules/components/mode-watcher.js deleted file mode 100644 index 9eecd09..0000000 --- a/_javascript/modules/components/mode-watcher.js +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Add listener for theme mode toggle - */ -const toggle = document.getElementById('mode-toggle'); - -export function modeWatcher() { - if (!toggle) { - return; - } - - toggle.addEventListener('click', () => { - modeToggle.flipMode(); - }); -} diff --git a/_javascript/modules/components/sidebar.js b/_javascript/modules/components/sidebar.js deleted file mode 100644 index aed759e..0000000 --- a/_javascript/modules/components/sidebar.js +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Expand or close the sidebar in mobile screens. - */ - -const $sidebar = document.getElementById('sidebar'); -const $trigger = document.getElementById('sidebar-trigger'); -const $mask = document.getElementById('mask'); - -class SidebarUtil { - static #isExpanded = false; - - static toggle() { - this.#isExpanded = !this.#isExpanded; - document.body.toggleAttribute('sidebar-display', this.#isExpanded); - $sidebar.classList.toggle('z-2', this.#isExpanded); - $mask.classList.toggle('d-none', !this.#isExpanded); - } -} - -export function sidebarExpand() { - $trigger.onclick = $mask.onclick = () => SidebarUtil.toggle(); -} diff --git a/_javascript/modules/layouts/basic.js b/_javascript/modules/layouts/basic.js index fb36a8b..b8eddf6 100644 --- a/_javascript/modules/layouts/basic.js +++ b/_javascript/modules/layouts/basic.js @@ -1,7 +1,7 @@ -import { back2top } from '../components/back-to-top'; -import { loadTooptip } from '../components/tooltip-loader'; +import { back2top, loadTooptip, modeWatcher } from '../components'; export function basic() { + modeWatcher(); back2top(); loadTooptip(); } diff --git a/_javascript/modules/layouts/sidebar.js b/_javascript/modules/layouts/sidebar.js index 8795693..bbf5e7d 100644 --- a/_javascript/modules/layouts/sidebar.js +++ b/_javascript/modules/layouts/sidebar.js @@ -1,7 +1,19 @@ -import { modeWatcher } from '../components/mode-watcher'; -import { sidebarExpand } from '../components/sidebar'; +const ATTR_DISPLAY = 'sidebar-display'; +const $sidebar = document.getElementById('sidebar'); +const $trigger = document.getElementById('sidebar-trigger'); +const $mask = document.getElementById('mask'); + +class SidebarUtil { + static #isExpanded = false; + + static toggle() { + this.#isExpanded = !this.#isExpanded; + document.body.toggleAttribute(ATTR_DISPLAY, this.#isExpanded); + $sidebar.classList.toggle('z-2', this.#isExpanded); + $mask.classList.toggle('d-none', !this.#isExpanded); + } +} export function initSidebar() { - modeWatcher(); - sidebarExpand(); + $trigger.onclick = $mask.onclick = () => SidebarUtil.toggle(); } diff --git a/_javascript/modules/theme.js b/_javascript/modules/theme.js new file mode 100644 index 0000000..f9ebf20 --- /dev/null +++ b/_javascript/modules/theme.js @@ -0,0 +1,135 @@ +/** + * Theme management class + * + * To reduce flickering during page load, this script should be loaded synchronously. + */ +class Theme { + static #modeKey = 'mode'; + static #modeAttr = 'data-mode'; + static #darkMedia = window.matchMedia('(prefers-color-scheme: dark)'); + static switchable = !document.documentElement.hasAttribute(this.#modeAttr); + + static get DARK() { + return 'dark'; + } + + static get LIGHT() { + return 'light'; + } + + /** + * @returns {string} Theme mode identifier + */ + static get ID() { + return 'theme-mode'; + } + + /** + * Gets the current visual state of the theme. + * + * @returns {string} The current visual state, either the mode if it exists, + * or the system dark mode state ('dark' or 'light'). + */ + static get visualState() { + if (this.#hasMode) { + return this.#mode; + } else { + return this.#sysDark ? this.DARK : this.LIGHT; + } + } + + static get #mode() { + return sessionStorage.getItem(this.#modeKey); + } + + static get #isDarkMode() { + return this.#mode === this.DARK; + } + + static get #hasMode() { + return this.#mode !== null; + } + + static get #sysDark() { + return this.#darkMedia.matches; + } + + /** + * Maps theme modes to provided values + * @param {string} light Value for light mode + * @param {string} dark Value for dark mode + * @returns {Object} Mapped values + */ + static getThemeMapper(light, dark) { + return { + [this.LIGHT]: light, + [this.DARK]: dark + }; + } + + /** + * Initializes the theme based on system preferences or stored mode + */ + static init() { + if (!this.switchable) { + return; + } + + this.#darkMedia.addEventListener('change', () => { + const lastMode = this.#mode; + this.#clearMode(); + + if (lastMode !== this.visualState) { + this.#notify(); + } + }); + + if (!this.#hasMode) { + return; + } + + if (this.#isDarkMode) { + this.#setDark(); + } else { + this.#setLight(); + } + } + + /** + * Flips the current theme mode + */ + static flip() { + if (this.#hasMode) { + this.#clearMode(); + } else { + this.#sysDark ? this.#setLight() : this.#setDark(); + } + this.#notify(); + } + + static #setDark() { + document.documentElement.setAttribute(this.#modeAttr, this.DARK); + sessionStorage.setItem(this.#modeKey, this.DARK); + } + + static #setLight() { + document.documentElement.setAttribute(this.#modeAttr, this.LIGHT); + sessionStorage.setItem(this.#modeKey, this.LIGHT); + } + + static #clearMode() { + document.documentElement.removeAttribute(this.#modeAttr); + sessionStorage.removeItem(this.#modeKey); + } + + /** + * Notifies other plugins that the theme mode has changed + */ + static #notify() { + window.postMessage({ id: this.ID }, '*'); + } +} + +Theme.init(); + +export default Theme; diff --git a/_javascript/page.js b/_javascript/page.js index 76e8ce9..4b03b79 100644 --- a/_javascript/page.js +++ b/_javascript/page.js @@ -1,9 +1,15 @@ import { basic, initSidebar, initTopbar } from './modules/layouts'; -import { loadImg, imgPopup, initClipboard } from './modules/plugins'; +import { + loadImg, + imgPopup, + initClipboard, + loadMermaid +} from './modules/components'; loadImg(); imgPopup(); initSidebar(); initTopbar(); initClipboard(); +loadMermaid(); basic(); diff --git a/_javascript/post.js b/_javascript/post.js index 1c616ec..dc472b4 100644 --- a/_javascript/post.js +++ b/_javascript/post.js @@ -5,8 +5,9 @@ import { imgPopup, initLocaleDatetime, initClipboard, - initToc -} from './modules/plugins'; + initToc, + loadMermaid +} from './modules/components'; loadImg(); initToc(); @@ -15,4 +16,5 @@ initSidebar(); initLocaleDatetime(); initClipboard(); initTopbar(); +loadMermaid(); basic(); diff --git a/_layouts/default.html b/_layouts/default.html index 1590ef6..a55bfef 100644 --- a/_layouts/default.html +++ b/_layouts/default.html @@ -33,7 +33,7 @@ layout: compress -