commit 22511eec8760403536861a62ad0375e79a153c3f
parent 1b8ca699801371ee7e2d26c90d0d9bd6dade85a1
Author: Ismail Dalgatov <29144912+ismaildalgatov@users.noreply.github.com>
Date: Wed, 24 Aug 2022 14:22:02 +0300
Add src
Diffstat:
282 files changed, 25603 insertions(+), 4 deletions(-)
diff --git a/.github/workflows/mkdocs-deploy.yml b/.github/workflows/mkdocs-deploy.yml
@@ -17,6 +17,7 @@ jobs:
run: |
pip install -r requirements.txt
pip install wheel
+ pip install mkdocs-redirects
pip install mkdocs-minify-plugin
pip install mkdocs-git-revision-date-localized-plugin
mkdocs build
diff --git a/material/overrides/partials/content.html b/material/overrides/partials/content.html
@@ -2,8 +2,8 @@
This file was automatically generated - do not edit
-#}
{% if page.edit_url %}
- {% set edit = "https://github.com/squidfunk/mkdocs-material/edit" %}
- {% set view = "https://raw.githubusercontent.com/squidfunk/mkdocs-material" %}
+ {% set edit = "https://github.com/ismaildalgatov/5-72/edit" %}
+ {% set view = "https://raw.githubusercontent.com/ismaildalgatov/5-72" %}
<a href="{{ page.edit_url }}" title="{{ lang.t('edit.link.title') }}" class="md-content__button md-icon">
{% include ".icons/material/file-edit-outline.svg" %}
</a>
diff --git a/mkdocs.yml b/mkdocs.yml
@@ -79,8 +79,8 @@ plugins:
# reference/variables.md: https://mkdocs-macros-plugin.readthedocs.io/
# sponsorship.md: insiders/index.md
# upgrading.md: upgrade.md
-# - minify:
-# minify_html: true
+ - minify:
+ minify_html: true
# Customization
extra:
diff --git a/src/.icons/logo.afdesign b/src/.icons/logo.afdesign
Binary files differ.
diff --git a/src/.icons/logo.svg b/src/.icons/logo.svg
@@ -0,0 +1,6 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 89 89">
+ <path d="M3.136,17.387l0,42.932l42.932,21.467l-42.932,-64.399Z" />
+ <path d="M21.91,8l42.933,64.398l-18.775,9.388l-42.932,-64.399l18.774,-9.387Z" style="fill-opacity: 0.5" />
+ <path d="M67.535,17.387l-27.262,18.156l21.878,32.818l5.384,2.691l0,-53.665Z" />
+ <path d="M67.535,17.387l0,53.666l18.774,-9.388l0,-53.665l-18.774,9.387Z" style="fill-opacity: 0.25" />
+</svg>
diff --git a/src/404.html b/src/404.html
@@ -0,0 +1,28 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+{% extends "main.html" %}
+
+<!-- Content -->
+{% block content %}
+ <h1>404 - Not found</h1>
+{% endblock %}
diff --git a/src/__init__.py b/src/__init__.py
diff --git a/src/assets/images/favicon.png b/src/assets/images/favicon.png
Binary files differ.
diff --git a/src/assets/javascripts/_/index.ts b/src/assets/javascripts/_/index.ts
@@ -0,0 +1,146 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import { getElement, getLocation } from "~/browser"
+
+/* ----------------------------------------------------------------------------
+ * Types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Feature flag
+ */
+export type Flag =
+ | "announce.dismiss" /* Dismissable announcement bar */
+ | "content.code.annotate" /* Code annotations */
+ | "content.tabs.link" /* Link content tabs */
+ | "header.autohide" /* Hide header */
+ | "navigation.expand" /* Automatic expansion */
+ | "navigation.indexes" /* Section pages */
+ | "navigation.instant" /* Instant loading */
+ | "navigation.sections" /* Section navigation */
+ | "navigation.tabs" /* Tabs navigation */
+ | "navigation.tabs.sticky" /* Tabs navigation (sticky) */
+ | "navigation.top" /* Back-to-top button */
+ | "navigation.tracking" /* Anchor tracking */
+ | "search.highlight" /* Search highlighting */
+ | "search.share" /* Search sharing */
+ | "search.suggest" /* Search suggestions */
+ | "toc.integrate" /* Integrated table of contents */
+
+/* ------------------------------------------------------------------------- */
+
+/**
+ * Translation
+ */
+export type Translation =
+ | "clipboard.copy" /* Copy to clipboard */
+ | "clipboard.copied" /* Copied to clipboard */
+ | "search.config.lang" /* Search language */
+ | "search.config.pipeline" /* Search pipeline */
+ | "search.config.separator" /* Search separator */
+ | "search.placeholder" /* Search */
+ | "search.result.placeholder" /* Type to start searching */
+ | "search.result.none" /* No matching documents */
+ | "search.result.one" /* 1 matching document */
+ | "search.result.other" /* # matching documents */
+ | "search.result.more.one" /* 1 more on this page */
+ | "search.result.more.other" /* # more on this page */
+ | "search.result.term.missing" /* Missing */
+ | "select.version.title" /* Version selector */
+
+/**
+ * Translations
+ */
+export type Translations = Record<Translation, string>
+
+/* ------------------------------------------------------------------------- */
+
+/**
+ * Versioning
+ */
+export interface Versioning {
+ provider: "mike" /* Version provider */
+ default?: string /* Default version */
+}
+
+/**
+ * Configuration
+ */
+export interface Config {
+ base: string /* Base URL */
+ features: Flag[] /* Feature flags */
+ translations: Translations /* Translations */
+ search: string /* Search worker URL */
+ version?: Versioning /* Versioning */
+}
+
+/* ----------------------------------------------------------------------------
+ * Data
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Retrieve global configuration and make base URL absolute
+ */
+const script = getElement("#__config")
+const config: Config = JSON.parse(script.textContent!)
+config.base = `${new URL(config.base, getLocation())}`
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Retrieve global configuration
+ *
+ * @returns Global configuration
+ */
+export function configuration(): Config {
+ return config
+}
+
+/**
+ * Check whether a feature flag is enabled
+ *
+ * @param flag - Feature flag
+ *
+ * @returns Test result
+ */
+export function feature(flag: Flag): boolean {
+ return config.features.includes(flag)
+}
+
+/**
+ * Retrieve the translation for the given key
+ *
+ * @param key - Key to be translated
+ * @param value - Positional value, if any
+ *
+ * @returns Translation
+ */
+export function translation(
+ key: Translation, value?: string | number
+): string {
+ return typeof value !== "undefined"
+ ? config.translations[key].replace("#", value.toString())
+ : config.translations[key]
+}
diff --git a/src/assets/javascripts/browser/document/index.ts b/src/assets/javascripts/browser/document/index.ts
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import {
+ ReplaySubject,
+ Subject,
+ fromEvent
+} from "rxjs"
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Watch document
+ *
+ * Documents are implemented as subjects, so all downstream observables are
+ * automatically updated when a new document is emitted.
+ *
+ * @returns Document subject
+ */
+export function watchDocument(): Subject<Document> {
+ const document$ = new ReplaySubject<Document>(1)
+ fromEvent(document, "DOMContentLoaded", { once: true })
+ .subscribe(() => document$.next(document))
+
+ /* Return document */
+ return document$
+}
diff --git a/src/assets/javascripts/browser/element/_/.eslintrc b/src/assets/javascripts/browser/element/_/.eslintrc
@@ -0,0 +1,5 @@
+{
+ "rules": {
+ "jsdoc/require-jsdoc": "off"
+ }
+}
diff --git a/src/assets/javascripts/browser/element/_/index.ts b/src/assets/javascripts/browser/element/_/index.ts
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Retrieve all elements matching the query selector
+ *
+ * @template T - Element type
+ *
+ * @param selector - Query selector
+ * @param node - Node of reference
+ *
+ * @returns Elements
+ */
+export function getElements<T extends keyof HTMLElementTagNameMap>(
+ selector: T, node?: ParentNode
+): HTMLElementTagNameMap[T][]
+
+export function getElements<T extends HTMLElement>(
+ selector: string, node?: ParentNode
+): T[]
+
+export function getElements<T extends HTMLElement>(
+ selector: string, node: ParentNode = document
+): T[] {
+ return Array.from(node.querySelectorAll<T>(selector))
+}
+
+/**
+ * Retrieve an element matching a query selector or throw a reference error
+ *
+ * Note that this function assumes that the element is present. If unsure if an
+ * element is existent, use the `getOptionalElement` function instead.
+ *
+ * @template T - Element type
+ *
+ * @param selector - Query selector
+ * @param node - Node of reference
+ *
+ * @returns Element
+ */
+export function getElement<T extends keyof HTMLElementTagNameMap>(
+ selector: T, node?: ParentNode
+): HTMLElementTagNameMap[T]
+
+export function getElement<T extends HTMLElement>(
+ selector: string, node?: ParentNode
+): T
+
+export function getElement<T extends HTMLElement>(
+ selector: string, node: ParentNode = document
+): T {
+ const el = getOptionalElement<T>(selector, node)
+ if (typeof el === "undefined")
+ throw new ReferenceError(
+ `Missing element: expected "${selector}" to be present`
+ )
+
+ /* Return element */
+ return el
+}
+
+/* ------------------------------------------------------------------------- */
+
+/**
+ * Retrieve an optional element matching the query selector
+ *
+ * @template T - Element type
+ *
+ * @param selector - Query selector
+ * @param node - Node of reference
+ *
+ * @returns Element or nothing
+ */
+export function getOptionalElement<T extends keyof HTMLElementTagNameMap>(
+ selector: T, node?: ParentNode
+): HTMLElementTagNameMap[T] | undefined
+
+export function getOptionalElement<T extends HTMLElement>(
+ selector: string, node?: ParentNode
+): T | undefined
+
+export function getOptionalElement<T extends HTMLElement>(
+ selector: string, node: ParentNode = document
+): T | undefined {
+ return node.querySelector<T>(selector) || undefined
+}
+
+/**
+ * Retrieve the currently active element
+ *
+ * @returns Element or nothing
+ */
+export function getActiveElement(): HTMLElement | undefined {
+ return document.activeElement instanceof HTMLElement
+ ? document.activeElement || undefined
+ : undefined
+}
diff --git a/src/assets/javascripts/browser/element/focus/index.ts b/src/assets/javascripts/browser/element/focus/index.ts
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import {
+ Observable,
+ debounceTime,
+ distinctUntilChanged,
+ fromEvent,
+ map,
+ merge,
+ startWith
+} from "rxjs"
+
+import { getActiveElement } from "../_"
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Watch element focus
+ *
+ * Previously, this function used `focus` and `blur` events to determine whether
+ * an element is focused, but this doesn't work if there are focusable elements
+ * within the elements itself. A better solutions are `focusin` and `focusout`
+ * events, which bubble up the tree and allow for more fine-grained control.
+ *
+ * `debounceTime` is necessary, because when a focus change happens inside an
+ * element, the observable would first emit `false` and then `true` again.
+ *
+ * @param el - Element
+ *
+ * @returns Element focus observable
+ */
+export function watchElementFocus(
+ el: HTMLElement
+): Observable<boolean> {
+ return merge(
+ fromEvent(document.body, "focusin"),
+ fromEvent(document.body, "focusout")
+ )
+ .pipe(
+ debounceTime(1),
+ map(() => {
+ const active = getActiveElement()
+ return typeof active !== "undefined"
+ ? el.contains(active)
+ : false
+ }),
+ startWith(el === getActiveElement()),
+ distinctUntilChanged()
+ )
+}
diff --git a/src/assets/javascripts/browser/element/index.ts b/src/assets/javascripts/browser/element/index.ts
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+export * from "./_"
+export * from "./focus"
+export * from "./offset"
+export * from "./size"
+export * from "./visibility"
diff --git a/src/assets/javascripts/browser/element/offset/_/index.ts b/src/assets/javascripts/browser/element/offset/_/index.ts
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import {
+ Observable,
+ animationFrameScheduler,
+ auditTime,
+ fromEvent,
+ map,
+ merge,
+ startWith
+} from "rxjs"
+
+/* ----------------------------------------------------------------------------
+ * Types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Element offset
+ */
+export interface ElementOffset {
+ x: number /* Horizontal offset */
+ y: number /* Vertical offset */
+}
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Retrieve element offset
+ *
+ * @param el - Element
+ *
+ * @returns Element offset
+ */
+export function getElementOffset(
+ el: HTMLElement
+): ElementOffset {
+ return {
+ x: el.offsetLeft,
+ y: el.offsetTop
+ }
+}
+
+/* ------------------------------------------------------------------------- */
+
+/**
+ * Watch element offset
+ *
+ * @param el - Element
+ *
+ * @returns Element offset observable
+ */
+export function watchElementOffset(
+ el: HTMLElement
+): Observable<ElementOffset> {
+ return merge(
+ fromEvent(window, "load"),
+ fromEvent(window, "resize")
+ )
+ .pipe(
+ auditTime(0, animationFrameScheduler),
+ map(() => getElementOffset(el)),
+ startWith(getElementOffset(el))
+ )
+}
diff --git a/src/assets/javascripts/browser/element/offset/content/index.ts b/src/assets/javascripts/browser/element/offset/content/index.ts
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import {
+ Observable,
+ animationFrameScheduler,
+ auditTime,
+ fromEvent,
+ map,
+ merge,
+ startWith
+} from "rxjs"
+
+import { ElementOffset } from "../_"
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Retrieve element content offset (= scroll offset)
+ *
+ * @param el - Element
+ *
+ * @returns Element content offset
+ */
+export function getElementContentOffset(
+ el: HTMLElement
+): ElementOffset {
+ return {
+ x: el.scrollLeft,
+ y: el.scrollTop
+ }
+}
+
+/* ------------------------------------------------------------------------- */
+
+/**
+ * Watch element content offset
+ *
+ * @param el - Element
+ *
+ * @returns Element content offset observable
+ */
+export function watchElementContentOffset(
+ el: HTMLElement
+): Observable<ElementOffset> {
+ return merge(
+ fromEvent(el, "scroll"),
+ fromEvent(window, "resize")
+ )
+ .pipe(
+ auditTime(0, animationFrameScheduler),
+ map(() => getElementContentOffset(el)),
+ startWith(getElementContentOffset(el))
+ )
+}
diff --git a/src/assets/javascripts/browser/element/offset/index.ts b/src/assets/javascripts/browser/element/offset/index.ts
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+export * from "./_"
+export * from "./content"
diff --git a/src/assets/javascripts/browser/element/size/_/index.ts b/src/assets/javascripts/browser/element/size/_/index.ts
@@ -0,0 +1,142 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import ResizeObserver from "resize-observer-polyfill"
+import {
+ NEVER,
+ Observable,
+ Subject,
+ defer,
+ filter,
+ finalize,
+ map,
+ merge,
+ of,
+ shareReplay,
+ startWith,
+ switchMap,
+ tap
+} from "rxjs"
+
+/* ----------------------------------------------------------------------------
+ * Types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Element offset
+ */
+export interface ElementSize {
+ width: number /* Element width */
+ height: number /* Element height */
+}
+
+/* ----------------------------------------------------------------------------
+ * Data
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Resize observer entry subject
+ */
+const entry$ = new Subject<ResizeObserverEntry>()
+
+/**
+ * Resize observer observable
+ *
+ * This observable will create a `ResizeObserver` on the first subscription
+ * and will automatically terminate it when there are no more subscribers.
+ * It's quite important to centralize observation in a single `ResizeObserver`,
+ * as the performance difference can be quite dramatic, as the link shows.
+ *
+ * @see https://bit.ly/3iIYfEm - Google Groups on performance
+ */
+const observer$ = defer(() => of(
+ new ResizeObserver(entries => {
+ for (const entry of entries)
+ entry$.next(entry)
+ })
+))
+ .pipe(
+ switchMap(observer => merge(NEVER, of(observer))
+ .pipe(
+ finalize(() => observer.disconnect())
+ )
+ ),
+ shareReplay(1)
+ )
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Retrieve element size
+ *
+ * @param el - Element
+ *
+ * @returns Element size
+ */
+export function getElementSize(
+ el: HTMLElement
+): ElementSize {
+ return {
+ width: el.offsetWidth,
+ height: el.offsetHeight
+ }
+}
+
+/* ------------------------------------------------------------------------- */
+
+/**
+ * Watch element size
+ *
+ * This function returns an observable that subscribes to a single internal
+ * instance of `ResizeObserver` upon subscription, and emit resize events until
+ * termination. Note that this function should not be called with the same
+ * element twice, as the first unsubscription will terminate observation.
+ *
+ * Sadly, we can't use the `DOMRect` objects returned by the observer, because
+ * we need the emitted values to be consistent with `getElementSize`, which will
+ * return the used values (rounded) and not actual values (unrounded). Thus, we
+ * use the `offset*` properties. See the linked GitHub issue.
+ *
+ * @see https://bit.ly/3m0k3he - GitHub issue
+ *
+ * @param el - Element
+ *
+ * @returns Element size observable
+ */
+export function watchElementSize(
+ el: HTMLElement
+): Observable<ElementSize> {
+ return observer$
+ .pipe(
+ tap(observer => observer.observe(el)),
+ switchMap(observer => entry$
+ .pipe(
+ filter(({ target }) => target === el),
+ finalize(() => observer.unobserve(el)),
+ map(() => getElementSize(el))
+ )
+ ),
+ startWith(getElementSize(el))
+ )
+}
diff --git a/src/assets/javascripts/browser/element/size/content/index.ts b/src/assets/javascripts/browser/element/size/content/index.ts
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import { ElementSize } from "../_"
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Retrieve element content size (= scroll width and height)
+ *
+ * @param el - Element
+ *
+ * @returns Element content size
+ */
+export function getElementContentSize(
+ el: HTMLElement
+): ElementSize {
+ return {
+ width: el.scrollWidth,
+ height: el.scrollHeight
+ }
+}
diff --git a/src/assets/javascripts/browser/element/size/index.ts b/src/assets/javascripts/browser/element/size/index.ts
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+export * from "./_"
+export * from "./content"
diff --git a/src/assets/javascripts/browser/element/visibility/index.ts b/src/assets/javascripts/browser/element/visibility/index.ts
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import {
+ NEVER,
+ Observable,
+ Subject,
+ defer,
+ distinctUntilChanged,
+ filter,
+ finalize,
+ map,
+ merge,
+ of,
+ shareReplay,
+ switchMap,
+ tap
+} from "rxjs"
+
+import {
+ getElementContentSize,
+ getElementSize,
+ watchElementContentOffset
+} from "~/browser"
+
+/* ----------------------------------------------------------------------------
+ * Data
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Intersection observer entry subject
+ */
+const entry$ = new Subject<IntersectionObserverEntry>()
+
+/**
+ * Intersection observer observable
+ *
+ * This observable will create an `IntersectionObserver` on first subscription
+ * and will automatically terminate it when there are no more subscribers.
+ *
+ * @see https://bit.ly/3iIYfEm - Google Groups on performance
+ */
+const observer$ = defer(() => of(
+ new IntersectionObserver(entries => {
+ for (const entry of entries)
+ entry$.next(entry)
+ }, {
+ threshold: 0
+ })
+))
+ .pipe(
+ switchMap(observer => merge(NEVER, of(observer))
+ .pipe(
+ finalize(() => observer.disconnect())
+ )
+ ),
+ shareReplay(1)
+ )
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Watch element visibility
+ *
+ * @param el - Element
+ *
+ * @returns Element visibility observable
+ */
+export function watchElementVisibility(
+ el: HTMLElement
+): Observable<boolean> {
+ return observer$
+ .pipe(
+ tap(observer => observer.observe(el)),
+ switchMap(observer => entry$
+ .pipe(
+ filter(({ target }) => target === el),
+ finalize(() => observer.unobserve(el)),
+ map(({ isIntersecting }) => isIntersecting)
+ )
+ )
+ )
+}
+
+/**
+ * Watch element boundary
+ *
+ * This function returns an observable which emits whether the bottom content
+ * boundary (= scroll offset) of an element is within a certain threshold.
+ *
+ * @param el - Element
+ * @param threshold - Threshold
+ *
+ * @returns Element boundary observable
+ */
+export function watchElementBoundary(
+ el: HTMLElement, threshold = 16
+): Observable<boolean> {
+ return watchElementContentOffset(el)
+ .pipe(
+ map(({ y }) => {
+ const visible = getElementSize(el)
+ const content = getElementContentSize(el)
+ return y >= (
+ content.height - visible.height - threshold
+ )
+ }),
+ distinctUntilChanged()
+ )
+}
diff --git a/src/assets/javascripts/browser/index.ts b/src/assets/javascripts/browser/index.ts
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+export * from "./document"
+export * from "./element"
+export * from "./keyboard"
+export * from "./location"
+export * from "./media"
+export * from "./request"
+export * from "./script"
+export * from "./toggle"
+export * from "./viewport"
+export * from "./worker"
diff --git a/src/assets/javascripts/browser/keyboard/index.ts b/src/assets/javascripts/browser/keyboard/index.ts
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import {
+ Observable,
+ filter,
+ fromEvent,
+ map,
+ share
+} from "rxjs"
+
+import { getActiveElement } from "../element"
+import { getToggle } from "../toggle"
+
+/* ----------------------------------------------------------------------------
+ * Types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Keyboard mode
+ */
+export type KeyboardMode =
+ | "global" /* Global */
+ | "search" /* Search is open */
+
+/* ------------------------------------------------------------------------- */
+
+/**
+ * Keyboard
+ */
+export interface Keyboard {
+ mode: KeyboardMode /* Keyboard mode */
+ type: string /* Key type */
+ claim(): void /* Key claim */
+}
+
+/* ----------------------------------------------------------------------------
+ * Helper functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Check whether an element may receive keyboard input
+ *
+ * @param el - Element
+ * @param type - Key type
+ *
+ * @returns Test result
+ */
+function isSusceptibleToKeyboard(
+ el: HTMLElement, type: string
+): boolean {
+ switch (el.constructor) {
+
+ /* Input elements */
+ case HTMLInputElement:
+ /* @ts-expect-error - omit unnecessary type cast */
+ if (el.type === "radio")
+ return /^Arrow/.test(type)
+ else
+ return true
+
+ /* Select element and textarea */
+ case HTMLSelectElement:
+ case HTMLTextAreaElement:
+ return true
+
+ /* Everything else */
+ default:
+ return el.isContentEditable
+ }
+}
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Watch keyboard
+ *
+ * @returns Keyboard observable
+ */
+export function watchKeyboard(): Observable<Keyboard> {
+ return fromEvent<KeyboardEvent>(window, "keydown")
+ .pipe(
+ filter(ev => !(ev.metaKey || ev.ctrlKey)),
+ map(ev => ({
+ mode: getToggle("search") ? "search" : "global",
+ type: ev.key,
+ claim() {
+ ev.preventDefault()
+ ev.stopPropagation()
+ }
+ } as Keyboard)),
+ filter(({ mode, type }) => {
+ if (mode === "global") {
+ const active = getActiveElement()
+ if (typeof active !== "undefined")
+ return !isSusceptibleToKeyboard(active, type)
+ }
+ return true
+ }),
+ share()
+ )
+}
diff --git a/src/assets/javascripts/browser/location/_/index.ts b/src/assets/javascripts/browser/location/_/index.ts
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import { Subject } from "rxjs"
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Retrieve location
+ *
+ * This function returns a `URL` object (and not `Location`) to normalize the
+ * typings across the application. Furthermore, locations need to be tracked
+ * without setting them and `Location` is a singleton which represents the
+ * current location.
+ *
+ * @returns URL
+ */
+export function getLocation(): URL {
+ return new URL(location.href)
+}
+
+/**
+ * Set location
+ *
+ * @param url - URL to change to
+ */
+export function setLocation(url: URL): void {
+ location.href = url.href
+}
+
+/* ------------------------------------------------------------------------- */
+
+/**
+ * Watch location
+ *
+ * @returns Location subject
+ */
+export function watchLocation(): Subject<URL> {
+ return new Subject<URL>()
+}
diff --git a/src/assets/javascripts/browser/location/hash/index.ts b/src/assets/javascripts/browser/location/hash/index.ts
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import {
+ Observable,
+ filter,
+ fromEvent,
+ map,
+ shareReplay,
+ startWith
+} from "rxjs"
+
+import { getOptionalElement } from "~/browser"
+import { h } from "~/utilities"
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Retrieve location hash
+ *
+ * @returns Location hash
+ */
+export function getLocationHash(): string {
+ return location.hash.substring(1)
+}
+
+/**
+ * Set location hash
+ *
+ * Setting a new fragment identifier via `location.hash` will have no effect
+ * if the value doesn't change. When a new fragment identifier is set, we want
+ * the browser to target the respective element at all times, which is why we
+ * use this dirty little trick.
+ *
+ * @param hash - Location hash
+ */
+export function setLocationHash(hash: string): void {
+ const el = h("a", { href: hash })
+ el.addEventListener("click", ev => ev.stopPropagation())
+ el.click()
+}
+
+/* ------------------------------------------------------------------------- */
+
+/**
+ * Watch location hash
+ *
+ * @returns Location hash observable
+ */
+export function watchLocationHash(): Observable<string> {
+ return fromEvent<HashChangeEvent>(window, "hashchange")
+ .pipe(
+ map(getLocationHash),
+ startWith(getLocationHash()),
+ filter(hash => hash.length > 0),
+ shareReplay(1)
+ )
+}
+
+/**
+ * Watch location target
+ *
+ * @returns Location target observable
+ */
+export function watchLocationTarget(): Observable<HTMLElement> {
+ return watchLocationHash()
+ .pipe(
+ map(id => getOptionalElement(`[id="${id}"]`)!),
+ filter(el => typeof el !== "undefined")
+ )
+}
diff --git a/src/assets/javascripts/browser/location/index.ts b/src/assets/javascripts/browser/location/index.ts
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+export * from "./_"
+export * from "./hash"
diff --git a/src/assets/javascripts/browser/media/index.ts b/src/assets/javascripts/browser/media/index.ts
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import {
+ EMPTY,
+ Observable,
+ fromEvent,
+ fromEventPattern,
+ map,
+ merge,
+ startWith,
+ switchMap
+} from "rxjs"
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Watch media query
+ *
+ * Note that although `MediaQueryList.addListener` is deprecated we have to
+ * use it, because it's the only way to ensure proper downward compatibility.
+ *
+ * @see https://bit.ly/3dUBH2m - GitHub issue
+ *
+ * @param query - Media query
+ *
+ * @returns Media observable
+ */
+export function watchMedia(query: string): Observable<boolean> {
+ const media = matchMedia(query)
+ return fromEventPattern<boolean>(next => (
+ media.addListener(() => next(media.matches))
+ ))
+ .pipe(
+ startWith(media.matches)
+ )
+}
+
+/**
+ * Watch print mode
+ *
+ * @returns Print observable
+ */
+export function watchPrint(): Observable<boolean> {
+ const media = matchMedia("print")
+ return merge(
+ fromEvent(window, "beforeprint").pipe(map(() => true)),
+ fromEvent(window, "afterprint").pipe(map(() => false))
+ )
+ .pipe(
+ startWith(media.matches)
+ )
+}
+
+/* ------------------------------------------------------------------------- */
+
+/**
+ * Toggle an observable with a media observable
+ *
+ * @template T - Data type
+ *
+ * @param query$ - Media observable
+ * @param factory - Observable factory
+ *
+ * @returns Toggled observable
+ */
+export function at<T>(
+ query$: Observable<boolean>, factory: () => Observable<T>
+): Observable<T> {
+ return query$
+ .pipe(
+ switchMap(active => active ? factory() : EMPTY)
+ )
+}
diff --git a/src/assets/javascripts/browser/request/index.ts b/src/assets/javascripts/browser/request/index.ts
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import {
+ EMPTY,
+ Observable,
+ catchError,
+ from,
+ map,
+ of,
+ shareReplay,
+ switchMap,
+ throwError
+} from "rxjs"
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Fetch the given URL
+ *
+ * If the request fails (e.g. when dispatched from `file://` locations), the
+ * observable will complete without emitting a value.
+ *
+ * @param url - Request URL
+ * @param options - Options
+ *
+ * @returns Response observable
+ */
+export function request(
+ url: URL | string, options: RequestInit = { credentials: "same-origin" }
+): Observable<Response> {
+ return from(fetch(`${url}`, options))
+ .pipe(
+ catchError(() => EMPTY),
+ switchMap(res => res.status !== 200
+ ? throwError(() => new Error(res.statusText))
+ : of(res)
+ )
+ )
+}
+
+/**
+ * Fetch JSON from the given URL
+ *
+ * @template T - Data type
+ *
+ * @param url - Request URL
+ * @param options - Options
+ *
+ * @returns Data observable
+ */
+export function requestJSON<T>(
+ url: URL | string, options?: RequestInit
+): Observable<T> {
+ return request(url, options)
+ .pipe(
+ switchMap(res => res.json()),
+ shareReplay(1)
+ )
+}
+
+/**
+ * Fetch XML from the given URL
+ *
+ * @param url - Request URL
+ * @param options - Options
+ *
+ * @returns Data observable
+ */
+export function requestXML(
+ url: URL | string, options?: RequestInit
+): Observable<Document> {
+ const dom = new DOMParser()
+ return request(url, options)
+ .pipe(
+ switchMap(res => res.text()),
+ map(res => dom.parseFromString(res, "text/xml")),
+ shareReplay(1)
+ )
+}
diff --git a/src/assets/javascripts/browser/script/index.ts b/src/assets/javascripts/browser/script/index.ts
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import {
+ Observable,
+ defer,
+ finalize,
+ fromEvent,
+ map,
+ merge,
+ switchMap,
+ take,
+ throwError
+} from "rxjs"
+
+import { h } from "~/utilities"
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Create and load a `script` element
+ *
+ * This function returns an observable that will emit when the script was
+ * successfully loaded, or throw an error if it didn't.
+ *
+ * @param src - Script URL
+ *
+ * @returns Script observable
+ */
+export function watchScript(src: string): Observable<void> {
+ const script = h("script", { src })
+ return defer(() => {
+ document.head.appendChild(script)
+ return merge(
+ fromEvent(script, "load"),
+ fromEvent(script, "error")
+ .pipe(
+ switchMap(() => (
+ throwError(() => new ReferenceError(`Invalid script: ${src}`))
+ ))
+ )
+ )
+ .pipe(
+ map(() => undefined),
+ finalize(() => document.head.removeChild(script)),
+ take(1)
+ )
+ })
+}
diff --git a/src/assets/javascripts/browser/toggle/index.ts b/src/assets/javascripts/browser/toggle/index.ts
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import {
+ Observable,
+ fromEvent,
+ map,
+ startWith
+} from "rxjs"
+
+import { getElement } from "../element"
+
+/* ----------------------------------------------------------------------------
+ * Types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Toggle
+ */
+export type Toggle =
+ | "drawer" /* Toggle for drawer */
+ | "search" /* Toggle for search */
+
+/* ----------------------------------------------------------------------------
+ * Data
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Toggle map
+ */
+const toggles: Record<Toggle, HTMLInputElement> = {
+ drawer: getElement("[data-md-toggle=drawer]"),
+ search: getElement("[data-md-toggle=search]")
+}
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Retrieve the value of a toggle
+ *
+ * @param name - Toggle
+ *
+ * @returns Toggle value
+ */
+export function getToggle(name: Toggle): boolean {
+ return toggles[name].checked
+}
+
+/**
+ * Set toggle
+ *
+ * Simulating a click event seems to be the most cross-browser compatible way
+ * of changing the value while also emitting a `change` event. Before, Material
+ * used `CustomEvent` to programmatically change the value of a toggle, but this
+ * is a much simpler and cleaner solution which doesn't require a polyfill.
+ *
+ * @param name - Toggle
+ * @param value - Toggle value
+ */
+export function setToggle(name: Toggle, value: boolean): void {
+ if (toggles[name].checked !== value)
+ toggles[name].click()
+}
+
+/* ------------------------------------------------------------------------- */
+
+/**
+ * Watch toggle
+ *
+ * @param name - Toggle
+ *
+ * @returns Toggle value observable
+ */
+export function watchToggle(name: Toggle): Observable<boolean> {
+ const el = toggles[name]
+ return fromEvent(el, "change")
+ .pipe(
+ map(() => el.checked),
+ startWith(el.checked)
+ )
+}
diff --git a/src/assets/javascripts/browser/viewport/_/index.ts b/src/assets/javascripts/browser/viewport/_/index.ts
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import {
+ Observable,
+ combineLatest,
+ map,
+ shareReplay
+} from "rxjs"
+
+import {
+ ViewportOffset,
+ watchViewportOffset
+} from "../offset"
+import {
+ ViewportSize,
+ watchViewportSize
+} from "../size"
+
+/* ----------------------------------------------------------------------------
+ * Types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Viewport
+ */
+export interface Viewport {
+ offset: ViewportOffset /* Viewport offset */
+ size: ViewportSize /* Viewport size */
+}
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Watch viewport
+ *
+ * @returns Viewport observable
+ */
+export function watchViewport(): Observable<Viewport> {
+ return combineLatest([
+ watchViewportOffset(),
+ watchViewportSize()
+ ])
+ .pipe(
+ map(([offset, size]) => ({ offset, size })),
+ shareReplay(1)
+ )
+}
diff --git a/src/assets/javascripts/browser/viewport/at/index.ts b/src/assets/javascripts/browser/viewport/at/index.ts
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import {
+ Observable,
+ combineLatest,
+ distinctUntilKeyChanged,
+ map
+} from "rxjs"
+
+import { Header } from "~/components"
+
+import { getElementOffset } from "../../element"
+import { Viewport } from "../_"
+
+/* ----------------------------------------------------------------------------
+ * Helper types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Watch options
+ */
+interface WatchOptions {
+ viewport$: Observable<Viewport> /* Viewport observable */
+ header$: Observable<Header> /* Header observable */
+}
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Watch viewport relative to element
+ *
+ * @param el - Element
+ * @param options - Options
+ *
+ * @returns Viewport observable
+ */
+export function watchViewportAt(
+ el: HTMLElement, { viewport$, header$ }: WatchOptions
+): Observable<Viewport> {
+ const size$ = viewport$
+ .pipe(
+ distinctUntilKeyChanged("size")
+ )
+
+ /* Compute element offset */
+ const offset$ = combineLatest([size$, header$])
+ .pipe(
+ map(() => getElementOffset(el))
+ )
+
+ /* Compute relative viewport, return hot observable */
+ return combineLatest([header$, viewport$, offset$])
+ .pipe(
+ map(([{ height }, { offset, size }, { x, y }]) => ({
+ offset: {
+ x: offset.x - x,
+ y: offset.y - y + height
+ },
+ size
+ }))
+ )
+}
diff --git a/src/assets/javascripts/browser/viewport/index.ts b/src/assets/javascripts/browser/viewport/index.ts
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+export * from "./_"
+export * from "./at"
+export * from "./offset"
+export * from "./size"
diff --git a/src/assets/javascripts/browser/viewport/offset/index.ts b/src/assets/javascripts/browser/viewport/offset/index.ts
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import {
+ Observable,
+ fromEvent,
+ map,
+ merge,
+ startWith
+} from "rxjs"
+
+/* ----------------------------------------------------------------------------
+ * Types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Viewport offset
+ */
+export interface ViewportOffset {
+ x: number /* Horizontal offset */
+ y: number /* Vertical offset */
+}
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Retrieve viewport offset
+ *
+ * On iOS Safari, viewport offset can be negative due to overflow scrolling.
+ * As this may induce strange behaviors downstream, we'll just limit it to 0.
+ *
+ * @returns Viewport offset
+ */
+export function getViewportOffset(): ViewportOffset {
+ return {
+ x: Math.max(0, scrollX),
+ y: Math.max(0, scrollY)
+ }
+}
+
+/* ------------------------------------------------------------------------- */
+
+/**
+ * Watch viewport offset
+ *
+ * @returns Viewport offset observable
+ */
+export function watchViewportOffset(): Observable<ViewportOffset> {
+ return merge(
+ fromEvent(window, "scroll", { passive: true }),
+ fromEvent(window, "resize", { passive: true })
+ )
+ .pipe(
+ map(getViewportOffset),
+ startWith(getViewportOffset())
+ )
+}
diff --git a/src/assets/javascripts/browser/viewport/size/index.ts b/src/assets/javascripts/browser/viewport/size/index.ts
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import {
+ Observable,
+ fromEvent,
+ map,
+ startWith
+} from "rxjs"
+
+/* ----------------------------------------------------------------------------
+ * Types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Viewport size
+ */
+export interface ViewportSize {
+ width: number /* Viewport width */
+ height: number /* Viewport height */
+}
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Retrieve viewport size
+ *
+ * @returns Viewport size
+ */
+export function getViewportSize(): ViewportSize {
+ return {
+ width: innerWidth,
+ height: innerHeight
+ }
+}
+
+/* ------------------------------------------------------------------------- */
+
+/**
+ * Watch viewport size
+ *
+ * @returns Viewport size observable
+ */
+export function watchViewportSize(): Observable<ViewportSize> {
+ return fromEvent(window, "resize", { passive: true })
+ .pipe(
+ map(getViewportSize),
+ startWith(getViewportSize())
+ )
+}
diff --git a/src/assets/javascripts/browser/worker/index.ts b/src/assets/javascripts/browser/worker/index.ts
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import {
+ Observable,
+ Subject,
+ fromEvent,
+ map,
+ share,
+ switchMap,
+ tap,
+ throttle
+} from "rxjs"
+
+/* ----------------------------------------------------------------------------
+ * Types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Worker message
+ */
+export interface WorkerMessage {
+ type: unknown /* Message type */
+ data?: unknown /* Message data */
+}
+
+/**
+ * Worker handler
+ *
+ * @template T - Message type
+ */
+export interface WorkerHandler<
+ T extends WorkerMessage
+> {
+ tx$: Subject<T> /* Message transmission subject */
+ rx$: Observable<T> /* Message receive observable */
+}
+
+/* ----------------------------------------------------------------------------
+ * Helper types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Watch options
+ *
+ * @template T - Worker message type
+ */
+interface WatchOptions<T extends WorkerMessage> {
+ tx$: Observable<T> /* Message transmission observable */
+}
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Watch a web worker
+ *
+ * This function returns an observable that sends all values emitted by the
+ * message observable to the web worker. Web worker communication is expected
+ * to be bidirectional (request-response) and synchronous. Messages that are
+ * emitted during a pending request are throttled, the last one is emitted.
+ *
+ * @param worker - Web worker
+ * @param options - Options
+ *
+ * @returns Worker message observable
+ */
+export function watchWorker<T extends WorkerMessage>(
+ worker: Worker, { tx$ }: WatchOptions<T>
+): Observable<T> {
+
+ /* Intercept messages from worker-like objects */
+ const rx$ = fromEvent<MessageEvent>(worker, "message")
+ .pipe(
+ map(({ data }) => data as T)
+ )
+
+ /* Send and receive messages, return hot observable */
+ return tx$
+ .pipe(
+ throttle(() => rx$, { leading: true, trailing: true }),
+ tap(message => worker.postMessage(message)),
+ switchMap(() => rx$),
+ share()
+ )
+}
diff --git a/src/assets/javascripts/bundle.ts b/src/assets/javascripts/bundle.ts
@@ -0,0 +1,273 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import "array-flat-polyfill"
+import "focus-visible"
+import "unfetch/polyfill"
+import "url-polyfill"
+
+import {
+ EMPTY,
+ NEVER,
+ Subject,
+ defer,
+ delay,
+ filter,
+ map,
+ merge,
+ mergeWith,
+ shareReplay,
+ switchMap
+} from "rxjs"
+
+import { configuration, feature } from "./_"
+import {
+ at,
+ getOptionalElement,
+ requestJSON,
+ setToggle,
+ watchDocument,
+ watchKeyboard,
+ watchLocation,
+ watchLocationTarget,
+ watchMedia,
+ watchPrint,
+ watchViewport
+} from "./browser"
+import {
+ getComponentElement,
+ getComponentElements,
+ mountAnnounce,
+ mountBackToTop,
+ mountConsent,
+ mountContent,
+ mountDialog,
+ mountHeader,
+ mountHeaderTitle,
+ mountPalette,
+ mountSearch,
+ mountSearchHiglight,
+ mountSidebar,
+ mountSource,
+ mountTableOfContents,
+ mountTabs,
+ watchHeader,
+ watchMain
+} from "./components"
+import {
+ SearchIndex,
+ setupClipboardJS,
+ setupInstantLoading,
+ setupVersionSelector
+} from "./integrations"
+import {
+ patchIndeterminate,
+ patchScrollfix,
+ patchScrolllock
+} from "./patches"
+import "./polyfills"
+
+/* ----------------------------------------------------------------------------
+ * Application
+ * ------------------------------------------------------------------------- */
+
+/* Yay, JavaScript is available */
+document.documentElement.classList.remove("no-js")
+document.documentElement.classList.add("js")
+
+/* Set up navigation observables and subjects */
+const document$ = watchDocument()
+const location$ = watchLocation()
+const target$ = watchLocationTarget()
+const keyboard$ = watchKeyboard()
+
+/* Set up media observables */
+const viewport$ = watchViewport()
+const tablet$ = watchMedia("(min-width: 960px)")
+const screen$ = watchMedia("(min-width: 1220px)")
+const print$ = watchPrint()
+
+/* Retrieve search index, if search is enabled */
+const config = configuration()
+const index$ = document.forms.namedItem("search")
+ ? __search?.index || requestJSON<SearchIndex>(
+ new URL("search/search_index.json", config.base)
+ )
+ : NEVER
+
+/* Set up Clipboard.js integration */
+const alert$ = new Subject<string>()
+setupClipboardJS({ alert$ })
+
+/* Set up instant loading, if enabled */
+if (feature("navigation.instant"))
+ setupInstantLoading({ document$, location$, viewport$ })
+
+/* Set up version selector */
+if (config.version?.provider === "mike")
+ setupVersionSelector({ document$ })
+
+/* Always close drawer and search on navigation */
+merge(location$, target$)
+ .pipe(
+ delay(125)
+ )
+ .subscribe(() => {
+ setToggle("drawer", false)
+ setToggle("search", false)
+ })
+
+/* Set up global keyboard handlers */
+keyboard$
+ .pipe(
+ filter(({ mode }) => mode === "global")
+ )
+ .subscribe(key => {
+ switch (key.type) {
+
+ /* Go to previous page */
+ case "p":
+ case ",":
+ const prev = getOptionalElement("[href][rel=prev]")
+ if (typeof prev !== "undefined")
+ prev.click()
+ break
+
+ /* Go to next page */
+ case "n":
+ case ".":
+ const next = getOptionalElement("[href][rel=next]")
+ if (typeof next !== "undefined")
+ next.click()
+ break
+ }
+ })
+
+/* Set up patches */
+patchIndeterminate({ document$, tablet$ })
+patchScrollfix({ document$ })
+patchScrolllock({ viewport$, tablet$ })
+
+/* Set up header and main area observable */
+const header$ = watchHeader(getComponentElement("header"), { viewport$ })
+const main$ = document$
+ .pipe(
+ map(() => getComponentElement("main")),
+ switchMap(el => watchMain(el, { viewport$, header$ })),
+ shareReplay(1)
+ )
+
+/* Set up control component observables */
+const control$ = merge(
+
+ /* Consent */
+ ...getComponentElements("consent")
+ .map(el => mountConsent(el, { target$ })),
+
+ /* Dialog */
+ ...getComponentElements("dialog")
+ .map(el => mountDialog(el, { alert$ })),
+
+ /* Header */
+ ...getComponentElements("header")
+ .map(el => mountHeader(el, { viewport$, header$, main$ })),
+
+ /* Color palette */
+ ...getComponentElements("palette")
+ .map(el => mountPalette(el)),
+
+ /* Search */
+ ...getComponentElements("search")
+ .map(el => mountSearch(el, { index$, keyboard$ })),
+
+ /* Repository information */
+ ...getComponentElements("source")
+ .map(el => mountSource(el))
+)
+
+/* Set up content component observables */
+const content$ = defer(() => merge(
+
+ /* Announcement bar */
+ ...getComponentElements("announce")
+ .map(el => mountAnnounce(el)),
+
+ /* Content */
+ ...getComponentElements("content")
+ .map(el => mountContent(el, { target$, print$ })),
+
+ /* Search highlighting */
+ ...getComponentElements("content")
+ .map(el => feature("search.highlight")
+ ? mountSearchHiglight(el, { index$, location$ })
+ : EMPTY
+ ),
+
+ /* Header title */
+ ...getComponentElements("header-title")
+ .map(el => mountHeaderTitle(el, { viewport$, header$ })),
+
+ /* Sidebar */
+ ...getComponentElements("sidebar")
+ .map(el => el.getAttribute("data-md-type") === "navigation"
+ ? at(screen$, () => mountSidebar(el, { viewport$, header$, main$ }))
+ : at(tablet$, () => mountSidebar(el, { viewport$, header$, main$ }))
+ ),
+
+ /* Navigation tabs */
+ ...getComponentElements("tabs")
+ .map(el => mountTabs(el, { viewport$, header$ })),
+
+ /* Table of contents */
+ ...getComponentElements("toc")
+ .map(el => mountTableOfContents(el, { viewport$, header$, target$ })),
+
+ /* Back-to-top button */
+ ...getComponentElements("top")
+ .map(el => mountBackToTop(el, { viewport$, header$, main$, target$ }))
+))
+
+/* Set up component observables */
+const component$ = document$
+ .pipe(
+ switchMap(() => content$),
+ mergeWith(control$),
+ shareReplay(1)
+ )
+
+/* Subscribe to all components */
+component$.subscribe()
+
+/* ----------------------------------------------------------------------------
+ * Exports
+ * ------------------------------------------------------------------------- */
+
+window.document$ = document$ /* Document observable */
+window.location$ = location$ /* Location subject */
+window.target$ = target$ /* Location target observable */
+window.keyboard$ = keyboard$ /* Keyboard observable */
+window.viewport$ = viewport$ /* Viewport observable */
+window.tablet$ = tablet$ /* Media tablet observable */
+window.screen$ = screen$ /* Media screen observable */
+window.print$ = print$ /* Media print observable */
+window.alert$ = alert$ /* Alert subject */
+window.component$ = component$ /* Component observable */
diff --git a/src/assets/javascripts/components/_/index.ts b/src/assets/javascripts/components/_/index.ts
@@ -0,0 +1,136 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import { getElement, getElements } from "~/browser"
+
+/* ----------------------------------------------------------------------------
+ * Types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Component type
+ */
+export type ComponentType =
+ | "announce" /* Announcement bar */
+ | "container" /* Container */
+ | "consent" /* Consent */
+ | "content" /* Content */
+ | "dialog" /* Dialog */
+ | "header" /* Header */
+ | "header-title" /* Header title */
+ | "header-topic" /* Header topic */
+ | "main" /* Main area */
+ | "outdated" /* Version warning */
+ | "palette" /* Color palette */
+ | "search" /* Search */
+ | "search-query" /* Search input */
+ | "search-result" /* Search results */
+ | "search-share" /* Search sharing */
+ | "search-suggest" /* Search suggestions */
+ | "sidebar" /* Sidebar */
+ | "skip" /* Skip link */
+ | "source" /* Repository information */
+ | "tabs" /* Navigation tabs */
+ | "toc" /* Table of contents */
+ | "top" /* Back-to-top button */
+
+/**
+ * Component
+ *
+ * @template T - Component type
+ * @template U - Reference type
+ */
+export type Component<
+ T extends {} = {},
+ U extends HTMLElement = HTMLElement
+> =
+ T & {
+ ref: U /* Component reference */
+ }
+
+/* ----------------------------------------------------------------------------
+ * Helper types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Component type map
+ */
+interface ComponentTypeMap {
+ "announce": HTMLElement /* Announcement bar */
+ "container": HTMLElement /* Container */
+ "consent": HTMLElement /* Consent */
+ "content": HTMLElement /* Content */
+ "dialog": HTMLElement /* Dialog */
+ "header": HTMLElement /* Header */
+ "header-title": HTMLElement /* Header title */
+ "header-topic": HTMLElement /* Header topic */
+ "main": HTMLElement /* Main area */
+ "outdated": HTMLElement /* Version warning */
+ "palette": HTMLElement /* Color palette */
+ "search": HTMLElement /* Search */
+ "search-query": HTMLInputElement /* Search input */
+ "search-result": HTMLElement /* Search results */
+ "search-share": HTMLAnchorElement /* Search sharing */
+ "search-suggest": HTMLElement /* Search suggestions */
+ "sidebar": HTMLElement /* Sidebar */
+ "skip": HTMLAnchorElement /* Skip link */
+ "source": HTMLAnchorElement /* Repository information */
+ "tabs": HTMLElement /* Navigation tabs */
+ "toc": HTMLElement /* Table of contents */
+ "top": HTMLAnchorElement /* Back-to-top button */
+}
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Retrieve the element for a given component or throw a reference error
+ *
+ * @template T - Component type
+ *
+ * @param type - Component type
+ * @param node - Node of reference
+ *
+ * @returns Element
+ */
+export function getComponentElement<T extends ComponentType>(
+ type: T, node: ParentNode = document
+): ComponentTypeMap[T] {
+ return getElement(`[data-md-component=${type}]`, node)
+}
+
+/**
+ * Retrieve all elements for a given component
+ *
+ * @template T - Component type
+ *
+ * @param type - Component type
+ * @param node - Node of reference
+ *
+ * @returns Elements
+ */
+export function getComponentElements<T extends ComponentType>(
+ type: T, node: ParentNode = document
+): ComponentTypeMap[T][] {
+ return getElements(`[data-md-component=${type}]`, node)
+}
diff --git a/src/assets/javascripts/components/announce/index.ts b/src/assets/javascripts/components/announce/index.ts
@@ -0,0 +1,110 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import {
+ EMPTY,
+ Observable,
+ Subject,
+ defer,
+ finalize,
+ fromEvent,
+ map,
+ startWith,
+ tap
+} from "rxjs"
+
+import { feature } from "~/_"
+import { getElement } from "~/browser"
+
+import { Component } from "../_"
+
+/* ----------------------------------------------------------------------------
+ * Types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Announcement bar
+ */
+export interface Announce {
+ hash: number /* Content hash */
+}
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Watch announcement bar
+ *
+ * @param el - Announcement bar element
+ *
+ * @returns Announcement bar observable
+ */
+export function watchAnnounce(
+ el: HTMLElement
+): Observable<Announce> {
+ const button = getElement(".md-typeset > :first-child", el)
+ return fromEvent(button, "click", { once: true })
+ .pipe(
+ map(() => getElement(".md-typeset", el)),
+ map(content => ({ hash: __md_hash(content.innerHTML) }))
+ )
+}
+
+/**
+ * Mount announcement bar
+ *
+ * @param el - Announcement bar element
+ *
+ * @returns Announcement bar component observable
+ */
+export function mountAnnounce(
+ el: HTMLElement
+): Observable<Component<Announce>> {
+ if (!feature("announce.dismiss") || !el.childElementCount)
+ return EMPTY
+
+ /* Mount component on subscription */
+ return defer(() => {
+ const push$ = new Subject<Announce>()
+ push$
+ .pipe(
+ startWith({ hash: __md_get<number>("__announce") })
+ )
+ .subscribe(({ hash }) => {
+ if (hash && hash === (__md_get<number>("__announce") ?? hash)) {
+ el.hidden = true
+
+ /* Persist preference in local storage */
+ __md_set<number>("__announce", hash)
+ }
+ })
+
+ /* Create and return component */
+ return watchAnnounce(el)
+ .pipe(
+ tap(state => push$.next(state)),
+ finalize(() => push$.complete()),
+ map(state => ({ ref: el, ...state }))
+ )
+ })
+}
diff --git a/src/assets/javascripts/components/consent/index.ts b/src/assets/javascripts/components/consent/index.ts
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import {
+ Observable,
+ Subject,
+ finalize,
+ map,
+ tap
+} from "rxjs"
+
+import { Component } from "../_"
+
+/* ----------------------------------------------------------------------------
+ * Types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Consent
+ */
+export interface Consent {
+ hidden: boolean /* Consent is hidden */
+}
+
+/* ----------------------------------------------------------------------------
+ * Helper types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Watch options
+ */
+interface WatchOptions {
+ target$: Observable<HTMLElement> /* Target observable */
+}
+
+/**
+ * Mount options
+ */
+interface MountOptions {
+ target$: Observable<HTMLElement> /* Target observable */
+}
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Watch consent
+ *
+ * @param el - Consent element
+ * @param options - Options
+ *
+ * @returns Consent observable
+ */
+export function watchConsent(
+ el: HTMLElement, { target$ }: WatchOptions
+): Observable<Consent> {
+ return target$
+ .pipe(
+ map(target => ({ hidden: target !== el }))
+ )
+}
+
+/* ------------------------------------------------------------------------- */
+
+/**
+ * Mount consent
+ *
+ * @param el - Consent element
+ * @param options - Options
+ *
+ * @returns Consent component observable
+ */
+export function mountConsent(
+ el: HTMLElement, options: MountOptions
+): Observable<Component<Consent>> {
+ const internal$ = new Subject<Consent>()
+ internal$.subscribe(({ hidden }) => {
+ el.hidden = hidden
+ })
+
+ /* Create and return component */
+ return watchConsent(el, options)
+ .pipe(
+ tap(state => internal$.next(state)),
+ finalize(() => internal$.complete()),
+ map(state => ({ ref: el, ...state }))
+ )
+}
diff --git a/src/assets/javascripts/components/content/_/index.ts b/src/assets/javascripts/components/content/_/index.ts
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import { Observable, merge } from "rxjs"
+
+import { getElements } from "~/browser"
+
+import { Component } from "../../_"
+import { Annotation } from "../annotation"
+import {
+ CodeBlock,
+ Mermaid,
+ mountCodeBlock,
+ mountMermaid
+} from "../code"
+import {
+ Details,
+ mountDetails
+} from "../details"
+import {
+ DataTable,
+ mountDataTable
+} from "../table"
+import {
+ ContentTabs,
+ mountContentTabs
+} from "../tabs"
+
+/* ----------------------------------------------------------------------------
+ * Types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Content
+ */
+export type Content =
+ | Annotation
+ | ContentTabs
+ | CodeBlock
+ | Mermaid
+ | DataTable
+ | Details
+
+/* ----------------------------------------------------------------------------
+ * Helper types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Mount options
+ */
+interface MountOptions {
+ target$: Observable<HTMLElement> /* Location target observable */
+ print$: Observable<boolean> /* Media print observable */
+}
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Mount content
+ *
+ * This function mounts all components that are found in the content of the
+ * actual article, including code blocks, data tables and details.
+ *
+ * @param el - Content element
+ * @param options - Options
+ *
+ * @returns Content component observable
+ */
+export function mountContent(
+ el: HTMLElement, { target$, print$ }: MountOptions
+): Observable<Component<Content>> {
+ return merge(
+
+ /* Code blocks */
+ ...getElements("pre:not(.mermaid) > code", el)
+ .map(child => mountCodeBlock(child, { print$ })),
+
+ /* Mermaid diagrams */
+ ...getElements("pre.mermaid", el)
+ .map(child => mountMermaid(child)),
+
+ /* Data tables */
+ ...getElements("table:not([class])", el)
+ .map(child => mountDataTable(child)),
+
+ /* Details */
+ ...getElements("details", el)
+ .map(child => mountDetails(child, { target$, print$ })),
+
+ /* Content tabs */
+ ...getElements("[data-tabs]", el)
+ .map(child => mountContentTabs(child))
+ )
+}
diff --git a/src/assets/javascripts/components/content/annotation/_/index.ts b/src/assets/javascripts/components/content/annotation/_/index.ts
@@ -0,0 +1,185 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import {
+ EMPTY,
+ Observable,
+ Subject,
+ animationFrameScheduler,
+ combineLatest,
+ defer,
+ finalize,
+ fromEvent,
+ map,
+ switchMap,
+ take,
+ takeLast,
+ takeUntil,
+ tap,
+ throttleTime
+} from "rxjs"
+
+import {
+ ElementOffset,
+ getElement,
+ getElementSize,
+ watchElementContentOffset,
+ watchElementFocus,
+ watchElementOffset,
+ watchElementVisibility
+} from "~/browser"
+
+import { Component } from "../../../_"
+
+/* ----------------------------------------------------------------------------
+ * Types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Annotation
+ */
+export interface Annotation {
+ active: boolean /* Annotation is active */
+ offset: ElementOffset /* Annotation offset */
+}
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Watch annotation
+ *
+ * @param el - Annotation element
+ * @param container - Containing element
+ *
+ * @returns Annotation observable
+ */
+export function watchAnnotation(
+ el: HTMLElement, container: HTMLElement
+): Observable<Annotation> {
+ const offset$ = defer(() => combineLatest([
+ watchElementOffset(el),
+ watchElementContentOffset(container)
+ ]))
+ .pipe(
+ map(([{ x, y }, scroll]) => {
+ const { width } = getElementSize(el)
+ return ({
+ x: x - scroll.x + width / 2,
+ y: y - scroll.y
+ })
+ })
+ )
+
+ /* Actively watch annotation on focus */
+ return watchElementFocus(el)
+ .pipe(
+ switchMap(active => offset$
+ .pipe(
+ map(offset => ({ active, offset })),
+ take(+!active || Infinity)
+ )
+ )
+ )
+}
+
+/**
+ * Mount annotation
+ *
+ * @param el - Annotation element
+ * @param container - Containing element
+ *
+ * @returns Annotation component observable
+ */
+export function mountAnnotation(
+ el: HTMLElement, container: HTMLElement
+): Observable<Component<Annotation>> {
+ return defer(() => {
+ const push$ = new Subject<Annotation>()
+ push$.subscribe({
+
+ /* Handle emission */
+ next({ offset }) {
+ el.style.setProperty("--md-tooltip-x", `${offset.x}px`)
+ el.style.setProperty("--md-tooltip-y", `${offset.y}px`)
+ },
+
+ /* Handle complete */
+ complete() {
+ el.style.removeProperty("--md-tooltip-x")
+ el.style.removeProperty("--md-tooltip-y")
+ }
+ })
+
+ /* Start animation only when annotation is visible */
+ const done$ = push$.pipe(takeLast(1))
+ watchElementVisibility(el)
+ .pipe(
+ takeUntil(done$)
+ )
+ .subscribe(visible => {
+ el.toggleAttribute("data-md-visible", visible)
+ })
+
+ /* Track relative origin of tooltip */
+ push$
+ .pipe(
+ throttleTime(500, animationFrameScheduler),
+ map(() => container.getBoundingClientRect()),
+ map(({ x }) => x)
+ )
+ .subscribe({
+
+ /* Handle emission */
+ next(origin) {
+ if (origin)
+ el.style.setProperty("--md-tooltip-0", `${-origin}px`)
+ else
+ el.style.removeProperty("--md-tooltip-0")
+ },
+
+ /* Handle complete */
+ complete() {
+ el.style.removeProperty("--md-tooltip-0")
+ }
+ })
+
+ /* Close open annotation on click */
+ const index = getElement(":scope > :last-child", el)
+ const blur$ = fromEvent(index, "mousedown", { once: true })
+ push$
+ .pipe(
+ switchMap(({ active }) => active ? blur$ : EMPTY),
+ tap(ev => ev.preventDefault())
+ )
+ .subscribe(() => el.blur())
+
+ /* Create and return component */
+ return watchAnnotation(el, container)
+ .pipe(
+ tap(state => push$.next(state)),
+ finalize(() => push$.complete()),
+ map(state => ({ ref: el, ...state }))
+ )
+ })
+}
diff --git a/src/assets/javascripts/components/content/annotation/index.ts b/src/assets/javascripts/components/content/annotation/index.ts
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+export * from "./_"
+export * from "./list"
diff --git a/src/assets/javascripts/components/content/annotation/list/index.ts b/src/assets/javascripts/components/content/annotation/list/index.ts
@@ -0,0 +1,167 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import {
+ EMPTY,
+ Observable,
+ Subject,
+ defer,
+ finalize,
+ merge,
+ share,
+ takeLast,
+ takeUntil
+} from "rxjs"
+
+import {
+ getElement,
+ getElements,
+ getOptionalElement
+} from "~/browser"
+import { renderAnnotation } from "~/templates"
+
+import { Component } from "../../../_"
+import {
+ Annotation,
+ mountAnnotation
+} from "../_"
+
+/* ----------------------------------------------------------------------------
+ * Helper types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Mount options
+ */
+interface MountOptions {
+ print$: Observable<boolean> /* Media print observable */
+}
+
+/* ----------------------------------------------------------------------------
+ * Helper functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Find all annotation markers in the given code block
+ *
+ * @param container - Containing element
+ *
+ * @returns Annotation markers
+ */
+function findAnnotationMarkers(container: HTMLElement): Text[] {
+ const markers: Text[] = []
+ for (const comment of getElements(".c, .c1, .cm", container)) {
+ let match: RegExpExecArray | null
+
+ /* Split text at marker and add to list */
+ let text = comment.firstChild as Text
+ if (text instanceof Text)
+ while ((match = /\((\d+)\)/.exec(text.textContent!))) {
+ const marker = text.splitText(match.index)
+ text = marker.splitText(match[0].length)
+ markers.push(marker)
+ }
+ }
+ return markers
+}
+
+/**
+ * Swap the child nodes of two elements
+ *
+ * @param source - Source element
+ * @param target - Target element
+ */
+function swap(source: HTMLElement, target: HTMLElement): void {
+ target.append(...Array.from(source.childNodes))
+}
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Mount annotation list
+ *
+ * This function analyzes the containing code block and checks for markers
+ * referring to elements in the given annotation list. If no markers are found,
+ * the list is left untouched. Otherwise, list elements are rendered as
+ * annotations inside the code block.
+ *
+ * @param el - Annotation list element
+ * @param container - Containing element
+ * @param options - Options
+ *
+ * @returns Annotation component observable
+ */
+export function mountAnnotationList(
+ el: HTMLElement, container: HTMLElement, { print$ }: MountOptions
+): Observable<Component<Annotation>> {
+
+ /* Find and replace all markers with empty annotations */
+ const annotations = new Map<number, HTMLElement>()
+ for (const marker of findAnnotationMarkers(container)) {
+ const [, id] = marker.textContent!.match(/\((\d+)\)/)!
+ if (getOptionalElement(`li:nth-child(${id})`, el)) {
+ annotations.set(+id, renderAnnotation(+id))
+ marker.replaceWith(annotations.get(+id)!)
+ }
+ }
+
+ /* Keep list if there are no annotations to render */
+ if (annotations.size === 0)
+ return EMPTY
+
+ /* Create and return component */
+ return defer(() => {
+ const done$ = new Subject<void>()
+
+ /* Handle print mode - see https://bit.ly/3rgPdpt */
+ print$
+ .pipe(
+ takeUntil(done$.pipe(takeLast(1)))
+ )
+ .subscribe(active => {
+ el.hidden = !active
+
+ /* Show annotations in code block or list (print) */
+ for (const [id, annotation] of annotations) {
+ const inner = getElement(".md-typeset", annotation)
+ const child = getElement(`li:nth-child(${id})`, el)
+ if (!active)
+ swap(child, inner)
+ else
+ swap(inner, child)
+ }
+ })
+
+ /* Create and return component */
+ return merge(...[...annotations]
+ .map(([, annotation]) => (
+ mountAnnotation(annotation, container)
+ ))
+ )
+ .pipe(
+ finalize(() => done$.complete()),
+ share()
+ )
+ })
+}
diff --git a/src/assets/javascripts/components/content/code/_/index.ts b/src/assets/javascripts/components/content/code/_/index.ts
@@ -0,0 +1,227 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import ClipboardJS from "clipboard"
+import {
+ EMPTY,
+ Observable,
+ Subject,
+ defer,
+ distinctUntilChanged,
+ distinctUntilKeyChanged,
+ filter,
+ finalize,
+ map,
+ mergeWith,
+ switchMap,
+ take,
+ takeLast,
+ takeUntil,
+ tap
+} from "rxjs"
+
+import { feature } from "~/_"
+import {
+ getElementContentSize,
+ watchElementSize,
+ watchElementVisibility
+} from "~/browser"
+import { renderClipboardButton } from "~/templates"
+
+import { Component } from "../../../_"
+import {
+ Annotation,
+ mountAnnotationList
+} from "../../annotation"
+
+/* ----------------------------------------------------------------------------
+ * Types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Code block
+ */
+export interface CodeBlock {
+ scrollable: boolean /* Code block overflows */
+}
+
+/* ----------------------------------------------------------------------------
+ * Helper types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Mount options
+ */
+interface MountOptions {
+ print$: Observable<boolean> /* Media print observable */
+}
+
+/* ----------------------------------------------------------------------------
+ * Data
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Global sequence number for Clipboard.js integration
+ */
+let sequence = 0
+
+/* ----------------------------------------------------------------------------
+ * Helper functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Find candidate list element directly following a code block
+ *
+ * @param el - Code block element
+ *
+ * @returns List element or nothing
+ */
+function findCandidateList(el: HTMLElement): HTMLElement | undefined {
+ if (el.nextElementSibling) {
+ const sibling = el.nextElementSibling as HTMLElement
+ if (sibling.tagName === "OL")
+ return sibling
+
+ /* Skip empty paragraphs - see https://bit.ly/3r4ZJ2O */
+ else if (sibling.tagName === "P" && !sibling.children.length)
+ return findCandidateList(sibling)
+ }
+
+ /* Everything else */
+ return undefined
+}
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Watch code block
+ *
+ * This function monitors size changes of the viewport, as well as switches of
+ * content tabs with embedded code blocks, as both may trigger overflow.
+ *
+ * @param el - Code block element
+ *
+ * @returns Code block observable
+ */
+export function watchCodeBlock(
+ el: HTMLElement
+): Observable<CodeBlock> {
+ return watchElementSize(el)
+ .pipe(
+ map(({ width }) => {
+ const content = getElementContentSize(el)
+ return {
+ scrollable: content.width > width
+ }
+ }),
+ distinctUntilKeyChanged("scrollable")
+ )
+}
+
+/**
+ * Mount code block
+ *
+ * This function ensures that an overflowing code block is focusable through
+ * keyboard, so it can be scrolled without a mouse to improve on accessibility.
+ * Furthermore, if code annotations are enabled, they are mounted if and only
+ * if the code block is currently visible, e.g., not in a hidden content tab.
+ *
+ * @param el - Code block element
+ * @param options - Options
+ *
+ * @returns Code block and annotation component observable
+ */
+export function mountCodeBlock(
+ el: HTMLElement, options: MountOptions
+): Observable<Component<CodeBlock | Annotation>> {
+ const { matches: hover } = matchMedia("(hover)")
+
+ /* Defer mounting of code block - see https://bit.ly/3vHVoVD */
+ const factory$ = defer(() => {
+ const push$ = new Subject<CodeBlock>()
+ push$.subscribe(({ scrollable }) => {
+ if (scrollable && hover)
+ el.setAttribute("tabindex", "0")
+ else
+ el.removeAttribute("tabindex")
+ })
+
+ /* Render button for Clipboard.js integration */
+ if (ClipboardJS.isSupported()) {
+ const parent = el.closest("pre")!
+ parent.id = `__code_${++sequence}`
+ parent.insertBefore(
+ renderClipboardButton(parent.id),
+ el
+ )
+ }
+
+ /* Handle code annotations */
+ const container = el.closest(".highlight")
+ if (container instanceof HTMLElement) {
+ const list = findCandidateList(container)
+
+ /* Mount code annotations, if enabled */
+ if (typeof list !== "undefined" && (
+ container.classList.contains("annotate") ||
+ feature("content.code.annotate")
+ )) {
+ const annotations$ = mountAnnotationList(list, el, options)
+
+ /* Create and return component */
+ return watchCodeBlock(el)
+ .pipe(
+ tap(state => push$.next(state)),
+ finalize(() => push$.complete()),
+ map(state => ({ ref: el, ...state })),
+ mergeWith(
+ watchElementSize(container)
+ .pipe(
+ takeUntil(push$.pipe(takeLast(1))),
+ map(({ width, height }) => width && height),
+ distinctUntilChanged(),
+ switchMap(active => active ? annotations$ : EMPTY)
+ )
+ )
+ )
+ }
+ }
+
+ /* Create and return component */
+ return watchCodeBlock(el)
+ .pipe(
+ tap(state => push$.next(state)),
+ finalize(() => push$.complete()),
+ map(state => ({ ref: el, ...state }))
+ )
+ })
+
+ /* Mount code block on first sight */
+ return watchElementVisibility(el)
+ .pipe(
+ filter(visible => visible),
+ take(1),
+ switchMap(() => factory$)
+ )
+}
diff --git a/src/assets/javascripts/components/content/code/index.ts b/src/assets/javascripts/components/content/code/index.ts
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+export * from "./_"
+export * from "./mermaid"
diff --git a/src/assets/javascripts/components/content/code/mermaid/index.css b/src/assets/javascripts/components/content/code/mermaid/index.css
@@ -0,0 +1,371 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+/* ----------------------------------------------------------------------------
+ * Rules: general
+ * ------------------------------------------------------------------------- */
+
+/* General node */
+.node circle,
+.node ellipse,
+.node path,
+.node polygon,
+.node rect {
+ fill: var(--md-mermaid-node-bg-color);
+ stroke: var(--md-mermaid-node-fg-color);
+}
+
+/* General marker */
+marker {
+ fill: var(--md-mermaid-edge-color) !important;
+}
+
+/* General edge label */
+.edgeLabel .label rect {
+ fill: transparent;
+}
+
+/* ----------------------------------------------------------------------------
+ * Rules: flowcharts
+ * ------------------------------------------------------------------------- */
+
+/* Flowchart node label */
+.label {
+ color: var(--md-mermaid-label-fg-color);
+ font-family: var(--md-mermaid-font-family);
+}
+
+/* Flowchart node label container */
+.label foreignObject {
+ overflow: visible;
+ line-height: initial;
+}
+
+/* Flowchart edge label in node label */
+.label div .edgeLabel {
+ color: var(--md-mermaid-label-fg-color);
+ background-color: var(--md-mermaid-label-bg-color);
+}
+
+/* Flowchart edge label */
+.edgeLabel,
+.edgeLabel rect {
+ color: var(--md-mermaid-edge-color);
+ background-color: var(--md-mermaid-label-bg-color);
+ fill: var(--md-mermaid-label-bg-color);
+}
+
+/* Flowchart edge path */
+.edgePath .path,
+.flowchart-link {
+ stroke: var(--md-mermaid-edge-color);
+}
+
+/* Flowchart arrow head */
+.edgePath .arrowheadPath {
+ fill: var(--md-mermaid-edge-color);
+ stroke: none;
+}
+
+/* Flowchart subgraph */
+.cluster rect {
+ fill: var(--md-default-fg-color--lightest);
+ stroke: var(--md-default-fg-color--lighter);
+}
+
+/* Flowchart subgraph labels */
+.cluster span {
+ color: var(--md-mermaid-label-fg-color);
+ font-family: var(--md-mermaid-font-family);
+}
+
+/* Flowchart markers */
+defs #flowchart-circleStart,
+defs #flowchart-circleEnd,
+defs #flowchart-crossStart,
+defs #flowchart-crossEnd,
+defs #flowchart-pointStart,
+defs #flowchart-pointEnd {
+ stroke: none;
+}
+
+/* ----------------------------------------------------------------------------
+ * Rules: class diagrams
+ * ------------------------------------------------------------------------- */
+
+/* Class group node */
+g.classGroup line,
+g.classGroup rect {
+ fill: var(--md-mermaid-node-bg-color);
+ stroke: var(--md-mermaid-node-fg-color);
+}
+
+/* Class group node text */
+g.classGroup text {
+ font-family: var(--md-mermaid-font-family);
+ fill: var(--md-mermaid-label-fg-color);
+}
+
+/* Class label box */
+.classLabel .box {
+ background-color: var(--md-mermaid-label-bg-color);
+ opacity: 1;
+ fill: var(--md-mermaid-label-bg-color);
+}
+
+/* Class label text */
+.classLabel .label {
+ font-family: var(--md-mermaid-font-family);
+ fill: var(--md-mermaid-label-fg-color);
+}
+
+/* Class group divider */
+.node .divider {
+ stroke: var(--md-mermaid-node-fg-color);
+}
+
+/* Class relation */
+.relation {
+ stroke: var(--md-mermaid-edge-color);
+}
+
+/* Class relation cardinality */
+.cardinality {
+ font-family: var(--md-mermaid-font-family);
+ fill: var(--md-mermaid-label-fg-color);
+}
+
+/* Class relation cardinality text */
+.cardinality text {
+ fill: inherit !important;
+}
+
+/* Class extension, composition and dependency marker */
+defs #classDiagram-extensionStart,
+defs #classDiagram-extensionEnd,
+defs #classDiagram-compositionStart,
+defs #classDiagram-compositionEnd,
+defs #classDiagram-dependencyStart,
+defs #classDiagram-dependencyEnd {
+ fill: var(--md-mermaid-edge-color) !important;
+ stroke: var(--md-mermaid-edge-color) !important;
+}
+
+/* Class aggregation marker */
+defs #classDiagram-aggregationStart,
+defs #classDiagram-aggregationEnd {
+ fill: var(--md-mermaid-label-bg-color) !important;
+ stroke: var(--md-mermaid-edge-color) !important;
+}
+
+/* ----------------------------------------------------------------------------
+ * Rules: state diagrams
+ * ------------------------------------------------------------------------- */
+
+/* State group node */
+g.stateGroup rect {
+ fill: var(--md-mermaid-node-bg-color);
+ stroke: var(--md-mermaid-node-fg-color);
+}
+
+/* State group title */
+g.stateGroup .state-title {
+ font-family: var(--md-mermaid-font-family);
+ fill: var(--md-mermaid-label-fg-color) !important;
+}
+
+/* State group background */
+g.stateGroup .composit {
+ fill: var(--md-mermaid-label-bg-color);
+}
+
+/* State node label */
+.nodeLabel {
+ color: var(--md-mermaid-label-fg-color);
+ font-family: var(--md-mermaid-font-family);
+}
+
+/* State start and end marker */
+.start-state,
+.node circle.state-start,
+.node circle.state-end {
+ fill: var(--md-mermaid-edge-color);
+ stroke: none;
+}
+
+/* State end marker */
+.end-state-outer,
+.end-state-inner {
+ fill: var(--md-mermaid-edge-color);
+}
+
+/* State end marker */
+.end-state-inner,
+.node circle.state-end {
+ stroke: var(--md-mermaid-label-bg-color);
+}
+
+/* State transition */
+.transition {
+ stroke: var(--md-mermaid-edge-color);
+}
+
+/* State fork and join */
+[id^=state-fork] rect,
+[id^=state-join] rect {
+ fill: var(--md-mermaid-edge-color) !important;
+ stroke: none !important;
+}
+
+/* State cluster (yes, 2x... Mermaid WTF) */
+.statediagram-cluster.statediagram-cluster .inner {
+ fill: var(--md-default-bg-color);
+}
+
+/* State cluster node */
+.statediagram-cluster rect {
+ fill: var(--md-mermaid-node-bg-color);
+ stroke: var(--md-mermaid-node-fg-color);
+}
+
+/* State cluster divider */
+.statediagram-state rect.divider {
+ fill: var(--md-default-fg-color--lightest);
+ stroke: var(--md-default-fg-color--lighter);
+}
+
+/* State diagram markers */
+defs #statediagram-barbEnd {
+ stroke: var(--md-mermaid-edge-color);
+}
+
+/* ----------------------------------------------------------------------------
+ * Rules: entity-relationship diagrams
+ * ------------------------------------------------------------------------- */
+
+ /* Entity node */
+.entityBox {
+ fill: var(--md-mermaid-label-bg-color);
+ stroke: var(--md-mermaid-node-fg-color);
+}
+
+/* Entity node label */
+.entityLabel {
+ font-family: var(--md-mermaid-font-family);
+ fill: var(--md-mermaid-label-fg-color);
+}
+
+/* Entity relationship label container */
+.relationshipLabelBox {
+ background-color: var(--md-mermaid-label-bg-color);
+ opacity: 1;
+ fill: var(--md-mermaid-label-bg-color);
+ fill-opacity: 1;
+}
+
+/* Entity relationship label */
+.relationshipLabel {
+ fill: var(--md-mermaid-label-fg-color);
+}
+
+/* Entity relationship line { */
+.relationshipLine {
+ stroke: var(--md-mermaid-edge-color);
+}
+
+/* Entity relationship line markers */
+defs #ZERO_OR_ONE_START *,
+defs #ZERO_OR_ONE_END *,
+defs #ZERO_OR_MORE_START *,
+defs #ZERO_OR_MORE_END *,
+defs #ONLY_ONE_START *,
+defs #ONLY_ONE_END *,
+defs #ONE_OR_MORE_START *,
+defs #ONE_OR_MORE_END * {
+ stroke: var(--md-mermaid-edge-color) !important;
+}
+
+/* Entity relationship line markers */
+defs #ZERO_OR_MORE_START circle,
+defs #ZERO_OR_MORE_END circle {
+ fill: var(--md-mermaid-label-bg-color);
+}
+
+/* ----------------------------------------------------------------------------
+ * Rules: sequence diagrams
+ * ------------------------------------------------------------------------- */
+
+/* Sequence actor */
+.actor {
+ fill: var(--md-mermaid-label-bg-color);
+ stroke: var(--md-mermaid-node-fg-color);
+}
+
+/* Sequence actor text */
+text.actor > tspan {
+ font-family: var(--md-mermaid-font-family);
+ fill: var(--md-mermaid-label-fg-color);
+}
+
+/* Sequence actor line */
+line {
+ stroke: var(--md-default-fg-color--lighter);
+}
+
+/* Sequence message line */
+.messageLine0,
+.messageLine1 {
+ stroke: var(--md-mermaid-edge-color);
+}
+
+/* Sequence message and loop text */
+.messageText,
+.loopText > tspan {
+ font-family: var(--md-mermaid-font-family) !important;
+ fill: var(--md-mermaid-edge-color);
+ stroke: none;
+}
+
+/* Sequence arrow head */
+#arrowhead path {
+ fill: var(--md-mermaid-edge-color);
+ stroke: none;
+}
+
+/* Sequence loop line */
+.loopLine {
+ fill: var(--md-mermaid-node-bg-color);
+ stroke: var(--md-mermaid-node-fg-color);
+}
+
+/* Sequence label box */
+.labelBox {
+ fill: var(--md-mermaid-node-bg-color);
+ stroke: none;
+}
+
+/* Sequence label text */
+.labelText,
+.labelText > span {
+ font-family: var(--md-mermaid-font-family);
+ fill: var(--md-mermaid-node-fg-color);
+}
diff --git a/src/assets/javascripts/components/content/code/mermaid/index.ts b/src/assets/javascripts/components/content/code/mermaid/index.ts
@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import {
+ Observable,
+ map,
+ of,
+ shareReplay,
+ tap
+} from "rxjs"
+
+import { watchScript } from "~/browser"
+import { h } from "~/utilities"
+
+import { Component } from "../../../_"
+
+import themeCSS from "./index.css"
+
+/* ----------------------------------------------------------------------------
+ * Types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Mermaid diagram
+ */
+export interface Mermaid {}
+
+/* ----------------------------------------------------------------------------
+ * Data
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Mermaid instance observable
+ */
+let mermaid$: Observable<void>
+
+/**
+ * Global sequence number for diagrams
+ */
+let sequence = 0
+
+/* ----------------------------------------------------------------------------
+ * Helper functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Fetch Mermaid script
+ *
+ * @returns Mermaid scripts observable
+ */
+function fetchScripts(): Observable<void> {
+ return typeof mermaid === "undefined" || mermaid instanceof Element
+ ? watchScript("https://unpkg.com/mermaid@9.0.1/dist/mermaid.min.js")
+ : of(undefined)
+}
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Mount Mermaid diagram
+ *
+ * @param el - Code block element
+ *
+ * @returns Mermaid diagram component observable
+ */
+export function mountMermaid(
+ el: HTMLElement
+): Observable<Component<Mermaid>> {
+ el.classList.remove("mermaid") // Hack: mitigate https://bit.ly/3CiN6Du
+ mermaid$ ||= fetchScripts()
+ .pipe(
+ tap(() => mermaid.initialize({
+ startOnLoad: false,
+ themeCSS
+ })),
+ map(() => undefined),
+ shareReplay(1)
+ )
+
+ /* Render diagram */
+ mermaid$.subscribe(() => {
+ el.classList.add("mermaid") // Hack: mitigate https://bit.ly/3CiN6Du
+ const id = `__mermaid_${sequence++}`
+ const host = h("div", { class: "mermaid" })
+ mermaid.mermaidAPI.render(id, el.textContent, (svg: string) => {
+
+ /* Create a shadow root and inject diagram */
+ const shadow = host.attachShadow({ mode: "closed" })
+ shadow.innerHTML = svg
+
+ /* Replace code block with diagram */
+ el.replaceWith(host)
+ })
+ })
+
+ /* Create and return component */
+ return mermaid$
+ .pipe(
+ map(() => ({ ref: el }))
+ )
+}
diff --git a/src/assets/javascripts/components/content/details/index.ts b/src/assets/javascripts/components/content/details/index.ts
@@ -0,0 +1,141 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import {
+ Observable,
+ Subject,
+ defer,
+ filter,
+ finalize,
+ map,
+ merge,
+ tap
+} from "rxjs"
+
+import { Component } from "../../_"
+
+/* ----------------------------------------------------------------------------
+ * Types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Details
+ */
+export interface Details {
+ action: "open" | "close" /* Details state */
+ reveal?: boolean /* Details is revealed */
+}
+
+/* ----------------------------------------------------------------------------
+ * Helper types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Watch options
+ */
+interface WatchOptions {
+ target$: Observable<HTMLElement> /* Location target observable */
+ print$: Observable<boolean> /* Media print observable */
+}
+
+/**
+ * Mount options
+ */
+interface MountOptions {
+ target$: Observable<HTMLElement> /* Location target observable */
+ print$: Observable<boolean> /* Media print observable */
+}
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Watch details
+ *
+ * @param el - Details element
+ * @param options - Options
+ *
+ * @returns Details observable
+ */
+export function watchDetails(
+ el: HTMLDetailsElement, { target$, print$ }: WatchOptions
+): Observable<Details> {
+ let open = true
+ return merge(
+
+ /* Open and focus details on location target */
+ target$
+ .pipe(
+ map(target => target.closest("details:not([open])")!),
+ filter(details => el === details),
+ map(() => ({
+ action: "open", reveal: true
+ }) as Details)
+ ),
+
+ /* Open details on print and close afterwards */
+ print$
+ .pipe(
+ filter(active => active || !open),
+ tap(() => open = el.open),
+ map(active => ({
+ action: active ? "open" : "close"
+ }) as Details)
+ )
+ )
+}
+
+/**
+ * Mount details
+ *
+ * This function ensures that `details` tags are opened on anchor jumps and
+ * prior to printing, so the whole content of the page is visible.
+ *
+ * @param el - Details element
+ * @param options - Options
+ *
+ * @returns Details component observable
+ */
+export function mountDetails(
+ el: HTMLDetailsElement, options: MountOptions
+): Observable<Component<Details>> {
+ return defer(() => {
+ const push$ = new Subject<Details>()
+ push$.subscribe(({ action, reveal }) => {
+ if (action === "open")
+ el.setAttribute("open", "")
+ else
+ el.removeAttribute("open")
+ if (reveal)
+ el.scrollIntoView()
+ })
+
+ /* Create and return component */
+ return watchDetails(el, options)
+ .pipe(
+ tap(state => push$.next(state)),
+ finalize(() => push$.complete()),
+ map(state => ({ ref: el, ...state }))
+ )
+ })
+}
diff --git a/src/assets/javascripts/components/content/index.ts b/src/assets/javascripts/components/content/index.ts
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+export * from "./_"
+export * from "./annotation"
+export * from "./code"
+export * from "./details"
+export * from "./table"
+export * from "./tabs"
diff --git a/src/assets/javascripts/components/content/table/index.ts b/src/assets/javascripts/components/content/table/index.ts
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import { Observable, of } from "rxjs"
+
+import { renderTable } from "~/templates"
+import { h } from "~/utilities"
+
+import { Component } from "../../_"
+
+/* ----------------------------------------------------------------------------
+ * Types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Data table
+ */
+export interface DataTable {}
+
+/* ----------------------------------------------------------------------------
+ * Data
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Sentinel for replacement
+ */
+const sentinel = h("table")
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Mount data table
+ *
+ * This function wraps a data table in another scrollable container, so it can
+ * be smoothly scrolled on smaller screen sizes and won't break the layout.
+ *
+ * @param el - Data table element
+ *
+ * @returns Data table component observable
+ */
+export function mountDataTable(
+ el: HTMLElement
+): Observable<Component<DataTable>> {
+ el.replaceWith(sentinel)
+ sentinel.replaceWith(renderTable(el))
+
+ /* Create and return component */
+ return of({ ref: el })
+}
diff --git a/src/assets/javascripts/components/content/tabs/index.ts b/src/assets/javascripts/components/content/tabs/index.ts
@@ -0,0 +1,222 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import {
+ Observable,
+ Subject,
+ animationFrameScheduler,
+ asyncScheduler,
+ auditTime,
+ combineLatest,
+ defer,
+ finalize,
+ fromEvent,
+ map,
+ merge,
+ skip,
+ startWith,
+ subscribeOn,
+ takeLast,
+ takeUntil,
+ tap
+} from "rxjs"
+
+import { feature } from "~/_"
+import {
+ getElement,
+ getElementContentOffset,
+ getElementContentSize,
+ getElementOffset,
+ getElementSize,
+ getElements,
+ watchElementContentOffset,
+ watchElementSize
+} from "~/browser"
+import { renderTabbedControl } from "~/templates"
+
+import { Component } from "../../_"
+
+/* ----------------------------------------------------------------------------
+ * Types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Content tabs
+ */
+export interface ContentTabs {
+ active: HTMLLabelElement /* Active tab label */
+}
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Watch content tabs
+ *
+ * @param el - Content tabs element
+ *
+ * @returns Content tabs observable
+ */
+export function watchContentTabs(
+ el: HTMLElement
+): Observable<ContentTabs> {
+ const inputs = getElements<HTMLInputElement>(":scope > input", el)
+ const initial = inputs.find(input => input.checked) || inputs[0]
+ return merge(...inputs.map(input => fromEvent(input, "change")
+ .pipe(
+ map(() => getElement<HTMLLabelElement>(`label[for="${input.id}"]`))
+ )
+ ))
+ .pipe(
+ startWith(getElement<HTMLLabelElement>(`label[for="${initial.id}"]`)),
+ map(active => ({ active }))
+ )
+}
+
+/**
+ * Mount content tabs
+ *
+ * This function scrolls the active tab into view. While this functionality is
+ * provided by browsers as part of `scrollInfoView`, browsers will always also
+ * scroll the vertical axis, which we do not want. Thus, we decided to provide
+ * this functionality ourselves.
+ *
+ * @param el - Content tabs element
+ *
+ * @returns Content tabs component observable
+ */
+export function mountContentTabs(
+ el: HTMLElement
+): Observable<Component<ContentTabs>> {
+
+ /* Render content tab previous button for pagination */
+ const prev = renderTabbedControl("prev")
+ el.append(prev)
+
+ /* Render content tab next button for pagination */
+ const next = renderTabbedControl("next")
+ el.append(next)
+
+ /* Mount component on subscription */
+ const container = getElement(".tabbed-labels", el)
+ return defer(() => {
+ const push$ = new Subject<ContentTabs>()
+ const done$ = push$.pipe(takeLast(1))
+ combineLatest([push$, watchElementSize(el)])
+ .pipe(
+ auditTime(1, animationFrameScheduler),
+ takeUntil(done$)
+ )
+ .subscribe({
+
+ /* Handle emission */
+ next([{ active }, size]) {
+ const offset = getElementOffset(active)
+ const { width } = getElementSize(active)
+
+ /* Set tab indicator offset and width */
+ el.style.setProperty("--md-indicator-x", `${offset.x}px`)
+ el.style.setProperty("--md-indicator-width", `${width}px`)
+
+ /* Scroll container to active content tab */
+ const content = getElementContentOffset(container)
+ if (
+ offset.x < content.x ||
+ offset.x + width > content.x + size.width
+ )
+ container.scrollTo({
+ left: Math.max(0, offset.x - 16),
+ behavior: "smooth"
+ })
+ },
+
+ /* Handle complete */
+ complete() {
+ el.style.removeProperty("--md-indicator-x")
+ el.style.removeProperty("--md-indicator-width")
+ }
+ })
+
+ /* Hide content tab buttons on borders */
+ combineLatest([
+ watchElementContentOffset(container),
+ watchElementSize(container)
+ ])
+ .pipe(
+ takeUntil(done$)
+ )
+ .subscribe(([offset, size]) => {
+ const content = getElementContentSize(container)
+ prev.hidden = offset.x < 16
+ next.hidden = offset.x > content.width - size.width - 16
+ })
+
+ /* Paginate content tab container on click */
+ merge(
+ fromEvent(prev, "click").pipe(map(() => -1)),
+ fromEvent(next, "click").pipe(map(() => +1))
+ )
+ .pipe(
+ takeUntil(done$)
+ )
+ .subscribe(direction => {
+ const { width } = getElementSize(container)
+ container.scrollBy({
+ left: width * direction,
+ behavior: "smooth"
+ })
+ })
+
+ /* Set up linking of content tabs, if enabled */
+ if (feature("content.tabs.link"))
+ push$.pipe(skip(1))
+ .subscribe(({ active }) => {
+ const tab = active.innerText.trim()
+ for (const set of getElements("[data-tabs]"))
+ for (const input of getElements<HTMLInputElement>(
+ ":scope > input", set
+ )) {
+ const label = getElement(`label[for="${input.id}"]`)
+ if (label.innerText.trim() === tab) {
+ input.click()
+ break
+ }
+ }
+
+ /* Persist active tabs in local storage */
+ const tabs = __md_get<string[]>("__tabs") || []
+ __md_set("__tabs", [...new Set([tab, ...tabs])])
+ })
+
+ /* Create and return component */
+ return watchContentTabs(el)
+ .pipe(
+ tap(state => push$.next(state)),
+ finalize(() => push$.complete()),
+ map(state => ({ ref: el, ...state }))
+ )
+ })
+ .pipe(
+ subscribeOn(asyncScheduler)
+ )
+}
diff --git a/src/assets/javascripts/components/dialog/index.ts b/src/assets/javascripts/components/dialog/index.ts
@@ -0,0 +1,128 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import {
+ Observable,
+ Subject,
+ defer,
+ delay,
+ finalize,
+ map,
+ merge,
+ of,
+ switchMap,
+ tap
+} from "rxjs"
+
+import { getElement } from "~/browser"
+
+import { Component } from "../_"
+
+/* ----------------------------------------------------------------------------
+ * Types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Dialog
+ */
+export interface Dialog {
+ message: string /* Dialog message */
+ active: boolean /* Dialog is active */
+}
+
+/* ----------------------------------------------------------------------------
+ * Helper types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Watch options
+ */
+interface WatchOptions {
+ alert$: Subject<string> /* Alert subject */
+}
+
+/**
+ * Mount options
+ */
+interface MountOptions {
+ alert$: Subject<string> /* Alert subject */
+}
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Watch dialog
+ *
+ * @param _el - Dialog element
+ * @param options - Options
+ *
+ * @returns Dialog observable
+ */
+export function watchDialog(
+ _el: HTMLElement, { alert$ }: WatchOptions
+): Observable<Dialog> {
+ return alert$
+ .pipe(
+ switchMap(message => merge(
+ of(true),
+ of(false).pipe(delay(2000))
+ )
+ .pipe(
+ map(active => ({ message, active }))
+ )
+ )
+ )
+}
+
+/**
+ * Mount dialog
+ *
+ * This function reveals the dialog in the right corner when a new alert is
+ * emitted through the subject that is passed as part of the options.
+ *
+ * @param el - Dialog element
+ * @param options - Options
+ *
+ * @returns Dialog component observable
+ */
+export function mountDialog(
+ el: HTMLElement, options: MountOptions
+): Observable<Component<Dialog>> {
+ const inner = getElement(".md-typeset", el)
+ return defer(() => {
+ const push$ = new Subject<Dialog>()
+ push$.subscribe(({ message, active }) => {
+ el.classList.toggle("md-dialog--active", active)
+ inner.textContent = message
+ })
+
+ /* Create and return component */
+ return watchDialog(el, options)
+ .pipe(
+ tap(state => push$.next(state)),
+ finalize(() => push$.complete()),
+ map(state => ({ ref: el, ...state }))
+ )
+ })
+}
diff --git a/src/assets/javascripts/components/header/_/index.ts b/src/assets/javascripts/components/header/_/index.ts
@@ -0,0 +1,199 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import {
+ Observable,
+ Subject,
+ bufferCount,
+ combineLatest,
+ combineLatestWith,
+ defer,
+ distinctUntilChanged,
+ distinctUntilKeyChanged,
+ filter,
+ map,
+ of,
+ shareReplay,
+ startWith,
+ switchMap,
+ takeLast,
+ takeUntil
+} from "rxjs"
+
+import { feature } from "~/_"
+import {
+ Viewport,
+ watchElementSize,
+ watchToggle
+} from "~/browser"
+
+import { Component } from "../../_"
+import { Main } from "../../main"
+
+/* ----------------------------------------------------------------------------
+ * Types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Header
+ */
+export interface Header {
+ height: number /* Header visible height */
+ hidden: boolean /* Header is hidden */
+}
+
+/* ----------------------------------------------------------------------------
+ * Helper types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Watch options
+ */
+interface WatchOptions {
+ viewport$: Observable<Viewport> /* Viewport observable */
+}
+
+/**
+ * Mount options
+ */
+interface MountOptions {
+ viewport$: Observable<Viewport> /* Viewport observable */
+ header$: Observable<Header> /* Header observable */
+ main$: Observable<Main> /* Main area observable */
+}
+
+/* ----------------------------------------------------------------------------
+ * Helper functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Compute whether the header is hidden
+ *
+ * If the user scrolls past a certain threshold, the header can be hidden when
+ * scrolling down, and shown when scrolling up.
+ *
+ * @param options - Options
+ *
+ * @returns Toggle observable
+ */
+function isHidden({ viewport$ }: WatchOptions): Observable<boolean> {
+ if (!feature("header.autohide"))
+ return of(false)
+
+ /* Compute direction and turning point */
+ const direction$ = viewport$
+ .pipe(
+ map(({ offset: { y } }) => y),
+ bufferCount(2, 1),
+ map(([a, b]) => [a < b, b] as const),
+ distinctUntilKeyChanged(0)
+ )
+
+ /* Compute whether header should be hidden */
+ const hidden$ = combineLatest([viewport$, direction$])
+ .pipe(
+ filter(([{ offset }, [, y]]) => Math.abs(y - offset.y) > 100),
+ map(([, [direction]]) => direction),
+ distinctUntilChanged()
+ )
+
+ /* Compute threshold for hiding */
+ const search$ = watchToggle("search")
+ return combineLatest([viewport$, search$])
+ .pipe(
+ map(([{ offset }, search]) => offset.y > 400 && !search),
+ distinctUntilChanged(),
+ switchMap(active => active ? hidden$ : of(false)),
+ startWith(false)
+ )
+}
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Watch header
+ *
+ * @param el - Header element
+ * @param options - Options
+ *
+ * @returns Header observable
+ */
+export function watchHeader(
+ el: HTMLElement, options: WatchOptions
+): Observable<Header> {
+ return defer(() => combineLatest([
+ watchElementSize(el),
+ isHidden(options)
+ ]))
+ .pipe(
+ map(([{ height }, hidden]) => ({
+ height,
+ hidden
+ })),
+ distinctUntilChanged((a, b) => (
+ a.height === b.height &&
+ a.hidden === b.hidden
+ )),
+ shareReplay(1)
+ )
+}
+
+/**
+ * Mount header
+ *
+ * This function manages the different states of the header, i.e. whether it's
+ * hidden or rendered with a shadow. This depends heavily on the main area.
+ *
+ * @param el - Header element
+ * @param options - Options
+ *
+ * @returns Header component observable
+ */
+export function mountHeader(
+ el: HTMLElement, { header$, main$ }: MountOptions
+): Observable<Component<Header>> {
+ return defer(() => {
+ const push$ = new Subject<Main>()
+ const done$ = push$.pipe(takeLast(1))
+ push$
+ .pipe(
+ distinctUntilKeyChanged("active"),
+ combineLatestWith(header$)
+ )
+ .subscribe(([{ active }, { hidden }]) => {
+ el.classList.toggle("md-header--shadow", active && !hidden)
+ el.hidden = hidden
+ })
+
+ /* Link to main area */
+ main$.subscribe(push$)
+
+ /* Create and return component */
+ return header$
+ .pipe(
+ takeUntil(done$),
+ map(state => ({ ref: el, ...state }))
+ )
+ })
+}
diff --git a/src/assets/javascripts/components/header/index.ts b/src/assets/javascripts/components/header/index.ts
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+export * from "./_"
+export * from "./title"
diff --git a/src/assets/javascripts/components/header/title/index.ts b/src/assets/javascripts/components/header/title/index.ts
@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import {
+ EMPTY,
+ Observable,
+ Subject,
+ defer,
+ distinctUntilKeyChanged,
+ finalize,
+ map,
+ tap
+} from "rxjs"
+
+import {
+ Viewport,
+ getElementSize,
+ getOptionalElement,
+ watchViewportAt
+} from "~/browser"
+
+import { Component } from "../../_"
+import { Header } from "../_"
+
+/* ----------------------------------------------------------------------------
+ * Types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Header
+ */
+export interface HeaderTitle {
+ active: boolean /* Header title is active */
+}
+
+/* ----------------------------------------------------------------------------
+ * Helper types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Watch options
+ */
+interface WatchOptions {
+ viewport$: Observable<Viewport> /* Viewport observable */
+ header$: Observable<Header> /* Header observable */
+}
+
+/**
+ * Mount options
+ */
+interface MountOptions {
+ viewport$: Observable<Viewport> /* Viewport observable */
+ header$: Observable<Header> /* Header observable */
+}
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Watch header title
+ *
+ * @param el - Heading element
+ * @param options - Options
+ *
+ * @returns Header title observable
+ */
+export function watchHeaderTitle(
+ el: HTMLElement, { viewport$, header$ }: WatchOptions
+): Observable<HeaderTitle> {
+ return watchViewportAt(el, { viewport$, header$ })
+ .pipe(
+ map(({ offset: { y } }) => {
+ const { height } = getElementSize(el)
+ return {
+ active: y >= height
+ }
+ }),
+ distinctUntilKeyChanged("active")
+ )
+}
+
+/**
+ * Mount header title
+ *
+ * This function swaps the header title from the site title to the title of the
+ * current page when the user scrolls past the first headline.
+ *
+ * @param el - Header title element
+ * @param options - Options
+ *
+ * @returns Header title component observable
+ */
+export function mountHeaderTitle(
+ el: HTMLElement, options: MountOptions
+): Observable<Component<HeaderTitle>> {
+ return defer(() => {
+ const push$ = new Subject<HeaderTitle>()
+ push$.subscribe(({ active }) => {
+ el.classList.toggle("md-header__title--active", active)
+ })
+
+ /* Obtain headline, if any */
+ const heading = getOptionalElement("article h1")
+ if (typeof heading === "undefined")
+ return EMPTY
+
+ /* Create and return component */
+ return watchHeaderTitle(heading, options)
+ .pipe(
+ tap(state => push$.next(state)),
+ finalize(() => push$.complete()),
+ map(state => ({ ref: el, ...state }))
+ )
+ })
+}
diff --git a/src/assets/javascripts/components/index.ts b/src/assets/javascripts/components/index.ts
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+export * from "./_"
+export * from "./announce"
+export * from "./consent"
+export * from "./content"
+export * from "./dialog"
+export * from "./header"
+export * from "./main"
+export * from "./palette"
+export * from "./search"
+export * from "./sidebar"
+export * from "./source"
+export * from "./tabs"
+export * from "./toc"
+export * from "./top"
diff --git a/src/assets/javascripts/components/main/index.ts b/src/assets/javascripts/components/main/index.ts
@@ -0,0 +1,125 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import {
+ Observable,
+ combineLatest,
+ distinctUntilChanged,
+ distinctUntilKeyChanged,
+ map,
+ switchMap
+} from "rxjs"
+
+import {
+ Viewport,
+ watchElementSize
+} from "~/browser"
+
+import { Header } from "../header"
+
+/* ----------------------------------------------------------------------------
+ * Types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Main area
+ */
+export interface Main {
+ offset: number /* Main area top offset */
+ height: number /* Main area visible height */
+ active: boolean /* Main area is active */
+}
+
+/* ----------------------------------------------------------------------------
+ * Helper types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Watch options
+ */
+interface WatchOptions {
+ viewport$: Observable<Viewport> /* Viewport observable */
+ header$: Observable<Header> /* Header observable */
+}
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Watch main area
+ *
+ * This function returns an observable that computes the visual parameters of
+ * the main area which depends on the viewport vertical offset and height, as
+ * well as the height of the header element, if the header is fixed.
+ *
+ * @param el - Main area element
+ * @param options - Options
+ *
+ * @returns Main area observable
+ */
+export function watchMain(
+ el: HTMLElement, { viewport$, header$ }: WatchOptions
+): Observable<Main> {
+
+ /* Compute necessary adjustment for header */
+ const adjust$ = header$
+ .pipe(
+ map(({ height }) => height),
+ distinctUntilChanged()
+ )
+
+ /* Compute the main area's top and bottom borders */
+ const border$ = adjust$
+ .pipe(
+ switchMap(() => watchElementSize(el)
+ .pipe(
+ map(({ height }) => ({
+ top: el.offsetTop,
+ bottom: el.offsetTop + height
+ })),
+ distinctUntilKeyChanged("bottom")
+ )
+ )
+ )
+
+ /* Compute the main area's offset, visible height and if we scrolled past */
+ return combineLatest([adjust$, border$, viewport$])
+ .pipe(
+ map(([header, { top, bottom }, { offset: { y }, size: { height } }]) => {
+ height = Math.max(0, height
+ - Math.max(0, top - y, header)
+ - Math.max(0, height + y - bottom)
+ )
+ return {
+ offset: top - header,
+ height,
+ active: top - header <= y
+ }
+ }),
+ distinctUntilChanged((a, b) => (
+ a.offset === b.offset &&
+ a.height === b.height &&
+ a.active === b.active
+ ))
+ )
+}
diff --git a/src/assets/javascripts/components/palette/index.ts b/src/assets/javascripts/components/palette/index.ts
@@ -0,0 +1,150 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import {
+ Observable,
+ Subject,
+ asyncScheduler,
+ defer,
+ finalize,
+ fromEvent,
+ map,
+ mergeMap,
+ observeOn,
+ of,
+ shareReplay,
+ startWith,
+ tap
+} from "rxjs"
+
+import { getElements } from "~/browser"
+
+import { Component } from "../_"
+
+/* ----------------------------------------------------------------------------
+ * Types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Palette colors
+ */
+export interface PaletteColor {
+ scheme?: string /* Color scheme */
+ primary?: string /* Primary color */
+ accent?: string /* Accent color */
+}
+
+/**
+ * Palette
+ */
+export interface Palette {
+ index: number /* Palette index */
+ color: PaletteColor /* Palette colors */
+}
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Watch color palette
+ *
+ * @param inputs - Color palette element
+ *
+ * @returns Color palette observable
+ */
+export function watchPalette(
+ inputs: HTMLInputElement[]
+): Observable<Palette> {
+ const current = __md_get<Palette>("__palette") || {
+ index: inputs.findIndex(input => matchMedia(
+ input.getAttribute("data-md-color-media")!
+ ).matches)
+ }
+
+ /* Emit changes in color palette */
+ return of(...inputs)
+ .pipe(
+ mergeMap(input => fromEvent(input, "change")
+ .pipe(
+ map(() => input)
+ )
+ ),
+ startWith(inputs[Math.max(0, current.index)]),
+ map(input => ({
+ index: inputs.indexOf(input),
+ color: {
+ scheme: input.getAttribute("data-md-color-scheme"),
+ primary: input.getAttribute("data-md-color-primary"),
+ accent: input.getAttribute("data-md-color-accent")
+ }
+ } as Palette)),
+ shareReplay(1)
+ )
+}
+
+/**
+ * Mount color palette
+ *
+ * @param el - Color palette element
+ *
+ * @returns Color palette component observable
+ */
+export function mountPalette(
+ el: HTMLElement
+): Observable<Component<Palette>> {
+ return defer(() => {
+ const push$ = new Subject<Palette>()
+ push$.subscribe(palette => {
+ document.body.setAttribute("data-md-color-switching", "")
+
+ /* Set color palette */
+ for (const [key, value] of Object.entries(palette.color))
+ document.body.setAttribute(`data-md-color-${key}`, value)
+
+ /* Toggle visibility */
+ for (let index = 0; index < inputs.length; index++) {
+ const label = inputs[index].nextElementSibling
+ if (label instanceof HTMLElement)
+ label.hidden = palette.index !== index
+ }
+
+ /* Persist preference in local storage */
+ __md_set("__palette", palette)
+ })
+
+ /* Revert transition durations after color switch */
+ push$.pipe(observeOn(asyncScheduler))
+ .subscribe(() => {
+ document.body.removeAttribute("data-md-color-switching")
+ })
+
+ /* Create and return component */
+ const inputs = getElements<HTMLInputElement>("input", el)
+ return watchPalette(inputs)
+ .pipe(
+ tap(state => push$.next(state)),
+ finalize(() => push$.complete()),
+ map(state => ({ ref: el, ...state }))
+ )
+ })
+}
diff --git a/src/assets/javascripts/components/search/_/index.ts b/src/assets/javascripts/components/search/_/index.ts
@@ -0,0 +1,242 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import {
+ NEVER,
+ Observable,
+ ObservableInput,
+ filter,
+ merge,
+ mergeWith,
+ sample,
+ take
+} from "rxjs"
+
+import { configuration } from "~/_"
+import {
+ Keyboard,
+ getActiveElement,
+ getElements,
+ setToggle
+} from "~/browser"
+import {
+ SearchIndex,
+ SearchResult,
+ isSearchQueryMessage,
+ isSearchReadyMessage,
+ setupSearchWorker
+} from "~/integrations"
+
+import {
+ Component,
+ getComponentElement,
+ getComponentElements
+} from "../../_"
+import {
+ SearchQuery,
+ mountSearchQuery
+} from "../query"
+import { mountSearchResult } from "../result"
+import {
+ SearchShare,
+ mountSearchShare
+} from "../share"
+import {
+ SearchSuggest,
+ mountSearchSuggest
+} from "../suggest"
+
+/* ----------------------------------------------------------------------------
+ * Types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Search
+ */
+export type Search =
+ | SearchQuery
+ | SearchResult
+ | SearchShare
+ | SearchSuggest
+
+/* ----------------------------------------------------------------------------
+ * Helper types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Mount options
+ */
+interface MountOptions {
+ index$: ObservableInput<SearchIndex> /* Search index observable */
+ keyboard$: Observable<Keyboard> /* Keyboard observable */
+}
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Mount search
+ *
+ * This function sets up the search functionality, including the underlying
+ * web worker and all keyboard bindings.
+ *
+ * @param el - Search element
+ * @param options - Options
+ *
+ * @returns Search component observable
+ */
+export function mountSearch(
+ el: HTMLElement, { index$, keyboard$ }: MountOptions
+): Observable<Component<Search>> {
+ const config = configuration()
+ try {
+ const url = __search?.worker || config.search
+ const worker = setupSearchWorker(url, index$)
+
+ /* Retrieve query and result components */
+ const query = getComponentElement("search-query", el)
+ const result = getComponentElement("search-result", el)
+
+ /* Re-emit query when search is ready */
+ const { tx$, rx$ } = worker
+ tx$
+ .pipe(
+ filter(isSearchQueryMessage),
+ sample(rx$.pipe(filter(isSearchReadyMessage))),
+ take(1)
+ )
+ .subscribe(tx$.next.bind(tx$))
+
+ /* Set up search keyboard handlers */
+ keyboard$
+ .pipe(
+ filter(({ mode }) => mode === "search")
+ )
+ .subscribe(key => {
+ const active = getActiveElement()
+ switch (key.type) {
+
+ /* Enter: go to first (best) result */
+ case "Enter":
+ if (active === query) {
+ const anchors = new Map<HTMLAnchorElement, number>()
+ for (const anchor of getElements<HTMLAnchorElement>(
+ ":first-child [href]", result
+ )) {
+ const article = anchor.firstElementChild!
+ anchors.set(anchor, parseFloat(
+ article.getAttribute("data-md-score")!
+ ))
+ }
+
+ /* Go to result with highest score, if any */
+ if (anchors.size) {
+ const [[best]] = [...anchors].sort(([, a], [, b]) => b - a)
+ best.click()
+ }
+
+ /* Otherwise omit form submission */
+ key.claim()
+ }
+ break
+
+ /* Escape or Tab: close search */
+ case "Escape":
+ case "Tab":
+ setToggle("search", false)
+ query.blur()
+ break
+
+ /* Vertical arrows: select previous or next search result */
+ case "ArrowUp":
+ case "ArrowDown":
+ if (typeof active === "undefined") {
+ query.focus()
+ } else {
+ const els = [query, ...getElements(
+ ":not(details) > [href], summary, details[open] [href]",
+ result
+ )]
+ const i = Math.max(0, (
+ Math.max(0, els.indexOf(active)) + els.length + (
+ key.type === "ArrowUp" ? -1 : +1
+ )
+ ) % els.length)
+ els[i].focus()
+ }
+
+ /* Prevent scrolling of page */
+ key.claim()
+ break
+
+ /* All other keys: hand to search query */
+ default:
+ if (query !== getActiveElement())
+ query.focus()
+ }
+ })
+
+ /* Set up global keyboard handlers */
+ keyboard$
+ .pipe(
+ filter(({ mode }) => mode === "global"),
+ )
+ .subscribe(key => {
+ switch (key.type) {
+
+ /* Open search and select query */
+ case "f":
+ case "s":
+ case "/":
+ query.focus()
+ query.select()
+
+ /* Prevent scrolling of page */
+ key.claim()
+ break
+ }
+ })
+
+ /* Create and return component */
+ const query$ = mountSearchQuery(query, worker)
+ const result$ = mountSearchResult(result, worker, { query$ })
+ return merge(query$, result$)
+ .pipe(
+ mergeWith(
+
+ /* Search sharing */
+ ...getComponentElements("search-share", el)
+ .map(child => mountSearchShare(child, { query$ })),
+
+ /* Search suggestions */
+ ...getComponentElements("search-suggest", el)
+ .map(child => mountSearchSuggest(child, worker, { keyboard$ }))
+ )
+ )
+
+ /* Gracefully handle broken search */
+ } catch (err) {
+ el.hidden = true
+ return NEVER
+ }
+}
diff --git a/src/assets/javascripts/components/search/highlight/.eslintrc b/src/assets/javascripts/components/search/highlight/.eslintrc
@@ -0,0 +1,5 @@
+{
+ "rules": {
+ "no-null/no-null": "off"
+ }
+}
diff --git a/src/assets/javascripts/components/search/highlight/index.ts b/src/assets/javascripts/components/search/highlight/index.ts
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import {
+ Observable,
+ ObservableInput,
+ combineLatest,
+ filter,
+ map,
+ startWith
+} from "rxjs"
+
+import { getLocation } from "~/browser"
+import {
+ SearchIndex,
+ setupSearchHighlighter
+} from "~/integrations"
+import { h } from "~/utilities"
+
+import { Component } from "../../_"
+
+/* ----------------------------------------------------------------------------
+ * Types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Search highlighting
+ */
+export interface SearchHighlight {
+ nodes: Map<ChildNode, string> /* Map of replacements */
+}
+
+/* ----------------------------------------------------------------------------
+ * Helper types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Mount options
+ */
+interface MountOptions {
+ index$: ObservableInput<SearchIndex> /* Search index observable */
+ location$: Observable<URL> /* Location observable */
+}
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Mount search highlighting
+ *
+ * @param el - Content element
+ * @param options - Options
+ *
+ * @returns Search highlighting component observable
+ */
+export function mountSearchHiglight(
+ el: HTMLElement, { index$, location$ }: MountOptions
+): Observable<Component<SearchHighlight>> {
+ return combineLatest([
+ index$,
+ location$
+ .pipe(
+ startWith(getLocation()),
+ filter(url => !!url.searchParams.get("h"))
+ )
+ ])
+ .pipe(
+ map(([index, url]) => setupSearchHighlighter(index.config, true)(
+ url.searchParams.get("h")!
+ )),
+ map(fn => {
+ const nodes = new Map<ChildNode, string>()
+
+ /* Traverse text nodes and collect matches */
+ const it = document.createNodeIterator(el, NodeFilter.SHOW_TEXT)
+ for (let node = it.nextNode(); node; node = it.nextNode()) {
+ if (node.parentElement?.offsetHeight) {
+ const original = node.textContent!
+ const replaced = fn(original)
+ if (replaced.length > original.length)
+ nodes.set(node as ChildNode, replaced)
+ }
+ }
+
+ /* Replace original nodes with matches */
+ for (const [node, text] of nodes) {
+ const { childNodes } = h("span", null, text)
+ node.replaceWith(...Array.from(childNodes))
+ }
+
+ /* Return component */
+ return { ref: el, nodes }
+ })
+ )
+}
diff --git a/src/assets/javascripts/components/search/index.ts b/src/assets/javascripts/components/search/index.ts
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+export * from "./_"
+export * from "./highlight"
+export * from "./query"
+export * from "./result"
+export * from "./share"
+export * from "./suggest"
diff --git a/src/assets/javascripts/components/search/query/index.ts b/src/assets/javascripts/components/search/query/index.ts
@@ -0,0 +1,201 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import {
+ Observable,
+ Subject,
+ combineLatest,
+ delay,
+ distinctUntilChanged,
+ distinctUntilKeyChanged,
+ filter,
+ finalize,
+ fromEvent,
+ map,
+ merge,
+ share,
+ shareReplay,
+ startWith,
+ take,
+ takeLast,
+ takeUntil,
+ tap
+} from "rxjs"
+
+import { translation } from "~/_"
+import {
+ getLocation,
+ setToggle,
+ watchElementFocus,
+ watchToggle
+} from "~/browser"
+import {
+ SearchMessageType,
+ SearchQueryMessage,
+ SearchWorker,
+ defaultTransform,
+ isSearchReadyMessage
+} from "~/integrations"
+
+import { Component } from "../../_"
+
+/* ----------------------------------------------------------------------------
+ * Types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Search query
+ */
+export interface SearchQuery {
+ value: string /* Query value */
+ focus: boolean /* Query focus */
+}
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Watch search query
+ *
+ * Note that the focus event which triggers re-reading the current query value
+ * is delayed by `1ms` so the input's empty state is allowed to propagate.
+ *
+ * @param el - Search query element
+ * @param worker - Search worker
+ *
+ * @returns Search query observable
+ */
+export function watchSearchQuery(
+ el: HTMLInputElement, { rx$ }: SearchWorker
+): Observable<SearchQuery> {
+ const fn = __search?.transform || defaultTransform
+
+ /* Immediately show search dialog */
+ const { searchParams } = getLocation()
+ if (searchParams.has("q"))
+ setToggle("search", true)
+
+ /* Intercept query parameter (deep link) */
+ const param$ = rx$
+ .pipe(
+ filter(isSearchReadyMessage),
+ take(1),
+ map(() => searchParams.get("q") || "")
+ )
+
+ /* Remove query parameter when search is closed */
+ watchToggle("search")
+ .pipe(
+ filter(active => !active),
+ take(1)
+ )
+ .subscribe(() => {
+ const url = new URL(location.href)
+ url.searchParams.delete("q")
+ history.replaceState({}, "", `${url}`)
+ })
+
+ /* Set query from parameter */
+ param$.subscribe(value => { // TODO: not ideal - find a better way
+ if (value) {
+ el.value = value
+ el.focus()
+ }
+ })
+
+ /* Intercept focus and input events */
+ const focus$ = watchElementFocus(el)
+ const value$ = merge(
+ fromEvent(el, "keyup"),
+ fromEvent(el, "focus").pipe(delay(1)),
+ param$
+ )
+ .pipe(
+ map(() => fn(el.value)),
+ startWith(""),
+ distinctUntilChanged(),
+ )
+
+ /* Combine into single observable */
+ return combineLatest([value$, focus$])
+ .pipe(
+ map(([value, focus]) => ({ value, focus })),
+ shareReplay(1)
+ )
+}
+
+/**
+ * Mount search query
+ *
+ * @param el - Search query element
+ * @param worker - Search worker
+ *
+ * @returns Search query component observable
+ */
+export function mountSearchQuery(
+ el: HTMLInputElement, { tx$, rx$ }: SearchWorker
+): Observable<Component<SearchQuery, HTMLInputElement>> {
+ const push$ = new Subject<SearchQuery>()
+ const done$ = push$.pipe(takeLast(1))
+
+ /* Handle value changes */
+ push$
+ .pipe(
+ distinctUntilKeyChanged("value"),
+ map(({ value }): SearchQueryMessage => ({
+ type: SearchMessageType.QUERY,
+ data: value
+ }))
+ )
+ .subscribe(tx$.next.bind(tx$))
+
+ /* Handle focus changes */
+ push$
+ .pipe(
+ distinctUntilKeyChanged("focus")
+ )
+ .subscribe(({ focus }) => {
+ if (focus) {
+ setToggle("search", focus)
+ el.placeholder = ""
+ } else {
+ el.placeholder = translation("search.placeholder")
+ }
+ })
+
+ /* Handle reset */
+ fromEvent(el.form!, "reset")
+ .pipe(
+ takeUntil(done$)
+ )
+ .subscribe(() => el.focus())
+
+ /* Create and return component */
+ return watchSearchQuery(el, { tx$, rx$ })
+ .pipe(
+ tap(state => push$.next(state)),
+ finalize(() => push$.complete()),
+ map(state => ({ ref: el, ...state })),
+ share()
+ )
+}
diff --git a/src/assets/javascripts/components/search/result/index.ts b/src/assets/javascripts/components/search/result/index.ts
@@ -0,0 +1,168 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import {
+ Observable,
+ Subject,
+ bufferCount,
+ filter,
+ finalize,
+ map,
+ merge,
+ of,
+ skipUntil,
+ switchMap,
+ take,
+ tap,
+ withLatestFrom,
+ zipWith
+} from "rxjs"
+
+import { translation } from "~/_"
+import {
+ getElement,
+ watchElementBoundary
+} from "~/browser"
+import {
+ SearchResult,
+ SearchWorker,
+ isSearchReadyMessage,
+ isSearchResultMessage
+} from "~/integrations"
+import { renderSearchResultItem } from "~/templates"
+import { round } from "~/utilities"
+
+import { Component } from "../../_"
+import { SearchQuery } from "../query"
+
+/* ----------------------------------------------------------------------------
+ * Helper types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Mount options
+ */
+interface MountOptions {
+ query$: Observable<SearchQuery> /* Search query observable */
+}
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Mount search result list
+ *
+ * This function performs a lazy rendering of the search results, depending on
+ * the vertical offset of the search result container.
+ *
+ * @param el - Search result list element
+ * @param worker - Search worker
+ * @param options - Options
+ *
+ * @returns Search result list component observable
+ */
+export function mountSearchResult(
+ el: HTMLElement, { rx$ }: SearchWorker, { query$ }: MountOptions
+): Observable<Component<SearchResult>> {
+ const push$ = new Subject<SearchResult>()
+ const boundary$ = watchElementBoundary(el.parentElement!)
+ .pipe(
+ filter(Boolean)
+ )
+
+ /* Retrieve nested components */
+ const meta = getElement(":scope > :first-child", el)
+ const list = getElement(":scope > :last-child", el)
+
+ /* Wait until search is ready */
+ const ready$ = rx$
+ .pipe(
+ filter(isSearchReadyMessage),
+ take(1)
+ )
+
+ /* Update search result metadata */
+ push$
+ .pipe(
+ withLatestFrom(query$),
+ skipUntil(ready$)
+ )
+ .subscribe(([{ items }, { value }]) => {
+ if (value) {
+ switch (items.length) {
+
+ /* No results */
+ case 0:
+ meta.textContent = translation("search.result.none")
+ break
+
+ /* One result */
+ case 1:
+ meta.textContent = translation("search.result.one")
+ break
+
+ /* Multiple result */
+ default:
+ meta.textContent = translation(
+ "search.result.other",
+ round(items.length)
+ )
+ }
+ } else {
+ meta.textContent = translation("search.result.placeholder")
+ }
+ })
+
+ /* Update search result list */
+ push$
+ .pipe(
+ tap(() => list.innerHTML = ""),
+ switchMap(({ items }) => merge(
+ of(...items.slice(0, 10)),
+ of(...items.slice(10))
+ .pipe(
+ bufferCount(4),
+ zipWith(boundary$),
+ switchMap(([chunk]) => chunk)
+ )
+ ))
+ )
+ .subscribe(result => list.appendChild(
+ renderSearchResultItem(result)
+ ))
+
+ /* Filter search result message */
+ const result$ = rx$
+ .pipe(
+ filter(isSearchResultMessage),
+ map(({ data }) => data)
+ )
+
+ /* Create and return component */
+ return result$
+ .pipe(
+ tap(state => push$.next(state)),
+ finalize(() => push$.complete()),
+ map(state => ({ ref: el, ...state }))
+ )
+}
diff --git a/src/assets/javascripts/components/search/share/index.ts b/src/assets/javascripts/components/search/share/index.ts
@@ -0,0 +1,121 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import {
+ Observable,
+ Subject,
+ finalize,
+ fromEvent,
+ map,
+ tap
+} from "rxjs"
+
+import { getLocation } from "~/browser"
+
+import { Component } from "../../_"
+import { SearchQuery } from "../query"
+
+/* ----------------------------------------------------------------------------
+ * Types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Search sharing
+ */
+export interface SearchShare {
+ url: URL /* Deep link for sharing */
+}
+
+/* ----------------------------------------------------------------------------
+ * Helper types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Watch options
+ */
+interface WatchOptions {
+ query$: Observable<SearchQuery> /* Search query observable */
+}
+
+/**
+ * Mount options
+ */
+interface MountOptions {
+ query$: Observable<SearchQuery> /* Search query observable */
+}
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Mount search sharing
+ *
+ * @param _el - Search sharing element
+ * @param options - Options
+ *
+ * @returns Search sharing observable
+ */
+export function watchSearchShare(
+ _el: HTMLElement, { query$ }: WatchOptions
+): Observable<SearchShare> {
+ return query$
+ .pipe(
+ map(({ value }) => {
+ const url = getLocation()
+ url.hash = ""
+ url.searchParams.delete("h")
+ url.searchParams.set("q", value)
+ return { url }
+ })
+ )
+}
+
+/**
+ * Mount search sharing
+ *
+ * @param el - Search sharing element
+ * @param options - Options
+ *
+ * @returns Search sharing component observable
+ */
+export function mountSearchShare(
+ el: HTMLAnchorElement, options: MountOptions
+): Observable<Component<SearchShare>> {
+ const push$ = new Subject<SearchShare>()
+ push$.subscribe(({ url }) => {
+ el.setAttribute("data-clipboard-text", el.href)
+ el.href = `${url}`
+ })
+
+ /* Prevent following of link */
+ fromEvent(el, "click")
+ .subscribe(ev => ev.preventDefault())
+
+ /* Create and return component */
+ return watchSearchShare(el, options)
+ .pipe(
+ tap(state => push$.next(state)),
+ finalize(() => push$.complete()),
+ map(state => ({ ref: el, ...state }))
+ )
+}
diff --git a/src/assets/javascripts/components/search/suggest/index.ts b/src/assets/javascripts/components/search/suggest/index.ts
@@ -0,0 +1,154 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import {
+ Observable,
+ Subject,
+ asyncScheduler,
+ combineLatestWith,
+ distinctUntilChanged,
+ filter,
+ finalize,
+ fromEvent,
+ map,
+ merge,
+ observeOn,
+ tap
+} from "rxjs"
+
+import { Keyboard } from "~/browser"
+import {
+ SearchResult,
+ SearchWorker,
+ isSearchResultMessage
+} from "~/integrations"
+
+import { Component, getComponentElement } from "../../_"
+
+/* ----------------------------------------------------------------------------
+ * Types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Search suggestions
+ */
+export interface SearchSuggest {}
+
+/* ----------------------------------------------------------------------------
+ * Helper types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Mount options
+ */
+interface MountOptions {
+ keyboard$: Observable<Keyboard> /* Keyboard observable */
+}
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Mount search suggestions
+ *
+ * This function will perform a lazy rendering of the search results, depending
+ * on the vertical offset of the search result container.
+ *
+ * @param el - Search result list element
+ * @param worker - Search worker
+ * @param options - Options
+ *
+ * @returns Search result list component observable
+ */
+export function mountSearchSuggest(
+ el: HTMLElement, { rx$ }: SearchWorker, { keyboard$ }: MountOptions
+): Observable<Component<SearchSuggest>> {
+ const push$ = new Subject<SearchResult>()
+
+ /* Retrieve query component and track all changes */
+ const query = getComponentElement("search-query")
+ const query$ = merge(
+ fromEvent(query, "keydown"),
+ fromEvent(query, "focus")
+ )
+ .pipe(
+ observeOn(asyncScheduler),
+ map(() => query.value),
+ distinctUntilChanged(),
+ )
+
+ /* Update search suggestions */
+ push$
+ .pipe(
+ combineLatestWith(query$),
+ map(([{ suggestions }, value]) => {
+ const words = value.split(/([\s-]+)/)
+ if (suggestions?.length && words[words.length - 1]) {
+ const last = suggestions[suggestions.length - 1]
+ if (last.startsWith(words[words.length - 1]))
+ words[words.length - 1] = last
+ } else {
+ words.length = 0
+ }
+ return words
+ })
+ )
+ .subscribe(words => el.innerHTML = words
+ .join("")
+ .replace(/\s/g, " ")
+ )
+
+ /* Set up search keyboard handlers */
+ keyboard$
+ .pipe(
+ filter(({ mode }) => mode === "search")
+ )
+ .subscribe(key => {
+ switch (key.type) {
+
+ /* Right arrow: accept current suggestion */
+ case "ArrowRight":
+ if (
+ el.innerText.length &&
+ query.selectionStart === query.value.length
+ )
+ query.value = el.innerText
+ break
+ }
+ })
+
+ /* Filter search result message */
+ const result$ = rx$
+ .pipe(
+ filter(isSearchResultMessage),
+ map(({ data }) => data)
+ )
+
+ /* Create and return component */
+ return result$
+ .pipe(
+ tap(state => push$.next(state)),
+ finalize(() => push$.complete()),
+ map(() => ({ ref: el }))
+ )
+}
diff --git a/src/assets/javascripts/components/sidebar/index.ts b/src/assets/javascripts/components/sidebar/index.ts
@@ -0,0 +1,180 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import {
+ Observable,
+ Subject,
+ animationFrameScheduler,
+ auditTime,
+ combineLatest,
+ defer,
+ distinctUntilChanged,
+ finalize,
+ map,
+ tap,
+ withLatestFrom
+} from "rxjs"
+
+import {
+ Viewport,
+ getElement,
+ getElementOffset
+} from "~/browser"
+
+import { Component } from "../_"
+import { Header } from "../header"
+import { Main } from "../main"
+
+/* ----------------------------------------------------------------------------
+ * Types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Sidebar
+ */
+export interface Sidebar {
+ height: number /* Sidebar height */
+ locked: boolean /* Sidebar is locked */
+}
+
+/* ----------------------------------------------------------------------------
+ * Helper types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Watch options
+ */
+interface WatchOptions {
+ viewport$: Observable<Viewport> /* Viewport observable */
+ main$: Observable<Main> /* Main area observable */
+}
+
+/**
+ * Mount options
+ */
+interface MountOptions {
+ viewport$: Observable<Viewport> /* Viewport observable */
+ header$: Observable<Header> /* Header observable */
+ main$: Observable<Main> /* Main area observable */
+}
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Watch sidebar
+ *
+ * This function returns an observable that computes the visual parameters of
+ * the sidebar which depends on the vertical viewport offset, as well as the
+ * height of the main area. When the page is scrolled beyond the header, the
+ * sidebar is locked and fills the remaining space.
+ *
+ * @param el - Sidebar element
+ * @param options - Options
+ *
+ * @returns Sidebar observable
+ */
+export function watchSidebar(
+ el: HTMLElement, { viewport$, main$ }: WatchOptions
+): Observable<Sidebar> {
+ const parent = el.parentElement!
+ const adjust =
+ parent.offsetTop -
+ parent.parentElement!.offsetTop
+
+ /* Compute the sidebar's available height and if it should be locked */
+ return combineLatest([main$, viewport$])
+ .pipe(
+ map(([{ offset, height }, { offset: { y } }]) => {
+ height = height
+ + Math.min(adjust, Math.max(0, y - offset))
+ - adjust
+ return {
+ height,
+ locked: y >= offset + adjust
+ }
+ }),
+ distinctUntilChanged((a, b) => (
+ a.height === b.height &&
+ a.locked === b.locked
+ ))
+ )
+}
+
+/**
+ * Mount sidebar
+ *
+ * This function doesn't set the height of the actual sidebar, but of its first
+ * child – the `.md-sidebar__scrollwrap` element in order to mitigiate jittery
+ * sidebars when the footer is scrolled into view. At some point we switched
+ * from `absolute` / `fixed` positioning to `sticky` positioning, significantly
+ * reducing jitter in some browsers (respectively Firefox and Safari) when
+ * scrolling from the top. However, top-aligned sticky positioning means that
+ * the sidebar snaps to the bottom when the end of the container is reached.
+ * This is what leads to the mentioned jitter, as the sidebar's height may be
+ * updated too slowly.
+ *
+ * This behaviour can be mitigiated by setting the height of the sidebar to `0`
+ * while preserving the padding, and the height on its first element.
+ *
+ * @param el - Sidebar element
+ * @param options - Options
+ *
+ * @returns Sidebar component observable
+ */
+export function mountSidebar(
+ el: HTMLElement, { header$, ...options }: MountOptions
+): Observable<Component<Sidebar>> {
+ const inner = getElement(".md-sidebar__scrollwrap", el)
+ const { y } = getElementOffset(inner)
+ return defer(() => {
+ const push$ = new Subject<Sidebar>()
+ push$
+ .pipe(
+ auditTime(0, animationFrameScheduler),
+ withLatestFrom(header$)
+ )
+ .subscribe({
+
+ /* Handle emission */
+ next([{ height }, { height: offset }]) {
+ inner.style.height = `${height - 2 * y}px`
+ el.style.top = `${offset}px`
+ },
+
+ /* Handle complete */
+ complete() {
+ inner.style.height = ""
+ el.style.top = ""
+ }
+ })
+
+ /* Create and return component */
+ return watchSidebar(el, options)
+ .pipe(
+ tap(state => push$.next(state)),
+ finalize(() => push$.complete()),
+ map(state => ({ ref: el, ...state }))
+ )
+ })
+}
diff --git a/src/assets/javascripts/components/source/_/index.ts b/src/assets/javascripts/components/source/_/index.ts
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import {
+ EMPTY,
+ Observable,
+ Subject,
+ catchError,
+ defer,
+ filter,
+ finalize,
+ map,
+ of,
+ shareReplay,
+ tap
+} from "rxjs"
+
+import { getElement } from "~/browser"
+import { renderSourceFacts } from "~/templates"
+
+import { Component } from "../../_"
+import {
+ SourceFacts,
+ fetchSourceFacts
+} from "../facts"
+
+/* ----------------------------------------------------------------------------
+ * Types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Repository information
+ */
+export interface Source {
+ facts: SourceFacts /* Repository facts */
+}
+
+/* ----------------------------------------------------------------------------
+ * Data
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Repository information observable
+ */
+let fetch$: Observable<Source>
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Watch repository information
+ *
+ * This function tries to read the repository facts from session storage, and
+ * if unsuccessful, fetches them from the underlying provider.
+ *
+ * @param el - Repository information element
+ *
+ * @returns Repository information observable
+ */
+export function watchSource(
+ el: HTMLAnchorElement
+): Observable<Source> {
+ return fetch$ ||= defer(() => {
+ const cached = __md_get<SourceFacts>("__source", sessionStorage)
+ if (cached)
+ return of(cached)
+ else
+ return fetchSourceFacts(el.href)
+ .pipe(
+ tap(facts => __md_set("__source", facts, sessionStorage))
+ )
+ })
+ .pipe(
+ catchError(() => EMPTY),
+ filter(facts => Object.keys(facts).length > 0),
+ map(facts => ({ facts })),
+ shareReplay(1)
+ )
+}
+
+/**
+ * Mount repository information
+ *
+ * @param el - Repository information element
+ *
+ * @returns Repository information component observable
+ */
+export function mountSource(
+ el: HTMLAnchorElement
+): Observable<Component<Source>> {
+ const inner = getElement(":scope > :last-child", el)
+ return defer(() => {
+ const push$ = new Subject<Source>()
+ push$.subscribe(({ facts }) => {
+ inner.appendChild(renderSourceFacts(facts))
+ inner.classList.add("md-source__repository--active")
+ })
+
+ /* Create and return component */
+ return watchSource(el)
+ .pipe(
+ tap(state => push$.next(state)),
+ finalize(() => push$.complete()),
+ map(state => ({ ref: el, ...state }))
+ )
+ })
+}
diff --git a/src/assets/javascripts/components/source/facts/_/index.ts b/src/assets/javascripts/components/source/facts/_/index.ts
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import { EMPTY, Observable } from "rxjs"
+
+import { fetchSourceFactsFromGitHub } from "../github"
+import { fetchSourceFactsFromGitLab } from "../gitlab"
+
+/* ----------------------------------------------------------------------------
+ * Types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Repository facts for repositories
+ */
+export interface RepositoryFacts {
+ stars?: number /* Number of stars */
+ forks?: number /* Number of forks */
+ version?: string /* Latest version */
+}
+
+/**
+ * Repository facts for organizations
+ */
+export interface OrganizationFacts {
+ repositories?: number /* Number of repositories */
+}
+
+/* ------------------------------------------------------------------------- */
+
+/**
+ * Repository facts
+ */
+export type SourceFacts =
+ | RepositoryFacts
+ | OrganizationFacts
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Fetch repository facts
+ *
+ * @param url - Repository URL
+ *
+ * @returns Repository facts observable
+ */
+export function fetchSourceFacts(
+ url: string
+): Observable<SourceFacts> {
+ const [type] = url.match(/(git(?:hub|lab))/i) || []
+ switch (type.toLowerCase()) {
+
+ /* GitHub repository */
+ case "github":
+ const [, user, repo] = url.match(/^.+github\.com\/([^/]+)\/?([^/]+)?/i)!
+ return fetchSourceFactsFromGitHub(user, repo)
+
+ /* GitLab repository */
+ case "gitlab":
+ const [, base, slug] = url.match(/^.+?([^/]*gitlab[^/]+)\/(.+?)\/?$/i)!
+ return fetchSourceFactsFromGitLab(base, slug)
+
+ /* Everything else */
+ default:
+ return EMPTY
+ }
+}
diff --git a/src/assets/javascripts/components/source/facts/github/index.ts b/src/assets/javascripts/components/source/facts/github/index.ts
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import { Repo, User } from "github-types"
+import {
+ EMPTY,
+ Observable,
+ catchError,
+ defaultIfEmpty,
+ map,
+ zip
+} from "rxjs"
+
+import { requestJSON } from "~/browser"
+
+import { SourceFacts } from "../_"
+
+/* ----------------------------------------------------------------------------
+ * Helper types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * GitHub release (partial)
+ */
+interface Release {
+ tag_name: string /* Tag name */
+}
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Fetch GitHub repository facts
+ *
+ * @param user - GitHub user or organization
+ * @param repo - GitHub repository
+ *
+ * @returns Repository facts observable
+ */
+export function fetchSourceFactsFromGitHub(
+ user: string, repo?: string
+): Observable<SourceFacts> {
+ if (typeof repo !== "undefined") {
+ const url = `https://api.github.com/repos/${user}/${repo}`
+ return zip(
+
+ /* Fetch version */
+ requestJSON<Release>(`${url}/releases/latest`)
+ .pipe(
+ catchError(() => EMPTY), // @todo refactor instant loading
+ map(release => ({
+ version: release.tag_name
+ })),
+ defaultIfEmpty({})
+ ),
+
+ /* Fetch stars and forks */
+ requestJSON<Repo>(url)
+ .pipe(
+ catchError(() => EMPTY), // @todo refactor instant loading
+ map(info => ({
+ stars: info.stargazers_count,
+ forks: info.forks_count
+ })),
+ defaultIfEmpty({})
+ )
+ )
+ .pipe(
+ map(([release, info]) => ({ ...release, ...info }))
+ )
+
+ /* User or organization */
+ } else {
+ const url = `https://api.github.com/users/${user}`
+ return requestJSON<User>(url)
+ .pipe(
+ map(info => ({
+ repositories: info.public_repos
+ })),
+ defaultIfEmpty({})
+ )
+ }
+}
diff --git a/src/assets/javascripts/components/source/facts/gitlab/index.ts b/src/assets/javascripts/components/source/facts/gitlab/index.ts
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import { ProjectSchema } from "gitlab"
+import {
+ EMPTY,
+ Observable,
+ catchError,
+ defaultIfEmpty,
+ map
+} from "rxjs"
+
+import { requestJSON } from "~/browser"
+
+import { SourceFacts } from "../_"
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Fetch GitLab repository facts
+ *
+ * @param base - GitLab base
+ * @param project - GitLab project
+ *
+ * @returns Repository facts observable
+ */
+export function fetchSourceFactsFromGitLab(
+ base: string, project: string
+): Observable<SourceFacts> {
+ const url = `https://${base}/api/v4/projects/${encodeURIComponent(project)}`
+ return requestJSON<ProjectSchema>(url)
+ .pipe(
+ catchError(() => EMPTY), // @todo refactor instant loading
+ map(({ star_count, forks_count }) => ({
+ stars: star_count,
+ forks: forks_count
+ })),
+ defaultIfEmpty({})
+ )
+}
diff --git a/src/assets/javascripts/components/source/facts/index.ts b/src/assets/javascripts/components/source/facts/index.ts
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+export * from "./_"
+export * from "./github"
+export * from "./gitlab"
diff --git a/src/assets/javascripts/components/source/index.ts b/src/assets/javascripts/components/source/index.ts
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+export * from "./_"
+export * from "./facts"
diff --git a/src/assets/javascripts/components/tabs/index.ts b/src/assets/javascripts/components/tabs/index.ts
@@ -0,0 +1,144 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import {
+ Observable,
+ Subject,
+ defer,
+ distinctUntilKeyChanged,
+ finalize,
+ map,
+ of,
+ switchMap,
+ tap
+} from "rxjs"
+
+import { feature } from "~/_"
+import {
+ Viewport,
+ watchElementSize,
+ watchViewportAt
+} from "~/browser"
+
+import { Component } from "../_"
+import { Header } from "../header"
+
+/* ----------------------------------------------------------------------------
+ * Types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Navigation tabs
+ */
+export interface Tabs {
+ hidden: boolean /* Navigation tabs are hidden */
+}
+
+/* ----------------------------------------------------------------------------
+ * Helper types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Watch options
+ */
+interface WatchOptions {
+ viewport$: Observable<Viewport> /* Viewport observable */
+ header$: Observable<Header> /* Header observable */
+}
+
+/**
+ * Mount options
+ */
+interface MountOptions {
+ viewport$: Observable<Viewport> /* Viewport observable */
+ header$: Observable<Header> /* Header observable */
+}
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Watch navigation tabs
+ *
+ * @param el - Navigation tabs element
+ * @param options - Options
+ *
+ * @returns Navigation tabs observable
+ */
+export function watchTabs(
+ el: HTMLElement, { viewport$, header$ }: WatchOptions
+): Observable<Tabs> {
+ return watchElementSize(document.body)
+ .pipe(
+ switchMap(() => watchViewportAt(el, { header$, viewport$ })),
+ map(({ offset: { y } }) => {
+ return {
+ hidden: y >= 10
+ }
+ }),
+ distinctUntilKeyChanged("hidden")
+ )
+}
+
+/**
+ * Mount navigation tabs
+ *
+ * This function hides the navigation tabs when scrolling past the threshold
+ * and makes them reappear in a nice CSS animation when scrolling back up.
+ *
+ * @param el - Navigation tabs element
+ * @param options - Options
+ *
+ * @returns Navigation tabs component observable
+ */
+export function mountTabs(
+ el: HTMLElement, options: MountOptions
+): Observable<Component<Tabs>> {
+ return defer(() => {
+ const push$ = new Subject<Tabs>()
+ push$.subscribe({
+
+ /* Handle emission */
+ next({ hidden }) {
+ el.hidden = hidden
+ },
+
+ /* Handle complete */
+ complete() {
+ el.hidden = false
+ }
+ })
+
+ /* Create and return component */
+ return (
+ feature("navigation.tabs.sticky")
+ ? of({ hidden: false })
+ : watchTabs(el, options)
+ )
+ .pipe(
+ tap(state => push$.next(state)),
+ finalize(() => push$.complete()),
+ map(state => ({ ref: el, ...state }))
+ )
+ })
+}
diff --git a/src/assets/javascripts/components/toc/index.ts b/src/assets/javascripts/components/toc/index.ts
@@ -0,0 +1,331 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import {
+ Observable,
+ Subject,
+ bufferCount,
+ combineLatestWith,
+ debounceTime,
+ defer,
+ distinctUntilChanged,
+ distinctUntilKeyChanged,
+ finalize,
+ map,
+ of,
+ repeat,
+ scan,
+ share,
+ skip,
+ startWith,
+ switchMap,
+ takeLast,
+ takeUntil,
+ tap,
+ withLatestFrom
+} from "rxjs"
+
+import { feature } from "~/_"
+import {
+ Viewport,
+ getElement,
+ getElements,
+ getLocation,
+ getOptionalElement,
+ watchElementSize
+} from "~/browser"
+
+import {
+ Component,
+ getComponentElement
+} from "../_"
+import { Header } from "../header"
+
+/* ----------------------------------------------------------------------------
+ * Types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Table of contents
+ */
+export interface TableOfContents {
+ prev: HTMLAnchorElement[][] /* Anchors (previous) */
+ next: HTMLAnchorElement[][] /* Anchors (next) */
+}
+
+/* ----------------------------------------------------------------------------
+ * Helper types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Watch options
+ */
+interface WatchOptions {
+ viewport$: Observable<Viewport> /* Viewport observable */
+ header$: Observable<Header> /* Header observable */
+}
+
+/**
+ * Mount options
+ */
+interface MountOptions {
+ viewport$: Observable<Viewport> /* Viewport observable */
+ header$: Observable<Header> /* Header observable */
+ target$: Observable<HTMLElement> /* Location target observable */
+}
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Watch table of contents
+ *
+ * This is effectively a scroll spy implementation which will account for the
+ * fixed header and automatically re-calculate anchor offsets when the viewport
+ * is resized. The returned observable will only emit if the table of contents
+ * needs to be repainted.
+ *
+ * This implementation tracks an anchor element's entire path starting from its
+ * level up to the top-most anchor element, e.g. `[h3, h2, h1]`. Although the
+ * Material theme currently doesn't make use of this information, it enables
+ * the styling of the entire hierarchy through customization.
+ *
+ * Note that the current anchor is the last item of the `prev` anchor list.
+ *
+ * @param el - Table of contents element
+ * @param options - Options
+ *
+ * @returns Table of contents observable
+ */
+export function watchTableOfContents(
+ el: HTMLElement, { viewport$, header$ }: WatchOptions
+): Observable<TableOfContents> {
+ const table = new Map<HTMLAnchorElement, HTMLElement>()
+
+ /* Compute anchor-to-target mapping */
+ const anchors = getElements<HTMLAnchorElement>("[href^=\\#]", el)
+ for (const anchor of anchors) {
+ const id = decodeURIComponent(anchor.hash.substring(1))
+ const target = getOptionalElement(`[id="${id}"]`)
+ if (typeof target !== "undefined")
+ table.set(anchor, target)
+ }
+
+ /* Compute necessary adjustment for header */
+ const adjust$ = header$
+ .pipe(
+ distinctUntilKeyChanged("height"),
+ map(({ height }) => {
+ const main = getComponentElement("main")
+ const grid = getElement(":scope > :first-child", main)
+ return height + 0.8 * (
+ grid.offsetTop -
+ main.offsetTop
+ )
+ }),
+ share()
+ )
+
+ /* Compute partition of previous and next anchors */
+ const partition$ = watchElementSize(document.body)
+ .pipe(
+ distinctUntilKeyChanged("height"),
+
+ /* Build index to map anchor paths to vertical offsets */
+ switchMap(body => defer(() => {
+ let path: HTMLAnchorElement[] = []
+ return of([...table].reduce((index, [anchor, target]) => {
+ while (path.length) {
+ const last = table.get(path[path.length - 1])!
+ if (last.tagName >= target.tagName) {
+ path.pop()
+ } else {
+ break
+ }
+ }
+
+ /* If the current anchor is hidden, continue with its parent */
+ let offset = target.offsetTop
+ while (!offset && target.parentElement) {
+ target = target.parentElement
+ offset = target.offsetTop
+ }
+
+ /* Map reversed anchor path to vertical offset */
+ return index.set(
+ [...path = [...path, anchor]].reverse(),
+ offset
+ )
+ }, new Map<HTMLAnchorElement[], number>()))
+ })
+ .pipe(
+
+ /* Sort index by vertical offset (see https://bit.ly/30z6QSO) */
+ map(index => new Map([...index].sort(([, a], [, b]) => a - b))),
+ combineLatestWith(adjust$),
+
+ /* Re-compute partition when viewport offset changes */
+ switchMap(([index, adjust]) => viewport$
+ .pipe(
+ scan(([prev, next], { offset: { y }, size }) => {
+ const last = y + size.height >= Math.floor(body.height)
+
+ /* Look forward */
+ while (next.length) {
+ const [, offset] = next[0]
+ if (offset - adjust < y || last) {
+ prev = [...prev, next.shift()!]
+ } else {
+ break
+ }
+ }
+
+ /* Look backward */
+ while (prev.length) {
+ const [, offset] = prev[prev.length - 1]
+ if (offset - adjust >= y && !last) {
+ next = [prev.pop()!, ...next]
+ } else {
+ break
+ }
+ }
+
+ /* Return partition */
+ return [prev, next]
+ }, [[], [...index]]),
+ distinctUntilChanged((a, b) => (
+ a[0] === b[0] &&
+ a[1] === b[1]
+ ))
+ )
+ )
+ )
+ )
+ )
+
+ /* Compute and return anchor list migrations */
+ return partition$
+ .pipe(
+ map(([prev, next]) => ({
+ prev: prev.map(([path]) => path),
+ next: next.map(([path]) => path)
+ })),
+
+ /* Extract anchor list migrations */
+ startWith({ prev: [], next: [] }),
+ bufferCount(2, 1),
+ map(([a, b]) => {
+
+ /* Moving down */
+ if (a.prev.length < b.prev.length) {
+ return {
+ prev: b.prev.slice(Math.max(0, a.prev.length - 1), b.prev.length),
+ next: []
+ }
+
+ /* Moving up */
+ } else {
+ return {
+ prev: b.prev.slice(-1),
+ next: b.next.slice(0, b.next.length - a.next.length)
+ }
+ }
+ })
+ )
+}
+
+/* ------------------------------------------------------------------------- */
+
+/**
+ * Mount table of contents
+ *
+ * @param el - Table of contents element
+ * @param options - Options
+ *
+ * @returns Table of contents component observable
+ */
+export function mountTableOfContents(
+ el: HTMLElement, { viewport$, header$, target$ }: MountOptions
+): Observable<Component<TableOfContents>> {
+ return defer(() => {
+ const push$ = new Subject<TableOfContents>()
+ const done$ = push$.pipe(takeLast(1))
+ push$.subscribe(({ prev, next }) => {
+
+ /* Look forward */
+ for (const [anchor] of next) {
+ anchor.classList.remove("md-nav__link--passed")
+ anchor.classList.remove("md-nav__link--active")
+ }
+
+ /* Look backward */
+ for (const [index, [anchor]] of prev.entries()) {
+ anchor.classList.add("md-nav__link--passed")
+ anchor.classList.toggle(
+ "md-nav__link--active",
+ index === prev.length - 1
+ )
+ }
+ })
+
+ /* Set up anchor tracking, if enabled */
+ if (feature("navigation.tracking"))
+ viewport$
+ .pipe(
+ takeUntil(done$),
+ distinctUntilKeyChanged("offset"),
+ debounceTime(250),
+ skip(1),
+ takeUntil(target$.pipe(skip(1))),
+ repeat({ delay: 250 }),
+ withLatestFrom(push$)
+ )
+ .subscribe(([, { prev }]) => {
+ const url = getLocation()
+
+ /* Set hash fragment to active anchor */
+ const anchor = prev[prev.length - 1]
+ if (anchor && anchor.length) {
+ const [active] = anchor
+ const { hash } = new URL(active.href)
+ if (url.hash !== hash) {
+ url.hash = hash
+ history.replaceState({}, "", `${url}`)
+ }
+
+ /* Reset anchor when at the top */
+ } else {
+ url.hash = ""
+ history.replaceState({}, "", `${url}`)
+ }
+ })
+
+ /* Create and return component */
+ return watchTableOfContents(el, { viewport$, header$ })
+ .pipe(
+ tap(state => push$.next(state)),
+ finalize(() => push$.complete()),
+ map(state => ({ ref: el, ...state }))
+ )
+ })
+}
diff --git a/src/assets/javascripts/components/top/index.ts b/src/assets/javascripts/components/top/index.ts
@@ -0,0 +1,176 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import {
+ Observable,
+ Subject,
+ bufferCount,
+ combineLatest,
+ distinctUntilChanged,
+ distinctUntilKeyChanged,
+ endWith,
+ finalize,
+ map,
+ repeat,
+ skip,
+ takeLast,
+ takeUntil,
+ tap
+} from "rxjs"
+
+import { Viewport } from "~/browser"
+
+import { Component } from "../_"
+import { Header } from "../header"
+import { Main } from "../main"
+
+/* ----------------------------------------------------------------------------
+ * Types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Back-to-top button
+ */
+export interface BackToTop {
+ hidden: boolean /* Back-to-top button is hidden */
+}
+
+/* ----------------------------------------------------------------------------
+ * Helper types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Watch options
+ */
+interface WatchOptions {
+ viewport$: Observable<Viewport> /* Viewport observable */
+ main$: Observable<Main> /* Main area observable */
+ target$: Observable<HTMLElement> /* Location target observable */
+}
+
+/**
+ * Mount options
+ */
+interface MountOptions {
+ viewport$: Observable<Viewport> /* Viewport observable */
+ header$: Observable<Header> /* Header observable */
+ main$: Observable<Main> /* Main area observable */
+ target$: Observable<HTMLElement> /* Location target observable */
+}
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Watch back-to-top
+ *
+ * @param _el - Back-to-top element
+ * @param options - Options
+ *
+ * @returns Back-to-top observable
+ */
+export function watchBackToTop(
+ _el: HTMLElement, { viewport$, main$, target$ }: WatchOptions
+): Observable<BackToTop> {
+
+ /* Compute direction */
+ const direction$ = viewport$
+ .pipe(
+ map(({ offset: { y } }) => y),
+ bufferCount(2, 1),
+ map(([a, b]) => a > b && b > 0),
+ distinctUntilChanged()
+ )
+
+ /* Compute whether main area is active */
+ const active$ = main$
+ .pipe(
+ map(({ active }) => active)
+ )
+
+ /* Compute threshold for hiding */
+ return combineLatest([active$, direction$])
+ .pipe(
+ map(([active, direction]) => !(active && direction)),
+ distinctUntilChanged(),
+ takeUntil(target$.pipe(skip(1))),
+ endWith(true),
+ repeat({ delay: 250 }),
+ map(hidden => ({ hidden }))
+ )
+}
+
+/* ------------------------------------------------------------------------- */
+
+/**
+ * Mount back-to-top
+ *
+ * @param el - Back-to-top element
+ * @param options - Options
+ *
+ * @returns Back-to-top component observable
+ */
+export function mountBackToTop(
+ el: HTMLElement, { viewport$, header$, main$, target$ }: MountOptions
+): Observable<Component<BackToTop>> {
+ const push$ = new Subject<BackToTop>()
+ const done$ = push$.pipe(takeLast(1))
+ push$.subscribe({
+
+ /* Handle emission */
+ next({ hidden }) {
+ el.hidden = hidden
+ if (hidden) {
+ el.setAttribute("tabindex", "-1")
+ el.blur()
+ } else {
+ el.removeAttribute("tabindex")
+ }
+ },
+
+ /* Handle complete */
+ complete() {
+ el.style.top = ""
+ el.hidden = true
+ el.removeAttribute("tabindex")
+ }
+ })
+
+ /* Watch header height */
+ header$
+ .pipe(
+ takeUntil(done$),
+ distinctUntilKeyChanged("height")
+ )
+ .subscribe(({ height }) => {
+ el.style.top = `${height + 16}px`
+ })
+
+ /* Create and return component */
+ return watchBackToTop(el, { viewport$, main$, target$ })
+ .pipe(
+ tap(state => push$.next(state)),
+ finalize(() => push$.complete()),
+ map(state => ({ ref: el, ...state }))
+ )
+}
diff --git a/src/assets/javascripts/integrations/clipboard/index.ts b/src/assets/javascripts/integrations/clipboard/index.ts
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import ClipboardJS from "clipboard"
+import {
+ Observable,
+ Subject,
+ map,
+ tap
+} from "rxjs"
+
+import { translation } from "~/_"
+import { getElement } from "~/browser"
+
+/* ----------------------------------------------------------------------------
+ * Helper types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Setup options
+ */
+interface SetupOptions {
+ alert$: Subject<string> /* Alert subject */
+}
+
+/* ----------------------------------------------------------------------------
+ * Helper functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Extract text to copy
+ *
+ * @param el - HTML element
+ *
+ * @returns Extracted text
+ */
+function extract(el: HTMLElement): string {
+ el.setAttribute("data-md-copying", "")
+ const text = el.innerText
+ el.removeAttribute("data-md-copying")
+ return text
+}
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Set up Clipboard.js integration
+ *
+ * @param options - Options
+ */
+export function setupClipboardJS(
+ { alert$ }: SetupOptions
+): void {
+ if (ClipboardJS.isSupported()) {
+ new Observable<ClipboardJS.Event>(subscriber => {
+ new ClipboardJS("[data-clipboard-target], [data-clipboard-text]", {
+ text: el => (
+ el.getAttribute("data-clipboard-text")! ||
+ extract(getElement(
+ el.getAttribute("data-clipboard-target")!
+ ))
+ )
+ })
+ .on("success", ev => subscriber.next(ev))
+ })
+ .pipe(
+ tap(ev => {
+ const trigger = ev.trigger as HTMLElement
+ trigger.focus()
+ }),
+ map(() => translation("clipboard.copied"))
+ )
+ .subscribe(alert$)
+ }
+}
diff --git a/src/assets/javascripts/integrations/index.ts b/src/assets/javascripts/integrations/index.ts
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+export * from "./clipboard"
+export * from "./instant"
+export * from "./search"
+export * from "./sitemap"
+export * from "./version"
diff --git a/src/assets/javascripts/integrations/instant/.eslintrc b/src/assets/javascripts/integrations/instant/.eslintrc
@@ -0,0 +1,6 @@
+{
+ "rules": {
+ "no-self-assign": "off",
+ "no-null/no-null": "off"
+ }
+}
diff --git a/src/assets/javascripts/integrations/instant/index.ts b/src/assets/javascripts/integrations/instant/index.ts
@@ -0,0 +1,320 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import {
+ EMPTY,
+ NEVER,
+ Observable,
+ Subject,
+ bufferCount,
+ catchError,
+ concatMap,
+ debounceTime,
+ distinctUntilChanged,
+ distinctUntilKeyChanged,
+ filter,
+ fromEvent,
+ map,
+ merge,
+ of,
+ sample,
+ share,
+ skip,
+ skipUntil,
+ switchMap
+} from "rxjs"
+
+import { configuration, feature } from "~/_"
+import {
+ Viewport,
+ ViewportOffset,
+ getElements,
+ getOptionalElement,
+ request,
+ setLocation,
+ setLocationHash
+} from "~/browser"
+import { getComponentElement } from "~/components"
+import { h } from "~/utilities"
+
+import { fetchSitemap } from "../sitemap"
+
+/* ----------------------------------------------------------------------------
+ * Types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * History state
+ */
+export interface HistoryState {
+ url: URL /* State URL */
+ offset?: ViewportOffset /* State viewport offset */
+}
+
+/* ----------------------------------------------------------------------------
+ * Helper types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Setup options
+ */
+interface SetupOptions {
+ document$: Subject<Document> /* Document subject */
+ location$: Subject<URL> /* Location subject */
+ viewport$: Observable<Viewport> /* Viewport observable */
+}
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Set up instant loading
+ *
+ * When fetching, theoretically, we could use `responseType: "document"`, but
+ * since all MkDocs links are relative, we need to make sure that the current
+ * location matches the document we just loaded. Otherwise any relative links
+ * in the document could use the old location.
+ *
+ * This is the reason why we need to synchronize history events and the process
+ * of fetching the document for navigation changes (except `popstate` events):
+ *
+ * 1. Fetch document via `XMLHTTPRequest`
+ * 2. Set new location via `history.pushState`
+ * 3. Parse and emit fetched document
+ *
+ * For `popstate` events, we must not use `history.pushState`, or the forward
+ * history will be irreversibly overwritten. In case the request fails, the
+ * location change is dispatched regularly.
+ *
+ * @param options - Options
+ */
+export function setupInstantLoading(
+ { document$, location$, viewport$ }: SetupOptions
+): void {
+ const config = configuration()
+ if (location.protocol === "file:")
+ return
+
+ /* Disable automatic scroll restoration */
+ if ("scrollRestoration" in history) {
+ history.scrollRestoration = "manual"
+
+ /* Hack: ensure that reloads restore viewport offset */
+ fromEvent(window, "beforeunload")
+ .subscribe(() => {
+ history.scrollRestoration = "auto"
+ })
+ }
+
+ /* Hack: ensure absolute favicon link to omit 404s when switching */
+ const favicon = getOptionalElement<HTMLLinkElement>("link[rel=icon]")
+ if (typeof favicon !== "undefined")
+ favicon.href = favicon.href
+
+ /* Intercept internal navigation */
+ const push$ = fetchSitemap()
+ .pipe(
+ map(paths => paths.map(path => `${new URL(path, config.base)}`)),
+ switchMap(urls => fromEvent<MouseEvent>(document.body, "click")
+ .pipe(
+ filter(ev => !ev.metaKey && !ev.ctrlKey),
+ switchMap(ev => {
+ if (ev.target instanceof Element) {
+ const el = ev.target.closest("a")
+ if (el && !el.target) {
+ const url = new URL(el.href)
+
+ /* Canonicalize URL */
+ url.search = ""
+ url.hash = ""
+
+ /* Check if URL should be intercepted */
+ if (
+ url.pathname !== location.pathname &&
+ urls.includes(url.toString())
+ ) {
+ ev.preventDefault()
+ return of({
+ url: new URL(el.href)
+ })
+ }
+ }
+ }
+ return NEVER
+ })
+ )
+ ),
+ share<HistoryState>()
+ )
+
+ /* Intercept history back and forward */
+ const pop$ = fromEvent<PopStateEvent>(window, "popstate")
+ .pipe(
+ filter(ev => ev.state !== null),
+ map(ev => ({
+ url: new URL(location.href),
+ offset: ev.state
+ })),
+ share<HistoryState>()
+ )
+
+ /* Emit location change */
+ merge(push$, pop$)
+ .pipe(
+ distinctUntilChanged((a, b) => a.url.href === b.url.href),
+ map(({ url }) => url)
+ )
+ .subscribe(location$)
+
+ /* Fetch document via `XMLHTTPRequest` */
+ const response$ = location$
+ .pipe(
+ distinctUntilKeyChanged("pathname"),
+ switchMap(url => request(url.href)
+ .pipe(
+ catchError(() => {
+ setLocation(url)
+ return NEVER
+ })
+ )
+ ),
+ share()
+ )
+
+ /* Set new location via `history.pushState` */
+ push$
+ .pipe(
+ sample(response$)
+ )
+ .subscribe(({ url }) => {
+ history.pushState({}, "", `${url}`)
+ })
+
+ /* Parse and emit fetched document */
+ const dom = new DOMParser()
+ response$
+ .pipe(
+ switchMap(res => res.text()),
+ map(res => dom.parseFromString(res, "text/html"))
+ )
+ .subscribe(document$)
+
+ /* Replace meta tags and components */
+ document$
+ .pipe(
+ skip(1)
+ )
+ .subscribe(replacement => {
+ for (const selector of [
+
+ /* Meta tags */
+ "title",
+ "link[rel=canonical]",
+ "meta[name=author]",
+ "meta[name=description]",
+
+ /* Components */
+ "[data-md-component=announce]",
+ "[data-md-component=container]",
+ "[data-md-component=header-topic]",
+ "[data-md-component=outdated]",
+ "[data-md-component=logo]",
+ "[data-md-component=skip]",
+ ...feature("navigation.tabs.sticky")
+ ? ["[data-md-component=tabs]"]
+ : []
+ ]) {
+ const source = getOptionalElement(selector)
+ const target = getOptionalElement(selector, replacement)
+ if (
+ typeof source !== "undefined" &&
+ typeof target !== "undefined"
+ ) {
+ source.replaceWith(target)
+ }
+ }
+ })
+
+ /* Re-evaluate scripts */
+ document$
+ .pipe(
+ skip(1),
+ map(() => getComponentElement("container")),
+ switchMap(el => getElements("script", el)),
+ concatMap(el => {
+ const script = h("script")
+ if (el.src) {
+ for (const name of el.getAttributeNames())
+ script.setAttribute(name, el.getAttribute(name)!)
+ el.replaceWith(script)
+
+ /* Complete when script is loaded */
+ return new Observable(observer => {
+ script.onload = () => observer.complete()
+ })
+
+ /* Complete immediately */
+ } else {
+ script.textContent = el.textContent
+ el.replaceWith(script)
+ return EMPTY
+ }
+ })
+ )
+ .subscribe()
+
+ /* Emit history state change */
+ merge(push$, pop$)
+ .pipe(
+ sample(document$)
+ )
+ .subscribe(({ url, offset }) => {
+ if (url.hash && !offset) {
+ setLocationHash(url.hash)
+ } else {
+ window.scrollTo(0, offset?.y || 0)
+ }
+ })
+
+ /* Debounce update of viewport offset */
+ viewport$
+ .pipe(
+ skipUntil(push$),
+ debounceTime(250),
+ distinctUntilKeyChanged("offset")
+ )
+ .subscribe(({ offset }) => {
+ history.replaceState(offset, "")
+ })
+
+ /* Set viewport offset from history */
+ merge(push$, pop$)
+ .pipe(
+ bufferCount(2, 1),
+ filter(([a, b]) => a.url.pathname === b.url.pathname),
+ map(([, state]) => state)
+ )
+ .subscribe(({ offset }) => {
+ window.scrollTo(0, offset?.y || 0)
+ })
+}
diff --git a/src/assets/javascripts/integrations/search/_/.eslintrc b/src/assets/javascripts/integrations/search/_/.eslintrc
@@ -0,0 +1,6 @@
+{
+ "rules": {
+ "@typescript-eslint/no-explicit-any": "off",
+ "no-console": "off"
+ }
+}
diff --git a/src/assets/javascripts/integrations/search/_/index.ts b/src/assets/javascripts/integrations/search/_/index.ts
@@ -0,0 +1,325 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import {
+ SearchDocument,
+ SearchDocumentMap,
+ setupSearchDocumentMap
+} from "../document"
+import {
+ SearchHighlightFactoryFn,
+ setupSearchHighlighter
+} from "../highlighter"
+import { SearchOptions } from "../options"
+import {
+ SearchQueryTerms,
+ getSearchQueryTerms,
+ parseSearchQuery
+} from "../query"
+
+/* ----------------------------------------------------------------------------
+ * Types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Search index configuration
+ */
+export interface SearchIndexConfig {
+ lang: string[] /* Search languages */
+ separator: string /* Search separator */
+}
+
+/**
+ * Search index document
+ */
+export interface SearchIndexDocument {
+ location: string /* Document location */
+ title: string /* Document title */
+ text: string /* Document text */
+ tags?: string[] /* Document tags */
+ boost?: number /* Document boost */
+}
+
+/* ------------------------------------------------------------------------- */
+
+/**
+ * Search index
+ *
+ * This interfaces describes the format of the `search_index.json` file which
+ * is automatically built by the MkDocs search plugin.
+ */
+export interface SearchIndex {
+ config: SearchIndexConfig /* Search index configuration */
+ docs: SearchIndexDocument[] /* Search index documents */
+ options: SearchOptions /* Search options */
+}
+
+/* ------------------------------------------------------------------------- */
+
+/**
+ * Search metadata
+ */
+export interface SearchMetadata {
+ score: number /* Score (relevance) */
+ terms: SearchQueryTerms /* Search query terms */
+}
+
+/* ------------------------------------------------------------------------- */
+
+/**
+ * Search result document
+ */
+export type SearchResultDocument = SearchDocument & SearchMetadata
+
+/**
+ * Search result item
+ */
+export type SearchResultItem = SearchResultDocument[]
+
+/* ------------------------------------------------------------------------- */
+
+/**
+ * Search result
+ */
+export interface SearchResult {
+ items: SearchResultItem[] /* Search result items */
+ suggestions?: string[] /* Search suggestions */
+}
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Compute the difference of two lists of strings
+ *
+ * @param a - 1st list of strings
+ * @param b - 2nd list of strings
+ *
+ * @returns Difference
+ */
+function difference(a: string[], b: string[]): string[] {
+ const [x, y] = [new Set(a), new Set(b)]
+ return [
+ ...new Set([...x].filter(value => !y.has(value)))
+ ]
+}
+
+/* ----------------------------------------------------------------------------
+ * Class
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Search index
+ */
+export class Search {
+
+ /**
+ * Search document mapping
+ *
+ * A mapping of URLs (including hash fragments) to the actual articles and
+ * sections of the documentation. The search document mapping must be created
+ * regardless of whether the index was prebuilt or not, as Lunr.js itself
+ * only stores the actual index.
+ */
+ protected documents: SearchDocumentMap
+
+ /**
+ * Search highlight factory function
+ */
+ protected highlight: SearchHighlightFactoryFn
+
+ /**
+ * The underlying Lunr.js search index
+ */
+ protected index: lunr.Index
+
+ /**
+ * Search options
+ */
+ protected options: SearchOptions
+
+ /**
+ * Create the search integration
+ *
+ * @param data - Search index
+ */
+ public constructor({ config, docs, options }: SearchIndex) {
+ this.options = options
+
+ /* Set up document map and highlighter factory */
+ this.documents = setupSearchDocumentMap(docs)
+ this.highlight = setupSearchHighlighter(config, false)
+
+ /* Set separator for tokenizer */
+ lunr.tokenizer.separator = new RegExp(config.separator)
+
+ /* Create search index */
+ this.index = lunr(function () {
+
+ /* Set up multi-language support */
+ if (config.lang.length === 1 && config.lang[0] !== "en") {
+ this.use((lunr as any)[config.lang[0]])
+ } else if (config.lang.length > 1) {
+ this.use((lunr as any).multiLanguage(...config.lang))
+ }
+
+ /* Compute functions to be removed from the pipeline */
+ const fns = difference([
+ "trimmer", "stopWordFilter", "stemmer"
+ ], options.pipeline)
+
+ /* Remove functions from the pipeline for registered languages */
+ for (const lang of config.lang.map(language => (
+ language === "en" ? lunr : (lunr as any)[language]
+ ))) {
+ for (const fn of fns) {
+ this.pipeline.remove(lang[fn])
+ this.searchPipeline.remove(lang[fn])
+ }
+ }
+
+ /* Set up reference */
+ this.ref("location")
+
+ /* Set up fields */
+ this.field("title", { boost: 1e3 })
+ this.field("text")
+ this.field("tags", { boost: 1e6, extractor: doc => {
+ const { tags = [] } = doc as SearchDocument
+ return tags.reduce((list, tag) => [
+ ...list,
+ ...lunr.tokenizer(tag)
+ ], [] as lunr.Token[])
+ } })
+
+ /* Index documents */
+ for (const doc of docs)
+ this.add(doc, { boost: doc.boost })
+ })
+ }
+
+ /**
+ * Search for matching documents
+ *
+ * The search index which MkDocs provides is divided up into articles, which
+ * contain the whole content of the individual pages, and sections, which only
+ * contain the contents of the subsections obtained by breaking the individual
+ * pages up at `h1` ... `h6`. As there may be many sections on different pages
+ * with identical titles (for example within this very project, e.g. "Usage"
+ * or "Installation"), they need to be put into the context of the containing
+ * page. For this reason, section results are grouped within their respective
+ * articles which are the top-level results that are returned.
+ *
+ * @param query - Query value
+ *
+ * @returns Search results
+ */
+ public search(query: string): SearchResult {
+ if (query) {
+ try {
+ const highlight = this.highlight(query)
+
+ /* Parse query to extract clauses for analysis */
+ const clauses = parseSearchQuery(query)
+ .filter(clause => (
+ clause.presence !== lunr.Query.presence.PROHIBITED
+ ))
+
+ /* Perform search and post-process results */
+ const groups = this.index.search(`${query}*`)
+
+ /* Apply post-query boosts based on title and search query terms */
+ .reduce<SearchResultItem>((item, { ref, score, matchData }) => {
+ const document = this.documents.get(ref)
+ if (typeof document !== "undefined") {
+ const { location, title, text, tags, parent } = document
+
+ /* Compute and analyze search query terms */
+ const terms = getSearchQueryTerms(
+ clauses,
+ Object.keys(matchData.metadata)
+ )
+
+ /* Highlight title and text and apply post-query boosts */
+ const boost = +!parent + +Object.values(terms).every(t => t)
+ item.push({
+ location,
+ title: highlight(title),
+ text: highlight(text),
+ ...tags && { tags: tags.map(highlight) },
+ score: score * (1 + boost),
+ terms
+ })
+ }
+ return item
+ }, [])
+
+ /* Sort search results again after applying boosts */
+ .sort((a, b) => b.score - a.score)
+
+ /* Group search results by page */
+ .reduce((items, result) => {
+ const document = this.documents.get(result.location)
+ if (typeof document !== "undefined") {
+ const ref = "parent" in document
+ ? document.parent!.location
+ : document.location
+ items.set(ref, [...items.get(ref) || [], result])
+ }
+ return items
+ }, new Map<string, SearchResultItem>())
+
+ /* Generate search suggestions, if desired */
+ let suggestions: string[] | undefined
+ if (this.options.suggestions) {
+ const titles = this.index.query(builder => {
+ for (const clause of clauses)
+ builder.term(clause.term, {
+ fields: ["title"],
+ presence: lunr.Query.presence.REQUIRED,
+ wildcard: lunr.Query.wildcard.TRAILING
+ })
+ })
+
+ /* Retrieve suggestions for best match */
+ suggestions = titles.length
+ ? Object.keys(titles[0].matchData.metadata)
+ : []
+ }
+
+ /* Return items and suggestions */
+ return {
+ items: [...groups.values()],
+ ...typeof suggestions !== "undefined" && { suggestions }
+ }
+
+ /* Log errors to console (for now) */
+ } catch {
+ console.warn(`Invalid query: ${query} – see https://bit.ly/2s3ChXG`)
+ }
+ }
+
+ /* Return nothing in case of error or empty query */
+ return { items: [] }
+ }
+}
diff --git a/src/assets/javascripts/integrations/search/document/index.ts b/src/assets/javascripts/integrations/search/document/index.ts
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import escapeHTML from "escape-html"
+
+import { SearchIndexDocument } from "../_"
+
+/* ----------------------------------------------------------------------------
+ * Types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Search document
+ */
+export interface SearchDocument extends SearchIndexDocument {
+ parent?: SearchIndexDocument /* Parent article */
+}
+
+/* ------------------------------------------------------------------------- */
+
+/**
+ * Search document mapping
+ */
+export type SearchDocumentMap = Map<string, SearchDocument>
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Create a search document mapping
+ *
+ * @param docs - Search index documents
+ *
+ * @returns Search document map
+ */
+export function setupSearchDocumentMap(
+ docs: SearchIndexDocument[]
+): SearchDocumentMap {
+ const documents = new Map<string, SearchDocument>()
+ const parents = new Set<SearchDocument>()
+ for (const doc of docs) {
+ const [path, hash] = doc.location.split("#")
+
+ /* Extract location, title and tags */
+ const location = doc.location
+ const title = doc.title
+ const tags = doc.tags
+
+ /* Escape and cleanup text */
+ const text = escapeHTML(doc.text)
+ .replace(/\s+(?=[,.:;!?])/g, "")
+ .replace(/\s+/g, " ")
+
+ /* Handle section */
+ if (hash) {
+ const parent = documents.get(path)!
+
+ /* Ignore first section, override article */
+ if (!parents.has(parent)) {
+ parent.title = doc.title
+ parent.text = text
+
+ /* Remember that we processed the article */
+ parents.add(parent)
+
+ /* Add subsequent section */
+ } else {
+ documents.set(location, {
+ location,
+ title,
+ text,
+ parent
+ })
+ }
+
+ /* Add article */
+ } else {
+ documents.set(location, {
+ location,
+ title,
+ text,
+ ...tags && { tags }
+ })
+ }
+ }
+ return documents
+}
diff --git a/src/assets/javascripts/integrations/search/highlighter/index.ts b/src/assets/javascripts/integrations/search/highlighter/index.ts
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import escapeHTML from "escape-html"
+
+import { SearchIndexConfig } from "../_"
+
+/* ----------------------------------------------------------------------------
+ * Types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Search highlight function
+ *
+ * @param value - Value
+ *
+ * @returns Highlighted value
+ */
+export type SearchHighlightFn = (value: string) => string
+
+/**
+ * Search highlight factory function
+ *
+ * @param query - Query value
+ *
+ * @returns Search highlight function
+ */
+export type SearchHighlightFactoryFn = (query: string) => SearchHighlightFn
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Create a search highlighter
+ *
+ * @param config - Search index configuration
+ * @param escape - Whether to escape HTML
+ *
+ * @returns Search highlight factory function
+ */
+export function setupSearchHighlighter(
+ config: SearchIndexConfig, escape: boolean
+): SearchHighlightFactoryFn {
+ const separator = new RegExp(config.separator, "img")
+ const highlight = (_: unknown, data: string, term: string) => {
+ return `${data}<mark data-md-highlight>${term}</mark>`
+ }
+
+ /* Return factory function */
+ return (query: string) => {
+ query = query
+ .replace(/[\s*+\-:~^]+/g, " ")
+ .trim()
+
+ /* Create search term match expression */
+ const match = new RegExp(`(^|${config.separator})(${
+ query
+ .replace(/[|\\{}()[\]^$+*?.-]/g, "\\$&")
+ .replace(separator, "|")
+ })`, "img")
+
+ /* Highlight string value */
+ return value => (
+ escape
+ ? escapeHTML(value)
+ : value
+ )
+ .replace(match, highlight)
+ .replace(/<\/mark>(\s+)<mark[^>]*>/img, "$1")
+ }
+}
diff --git a/src/assets/javascripts/integrations/search/index.ts b/src/assets/javascripts/integrations/search/index.ts
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+export * from "./_"
+export * from "./document"
+export * from "./highlighter"
+export * from "./options"
+export * from "./query"
+export * from "./worker"
diff --git a/src/assets/javascripts/integrations/search/options/index.ts b/src/assets/javascripts/integrations/search/options/index.ts
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+/* ----------------------------------------------------------------------------
+ * Types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Search pipeline function
+ */
+export type SearchPipelineFn =
+ | "trimmer" /* Trimmer */
+ | "stopWordFilter" /* Stop word filter */
+ | "stemmer" /* Stemmer */
+
+/**
+ * Search pipeline
+ */
+export type SearchPipeline = SearchPipelineFn[]
+
+/* ------------------------------------------------------------------------- */
+
+/**
+ * Search options
+ */
+export interface SearchOptions {
+ pipeline: SearchPipeline /* Search pipeline */
+ suggestions: boolean /* Search suggestions */
+}
diff --git a/src/assets/javascripts/integrations/search/query/_/.eslintrc b/src/assets/javascripts/integrations/search/query/_/.eslintrc
@@ -0,0 +1,5 @@
+{
+ "rules": {
+ "@typescript-eslint/no-explicit-any": "off"
+ }
+}
diff --git a/src/assets/javascripts/integrations/search/query/_/index.ts b/src/assets/javascripts/integrations/search/query/_/index.ts
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+/* ----------------------------------------------------------------------------
+ * Types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Search query clause
+ */
+export interface SearchQueryClause {
+ presence: lunr.Query.presence /* Clause presence */
+ term: string /* Clause term */
+}
+
+/* ------------------------------------------------------------------------- */
+
+/**
+ * Search query terms
+ */
+export type SearchQueryTerms = Record<string, boolean>
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Parse a search query for analysis
+ *
+ * @param value - Query value
+ *
+ * @returns Search query clauses
+ */
+export function parseSearchQuery(
+ value: string
+): SearchQueryClause[] {
+ const query = new (lunr as any).Query(["title", "text"])
+ const parser = new (lunr as any).QueryParser(value, query)
+
+ /* Parse and return query clauses */
+ parser.parse()
+ return query.clauses
+}
+
+/**
+ * Analyze the search query clauses in regard to the search terms found
+ *
+ * @param query - Search query clauses
+ * @param terms - Search terms
+ *
+ * @returns Search query terms
+ */
+export function getSearchQueryTerms(
+ query: SearchQueryClause[], terms: string[]
+): SearchQueryTerms {
+ const clauses = new Set<SearchQueryClause>(query)
+
+ /* Match query clauses against terms */
+ const result: SearchQueryTerms = {}
+ for (let t = 0; t < terms.length; t++)
+ for (const clause of clauses)
+ if (terms[t].startsWith(clause.term)) {
+ result[clause.term] = true
+ clauses.delete(clause)
+ }
+
+ /* Annotate unmatched non-stopword query clauses */
+ for (const clause of clauses)
+ if (lunr.stopWordFilter?.(clause.term as any))
+ result[clause.term] = false
+
+ /* Return query terms */
+ return result
+}
diff --git a/src/assets/javascripts/integrations/search/query/index.ts b/src/assets/javascripts/integrations/search/query/index.ts
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+export * from "./_"
+export * from "./transform"
diff --git a/src/assets/javascripts/integrations/search/query/transform/.eslintrc b/src/assets/javascripts/integrations/search/query/transform/.eslintrc
@@ -0,0 +1,5 @@
+{
+ "rules": {
+ "no-control-regex": "off"
+ }
+}
diff --git a/src/assets/javascripts/integrations/search/query/transform/index.ts b/src/assets/javascripts/integrations/search/query/transform/index.ts
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+/* ----------------------------------------------------------------------------
+ * Types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Search transformation function
+ *
+ * @param value - Query value
+ *
+ * @returns Transformed query value
+ */
+export type SearchTransformFn = (value: string) => string
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Default transformation function
+ *
+ * 1. Search for terms in quotation marks and prepend a `+` modifier to denote
+ * that the resulting document must contain all terms, converting the query
+ * to an `AND` query (as opposed to the default `OR` behavior). While users
+ * may expect terms enclosed in quotation marks to map to span queries, i.e.
+ * for which order is important, Lunr.js doesn't support them, so the best
+ * we can do is to convert the terms to an `AND` query.
+ *
+ * 2. Replace control characters which are not located at the beginning of the
+ * query or preceded by white space, or are not followed by a non-whitespace
+ * character or are at the end of the query string. Furthermore, filter
+ * unmatched quotation marks.
+ *
+ * 3. Trim excess whitespace from left and right.
+ *
+ * @param query - Query value
+ *
+ * @returns Transformed query value
+ */
+export function defaultTransform(query: string): string {
+ return query
+ .split(/"([^"]+)"/g) /* => 1 */
+ .map((terms, index) => index & 1
+ ? terms.replace(/^\b|^(?![^\x00-\x7F]|$)|\s+/g, " +")
+ : terms
+ )
+ .join("")
+ .replace(/"|(?:^|\s+)[*+\-:^~]+(?=\s+|$)/g, "") /* => 2 */
+ .trim() /* => 3 */
+}
diff --git a/src/assets/javascripts/integrations/search/worker/_/index.ts b/src/assets/javascripts/integrations/search/worker/_/index.ts
@@ -0,0 +1,142 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A RTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import {
+ ObservableInput,
+ Subject,
+ from,
+ map,
+ share
+} from "rxjs"
+
+import { configuration, feature, translation } from "~/_"
+import { WorkerHandler, watchWorker } from "~/browser"
+
+import { SearchIndex } from "../../_"
+import {
+ SearchOptions,
+ SearchPipeline
+} from "../../options"
+import {
+ SearchMessage,
+ SearchMessageType,
+ SearchSetupMessage,
+ isSearchResultMessage
+} from "../message"
+
+/* ----------------------------------------------------------------------------
+ * Types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Search worker
+ */
+export type SearchWorker = WorkerHandler<SearchMessage>
+
+/* ----------------------------------------------------------------------------
+ * Helper functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Set up search index
+ *
+ * @param data - Search index
+ *
+ * @returns Search index
+ */
+function setupSearchIndex({ config, docs }: SearchIndex): SearchIndex {
+
+ /* Override default language with value from translation */
+ if (config.lang.length === 1 && config.lang[0] === "en")
+ config.lang = [
+ translation("search.config.lang")
+ ]
+
+ /* Override default separator with value from translation */
+ if (config.separator === "[\\s\\-]+")
+ config.separator = translation("search.config.separator")
+
+ /* Set pipeline from translation */
+ const pipeline = translation("search.config.pipeline")
+ .split(/\s*,\s*/)
+ .filter(Boolean) as SearchPipeline
+
+ /* Determine search options */
+ const options: SearchOptions = {
+ pipeline,
+ suggestions: feature("search.suggest")
+ }
+
+ /* Return search index after defaulting */
+ return { config, docs, options }
+}
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Set up search worker
+ *
+ * This function creates a web worker to set up and query the search index,
+ * which is done using Lunr.js. The index must be passed as an observable to
+ * enable hacks like _localsearch_ via search index embedding as JSON.
+ *
+ * @param url - Worker URL
+ * @param index - Search index observable input
+ *
+ * @returns Search worker
+ */
+export function setupSearchWorker(
+ url: string, index: ObservableInput<SearchIndex>
+): SearchWorker {
+ const config = configuration()
+ const worker = new Worker(url)
+
+ /* Create communication channels and resolve relative links */
+ const tx$ = new Subject<SearchMessage>()
+ const rx$ = watchWorker(worker, { tx$ })
+ .pipe(
+ map(message => {
+ if (isSearchResultMessage(message)) {
+ for (const result of message.data.items)
+ for (const document of result)
+ document.location = `${new URL(document.location, config.base)}`
+ }
+ return message
+ }),
+ share()
+ )
+
+ /* Set up search index */
+ from(index)
+ .pipe(
+ map(data => ({
+ type: SearchMessageType.SETUP,
+ data: setupSearchIndex(data)
+ } as SearchSetupMessage))
+ )
+ .subscribe(tx$.next.bind(tx$))
+
+ /* Return search worker */
+ return { tx$, rx$ }
+}
diff --git a/src/assets/javascripts/integrations/search/worker/index.ts b/src/assets/javascripts/integrations/search/worker/index.ts
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+export * from "./_"
+export * from "./message"
diff --git a/src/assets/javascripts/integrations/search/worker/main/.eslintrc b/src/assets/javascripts/integrations/search/worker/main/.eslintrc
@@ -0,0 +1,5 @@
+{
+ "rules": {
+ "@typescript-eslint/no-misused-promises": "off"
+ }
+}
diff --git a/src/assets/javascripts/integrations/search/worker/main/index.ts b/src/assets/javascripts/integrations/search/worker/main/index.ts
@@ -0,0 +1,174 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A RTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import lunr from "lunr"
+
+import "~/polyfills"
+
+import { Search, SearchIndexConfig } from "../../_"
+import {
+ SearchMessage,
+ SearchMessageType
+} from "../message"
+
+/* ----------------------------------------------------------------------------
+ * Types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Add support for usage with `iframe-worker` polyfill
+ *
+ * While `importScripts` is synchronous when executed inside of a web worker,
+ * it's not possible to provide a synchronous polyfilled implementation. The
+ * cool thing is that awaiting a non-Promise is a noop, so extending the type
+ * definition to return a `Promise` shouldn't break anything.
+ *
+ * @see https://bit.ly/2PjDnXi - GitHub comment
+ */
+declare global {
+ function importScripts(...urls: string[]): Promise<void> | void
+}
+
+/* ----------------------------------------------------------------------------
+ * Data
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Search index
+ */
+let index: Search
+
+/* ----------------------------------------------------------------------------
+ * Helper functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Fetch (= import) multi-language support through `lunr-languages`
+ *
+ * This function automatically imports the stemmers necessary to process the
+ * languages, which are defined through the search index configuration.
+ *
+ * If the worker runs inside of an `iframe` (when using `iframe-worker` as
+ * a shim), the base URL for the stemmers to be loaded must be determined by
+ * searching for the first `script` element with a `src` attribute, which will
+ * contain the contents of this script.
+ *
+ * @param config - Search index configuration
+ *
+ * @returns Promise resolving with no result
+ */
+async function setupSearchLanguages(
+ config: SearchIndexConfig
+): Promise<void> {
+ let base = "../lunr"
+
+ /* Detect `iframe-worker` and fix base URL */
+ if (typeof parent !== "undefined" && "IFrameWorker" in parent) {
+ const worker = document.querySelector<HTMLScriptElement>("script[src]")!
+ const [path] = worker.src.split("/worker")
+
+ /* Prefix base with path */
+ base = base.replace("..", path)
+ }
+
+ /* Add scripts for languages */
+ const scripts = []
+ for (const lang of config.lang) {
+ switch (lang) {
+
+ /* Add segmenter for Japanese */
+ case "ja":
+ scripts.push(`${base}/tinyseg.js`)
+ break
+
+ /* Add segmenter for Hindi and Thai */
+ case "hi":
+ case "th":
+ scripts.push(`${base}/wordcut.js`)
+ break
+ }
+
+ /* Add language support */
+ if (lang !== "en")
+ scripts.push(`${base}/min/lunr.${lang}.min.js`)
+ }
+
+ /* Add multi-language support */
+ if (config.lang.length > 1)
+ scripts.push(`${base}/min/lunr.multi.min.js`)
+
+ /* Load scripts synchronously */
+ if (scripts.length)
+ await importScripts(
+ `${base}/min/lunr.stemmer.support.min.js`,
+ ...scripts
+ )
+}
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Message handler
+ *
+ * @param message - Source message
+ *
+ * @returns Target message
+ */
+export async function handler(
+ message: SearchMessage
+): Promise<SearchMessage> {
+ switch (message.type) {
+
+ /* Search setup message */
+ case SearchMessageType.SETUP:
+ await setupSearchLanguages(message.data.config)
+ index = new Search(message.data)
+ return {
+ type: SearchMessageType.READY
+ }
+
+ /* Search query message */
+ case SearchMessageType.QUERY:
+ return {
+ type: SearchMessageType.RESULT,
+ data: index ? index.search(message.data) : { items: [] }
+ }
+
+ /* All other messages */
+ default:
+ throw new TypeError("Invalid message type")
+ }
+}
+
+/* ----------------------------------------------------------------------------
+ * Worker
+ * ------------------------------------------------------------------------- */
+
+/* @ts-expect-error - expose Lunr.js in global scope, or stemmers won't work */
+self.lunr = lunr
+
+/* Handle messages */
+addEventListener("message", async ev => {
+ postMessage(await handler(ev.data))
+})
diff --git a/src/assets/javascripts/integrations/search/worker/message/index.ts b/src/assets/javascripts/integrations/search/worker/message/index.ts
@@ -0,0 +1,137 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A RTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import { SearchIndex, SearchResult } from "../../_"
+
+/* ----------------------------------------------------------------------------
+ * Types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Search message type
+ */
+export const enum SearchMessageType {
+ SETUP, /* Search index setup */
+ READY, /* Search index ready */
+ QUERY, /* Search query */
+ RESULT /* Search results */
+}
+
+/* ------------------------------------------------------------------------- */
+
+/**
+ * Message containing the data necessary to setup the search index
+ */
+export interface SearchSetupMessage {
+ type: SearchMessageType.SETUP /* Message type */
+ data: SearchIndex /* Message data */
+}
+
+/**
+ * Message indicating the search index is ready
+ */
+export interface SearchReadyMessage {
+ type: SearchMessageType.READY /* Message type */
+}
+
+/**
+ * Message containing a search query
+ */
+export interface SearchQueryMessage {
+ type: SearchMessageType.QUERY /* Message type */
+ data: string /* Message data */
+}
+
+/**
+ * Message containing results for a search query
+ */
+export interface SearchResultMessage {
+ type: SearchMessageType.RESULT /* Message type */
+ data: SearchResult /* Message data */
+}
+
+/* ------------------------------------------------------------------------- */
+
+/**
+ * Message exchanged with the search worker
+ */
+export type SearchMessage =
+ | SearchSetupMessage
+ | SearchReadyMessage
+ | SearchQueryMessage
+ | SearchResultMessage
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Type guard for search setup messages
+ *
+ * @param message - Search worker message
+ *
+ * @returns Test result
+ */
+export function isSearchSetupMessage(
+ message: SearchMessage
+): message is SearchSetupMessage {
+ return message.type === SearchMessageType.SETUP
+}
+
+/**
+ * Type guard for search ready messages
+ *
+ * @param message - Search worker message
+ *
+ * @returns Test result
+ */
+export function isSearchReadyMessage(
+ message: SearchMessage
+): message is SearchReadyMessage {
+ return message.type === SearchMessageType.READY
+}
+
+/**
+ * Type guard for search query messages
+ *
+ * @param message - Search worker message
+ *
+ * @returns Test result
+ */
+export function isSearchQueryMessage(
+ message: SearchMessage
+): message is SearchQueryMessage {
+ return message.type === SearchMessageType.QUERY
+}
+
+/**
+ * Type guard for search result messages
+ *
+ * @param message - Search worker message
+ *
+ * @returns Test result
+ */
+export function isSearchResultMessage(
+ message: SearchMessage
+): message is SearchResultMessage {
+ return message.type === SearchMessageType.RESULT
+}
diff --git a/src/assets/javascripts/integrations/sitemap/index.ts b/src/assets/javascripts/integrations/sitemap/index.ts
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import {
+ EMPTY,
+ Observable,
+ catchError,
+ defaultIfEmpty,
+ map,
+ of,
+ tap
+} from "rxjs"
+
+import { configuration } from "~/_"
+import { getElements, requestXML } from "~/browser"
+
+/* ----------------------------------------------------------------------------
+ * Types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Sitemap, i.e. a list of URLs
+ */
+export type Sitemap = string[]
+
+/* ----------------------------------------------------------------------------
+ * Helper functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Preprocess a list of URLs
+ *
+ * This function replaces the `site_url` in the sitemap with the actual base
+ * URL, to allow instant loading to work in occasions like Netlify previews.
+ *
+ * @param urls - URLs
+ *
+ * @returns URL path parts
+ */
+function preprocess(urls: Sitemap): Sitemap {
+ if (urls.length < 2)
+ return [""]
+
+ /* Take the first two URLs and remove everything after the last slash */
+ const [root, next] = [...urls]
+ .sort((a, b) => a.length - b.length)
+ .map(url => url.replace(/[^/]+$/, ""))
+
+ /* Compute common prefix */
+ let index = 0
+ if (root === next)
+ index = root.length
+ else
+ while (root.charCodeAt(index) === next.charCodeAt(index))
+ index++
+
+ /* Remove common prefix and return in original order */
+ return urls.map(url => url.replace(root.slice(0, index), ""))
+}
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Fetch the sitemap for the given base URL
+ *
+ * @param base - Base URL
+ *
+ * @returns Sitemap observable
+ */
+export function fetchSitemap(base?: URL): Observable<Sitemap> {
+ const cached = __md_get<Sitemap>("__sitemap", sessionStorage, base)
+ if (cached) {
+ return of(cached)
+ } else {
+ const config = configuration()
+ return requestXML(new URL("sitemap.xml", base || config.base))
+ .pipe(
+ map(sitemap => preprocess(getElements("loc", sitemap)
+ .map(node => node.textContent!)
+ )),
+ catchError(() => EMPTY), // @todo refactor instant loading
+ defaultIfEmpty([]),
+ tap(sitemap => __md_set("__sitemap", sitemap, sessionStorage, base))
+ )
+ }
+}
diff --git a/src/assets/javascripts/integrations/version/.eslintrc b/src/assets/javascripts/integrations/version/.eslintrc
@@ -0,0 +1,5 @@
+{
+ "rules": {
+ "no-null/no-null": "off"
+ }
+}
diff --git a/src/assets/javascripts/integrations/version/index.ts b/src/assets/javascripts/integrations/version/index.ts
@@ -0,0 +1,174 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import {
+ EMPTY,
+ Subject,
+ catchError,
+ combineLatest,
+ filter,
+ fromEvent,
+ map,
+ of,
+ switchMap,
+ withLatestFrom
+} from "rxjs"
+
+import { configuration } from "~/_"
+import {
+ getElement,
+ getLocation,
+ requestJSON,
+ setLocation
+} from "~/browser"
+import { getComponentElements } from "~/components"
+import {
+ Version,
+ renderVersionSelector
+} from "~/templates"
+
+import { fetchSitemap } from "../sitemap"
+
+/* ----------------------------------------------------------------------------
+ * Helper types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Setup options
+ */
+interface SetupOptions {
+ document$: Subject<Document> /* Document subject */
+}
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Set up version selector
+ *
+ * @param options - Options
+ */
+export function setupVersionSelector(
+ { document$ }: SetupOptions
+): void {
+ const config = configuration()
+ const versions$ = requestJSON<Version[]>(
+ new URL("../versions.json", config.base)
+ )
+ .pipe(
+ catchError(() => EMPTY) // @todo refactor instant loading
+ )
+
+ /* Determine current version */
+ const current$ = versions$
+ .pipe(
+ map(versions => {
+ const [, current] = config.base.match(/([^/]+)\/?$/)!
+ return versions.find(({ version, aliases }) => (
+ version === current || aliases.includes(current)
+ )) || versions[0]
+ })
+ )
+
+ /* Intercept inter-version navigation */
+ versions$
+ .pipe(
+ map(versions => new Map(versions.map(version => [
+ `${new URL(`../${version.version}/`, config.base)}`,
+ version
+ ]))),
+ switchMap(urls => fromEvent<MouseEvent>(document.body, "click")
+ .pipe(
+ filter(ev => !ev.metaKey && !ev.ctrlKey),
+ withLatestFrom(current$),
+ switchMap(([ev, current]) => {
+ if (ev.target instanceof Element) {
+ const el = ev.target.closest("a")
+ if (el && !el.target && urls.has(el.href)) {
+ const url = el.href
+ // This is a temporary hack to detect if a version inside the
+ // version selector or on another part of the site was clicked.
+ // If we're inside the version selector, we definitely want to
+ // find the same page, as we might have different deployments
+ // due to aliases. However, if we're outside the version
+ // selector, we must abort here, because we might otherwise
+ // interfere with instant loading. We need to refactor this
+ // at some point together with instant loading.
+ //
+ // See https://github.com/squidfunk/mkdocs-material/issues/4012
+ if (!ev.target.closest(".md-version")) {
+ const version = urls.get(url)!
+ if (version === current)
+ return EMPTY
+ }
+ ev.preventDefault()
+ return of(url)
+ }
+ }
+ return EMPTY
+ }),
+ switchMap(url => {
+ const { version } = urls.get(url)!
+ return fetchSitemap(new URL(url))
+ .pipe(
+ map(sitemap => {
+ const location = getLocation()
+ const path = location.href.replace(config.base, "")
+ return sitemap.includes(path)
+ ? new URL(`../${version}/${path}`, config.base)
+ : new URL(url)
+ })
+ )
+ })
+ )
+ )
+ )
+ .subscribe(url => setLocation(url))
+
+ /* Render version selector and warning */
+ combineLatest([versions$, current$])
+ .subscribe(([versions, current]) => {
+ const topic = getElement(".md-header__topic")
+ topic.appendChild(renderVersionSelector(versions, current))
+ })
+
+ /* Integrate outdated version banner with instant loading */
+ document$.pipe(switchMap(() => current$))
+ .subscribe(current => {
+
+ /* Check if version state was already determined */
+ let outdated = __md_get("__outdated", sessionStorage)
+ if (outdated === null) {
+ const latest = config.version?.default || "latest"
+ outdated = !current.aliases.includes(latest)
+
+ /* Persist version state in session storage */
+ __md_set("__outdated", outdated, sessionStorage)
+ }
+
+ /* Unhide outdated version banner */
+ if (outdated)
+ for (const warning of getComponentElements("outdated"))
+ warning.hidden = false
+ })
+}
diff --git a/src/assets/javascripts/patches/indeterminate/index.ts b/src/assets/javascripts/patches/indeterminate/index.ts
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import {
+ Observable,
+ fromEvent,
+ map,
+ mergeMap,
+ switchMap,
+ takeWhile,
+ tap,
+ withLatestFrom
+} from "rxjs"
+
+import { getElements } from "~/browser"
+
+/* ----------------------------------------------------------------------------
+ * Helper types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Patch options
+ */
+interface PatchOptions {
+ document$: Observable<Document> /* Document observable */
+ tablet$: Observable<boolean> /* Media tablet observable */
+}
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Patch indeterminate checkboxes
+ *
+ * This function replaces the indeterminate "pseudo state" with the actual
+ * indeterminate state, which is used to keep navigation always expanded.
+ *
+ * @param options - Options
+ */
+export function patchIndeterminate(
+ { document$, tablet$ }: PatchOptions
+): void {
+ document$
+ .pipe(
+ switchMap(() => getElements<HTMLInputElement>(
+ // @todo `data-md-state` is deprecated and removed in v9
+ ".md-toggle--indeterminate, [data-md-state=indeterminate]"
+ )),
+ tap(el => {
+ el.indeterminate = true
+ el.checked = false
+ }),
+ mergeMap(el => fromEvent(el, "change")
+ .pipe(
+ takeWhile(() => el.classList.contains("md-toggle--indeterminate")),
+ map(() => el)
+ )
+ ),
+ withLatestFrom(tablet$)
+ )
+ .subscribe(([el, tablet]) => {
+ el.classList.remove("md-toggle--indeterminate")
+ if (tablet)
+ el.checked = false
+ })
+}
diff --git a/src/assets/javascripts/patches/index.ts b/src/assets/javascripts/patches/index.ts
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+export * from "./indeterminate"
+export * from "./scrollfix"
+export * from "./scrolllock"
diff --git a/src/assets/javascripts/patches/scrollfix/index.ts b/src/assets/javascripts/patches/scrollfix/index.ts
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import {
+ Observable,
+ filter,
+ fromEvent,
+ map,
+ mergeMap,
+ switchMap,
+ tap
+} from "rxjs"
+
+import { getElements } from "~/browser"
+
+/* ----------------------------------------------------------------------------
+ * Helper types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Patch options
+ */
+interface PatchOptions {
+ document$: Observable<Document> /* Document observable */
+}
+
+/* ----------------------------------------------------------------------------
+ * Helper functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Check whether the given device is an Apple device
+ *
+ * @returns Test result
+ */
+function isAppleDevice(): boolean {
+ return /(iPad|iPhone|iPod)/.test(navigator.userAgent)
+}
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Patch all elements with `data-md-scrollfix` attributes
+ *
+ * This is a year-old patch which ensures that overflow scrolling works at the
+ * top and bottom of containers on iOS by ensuring a `1px` scroll offset upon
+ * the start of a touch event.
+ *
+ * @see https://bit.ly/2SCtAOO - Original source
+ *
+ * @param options - Options
+ */
+export function patchScrollfix(
+ { document$ }: PatchOptions
+): void {
+ document$
+ .pipe(
+ switchMap(() => getElements("[data-md-scrollfix]")),
+ tap(el => el.removeAttribute("data-md-scrollfix")),
+ filter(isAppleDevice),
+ mergeMap(el => fromEvent(el, "touchstart")
+ .pipe(
+ map(() => el)
+ )
+ )
+ )
+ .subscribe(el => {
+ const top = el.scrollTop
+
+ /* We're at the top of the container */
+ if (top === 0) {
+ el.scrollTop = 1
+
+ /* We're at the bottom of the container */
+ } else if (top + el.offsetHeight === el.scrollHeight) {
+ el.scrollTop = top - 1
+ }
+ })
+}
diff --git a/src/assets/javascripts/patches/scrolllock/index.ts b/src/assets/javascripts/patches/scrolllock/index.ts
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import {
+ Observable,
+ combineLatest,
+ delay,
+ map,
+ of,
+ switchMap,
+ withLatestFrom
+} from "rxjs"
+
+import {
+ Viewport,
+ watchToggle
+} from "~/browser"
+
+/* ----------------------------------------------------------------------------
+ * Helper types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Patch options
+ */
+interface PatchOptions {
+ viewport$: Observable<Viewport> /* Viewport observable */
+ tablet$: Observable<boolean> /* Media tablet observable */
+}
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Patch the document body to lock when search is open
+ *
+ * For mobile and tablet viewports, the search is rendered full screen, which
+ * leads to scroll leaking when at the top or bottom of the search result. This
+ * function locks the body when the search is in full screen mode, and restores
+ * the scroll position when leaving.
+ *
+ * @param options - Options
+ */
+export function patchScrolllock(
+ { viewport$, tablet$ }: PatchOptions
+): void {
+ combineLatest([watchToggle("search"), tablet$])
+ .pipe(
+ map(([active, tablet]) => active && !tablet),
+ switchMap(active => of(active)
+ .pipe(
+ delay(active ? 400 : 100)
+ )
+ ),
+ withLatestFrom(viewport$)
+ )
+ .subscribe(([active, { offset: { y }}]) => {
+ if (active) {
+ document.body.setAttribute("data-md-scrolllock", "")
+ document.body.style.top = `-${y}px`
+ } else {
+ const value = -1 * parseInt(document.body.style.top, 10)
+ document.body.removeAttribute("data-md-scrolllock")
+ document.body.style.top = ""
+ if (value)
+ window.scrollTo(0, value)
+ }
+ })
+}
diff --git a/src/assets/javascripts/polyfills/index.ts b/src/assets/javascripts/polyfills/index.ts
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+/* ----------------------------------------------------------------------------
+ * Polyfills
+ * ------------------------------------------------------------------------- */
+
+/* Polyfill `Object.entries` */
+if (!Object.entries)
+ Object.entries = function (obj: object) {
+ const data: [string, string][] = []
+ for (const key of Object.keys(obj))
+ // @ts-expect-error - ignore property access warning
+ data.push([key, obj[key]])
+
+ /* Return entries */
+ return data
+ }
+
+/* Polyfill `Object.values` */
+if (!Object.values)
+ Object.values = function (obj: object) {
+ const data: string[] = []
+ for (const key of Object.keys(obj))
+ // @ts-expect-error - ignore property access warning
+ data.push(obj[key])
+
+ /* Return values */
+ return data
+ }
+
+/* ------------------------------------------------------------------------- */
+
+/* Polyfills for `Element` */
+if (typeof Element !== "undefined") {
+
+ /* Polyfill `Element.scrollTo` */
+ if (!Element.prototype.scrollTo)
+ Element.prototype.scrollTo = function (
+ x?: ScrollToOptions | number, y?: number
+ ): void {
+ if (typeof x === "object") {
+ this.scrollLeft = x.left!
+ this.scrollTop = x.top!
+ } else {
+ this.scrollLeft = x!
+ this.scrollTop = y!
+ }
+ }
+
+ /* Polyfill `Element.replaceWith` */
+ if (!Element.prototype.replaceWith)
+ Element.prototype.replaceWith = function (
+ ...nodes: Array<string | Node>
+ ): void {
+ const parent = this.parentNode
+ if (parent) {
+ if (nodes.length === 0)
+ parent.removeChild(this)
+
+ /* Replace children and create text nodes */
+ for (let i = nodes.length - 1; i >= 0; i--) {
+ let node = nodes[i]
+ if (typeof node !== "object")
+ node = document.createTextNode(node)
+ else if (node.parentNode)
+ node.parentNode.removeChild(node)
+
+ /* Replace child or insert before previous sibling */
+ if (!i)
+ parent.replaceChild(node, this)
+ else
+ parent.insertBefore(this.previousSibling!, node)
+ }
+ }
+ }
+}
diff --git a/src/assets/javascripts/templates/annotation/index.tsx b/src/assets/javascripts/templates/annotation/index.tsx
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import { h } from "~/utilities"
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Render an empty annotation
+ *
+ * @param id - Annotation identifier
+ *
+ * @returns Element
+ */
+export function renderAnnotation(id: number): HTMLElement {
+ return (
+ <aside class="md-annotation" tabIndex={0}>
+ <div class="md-annotation__inner md-tooltip">
+ <div class="md-tooltip__inner md-typeset"></div>
+ </div>
+ <span class="md-annotation__index">
+ <span data-md-annotation-id={id}></span>
+ </span>
+ </aside>
+ )
+}
diff --git a/src/assets/javascripts/templates/clipboard/index.tsx b/src/assets/javascripts/templates/clipboard/index.tsx
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import { translation } from "~/_"
+import { h } from "~/utilities"
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Render a 'copy-to-clipboard' button
+ *
+ * @param id - Unique identifier
+ *
+ * @returns Element
+ */
+export function renderClipboardButton(id: string): HTMLElement {
+ return (
+ <button
+ class="md-clipboard md-icon"
+ title={translation("clipboard.copy")}
+ data-clipboard-target={`#${id} > code`}
+ ></button>
+ )
+}
diff --git a/src/assets/javascripts/templates/index.ts b/src/assets/javascripts/templates/index.ts
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+export * from "./annotation"
+export * from "./clipboard"
+export * from "./search"
+export * from "./source"
+export * from "./tabbed"
+export * from "./table"
+export * from "./version"
diff --git a/src/assets/javascripts/templates/search/index.tsx b/src/assets/javascripts/templates/search/index.tsx
@@ -0,0 +1,162 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import { ComponentChild } from "preact"
+
+import { feature, translation } from "~/_"
+import {
+ SearchDocument,
+ SearchMetadata,
+ SearchResultItem
+} from "~/integrations/search"
+import { h, truncate } from "~/utilities"
+
+/* ----------------------------------------------------------------------------
+ * Helper types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Render flag
+ */
+const enum Flag {
+ TEASER = 1, /* Render teaser */
+ PARENT = 2 /* Render as parent */
+}
+
+/* ----------------------------------------------------------------------------
+ * Helper function
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Render a search document
+ *
+ * @param document - Search document
+ * @param flag - Render flags
+ *
+ * @returns Element
+ */
+function renderSearchDocument(
+ document: SearchDocument & SearchMetadata, flag: Flag
+): HTMLElement {
+ const parent = flag & Flag.PARENT
+ const teaser = flag & Flag.TEASER
+
+ /* Render missing query terms */
+ const missing = Object.keys(document.terms)
+ .filter(key => !document.terms[key])
+ .reduce<ComponentChild[]>((list, key) => [
+ ...list, <del>{key}</del>, " "
+ ], [])
+ .slice(0, -1)
+
+ /* Assemble query string for highlighting */
+ const url = new URL(document.location)
+ if (feature("search.highlight"))
+ url.searchParams.set("h", Object.entries(document.terms)
+ .filter(([, match]) => match)
+ .reduce((highlight, [value]) => `${highlight} ${value}`.trim(), "")
+ )
+
+ /* Render article or section, depending on flags */
+ return (
+ <a href={`${url}`} class="md-search-result__link" tabIndex={-1}>
+ <article
+ class={["md-search-result__article", ...parent
+ ? ["md-search-result__article--document"]
+ : []
+ ].join(" ")}
+ data-md-score={document.score.toFixed(2)}
+ >
+ {parent > 0 && <div class="md-search-result__icon md-icon"></div>}
+ <h1 class="md-search-result__title">{document.title}</h1>
+ {teaser > 0 && document.text.length > 0 &&
+ <p class="md-search-result__teaser">
+ {truncate(document.text, 320)}
+ </p>
+ }
+ {document.tags && document.tags.map(tag => (
+ <span class="md-tag">{tag}</span>
+ ))}
+ {teaser > 0 && missing.length > 0 &&
+ <p class="md-search-result__terms">
+ {translation("search.result.term.missing")}: {...missing}
+ </p>
+ }
+ </article>
+ </a>
+ )
+}
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Render a search result
+ *
+ * @param result - Search result
+ *
+ * @returns Element
+ */
+export function renderSearchResultItem(
+ result: SearchResultItem
+): HTMLElement {
+ const threshold = result[0].score
+ const docs = [...result]
+
+ /* Find and extract parent article */
+ const parent = docs.findIndex(doc => !doc.location.includes("#"))
+ const [article] = docs.splice(parent, 1)
+
+ /* Determine last index above threshold */
+ let index = docs.findIndex(doc => doc.score < threshold)
+ if (index === -1)
+ index = docs.length
+
+ /* Partition sections */
+ const best = docs.slice(0, index)
+ const more = docs.slice(index)
+
+ /* Render children */
+ const children = [
+ renderSearchDocument(article, Flag.PARENT | +(!parent && index === 0)),
+ ...best.map(section => renderSearchDocument(section, Flag.TEASER)),
+ ...more.length ? [
+ <details class="md-search-result__more">
+ <summary tabIndex={-1}>
+ {more.length > 0 && more.length === 1
+ ? translation("search.result.more.one")
+ : translation("search.result.more.other", more.length)
+ }
+ </summary>
+ {...more.map(section => renderSearchDocument(section, Flag.TEASER))}
+ </details>
+ ] : []
+ ]
+
+ /* Render search result */
+ return (
+ <li class="md-search-result__item">
+ {children}
+ </li>
+ )
+}
diff --git a/src/assets/javascripts/templates/source/index.tsx b/src/assets/javascripts/templates/source/index.tsx
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import { SourceFacts } from "~/components"
+import { h, round } from "~/utilities"
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Render repository facts
+ *
+ * @param facts - Repository facts
+ *
+ * @returns Element
+ */
+export function renderSourceFacts(facts: SourceFacts): HTMLElement {
+ return (
+ <ul class="md-source__facts">
+ {Object.entries(facts).map(([key, value]) => (
+ <li class={`md-source__fact md-source__fact--${key}`}>
+ {typeof value === "number" ? round(value) : value}
+ </li>
+ ))}
+ </ul>
+ )
+}
diff --git a/src/assets/javascripts/templates/tabbed/index.tsx b/src/assets/javascripts/templates/tabbed/index.tsx
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import { h } from "~/utilities"
+
+/* ----------------------------------------------------------------------------
+ * Helper types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Tabbed control type
+ */
+type TabbedControlType =
+ | "prev"
+ | "next"
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Render control for content tabs
+ *
+ * @param type - Control type
+ *
+ * @returns Element
+ */
+export function renderTabbedControl(
+ type: TabbedControlType
+): HTMLElement {
+ const classes = `tabbed-control tabbed-control--${type}`
+ return (
+ <div class={classes} hidden>
+ <button class="tabbed-button" tabIndex={-1}></button>
+ </div>
+ )
+}
diff --git a/src/assets/javascripts/templates/table/index.tsx b/src/assets/javascripts/templates/table/index.tsx
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import { h } from "~/utilities"
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Render a table inside a wrapper to improve scrolling on mobile
+ *
+ * @param table - Table element
+ *
+ * @returns Element
+ */
+export function renderTable(table: HTMLElement): HTMLElement {
+ return (
+ <div class="md-typeset__scrollwrap">
+ <div class="md-typeset__table">
+ {table}
+ </div>
+ </div>
+ )
+}
diff --git a/src/assets/javascripts/templates/version/index.tsx b/src/assets/javascripts/templates/version/index.tsx
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import { configuration, translation } from "~/_"
+import { h } from "~/utilities"
+
+/* ----------------------------------------------------------------------------
+ * Types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Version
+ */
+export interface Version {
+ version: string /* Version identifier */
+ title: string /* Version title */
+ aliases: string[] /* Version aliases */
+}
+
+/* ----------------------------------------------------------------------------
+ * Helper functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Render a version
+ *
+ * @param version - Version
+ *
+ * @returns Element
+ */
+function renderVersion(version: Version): HTMLElement {
+ const config = configuration()
+
+ /* Ensure trailing slash, see https://bit.ly/3rL5u3f */
+ const url = new URL(`../${version.version}/`, config.base)
+ return (
+ <li class="md-version__item">
+ <a href={`${url}`} class="md-version__link">
+ {version.title}
+ </a>
+ </li>
+ )
+}
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Render a version selector
+ *
+ * @param versions - Versions
+ * @param active - Active version
+ *
+ * @returns Element
+ */
+export function renderVersionSelector(
+ versions: Version[], active: Version
+): HTMLElement {
+ return (
+ <div class="md-version">
+ <button
+ class="md-version__current"
+ aria-label={translation("select.version.title")}
+ >
+ {active.title}
+ </button>
+ <ul class="md-version__list">
+ {versions.map(renderVersion)}
+ </ul>
+ </div>
+ )
+}
diff --git a/src/assets/javascripts/utilities/h/.eslintrc b/src/assets/javascripts/utilities/h/.eslintrc
@@ -0,0 +1,7 @@
+{
+ "rules": {
+ "@typescript-eslint/no-explicit-any": "off",
+ "@typescript-eslint/no-namespace": "off",
+ "jsdoc/require-jsdoc": "off"
+ }
+}
diff --git a/src/assets/javascripts/utilities/h/index.ts b/src/assets/javascripts/utilities/h/index.ts
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import { JSX as JSXInternal } from "preact"
+
+/* ----------------------------------------------------------------------------
+ * Helper types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * HTML attributes
+ */
+type Attributes =
+ & JSXInternal.HTMLAttributes
+ & JSXInternal.SVGAttributes
+ & Record<string, any>
+
+/**
+ * Child element
+ */
+type Child =
+ | HTMLElement
+ | Text
+ | string
+ | number
+
+/* ----------------------------------------------------------------------------
+ * Helper functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Append a child node to an element
+ *
+ * @param el - Element
+ * @param child - Child node(s)
+ */
+function appendChild(el: HTMLElement, child: Child | Child[]): void {
+
+ /* Handle primitive types (including raw HTML) */
+ if (typeof child === "string" || typeof child === "number") {
+ el.innerHTML += child.toString()
+
+ /* Handle nodes */
+ } else if (child instanceof Node) {
+ el.appendChild(child)
+
+ /* Handle nested children */
+ } else if (Array.isArray(child)) {
+ for (const node of child)
+ appendChild(el, node)
+ }
+}
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * JSX factory
+ *
+ * @template T - Element type
+ *
+ * @param tag - HTML tag
+ * @param attributes - HTML attributes
+ * @param children - Child elements
+ *
+ * @returns Element
+ */
+export function h<T extends keyof HTMLElementTagNameMap>(
+ tag: T, attributes?: Attributes | null, ...children: Child[]
+): HTMLElementTagNameMap[T]
+
+export function h<T extends h.JSX.Element>(
+ tag: string, attributes?: Attributes | null, ...children: Child[]
+): T
+
+export function h<T extends h.JSX.Element>(
+ tag: string, attributes?: Attributes | null, ...children: Child[]
+): T {
+ const el = document.createElement(tag)
+
+ /* Set attributes, if any */
+ if (attributes)
+ for (const attr of Object.keys(attributes)) {
+ if (typeof attributes[attr] === "undefined")
+ continue
+
+ /* Set default attribute or boolean */
+ if (typeof attributes[attr] !== "boolean")
+ el.setAttribute(attr, attributes[attr])
+ else
+ el.setAttribute(attr, "")
+ }
+
+ /* Append child nodes */
+ for (const child of children)
+ appendChild(el, child)
+
+ /* Return element */
+ return el as T
+}
+
+/* ----------------------------------------------------------------------------
+ * Namespace
+ * ------------------------------------------------------------------------- */
+
+export declare namespace h {
+ namespace JSX {
+ type Element = HTMLElement
+ type IntrinsicElements = JSXInternal.IntrinsicElements
+ }
+}
diff --git a/src/assets/javascripts/utilities/index.ts b/src/assets/javascripts/utilities/index.ts
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+export * from "./h"
+export * from "./string"
diff --git a/src/assets/javascripts/utilities/string/index.ts b/src/assets/javascripts/utilities/string/index.ts
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Truncate a string after the given number of characters
+ *
+ * This is not a very reasonable approach, since the summaries kind of suck.
+ * It would be better to create something more intelligent, highlighting the
+ * search occurrences and making a better summary out of it, but this note was
+ * written three years ago, so who knows if we'll ever fix it.
+ *
+ * @param value - Value to be truncated
+ * @param n - Number of characters
+ *
+ * @returns Truncated value
+ */
+export function truncate(value: string, n: number): string {
+ let i = n
+ if (value.length > i) {
+ while (value[i] !== " " && --i > 0) { /* keep eating */ }
+ return `${value.substring(0, i)}...`
+ }
+ return value
+}
+
+/**
+ * Round a number for display with repository facts
+ *
+ * This is a reverse-engineered version of GitHub's weird rounding algorithm
+ * for stars, forks and all other numbers. While all numbers below `1,000` are
+ * returned as-is, bigger numbers are converted to fixed numbers:
+ *
+ * - `1,049` => `1k`
+ * - `1,050` => `1.1k`
+ * - `1,949` => `1.9k`
+ * - `1,950` => `2k`
+ *
+ * @param value - Original value
+ *
+ * @returns Rounded value
+ */
+export function round(value: number): string {
+ if (value > 999) {
+ const digits = +((value - 950) % 1000 > 99)
+ return `${((value + 0.000001) / 1000).toFixed(digits)}k`
+ } else {
+ return value.toString()
+ }
+}
diff --git a/src/assets/javascripts/workers/search.ts b/src/assets/javascripts/workers/search.ts
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A RTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import "~/integrations/search/worker/main"
diff --git a/src/assets/stylesheets/_config.scss b/src/assets/stylesheets/_config.scss
@@ -0,0 +1,42 @@
+////
+/// Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+///
+/// Permission is hereby granted, free of charge, to any person obtaining a
+/// copy of this software and associated documentation files (the "Software"),
+/// to deal in the Software without restriction, including without limitation
+/// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+/// and/or sell copies of the Software, and to permit persons to whom the
+/// Software is furnished to do so, subject to the following conditions:
+///
+/// The above copyright notice and this permission notice shall be included in
+/// all copies or substantial portions of the Software.
+///
+/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+/// DEALINGS
+////
+
+// ----------------------------------------------------------------------------
+// Variables: breakpoints
+// ----------------------------------------------------------------------------
+
+// Device-specific breakpoints
+$break-devices: (
+ mobile: (
+ portrait: px2em(220px) px2em(479px),
+ landscape: px2em(480px) px2em(719px)
+ ),
+ tablet: (
+ portrait: px2em(720px) px2em(959px),
+ landscape: px2em(960px) px2em(1219px)
+ ),
+ screen: (
+ small: px2em(1220px) px2em(1599px),
+ medium: px2em(1600px) px2em(1999px),
+ large: px2em(2000px)
+ )
+);
diff --git a/src/assets/stylesheets/main.scss b/src/assets/stylesheets/main.scss
@@ -0,0 +1,80 @@
+////
+/// Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+///
+/// Permission is hereby granted, free of charge, to any person obtaining a
+/// copy of this software and associated documentation files (the "Software"),
+/// to deal in the Software without restriction, including without limitation
+/// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+/// and/or sell copies of the Software, and to permit persons to whom the
+/// Software is furnished to do so, subject to the following conditions:
+///
+/// The above copyright notice and this permission notice shall be included in
+/// all copies or substantial portions of the Software.
+///
+/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+/// DEALINGS
+////
+
+// ----------------------------------------------------------------------------
+// Dependencies
+// ----------------------------------------------------------------------------
+
+@import "material-color";
+@import "material-shadows";
+
+// ----------------------------------------------------------------------------
+// Local imports
+// ----------------------------------------------------------------------------
+
+@import "utilities/break";
+@import "utilities/convert";
+
+@import "config";
+
+@import "main/resets";
+@import "main/colors";
+@import "main/icons";
+@import "main/typeset";
+
+@import "main/layout/banner";
+@import "main/layout/base";
+@import "main/layout/clipboard";
+@import "main/layout/consent";
+@import "main/layout/content";
+@import "main/layout/dialog";
+@import "main/layout/feedback";
+@import "main/layout/footer";
+@import "main/layout/form";
+@import "main/layout/header";
+@import "main/layout/nav";
+@import "main/layout/search";
+@import "main/layout/select";
+@import "main/layout/sidebar";
+@import "main/layout/source";
+@import "main/layout/tabs";
+@import "main/layout/tag";
+@import "main/layout/tooltip";
+@import "main/layout/top";
+@import "main/layout/version";
+
+@import "main/extensions/markdown/admonition";
+@import "main/extensions/markdown/footnotes";
+@import "main/extensions/markdown/toc";
+
+@import "main/extensions/pymdownx/arithmatex";
+@import "main/extensions/pymdownx/critic";
+@import "main/extensions/pymdownx/details";
+@import "main/extensions/pymdownx/emoji";
+@import "main/extensions/pymdownx/highlight";
+@import "main/extensions/pymdownx/keys";
+@import "main/extensions/pymdownx/tabbed";
+@import "main/extensions/pymdownx/tasklist";
+
+@import "main/integrations/mermaid";
+
+@import "main/modifiers";
diff --git a/src/assets/stylesheets/main/_colors.scss b/src/assets/stylesheets/main/_colors.scss
@@ -0,0 +1,134 @@
+////
+/// Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+///
+/// Permission is hereby granted, free of charge, to any person obtaining a
+/// copy of this software and associated documentation files (the "Software"),
+/// to deal in the Software without restriction, including without limitation
+/// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+/// and/or sell copies of the Software, and to permit persons to whom the
+/// Software is furnished to do so, subject to the following conditions:
+///
+/// The above copyright notice and this permission notice shall be included in
+/// all copies or substantial portions of the Software.
+///
+/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+/// DEALINGS
+////
+
+// ----------------------------------------------------------------------------
+// Rules
+// ----------------------------------------------------------------------------
+
+// Color variables
+:root {
+ @extend %root;
+}
+
+// ----------------------------------------------------------------------------
+
+// Allow to explicitly use color schemes in nested content
+[data-md-color-scheme="default"] {
+ @extend %root;
+}
+
+// ----------------------------------------------------------------------------
+// Placeholders
+// ----------------------------------------------------------------------------
+
+// Default theme, i.e. light mode
+%root {
+
+ // Default color shades
+ --md-default-fg-color: hsla(0, 0%, 0%, 0.87);
+ --md-default-fg-color--light: hsla(0, 0%, 0%, 0.54);
+ --md-default-fg-color--lighter: hsla(0, 0%, 0%, 0.32);
+ --md-default-fg-color--lightest: hsla(0, 0%, 0%, 0.07);
+ --md-default-bg-color: hsla(0, 0%, 100%, 1);
+ --md-default-bg-color--light: hsla(0, 0%, 100%, 0.7);
+ --md-default-bg-color--lighter: hsla(0, 0%, 100%, 0.3);
+ --md-default-bg-color--lightest: hsla(0, 0%, 100%, 0.12);
+
+ // Primary color shades
+ --md-primary-fg-color: hsla(#{hex2hsl($clr-indigo-500)}, 1);
+ --md-primary-fg-color--light: hsla(#{hex2hsl($clr-indigo-400)}, 1);
+ --md-primary-fg-color--dark: hsla(#{hex2hsl($clr-indigo-700)}, 1);
+ --md-primary-bg-color: hsla(0, 0%, 100%, 1);
+ --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);
+
+ // Accent color shades
+ --md-accent-fg-color: hsla(#{hex2hsl($clr-indigo-a200)}, 1);
+ --md-accent-fg-color--transparent: hsla(#{hex2hsl($clr-indigo-a200)}, 0.1);
+ --md-accent-bg-color: hsla(0, 0%, 100%, 1);
+ --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7);
+
+ // Code color shades
+ --md-code-fg-color: hsla(200, 18%, 26%, 1);
+ --md-code-bg-color: hsla(0, 0%, 96%, 1);
+
+ // Code highlighting color shades
+ --md-code-hl-color: hsla(#{hex2hsl($clr-yellow-a200)}, 0.5);
+ --md-code-hl-number-color: hsla(0, 67%, 50%, 1);
+ --md-code-hl-special-color: hsla(340, 83%, 47%, 1);
+ --md-code-hl-function-color: hsla(291, 45%, 50%, 1);
+ --md-code-hl-constant-color: hsla(250, 63%, 60%, 1);
+ --md-code-hl-keyword-color: hsla(219, 54%, 51%, 1);
+ --md-code-hl-string-color: hsla(150, 63%, 30%, 1);
+ --md-code-hl-name-color: var(--md-code-fg-color);
+ --md-code-hl-operator-color: var(--md-default-fg-color--light);
+ --md-code-hl-punctuation-color: var(--md-default-fg-color--light);
+ --md-code-hl-comment-color: var(--md-default-fg-color--light);
+ --md-code-hl-generic-color: var(--md-default-fg-color--light);
+ --md-code-hl-variable-color: var(--md-default-fg-color--light);
+
+ // Typeset color shades
+ --md-typeset-color: var(--md-default-fg-color);
+
+ // Typeset `a` color shades
+ --md-typeset-a-color: var(--md-primary-fg-color);
+
+ // Typeset `mark` color shades
+ --md-typeset-mark-color: hsla(#{hex2hsl($clr-yellow-a200)}, 0.5);
+
+ // Typeset `del` and `ins` color shades
+ --md-typeset-del-color: hsla(6, 90%, 60%, 0.15);
+ --md-typeset-ins-color: hsla(150, 90%, 44%, 0.15);
+
+ // Typeset `kbd` color shades
+ --md-typeset-kbd-color: hsla(0, 0%, 98%, 1);
+ --md-typeset-kbd-accent-color: hsla(0, 100%, 100%, 1);
+ --md-typeset-kbd-border-color: hsla(0, 0%, 72%, 1);
+
+ // Typeset `table` color shades
+ --md-typeset-table-color: hsla(0, 0%, 0%, 0.12);
+
+ // Admonition color shades
+ --md-admonition-fg-color: var(--md-default-fg-color);
+ --md-admonition-bg-color: var(--md-default-bg-color);
+
+ // Footer color shades
+ --md-footer-fg-color: hsla(0, 0%, 100%, 1);
+ --md-footer-fg-color--light: hsla(0, 0%, 100%, 0.7);
+ --md-footer-fg-color--lighter: hsla(0, 0%, 100%, 0.3);
+ --md-footer-bg-color: hsla(0, 0%, 0%, 0.87);
+ --md-footer-bg-color--dark: hsla(0, 0%, 0%, 0.32);
+
+ // Shadow depth 1
+ --md-shadow-z1:
+ 0 #{px2rem(4px)} #{px2rem(10px)} hsla(0, 0%, 0%, 0.05),
+ 0 0 #{px2rem(1px)} hsla(0, 0%, 0%, 0.1);
+
+ // Shadow depth 2
+ --md-shadow-z2:
+ 0 #{px2rem(4px)} #{px2rem(10px)} hsla(0, 0%, 0%, 0.1),
+ 0 0 #{px2rem(1px)} hsla(0, 0%, 0%, 0.25);
+
+ // Shadow depth 3
+ --md-shadow-z3:
+ 0 #{px2rem(4px)} #{px2rem(10px)} hsla(0, 0%, 0%, 0.2),
+ 0 0 #{px2rem(1px)} hsla(0, 0%, 0%, 0.35);
+}
diff --git a/src/assets/stylesheets/main/_icons.scss b/src/assets/stylesheets/main/_icons.scss
@@ -0,0 +1,37 @@
+////
+/// Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+///
+/// Permission is hereby granted, free of charge, to any person obtaining a
+/// copy of this software and associated documentation files (the "Software"),
+/// to deal in the Software without restriction, including without limitation
+/// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+/// and/or sell copies of the Software, and to permit persons to whom the
+/// Software is furnished to do so, subject to the following conditions:
+///
+/// The above copyright notice and this permission notice shall be included in
+/// all copies or substantial portions of the Software.
+///
+/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+/// DEALINGS
+////
+
+// ----------------------------------------------------------------------------
+// Rules
+// ----------------------------------------------------------------------------
+
+// Icon
+.md-icon {
+
+ // SVG defaults
+ svg {
+ display: block;
+ width: px2rem(24px);
+ height: px2rem(24px);
+ fill: currentcolor;
+ }
+}
diff --git a/src/assets/stylesheets/main/_modifiers.scss b/src/assets/stylesheets/main/_modifiers.scss
@@ -0,0 +1,58 @@
+////
+/// Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+///
+/// Permission is hereby granted, free of charge, to any person obtaining a
+/// copy of this software and associated documentation files (the "Software"),
+/// to deal in the Software without restriction, including without limitation
+/// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+/// and/or sell copies of the Software, and to permit persons to whom the
+/// Software is furnished to do so, subject to the following conditions:
+///
+/// The above copyright notice and this permission notice shall be included in
+/// all copies or substantial portions of the Software.
+///
+/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+/// DEALINGS
+////
+
+// ----------------------------------------------------------------------------
+// Rules
+// ----------------------------------------------------------------------------
+
+// Scoped in typesetted content to match specificity of regular content
+.md-typeset {
+
+ // [tablet +]: Allow for rendering content as sidebars
+ @include break-from-device(tablet) {
+
+ // Modifier to float block elements
+ .inline {
+ float: left;
+ width: px2rem(234px);
+ margin-top: 0;
+ margin-inline-end: px2rem(16px);
+ margin-bottom: px2rem(16px);
+
+ // Adjust for right-to-left languages
+ [dir="rtl"] & {
+ float: right;
+ }
+
+ // Modifier to move to end (ltr: right, rtl: left)
+ &.end {
+ float: right;
+ margin-inline: px2rem(16px) 0;
+
+ // Adjust for right-to-left languages
+ [dir="rtl"] & {
+ float: left;
+ }
+ }
+ }
+ }
+}
diff --git a/src/assets/stylesheets/main/_resets.scss b/src/assets/stylesheets/main/_resets.scss
@@ -0,0 +1,118 @@
+////
+/// Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+///
+/// Permission is hereby granted, free of charge, to any person obtaining a
+/// copy of this software and associated documentation files (the "Software"),
+/// to deal in the Software without restriction, including without limitation
+/// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+/// and/or sell copies of the Software, and to permit persons to whom the
+/// Software is furnished to do so, subject to the following conditions:
+///
+/// The above copyright notice and this permission notice shall be included in
+/// all copies or substantial portions of the Software.
+///
+/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+/// DEALINGS
+////
+
+// ----------------------------------------------------------------------------
+// Rules
+// ----------------------------------------------------------------------------
+
+// Enforce correct box model and prevent adjustments of font size after
+// orientation changes in IE and iOS
+html {
+ box-sizing: border-box;
+ text-size-adjust: none;
+}
+
+// All elements shall inherit the document default
+*,
+*::before,
+*::after {
+ box-sizing: inherit;
+
+ // [reduced motion]: Disable all transitions
+ @media (prefers-reduced-motion) {
+ transition: none !important; // stylelint-disable-line
+ }
+}
+
+// Remove margin in all browsers
+body {
+ margin: 0;
+}
+
+// Reset tap outlines on iOS and Android
+a,
+button,
+label,
+input {
+ -webkit-tap-highlight-color: transparent;
+}
+
+// Reset link styles
+a {
+ color: inherit;
+ text-decoration: none;
+}
+
+// Normalize horizontal separator styles
+hr {
+ display: block;
+ box-sizing: content-box;
+ height: px2rem(1px);
+ padding: 0;
+ overflow: visible;
+ border: 0;
+}
+
+// Normalize font-size in all browsers
+small {
+ font-size: 80%;
+}
+
+// Prevent subscript and superscript from affecting line-height
+sub,
+sup {
+ line-height: 1em;
+}
+
+// Remove border on image
+img {
+ border-style: none;
+}
+
+// Reset table styles
+table {
+ border-collapse: separate;
+ border-spacing: 0;
+}
+
+// Reset table cell styles
+td,
+th {
+ font-weight: 400;
+ vertical-align: top;
+}
+
+// Reset button styles
+button {
+ margin: 0;
+ padding: 0;
+ font-size: inherit;
+ font-family: inherit;
+ background: transparent;
+ border: 0;
+}
+
+// Reset input styles
+input {
+ border: 0;
+ outline: none;
+}
diff --git a/src/assets/stylesheets/main/_typeset.scss b/src/assets/stylesheets/main/_typeset.scss
@@ -0,0 +1,619 @@
+////
+/// Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+///
+/// Permission is hereby granted, free of charge, to any person obtaining a
+/// copy of this software and associated documentation files (the "Software"),
+/// to deal in the Software without restriction, including without limitation
+/// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+/// and/or sell copies of the Software, and to permit persons to whom the
+/// Software is furnished to do so, subject to the following conditions:
+///
+/// The above copyright notice and this permission notice shall be included in
+/// all copies or substantial portions of the Software.
+///
+/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+/// DEALINGS
+////
+
+// ----------------------------------------------------------------------------
+// Rules: font definitions
+// ----------------------------------------------------------------------------
+
+// Enable font-smoothing in Webkit and FF
+body {
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+
+ // Font with fallback for body copy
+ --md-text-font-family:
+ var(--md-text-font, _),
+ -apple-system, BlinkMacSystemFont, Helvetica, Arial, sans-serif;
+
+ // Font with fallback for code
+ --md-code-font-family:
+ var(--md-code-font, _),
+ SFMono-Regular, Consolas, Menlo, monospace;
+}
+
+// Define default fonts
+body,
+input {
+ color: var(--md-typeset-color);
+ font-feature-settings: "kern", "liga";
+ font-family: var(--md-text-font-family);
+}
+
+// Define monospaced fonts
+code,
+pre,
+kbd {
+ color: var(--md-typeset-color);
+ font-feature-settings: "kern";
+ font-family: var(--md-code-font-family);
+}
+
+// ----------------------------------------------------------------------------
+// Rules: typesetted content
+// ----------------------------------------------------------------------------
+
+// General variables
+:root {
+ --md-typeset-table-sort-icon: svg-load("material/sort.svg");
+ --md-typeset-table-sort-icon--asc: svg-load("material/sort-ascending.svg");
+ --md-typeset-table-sort-icon--desc: svg-load("material/sort-descending.svg");
+}
+
+// ----------------------------------------------------------------------------
+
+// Content that is typeset - if possible, all margins, paddings and font sizes
+// should be set in ems, so nested blocks (e.g. admonitions) render correctly.
+.md-typeset {
+ font-size: px2rem(16px);
+ line-height: 1.6;
+ color-adjust: exact;
+
+ // [print]: We'll use a smaller `font-size` for printing, so code examples
+ // don't break too early, and `16px` looks too big anyway.
+ @media print {
+ font-size: px2rem(13.6px);
+ }
+
+ // Default spacing
+ ul,
+ ol,
+ dl,
+ figure,
+ blockquote,
+ pre {
+ margin-block: 1em;
+ }
+
+ // Headline on level 1
+ h1 {
+ margin: 0 0 px2em(40px, 32px);
+ color: var(--md-default-fg-color--light);
+ font-weight: 300;
+ font-size: px2em(32px);
+ line-height: 1.3;
+ letter-spacing: -0.01em;
+ }
+
+ // Headline on level 2
+ h2 {
+ margin: px2em(40px, 25px) 0 px2em(16px, 25px);
+ font-weight: 300;
+ font-size: px2em(25px);
+ line-height: 1.4;
+ letter-spacing: -0.01em;
+ }
+
+ // Headline on level 3
+ h3 {
+ margin: px2em(32px, 20px) 0 px2em(16px, 20px);
+ font-weight: 400;
+ font-size: px2em(20px);
+ line-height: 1.5;
+ letter-spacing: -0.01em;
+ }
+
+ // Headline on level 3 following level 2
+ h2 + h3 {
+ margin-top: px2em(16px, 20px);
+ }
+
+ // Headline on level 4
+ h4 {
+ margin: px2em(16px) 0;
+ font-weight: 700;
+ letter-spacing: -0.01em;
+ }
+
+ // Headline on level 5-6
+ h5,
+ h6 {
+ margin: px2em(16px, 12.8px) 0;
+ color: var(--md-default-fg-color--light);
+ font-weight: 700;
+ font-size: px2em(12.8px);
+ letter-spacing: -0.01em;
+ }
+
+ // Headline on level 5
+ h5 {
+ text-transform: uppercase;
+ }
+
+ // Horizontal separator
+ hr {
+ display: flow-root;
+ margin: 1.5em 0;
+ border-bottom: px2rem(1px) solid var(--md-default-fg-color--lightest);
+ }
+
+ // Text link
+ a {
+ color: var(--md-typeset-a-color);
+ word-break: break-word;
+
+ // Also enable color transition on pseudo elements
+ &,
+ &::before {
+ transition: color 125ms;
+ }
+
+ // Text link on focus/hover
+ &:focus,
+ &:hover {
+ color: var(--md-accent-fg-color);
+
+ // Inline code block
+ code {
+ background-color: var(--md-accent-fg-color--transparent);
+ }
+ }
+
+ // Inline code block
+ code {
+ color: currentcolor;
+ transition: background-color 125ms;
+ }
+
+ // Show outline for keyboard devices
+ &.focus-visible {
+ outline-color: var(--md-accent-fg-color);
+ outline-offset: px2rem(4px);
+ }
+ }
+
+ // Code block
+ code,
+ pre,
+ kbd {
+ color: var(--md-code-fg-color);
+ direction: ltr;
+
+ // [print]: Wrap text and hide scollbars
+ @media print {
+ white-space: pre-wrap;
+ }
+ }
+
+ // Inline code block
+ code {
+ padding: 0 px2em(4px, 13.6px);
+ font-size: px2em(13.6px);
+ word-break: break-word;
+ background-color: var(--md-code-bg-color);
+ border-radius: px2rem(2px);
+ box-decoration-break: clone;
+
+ // Hide outline for pointer devices
+ &:not(.focus-visible) {
+ outline: none;
+ -webkit-tap-highlight-color: transparent;
+ }
+ }
+
+ // Unformatted content
+ pre {
+ position: relative;
+ display: flow-root;
+ line-height: 1.4;
+
+ // Code block
+ > code {
+ display: block;
+ margin: 0;
+ padding: px2em(10.5px, 13.6px) px2em(16px, 13.6px);
+ overflow: auto;
+ word-break: normal;
+ outline-color: var(--md-accent-fg-color);
+ box-shadow: none;
+ box-decoration-break: slice;
+ touch-action: auto;
+ scrollbar-width: thin;
+ scrollbar-color: var(--md-default-fg-color--lighter) transparent;
+
+ // Code block on hover
+ &:hover {
+ scrollbar-color: var(--md-accent-fg-color) transparent;
+ }
+
+ // Webkit scrollbar
+ &::-webkit-scrollbar {
+ width: px2rem(4px);
+ height: px2rem(4px);
+ }
+
+ // Webkit scrollbar thumb
+ &::-webkit-scrollbar-thumb {
+ background-color: var(--md-default-fg-color--lighter);
+
+ // Webkit scrollbar thumb on hover
+ &:hover {
+ background-color: var(--md-accent-fg-color);
+ }
+ }
+ }
+ }
+
+ // Keyboard key
+ kbd {
+ display: inline-block;
+ padding: 0 px2em(8px, 12px);
+ color: var(--md-default-fg-color);
+ font-size: px2em(12px);
+ vertical-align: text-top;
+ word-break: break-word;
+ background-color: var(--md-typeset-kbd-color);
+ border-radius: px2rem(2px);
+ box-shadow:
+ 0 px2rem(2px) 0 px2rem(1px) var(--md-typeset-kbd-border-color),
+ 0 px2rem(2px) 0 var(--md-typeset-kbd-border-color),
+ 0 px2rem(-2px) px2rem(4px) var(--md-typeset-kbd-accent-color) inset;
+ }
+
+ // Text highlighting marker
+ mark {
+ color: inherit;
+ word-break: break-word;
+ background-color: var(--md-typeset-mark-color);
+ box-decoration-break: clone;
+ }
+
+ // Abbreviation
+ abbr {
+ text-decoration: none;
+ border-bottom: px2rem(1px) dotted var(--md-default-fg-color--light);
+ cursor: help;
+
+ // Show tooltip for touch devices
+ @media (hover: none) {
+ position: relative;
+
+ // Tooltip
+ &[title]:is(:focus, :hover)::after {
+ position: absolute;
+ inset-inline-start: 0;
+ display: inline-block;
+ width: auto;
+ min-width: max-content;
+ max-width: 80%;
+ margin-top: 2em;
+ padding: px2rem(4px) px2rem(6px);
+ color: var(--md-default-bg-color);
+ font-size: px2rem(14px);
+ background-color: var(--md-default-fg-color);
+ border-radius: px2rem(2px);
+ box-shadow: var(--md-shadow-z3);
+ content: attr(title);
+ }
+ }
+ }
+
+ // Small text
+ small {
+ opacity: 0.75;
+ }
+
+ // Superscript and subscript
+ sup,
+ sub {
+ margin-inline-start: px2em(1px, 12.8px);
+ }
+
+ // Blockquotes, possibly nested
+ blockquote {
+ padding-inline-start: px2rem(12px);
+ margin-inline: 0;
+ color: var(--md-default-fg-color--light);
+ border-inline-start: px2rem(4px) solid var(--md-default-fg-color--lighter);
+ }
+
+ // Unordered list
+ ul {
+ list-style-type: disc;
+ }
+
+ // Unordered and ordered list
+ ul,
+ ol {
+ margin-inline-start: px2em(10px);
+ padding: 0;
+
+ // Adjust display mode if not hidden
+ &:not([hidden]) {
+ display: flow-root;
+ }
+
+ // Nested ordered list
+ ol {
+ list-style-type: lower-alpha;
+
+ // Triply nested ordered list
+ ol {
+ list-style-type: lower-roman;
+ }
+ }
+
+ // List element
+ li {
+ margin-bottom: 0.5em;
+ margin-inline-start: px2em(20px);
+
+ // Adjust spacing
+ p,
+ blockquote {
+ margin: 0.5em 0;
+ }
+
+ // Adjust spacing on last child
+ &:last-child {
+ margin-bottom: 0;
+ }
+
+ // Nested list
+ :is(ul, ol) {
+ margin-block: 0.5em;
+ margin-inline-start: px2em(10px);
+ }
+ }
+ }
+
+ // Definition list
+ dd {
+ margin-block: 1em 1.5em;
+ margin-inline-start: px2em(30px);
+ }
+
+ // Image or video
+ img,
+ svg,
+ video {
+ max-width: 100%;
+ height: auto;
+ }
+
+ // Image
+ img {
+
+ // Adjust spacing when left-aligned
+ &[align="left"] {
+ margin: 1em;
+ margin-left: 0;
+ }
+
+ // Adjust spacing when right-aligned
+ &[align="right"] {
+ margin: 1em;
+ margin-right: 0;
+ }
+
+ // Adjust spacing when sole children
+ &[align]:only-child {
+ margin-top: 0;
+ }
+
+ // Hide images for dark mode
+ &[src$="#only-dark"],
+ &[src$="#gh-dark-mode-only"] {
+ display: none;
+ }
+ }
+
+ // Figure
+ figure {
+ display: flow-root;
+ width: fit-content;
+ max-width: 100%;
+ margin: 1em auto;
+ text-align: center;
+
+ // Figure images
+ img {
+ display: block;
+ }
+ }
+
+ // Figure caption
+ figcaption {
+ max-width: px2rem(480px);
+ margin: 1em auto;
+ font-style: italic;
+ }
+
+ // Limit width to container
+ iframe {
+ max-width: 100%;
+ }
+
+ // Data table
+ table:not([class]) {
+ display: inline-block;
+ max-width: 100%;
+ overflow: auto;
+ font-size: px2rem(12.8px);
+ background-color: var(--md-default-bg-color);
+ border: px2rem(1px) solid var(--md-typeset-table-color);
+ border-radius: px2rem(2px);
+ touch-action: auto;
+
+ // [print]: Reset display mode so table header wraps when printing
+ @media print {
+ display: table;
+ }
+
+ // Due to margin collapse because of the necessary inline-block hack, we
+ // cannot increase the bottom margin on the table, so we just increase the
+ // top margin on the following element
+ + * {
+ margin-top: 1.5em;
+ }
+
+ // Elements in table heading and cell
+ :is(th, td) > * {
+
+ // Adjust spacing on first child
+ &:first-child {
+ margin-top: 0;
+ }
+
+ // Adjust spacing on last child
+ &:last-child {
+ margin-bottom: 0;
+ }
+ }
+
+ // Table heading and cell
+ :is(th, td):not([align]) {
+ text-align: left;
+
+ // Adjust for right-to-left languages
+ [dir="rtl"] & {
+ text-align: right;
+ }
+ }
+
+ // Table heading
+ th {
+ min-width: px2rem(100px);
+ padding: px2em(12px, 12.8px) px2em(16px, 12.8px);
+ font-weight: 700;
+ vertical-align: top;
+
+ // Links in table headings
+ a {
+ color: inherit;
+ }
+ }
+
+ // Table cell
+ td {
+ padding: px2em(12px, 12.8px) px2em(16px, 12.8px);
+ vertical-align: top;
+ border-top: px2rem(1px) solid var(--md-typeset-table-color);
+ }
+
+ // Table body row
+ tbody tr {
+ transition: background-color 125ms;
+
+ // Table row on hover
+ &:hover {
+ background-color: rgba(0, 0, 0, 0.035);
+ box-shadow: 0 px2rem(1px) 0 var(--md-default-bg-color) inset;
+ }
+ }
+
+ // Text link in table
+ a {
+ word-break: normal;
+ }
+ }
+
+ // Sortable table
+ table th[role="columnheader"] {
+ cursor: pointer;
+
+ // Sort icon
+ &::after {
+ display: inline-block;
+ width: 1.2em;
+ height: 1.2em;
+ margin-inline-start: 0.5em;
+ vertical-align: text-bottom;
+ mask-image: var(--md-typeset-table-sort-icon);
+ mask-repeat: no-repeat;
+ mask-size: contain;
+ transition: background-color 125ms;
+ content: "";
+ }
+
+ // Show sort icon on hover
+ &:hover::after {
+ background-color: var(--md-default-fg-color--lighter);
+ }
+
+ // Sort ascending icon
+ &[aria-sort="ascending"]::after {
+ background-color: var(--md-default-fg-color--light);
+ mask-image: var(--md-typeset-table-sort-icon--asc);
+ }
+
+ // Sort descending icon
+ &[aria-sort="descending"]::after {
+ background-color: var(--md-default-fg-color--light);
+ mask-image: var(--md-typeset-table-sort-icon--desc);
+ }
+ }
+
+ // Data table scroll wrapper
+ &__scrollwrap {
+ margin: 1em px2rem(-16px);
+ overflow-x: auto;
+ touch-action: auto;
+ }
+
+ // Data table wrapper
+ &__table {
+ display: inline-block;
+ margin-bottom: 0.5em;
+ padding: 0 px2rem(16px);
+
+ // [print]: Reset display mode so table header wraps when printing
+ @media print {
+ display: block;
+ }
+
+ // Data table
+ html & table {
+ display: table;
+ width: 100%;
+ margin: 0;
+ overflow: hidden;
+ }
+ }
+}
+
+// ----------------------------------------------------------------------------
+// Rules: top-level
+// ----------------------------------------------------------------------------
+
+// [mobile -]: Align with body copy
+@include break-to-device(mobile) {
+
+ // Top-level unformatted content
+ .md-content__inner > pre {
+ margin: 1em px2rem(-16px);
+
+ // Code block
+ code {
+ border-radius: 0;
+ }
+ }
+}
diff --git a/src/assets/stylesheets/main/extensions/markdown/_admonition.scss b/src/assets/stylesheets/main/extensions/markdown/_admonition.scss
@@ -0,0 +1,183 @@
+////
+/// Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+///
+/// Permission is hereby granted, free of charge, to any person obtaining a
+/// copy of this software and associated documentation files (the "Software"),
+/// to deal in the Software without restriction, including without limitation
+/// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+/// and/or sell copies of the Software, and to permit persons to whom the
+/// Software is furnished to do so, subject to the following conditions:
+///
+/// The above copyright notice and this permission notice shall be included in
+/// all copies or substantial portions of the Software.
+///
+/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+/// DEALINGS
+////
+
+@use "sass:color";
+@use "sass:list";
+
+// ----------------------------------------------------------------------------
+// Variables
+// ----------------------------------------------------------------------------
+
+/// Admonition flavours
+$admonitions: (
+ note: pencil $clr-blue-a200,
+ abstract summary tldr: clipboard-text $clr-light-blue-a400,
+ info todo: information $clr-cyan-a700,
+ tip hint important: fire $clr-teal-a700,
+ success check done: check-bold $clr-green-a700,
+ question help faq: help-circle $clr-light-green-a700,
+ warning caution attention: alert $clr-orange-a400,
+ failure fail missing: close-thick $clr-red-a200,
+ danger error: lightning-bolt $clr-red-a400,
+ bug: bug $clr-pink-a400,
+ example: format-list-numbered $clr-deep-purple-a200,
+ quote cite: format-quote-close $clr-grey
+) !default;
+
+// ----------------------------------------------------------------------------
+// Rules: layout
+// ----------------------------------------------------------------------------
+
+// Admonition variables
+:root {
+ @each $names, $props in $admonitions {
+ --md-admonition-icon--#{nth($names, 1)}:
+ svg-load("material/#{nth($props, 1)}.svg");
+ }
+}
+
+// ----------------------------------------------------------------------------
+
+// Scoped in typesetted content to match specificity of regular content
+.md-typeset {
+
+ // Admonition - note that all styles also apply to details tags, which are
+ // rendered as collapsible admonitions with summary elements as titles.
+ :is(.admonition, details) {
+ display: flow-root;
+ margin: px2em(20px, 12.8px) 0;
+ padding: 0 px2rem(12px);
+ color: var(--md-admonition-fg-color);
+ font-size: px2rem(12.8px);
+ page-break-inside: avoid;
+ background-color: var(--md-admonition-bg-color);
+ border: 0 solid $clr-blue-a200;
+ border-inline-start-width: px2rem(4px);
+ border-radius: px2rem(2px);
+ box-shadow: var(--md-shadow-z1);
+
+ // [print]: Omit shadow as it may lead to rendering errors
+ @media print {
+ box-shadow: none;
+ }
+
+ // Hack: Chrome exhibits a weird issue where it will set nested elements to
+ // content-box. Doesn't happen in other browsers, so looks like a bug.
+ > * {
+ box-sizing: border-box;
+ }
+
+ // Adjust vertical spacing for nested admonitions
+ :is(.admonition, details) {
+ margin-top: 1em;
+ margin-bottom: 1em;
+ }
+
+ // Adjust spacing for contained table wrappers
+ .md-typeset__scrollwrap {
+ margin: 1em px2rem(-12px);
+ }
+
+ // Adjust spacing for contained tables
+ .md-typeset__table {
+ padding: 0 px2rem(12px);
+ }
+
+ // Adjust spacing for single-child tabbed block container
+ > .tabbed-set:only-child {
+ margin-top: 0;
+ }
+
+ // Adjust spacing on last child
+ html & > :last-child {
+ margin-bottom: px2rem(12px);
+ }
+ }
+
+ // Admonition title
+ :is(.admonition-title, summary) {
+ position: relative;
+ margin-block: 0;
+ margin-inline: px2rem(-16px) px2rem(-12px);
+ padding-block: px2rem(8px);
+ padding-inline: px2rem(44px) px2rem(12px);
+ font-weight: 700;
+ background-color: color.adjust($clr-blue-a200, $alpha: -0.9);
+ border: none;
+ border-inline-start-width: px2rem(4px);
+ border-start-start-radius: px2rem(2px);
+ border-start-end-radius: px2rem(2px);
+
+ // Adjust spacing for title-only admonitions
+ html &:last-child {
+ margin-bottom: 0;
+ }
+
+ // Admonition icon
+ &::before {
+ position: absolute;
+ top: px2em(10px);
+ inset-inline-start: px2rem(16px);
+ width: px2rem(20px);
+ height: px2rem(20px);
+ background-color: $clr-blue-a200;
+ mask-image: var(--md-admonition-icon--note);
+ mask-repeat: no-repeat;
+ mask-size: contain;
+ content: "";
+ }
+ }
+}
+
+// ----------------------------------------------------------------------------
+// Rules: flavours
+// ----------------------------------------------------------------------------
+
+// Define admonition flavors
+@each $names, $props in $admonitions {
+ $name: list.nth($names, 1);
+ $tint: list.nth($props, 2);
+
+ // Admonition flavour selectors
+ $flavours: ();
+ @each $name in $names {
+ $flavours: list.join($flavours, ".#{$name}", $separator: comma);
+ }
+
+ // Admonition flavour
+ .md-typeset :is(.admonition, details):is(#{$flavours}) {
+ border-color: $tint;
+ }
+
+ // Admonition flavour title
+ .md-typeset :is(#{$flavours}) > :is(.admonition-title, summary) {
+ background-color: color.adjust($tint, $alpha: -0.9);
+
+ // Admonition icon
+ &::before {
+ background-color: $tint;
+ mask-image: var(--md-admonition-icon--#{$name});
+ mask-repeat: no-repeat;
+ mask-size: contain;
+ }
+ }
+}
diff --git a/src/assets/stylesheets/main/extensions/markdown/_footnotes.scss b/src/assets/stylesheets/main/extensions/markdown/_footnotes.scss
@@ -0,0 +1,145 @@
+////
+/// Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+///
+/// Permission is hereby granted, free of charge, to any person obtaining a
+/// copy of this software and associated documentation files (the "Software"),
+/// to deal in the Software without restriction, including without limitation
+/// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+/// and/or sell copies of the Software, and to permit persons to whom the
+/// Software is furnished to do so, subject to the following conditions:
+///
+/// The above copyright notice and this permission notice shall be included in
+/// all copies or substantial portions of the Software.
+///
+/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+/// DEALINGS
+////
+
+// ----------------------------------------------------------------------------
+// Rules
+// ----------------------------------------------------------------------------
+
+// Footnotes variables
+:root {
+ --md-footnotes-icon: svg-load("material/keyboard-return.svg");
+}
+
+// ----------------------------------------------------------------------------
+
+// Scoped in typesetted content to match specificity of regular content
+.md-typeset {
+
+ // Footnote container
+ .footnote {
+ color: var(--md-default-fg-color--light);
+ font-size: px2rem(12.8px);
+
+ // Footnote list - omit left indentation
+ > ol {
+ margin-inline-start: 0;
+
+ // Footnote item - footnote items can contain lists, so we need to scope
+ // the spacing adjustments to the top-level footnote item.
+ > li {
+ transition: color 125ms;
+
+ // Darken color on target
+ &:target {
+ color: var(--md-default-fg-color);
+ }
+
+ // Show backreferences on footnote focus without transition
+ &:focus-within .footnote-backref {
+ transform: translateX(0);
+ opacity: 1;
+ transition: none;
+ }
+
+ // Show backreferences on footnote hover/target
+ &:is(:hover, :target) .footnote-backref {
+ transform: translateX(0);
+ opacity: 1;
+ }
+
+ // Adjust spacing on first child
+ > :first-child {
+ margin-top: 0;
+ }
+ }
+ }
+ }
+
+ // Footnote reference
+ .footnote-ref {
+ font-weight: 700;
+ font-size: px2em(12px, 16px);
+
+ // Hack: increase specificity to override default
+ html & {
+ outline-offset: px2rem(2px);
+ }
+ }
+
+ // Show outline for all devices
+ [id^="fnref:"]:target > .footnote-ref {
+ outline: auto;
+ }
+
+ // Footnote backreference
+ .footnote-backref {
+ display: inline-block;
+ color: var(--md-typeset-a-color);
+ // Hack: omit Unicode arrow for replacement with icon
+ font-size: 0;
+ vertical-align: text-bottom;
+ transform: translateX(px2rem(5px));
+ opacity: 0;
+ transition:
+ color 250ms,
+ transform 250ms 250ms,
+ opacity 125ms 250ms;
+
+ // [print]: Show footnote backreferences
+ @media print {
+ color: var(--md-typeset-a-color);
+ transform: translateX(0);
+ opacity: 1;
+ }
+
+ // Adjust for right-to-left languages
+ [dir="rtl"] & {
+ transform: translateX(px2rem(-5px));
+ }
+
+ // Adjust color on hover
+ &:hover {
+ color: var(--md-accent-fg-color);
+ }
+
+ // Footnote backreference icon
+ &::before {
+ display: inline-block;
+ width: px2rem(16px);
+ height: px2rem(16px);
+ background-color: currentcolor;
+ mask-image: var(--md-footnotes-icon);
+ mask-repeat: no-repeat;
+ mask-size: contain;
+ content: "";
+
+ // Adjust for right-to-left languages
+ [dir="rtl"] & {
+
+ // Flip icon vertically
+ svg {
+ transform: scaleX(-1);
+ }
+ }
+ }
+ }
+}
diff --git a/src/assets/stylesheets/main/extensions/markdown/_toc.scss b/src/assets/stylesheets/main/extensions/markdown/_toc.scss
@@ -0,0 +1,92 @@
+////
+/// Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+///
+/// Permission is hereby granted, free of charge, to any person obtaining a
+/// copy of this software and associated documentation files (the "Software"),
+/// to deal in the Software without restriction, including without limitation
+/// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+/// and/or sell copies of the Software, and to permit persons to whom the
+/// Software is furnished to do so, subject to the following conditions:
+///
+/// The above copyright notice and this permission notice shall be included in
+/// all copies or substantial portions of the Software.
+///
+/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+/// DEALINGS
+////
+
+// ----------------------------------------------------------------------------
+// Rules
+// ----------------------------------------------------------------------------
+
+// Scoped in typesetted content to match specificity of regular content
+.md-typeset {
+
+ // Headerlink
+ .headerlink {
+ display: inline-block;
+ margin-inline-start: px2rem(10px);
+ color: var(--md-default-fg-color--lighter);
+ opacity: 0;
+ transition:
+ color 250ms,
+ opacity 125ms;
+
+ // [print]: Hide headerlinks
+ @media print {
+ display: none;
+ }
+ }
+
+ // Show headerlinks on parent hover
+ :is(:hover, :target) > .headerlink,
+ .headerlink:focus {
+ opacity: 1;
+ transition:
+ color 250ms,
+ opacity 125ms;
+ }
+
+ // Adjust color on parent target or focus/hover
+ :target > .headerlink,
+ .headerlink:is(:focus, :hover) {
+ color: var(--md-accent-fg-color);
+ }
+
+ // Adjust scroll margin for all elements with `id` attributes
+ :target {
+ --md-scroll-margin: #{px2rem(48px + 24px)};
+ --md-scroll-offset: #{px2rem(0px)};
+ // Scroll margin is finally ready for prime time - before, we used a hack
+ // for anchor correction based on pseudo elements but those times are gone.
+ scroll-margin-top:
+ calc(
+ var(--md-scroll-margin) -
+ var(--md-scroll-offset)
+ );
+
+ // [screen +]: Sticky navigation tabs
+ @include break-from-device(screen) {
+
+ // Adjust scroll margin for sticky navigation tabs
+ .md-header--lifted ~ .md-container & {
+ --md-scroll-margin: #{px2rem(96px + 24px)};
+ }
+ }
+ }
+
+ // Adjust scroll offset for headlines of level 1-3
+ :is(h1, h2, h3):target {
+ --md-scroll-offset: #{px2rem(4px)};
+ }
+
+ // Adjust scroll offset for headlines of level 4
+ h4:target {
+ --md-scroll-offset: #{px2rem(3px)};
+ }
+}
diff --git a/src/assets/stylesheets/main/extensions/pymdownx/_arithmatex.scss b/src/assets/stylesheets/main/extensions/pymdownx/_arithmatex.scss
@@ -0,0 +1,52 @@
+////
+/// Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+///
+/// Permission is hereby granted, free of charge, to any person obtaining a
+/// copy of this software and associated documentation files (the "Software"),
+/// to deal in the Software without restriction, including without limitation
+/// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+/// and/or sell copies of the Software, and to permit persons to whom the
+/// Software is furnished to do so, subject to the following conditions:
+///
+/// The above copyright notice and this permission notice shall be included in
+/// all copies or substantial portions of the Software.
+///
+/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+/// DEALINGS
+////
+
+// ----------------------------------------------------------------------------
+// Rules
+// ----------------------------------------------------------------------------
+
+// Scoped in typesetted content to match specificity of regular content
+.md-typeset {
+
+ // Arithmatex container
+ div.arithmatex {
+ overflow: auto;
+
+ // [mobile -]: Align with body copy
+ @include break-to-device(mobile) {
+ margin: 0 px2rem(-16px);
+ }
+
+ // Arithmatex content
+ > * {
+ width: min-content;
+ margin-inline: auto !important; // stylelint-disable-line
+ padding: 0 px2rem(16px);
+ touch-action: auto;
+
+ // MathJax container - see https://bit.ly/3HR8YJ5
+ mjx-container {
+ margin: 0 !important; // stylelint-disable-line
+ }
+ }
+ }
+}
diff --git a/src/assets/stylesheets/main/extensions/pymdownx/_critic.scss b/src/assets/stylesheets/main/extensions/pymdownx/_critic.scss
@@ -0,0 +1,78 @@
+////
+/// Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+///
+/// Permission is hereby granted, free of charge, to any person obtaining a
+/// copy of this software and associated documentation files (the "Software"),
+/// to deal in the Software without restriction, including without limitation
+/// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+/// and/or sell copies of the Software, and to permit persons to whom the
+/// Software is furnished to do so, subject to the following conditions:
+///
+/// The above copyright notice and this permission notice shall be included in
+/// all copies or substantial portions of the Software.
+///
+/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+/// DEALINGS
+////
+
+// ----------------------------------------------------------------------------
+// Rules
+// ----------------------------------------------------------------------------
+
+// Scoped in typesetted content to match specificity of regular content
+.md-typeset {
+
+ // Deletion, addition or comment
+ :is(del, ins, .comment).critic {
+ box-decoration-break: clone;
+ }
+
+ // Deletion
+ del.critic {
+ background-color: var(--md-typeset-del-color);
+ }
+
+ // Addition
+ ins.critic {
+ background-color: var(--md-typeset-ins-color);
+ }
+
+ // Comment
+ .critic.comment {
+ color: var(--md-code-hl-comment-color);
+
+ // Comment opening mark
+ &::before {
+ content: "/* ";
+ }
+
+ // Comment closing mark
+ &::after {
+ content: " */";
+ }
+ }
+
+ // Critic block
+ .critic.block {
+ display: block;
+ margin: 1em 0;
+ padding-inline: px2rem(16px);
+ overflow: auto;
+ box-shadow: none;
+
+ // Adjust spacing on first child
+ > :first-child {
+ margin-top: 0.5em;
+ }
+
+ // Adjust spacing on last child
+ > :last-child {
+ margin-bottom: 0.5em;
+ }
+ }
+}
diff --git a/src/assets/stylesheets/main/extensions/pymdownx/_details.scss b/src/assets/stylesheets/main/extensions/pymdownx/_details.scss
@@ -0,0 +1,116 @@
+////
+/// Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+///
+/// Permission is hereby granted, free of charge, to any person obtaining a
+/// copy of this software and associated documentation files (the "Software"),
+/// to deal in the Software without restriction, including without limitation
+/// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+/// and/or sell copies of the Software, and to permit persons to whom the
+/// Software is furnished to do so, subject to the following conditions:
+///
+/// The above copyright notice and this permission notice shall be included in
+/// all copies or substantial portions of the Software.
+///
+/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+/// DEALINGS
+////
+
+// ----------------------------------------------------------------------------
+// Rules
+// ----------------------------------------------------------------------------
+
+// Details variables
+:root {
+ --md-details-icon: svg-load("material/chevron-right.svg");
+}
+
+// ----------------------------------------------------------------------------
+
+// Scoped in typesetted content to match specificity of regular content
+.md-typeset {
+
+ // Details
+ details {
+ display: flow-root;
+ padding-top: 0;
+ overflow: visible;
+
+ // Details title icon - rotate icon on transition to open state
+ &[open] > summary::after {
+ transform: rotate(90deg);
+ }
+
+ // Adjust spacing for details in closed state
+ &:not([open]) {
+ padding-bottom: 0;
+ box-shadow: none;
+
+ // Hack: we cannot set `overflow: hidden` on the `details` element (which
+ // is why we set it to `overflow: visible`, as the outline would not be
+ // visible when focusing. Therefore, we must set the border radius on the
+ // summary explicitly.
+ > summary {
+ border-radius: px2rem(2px);
+ }
+ }
+ }
+
+ // Details title
+ summary {
+ display: block;
+ min-height: px2rem(20px);
+ padding-inline-end: px2rem(36px);
+ border-start-start-radius: px2rem(2px);
+ border-start-end-radius: px2rem(2px);
+ cursor: pointer;
+
+ // Show outline for keyboard devices
+ &.focus-visible {
+ outline-color: var(--md-accent-fg-color);
+ outline-offset: px2rem(4px);
+ }
+
+ // Hide outline for pointer devices
+ &:not(.focus-visible) {
+ outline: none;
+ -webkit-tap-highlight-color: transparent;
+ }
+
+ // Details marker
+ &::after {
+ position: absolute;
+ top: px2em(10px);
+ inset-inline-end: px2rem(8px);
+ width: px2rem(20px);
+ height: px2rem(20px);
+ background-color: currentcolor;
+ mask-image: var(--md-details-icon);
+ mask-repeat: no-repeat;
+ mask-size: contain;
+ transform: rotate(0deg);
+ transition: transform 250ms;
+ content: "";
+
+ // Adjust for right-to-left languages
+ [dir="rtl"] & {
+ transform: rotate(180deg);
+ }
+ }
+
+ // Hide native details marker - modern
+ &::marker {
+ display: none;
+ }
+
+ // Hide native details marker - legacy, must be split into a seprate rule,
+ // so older browsers don't consider the selector list as invalid
+ &::-webkit-details-marker {
+ display: none;
+ }
+ }
+}
diff --git a/src/assets/stylesheets/main/extensions/pymdownx/_emoji.scss b/src/assets/stylesheets/main/extensions/pymdownx/_emoji.scss
@@ -0,0 +1,43 @@
+////
+/// Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+///
+/// Permission is hereby granted, free of charge, to any person obtaining a
+/// copy of this software and associated documentation files (the "Software"),
+/// to deal in the Software without restriction, including without limitation
+/// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+/// and/or sell copies of the Software, and to permit persons to whom the
+/// Software is furnished to do so, subject to the following conditions:
+///
+/// The above copyright notice and this permission notice shall be included in
+/// all copies or substantial portions of the Software.
+///
+/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+/// DEALINGS
+////
+
+// ----------------------------------------------------------------------------
+// Rules
+// ----------------------------------------------------------------------------
+
+// Scoped in typesetted content to match specificity of regular content
+.md-typeset {
+
+ // Emoji and icon container
+ :is(.emojione, .twemoji, .gemoji) {
+ display: inline-flex;
+ height: px2em(18px);
+ vertical-align: text-top;
+
+ // Icon - inlined via mkdocs-material-extensions
+ svg {
+ width: px2em(18px);
+ max-height: 100%;
+ fill: currentcolor;
+ }
+ }
+}
diff --git a/src/assets/stylesheets/main/extensions/pymdownx/_highlight.scss b/src/assets/stylesheets/main/extensions/pymdownx/_highlight.scss
@@ -0,0 +1,381 @@
+////
+/// Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+///
+/// Permission is hereby granted, free of charge, to any person obtaining a
+/// copy of this software and associated documentation files (the "Software"),
+/// to deal in the Software without restriction, including without limitation
+/// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+/// and/or sell copies of the Software, and to permit persons to whom the
+/// Software is furnished to do so, subject to the following conditions:
+///
+/// The above copyright notice and this permission notice shall be included in
+/// all copies or substantial portions of the Software.
+///
+/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+/// DEALINGS
+////
+
+// ----------------------------------------------------------------------------
+// Rules: syntax highlighting
+// ----------------------------------------------------------------------------
+
+// Code block
+.highlight {
+
+ // .o = Operator
+ // .ow = Operator, word
+ :is(.o, .ow) {
+ color: var(--md-code-hl-operator-color);
+ }
+
+ .p { // Punctuation
+ color: var(--md-code-hl-punctuation-color);
+ }
+
+ // .cpf = Comment, preprocessor file
+ // .l = Literal
+ // .s = Literal, string
+ // .sb = Literal, string backticks
+ // .sc = Literal, string char
+ // .s2 = Literal, string double
+ // .si = Literal, string interpol
+ // .s1 = Literal, string single
+ // .ss = Literal, string symbol
+ :is(.cpf, .l, .s, .sb, .sc, .s2, .si, .s1, .ss) {
+ color: var(--md-code-hl-string-color);
+ }
+
+ // .cp = Comment, pre-processor
+ // .se = Literal, string escape
+ // .sh = Literal, string heredoc
+ // .sr = Literal, string regex
+ // .sx = Literal, string other
+ :is(.cp, .se, .sh, .sr, .sx) {
+ color: var(--md-code-hl-special-color);
+ }
+
+ // .m = Number
+ // .mb = Number, binary
+ // .mf = Number, float
+ // .mh = Number, hex
+ // .mi = Number, integer
+ // .il = Number, integer long
+ // .mo = Number, octal
+ :is(.m, .mb, .mf, .mh, .mi, .il, .mo) {
+ color: var(--md-code-hl-number-color);
+ }
+
+ // .k = Keyword,
+ // .kd = Keyword, declaration
+ // .kn = Keyword, namespace
+ // .kp = Keyword, pseudo
+ // .kr = Keyword, reserved
+ // .kt = Keyword, type
+ :is(.k, .kd, .kn, .kp, .kr, .kt) {
+ color: var(--md-code-hl-keyword-color);
+ }
+
+ // .kc = Keyword, constant
+ // .n = Name
+ :is(.kc, .n) {
+ color: var(--md-code-hl-name-color);
+ }
+
+ // .no = Name, constant
+ // .nb = Name, builtin
+ // .bp = Name, builtin pseudo
+ :is(.no, .nb, .bp) {
+ color: var(--md-code-hl-constant-color);
+ }
+
+ // .nc = Name, class
+ // .ne = Name, exception
+ // .nf = Name, function
+ // .nn = Name, namespace
+ :is(.nc, .ne, .nf, .nn) {
+ color: var(--md-code-hl-function-color);
+ }
+
+ // .nd = Name, decorator
+ // .ni = Name, entity
+ // .nl = Name, label
+ // .nt = Name, tag
+ :is(.nd, .ni, .nl, .nt) {
+ color: var(--md-code-hl-keyword-color);
+ }
+
+ // .c = Comment
+ // .cm = Comment, multiline
+ // .c1 = Comment, single
+ // .ch = Comment, shebang
+ // .cs = Comment, special
+ // .sd = Literal, string doc
+ :is(.c, .cm, .c1, .ch, .cs, .sd) {
+ color: var(--md-code-hl-comment-color);
+ }
+
+ // .na = Name, attribute
+ // .nv = Variable,
+ // .vc = Variable, class
+ // .vg = Variable, global
+ // .vi = Variable, instance
+ :is(.na, .nv, .vc, .vg, .vi) {
+ color: var(--md-code-hl-variable-color);
+ }
+
+ // .ge = Generic, emph
+ // .gr = Generic, error
+ // .gh = Generic, heading
+ // .go = Generic, output
+ // .gp = Generic, prompt
+ // .gs = Generic, strong
+ // .gu = Generic, subheading
+ // .gt = Generic, traceback
+ :is(.ge, .gr, .gh, .go, .gp, .gs, .gu, .gt) {
+ color: var(--md-code-hl-generic-color);
+ }
+
+ // .gd = Diff, delete
+ // .gi = Diff, insert
+ :is(.gd, .gi) {
+ margin: 0 px2em(-2px);
+ padding: 0 px2em(2px);
+ border-radius: px2rem(2px);
+ }
+
+ .gd { // Diff, delete
+ background-color: var(--md-typeset-del-color);
+ }
+
+ .gi { // Diff, insert
+ background-color: var(--md-typeset-ins-color);
+ }
+
+ // Highlighted line
+ .hll {
+ display: block;
+ margin: 0 px2em(-16px, 13.6px);
+ padding: 0 px2em(16px, 13.6px);
+ background-color: var(--md-code-hl-color);
+ }
+
+ // Code block title
+ span.filename {
+ position: relative;
+ display: flow-root;
+ margin-top: 1em;
+ padding: px2em(9px, 13.6px) px2em(16px, 13.6px);
+ font-weight: 700;
+ font-size: px2em(13.6px);
+ background-color: var(--md-code-bg-color);
+ border-bottom: px2rem(1px) solid var(--md-default-fg-color--lightest);
+ border-top-left-radius: px2rem(2px);
+ border-top-right-radius: px2rem(2px);
+
+ // Adjust spacing for code block
+ + pre {
+ margin-top: 0;
+
+ // Remove rounded border on top side
+ > code {
+ border-top-left-radius: 0;
+ border-top-right-radius: 0;
+ }
+ }
+ }
+
+ // Code block line numbers (pymdownx-inline)
+ [data-linenos]::before {
+ position: sticky;
+ left: px2em(-16px, 13.6px);
+ // A `z-index` of 3 is necessary for ensuring that code block annotations
+ // don't overlay line numbers, as active annotations have a `z-index` of 2.
+ z-index: 3;
+ float: left;
+ margin-right: px2em(16px, 13.6px);
+ margin-left: px2em(-16px, 13.6px);
+ padding-left: px2em(16px, 13.6px);
+ color: var(--md-default-fg-color--light);
+ background-color: var(--md-code-bg-color);
+ box-shadow: px2rem(-1px) 0 var(--md-default-fg-color--lightest) inset;
+ content: attr(data-linenos);
+ user-select: none;
+ }
+
+ // Code block line anchors - Chrome and Safari seem to have a strange bug
+ // where scroll margin is not applied to anchors inside code blocks. Setting
+ // positioning to absolute seems to fix the problem. Interestingly, this does
+ // not happen in Firefox. Furthermore we must set `visibility: hidden` or
+ // the copy to clipboard functionality will include an empty line between
+ // each set of lines.
+ code a[id] {
+ position: absolute;
+ visibility: hidden;
+ }
+
+ // Copying in progress - this class is set before the content is copied and
+ // removed after copying is done to mitigate whitespace-related issues.
+ code[data-md-copying] {
+
+ // Temporarily remove highlighted lines - see https://bit.ly/32iVGWh
+ .hll {
+ display: contents;
+ }
+
+ // Temporarily remove annotations
+ .md-annotation {
+ display: none;
+ }
+ }
+}
+
+// ----------------------------------------------------------------------------
+// Rules: layout
+// ----------------------------------------------------------------------------
+
+// Code block with line numbers
+.highlighttable {
+ display: flow-root;
+
+ // Set table elements to block layout, because otherwise the whole flexbox
+ // hacking won't work correctly
+ :is(tbody, td) {
+ display: block;
+ padding: 0;
+ }
+
+ // We need to use flexbox layout, because otherwise it's not possible to
+ // make the code container scroll while keeping the line numbers static
+ tr {
+ display: flex;
+ }
+
+ // The pre tags are nested inside a table, so we need to omit the margin
+ // because it collapses below all the overflows
+ pre {
+ margin: 0;
+ }
+
+ // Code block title container
+ th.filename {
+ flex-grow: 1;
+ padding: 0;
+ text-align: left;
+
+ // Adjust spacing
+ span.filename {
+ margin-top: 0;
+ }
+ }
+
+ // Code block line numbers - disable user selection, so code can be easily
+ // copied without accidentally also copying the line numbers
+ .linenos {
+ padding: px2em(10.5px, 13.6px) px2em(16px, 13.6px);
+ padding-right: 0;
+ font-size: px2em(13.6px);
+ background-color: var(--md-code-bg-color);
+ border-top-left-radius: px2rem(2px);
+ border-bottom-left-radius: px2rem(2px);
+ user-select: none;
+ }
+
+ // Code block line numbers container
+ .linenodiv {
+ padding-right: px2em(8px, 13.6px);
+ box-shadow: px2rem(-1px) 0 var(--md-default-fg-color--lightest) inset;
+
+ // Adjust colors and alignment
+ pre {
+ color: var(--md-default-fg-color--light);
+ text-align: right;
+ }
+ }
+
+ // Code block container - stretch to remaining space
+ .code {
+ flex: 1;
+ min-width: 0;
+ }
+}
+
+// Code block line numbers container
+.linenodiv a {
+ color: inherit;
+}
+
+// ----------------------------------------------------------------------------
+
+// Scoped in typesetted content to match specificity of regular content
+.md-typeset {
+
+ // Code block with line numbers - unfortunately, these selectors need to be
+ // overly specific so they don't bleed into code blocks in annotations.
+ .highlighttable {
+ margin: 1em 0;
+ direction: ltr;
+
+ // Remove rounded borders on code blocks
+ > tbody > tr > .code > div > pre > code {
+ border-top-left-radius: 0;
+ border-bottom-left-radius: 0;
+ }
+ }
+
+ // Code block result container
+ .highlight + .result {
+ margin-top: calc(-1em + #{px2em(-2px)});
+ padding: 0 px2em(16px);
+ overflow: visible;
+ border: px2rem(1px) solid var(--md-code-bg-color);
+ border-top-width: px2rem(2px);
+ border-bottom-right-radius: px2rem(2px);
+ border-bottom-left-radius: px2rem(2px);
+
+ // Clearfix, because we can't use overflow: auto
+ &::after {
+ display: block;
+ clear: both;
+ content: "";
+ }
+ }
+}
+
+// ----------------------------------------------------------------------------
+// Rules: top-level
+// ----------------------------------------------------------------------------
+
+// [mobile -]: Align with body copy
+@include break-to-device(mobile) {
+
+ // Top-level code block
+ .md-content__inner > .highlight {
+ margin: 1em px2rem(-16px);
+
+ // Remove rounded borders
+ > .filename,
+ > pre > code {
+ border-radius: 0;
+ }
+
+ // Code block with line numbers - unfortunately, these selectors need to be
+ // overly specific so they don't bleed into code blocks in annotations.
+ > .highlighttable > tbody > tr > .filename span.filename,
+ > .highlighttable > tbody > tr > .linenos,
+ > .highlighttable > tbody > tr > .code > div > pre > code {
+ border-radius: 0;
+ }
+
+ // Code block result container
+ + .result {
+ margin-inline: px2rem(-16px);
+ border-inline-width: 0;
+ border-radius: 0;
+ }
+ }
+}
diff --git a/src/assets/stylesheets/main/extensions/pymdownx/_keys.scss b/src/assets/stylesheets/main/extensions/pymdownx/_keys.scss
@@ -0,0 +1,115 @@
+////
+/// Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+///
+/// Permission is hereby granted, free of charge, to any person obtaining a
+/// copy of this software and associated documentation files (the "Software"),
+/// to deal in the Software without restriction, including without limitation
+/// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+/// and/or sell copies of the Software, and to permit persons to whom the
+/// Software is furnished to do so, subject to the following conditions:
+///
+/// The above copyright notice and this permission notice shall be included in
+/// all copies or substantial portions of the Software.
+///
+/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+/// DEALINGS
+////
+
+// ----------------------------------------------------------------------------
+// Rules
+// ----------------------------------------------------------------------------
+
+// Scoped in typesetted content to match specificity of regular content
+.md-typeset {
+
+ // Keyboard key
+ .keys {
+
+ // Keyboard key icon
+ kbd:is(::before, ::after) {
+ position: relative;
+ margin: 0;
+ color: inherit;
+ -moz-osx-font-smoothing: initial;
+ -webkit-font-smoothing: initial;
+ }
+
+ // Surrounding text
+ span {
+ padding: 0 px2em(3.2px);
+ color: var(--md-default-fg-color--light);
+ }
+
+ // Define keyboard keys with left icon
+ @each $name, $code in (
+
+ // Modifiers
+ "alt": "\2387",
+ "left-alt": "\2387",
+ "right-alt": "\2387",
+ "command": "\2318",
+ "left-command": "\2318",
+ "right-command": "\2318",
+ "control": "\2303",
+ "left-control": "\2303",
+ "right-control": "\2303",
+ "meta": "\25C6",
+ "left-meta": "\25C6",
+ "right-meta": "\25C6",
+ "option": "\2325",
+ "left-option": "\2325",
+ "right-option": "\2325",
+ "shift": "\21E7",
+ "left-shift": "\21E7",
+ "right-shift": "\21E7",
+ "super": "\2756",
+ "left-super": "\2756",
+ "right-super": "\2756",
+ "windows": "\229E",
+ "left-windows": "\229E",
+ "right-windows": "\229E",
+
+ // Other keys
+ "arrow-down": "\2193",
+ "arrow-left": "\2190",
+ "arrow-right": "\2192",
+ "arrow-up": "\2191",
+ "backspace": "\232B",
+ "backtab": "\21E4",
+ "caps-lock": "\21EA",
+ "clear": "\2327",
+ "context-menu": "\2630",
+ "delete": "\2326",
+ "eject": "\23CF",
+ "end": "\2913",
+ "escape": "\238B",
+ "home": "\2912",
+ "insert": "\2380",
+ "page-down": "\21DF",
+ "page-up": "\21DE",
+ "print-screen": "\2399"
+ ) {
+ .key-#{$name}::before {
+ padding-right: px2em(6.4px);
+ content: $code;
+ }
+ }
+
+ // Define keyboard keys with right icon
+ @each $name, $code in (
+ "tab": "\21E5",
+ "num-enter": "\2324",
+ "enter": "\23CE"
+ ) {
+ .key-#{$name}::after {
+ padding-left: px2em(6.4px);
+ content: $code;
+ }
+ }
+ }
+}
diff --git a/src/assets/stylesheets/main/extensions/pymdownx/_tabbed.scss b/src/assets/stylesheets/main/extensions/pymdownx/_tabbed.scss
@@ -0,0 +1,393 @@
+////
+/// Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+///
+/// Permission is hereby granted, free of charge, to any person obtaining a
+/// copy of this software and associated documentation files (the "Software"),
+/// to deal in the Software without restriction, including without limitation
+/// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+/// and/or sell copies of the Software, and to permit persons to whom the
+/// Software is furnished to do so, subject to the following conditions:
+///
+/// The above copyright notice and this permission notice shall be included in
+/// all copies or substantial portions of the Software.
+///
+/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+/// DEALINGS
+////
+
+// ----------------------------------------------------------------------------
+// Rules
+// ----------------------------------------------------------------------------
+
+// Tabbed variables
+:root {
+ --md-tabbed-icon--prev: svg-load("material/chevron-left.svg");
+ --md-tabbed-icon--next: svg-load("material/chevron-right.svg");
+}
+
+// ----------------------------------------------------------------------------
+
+// Scoped in typesetted content to match specificity of regular content
+.md-typeset {
+
+ // Tabbed container
+ .tabbed-set {
+ position: relative;
+ display: flex;
+ flex-flow: column wrap;
+ margin: 1em 0;
+ border-radius: px2rem(2px);
+
+ // Tab radio button - the Tabbed extension will generate radio buttons with
+ // labels, so tabs can be triggered without the necessity for JavaScript.
+ // This is pretty cool, as it has great accessibility out-of-the box, so
+ // we just hide the radio button and toggle the label color for indication.
+ > input {
+ position: absolute;
+ width: 0;
+ height: 0;
+ opacity: 0;
+
+ // Adjust scroll margin
+ &:target {
+ --md-scroll-offset: #{px2em(10px, 16px)};
+ }
+
+ // Tab label states
+ @for $i from 20 through 1 {
+ &:nth-child(#{$i}) {
+
+ // Tab is active
+ &:checked {
+
+ // Tab label
+ ~ .tabbed-labels > :nth-child(#{$i}) {
+ @extend %tabbed-label;
+ }
+
+ // Tab content
+ ~ .tabbed-content > :nth-child(#{$i}) {
+ @extend %tabbed-content;
+ }
+ }
+
+ // Tab label on keyboard focus
+ &.focus-visible ~ .tabbed-labels > :nth-child(#{$i}) {
+ @extend %tabbed-label-focus-visible;
+ }
+ }
+ }
+ }
+ }
+
+ // Tabbed labels
+ .tabbed-labels {
+ display: flex;
+ max-width: 100%;
+ overflow: auto;
+ box-shadow: 0 px2rem(-1px) var(--md-default-fg-color--lightest) inset;
+ -ms-overflow-style: none; // IE, Edge
+ scrollbar-width: none; // Firefox
+
+ // [print]: Move one layer up for ordering
+ @media print {
+ display: contents;
+ }
+
+ // [screen and no reduced motion]: Disable animation
+ @media screen {
+
+ // [js]: Show animated tab indicator
+ .js & {
+ position: relative;
+
+ // Tab indicator
+ &::before {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ display: block;
+ width: var(--md-indicator-width);
+ height: 2px;
+ background: var(--md-accent-fg-color);
+ transform: translateX(var(--md-indicator-x));
+ transition:
+ width 225ms,
+ transform 250ms;
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+ content: "";
+ }
+ }
+ }
+
+ // Webkit scrollbar
+ &::-webkit-scrollbar {
+ display: none; // Chrome, Safari
+ }
+
+ // Tab label
+ > label {
+ flex-shrink: 0;
+ width: auto;
+ padding: px2em(10px, 12.8px) 1.25em px2em(8px, 12.8px);
+ color: var(--md-default-fg-color--light);
+ font-weight: 700;
+ font-size: px2rem(12.8px);
+ white-space: nowrap;
+ border-bottom: px2rem(2px) solid transparent;
+ border-radius: px2rem(2px) px2rem(2px) 0 0;
+ cursor: pointer;
+ transition:
+ background-color 250ms,
+ color 250ms;
+ scroll-margin-inline-start: px2rem(20px);
+
+ // [print]: Intersperse labels with containers
+ @media print {
+
+ // Ensure correct order of labels
+ @for $i from 1 through 20 {
+ &:nth-child(#{$i}) {
+ order: $i;
+ }
+ }
+ }
+
+ // Tab label on hover
+ &:hover {
+ color: var(--md-accent-fg-color);
+ }
+ }
+ }
+
+ // Tabbed content
+ .tabbed-content {
+ width: 100%;
+
+ // [print]: Move one layer up for ordering
+ @media print {
+ display: contents;
+ }
+ }
+
+ // Tabbed block
+ .tabbed-block {
+ display: none;
+
+ // [print]: Intersperse labels with containers
+ @media print {
+ display: block;
+
+ // Ensure correct order of containers
+ @for $i from 1 through 20 {
+ &:nth-child(#{$i}) {
+ order: $i;
+ }
+ }
+ }
+
+ // Code block is the first child of a tab - remove margin and mirror
+ // previous (now deprecated) SuperFences code block grouping behavior
+ > pre:first-child,
+ > .highlight:first-child > pre {
+ margin: 0;
+
+ // Remove rounded borders on code block
+ > code {
+ border-top-left-radius: 0;
+ border-top-right-radius: 0;
+ }
+ }
+
+ // Code block is the first child of a tab - remove margin and mirror
+ // previous (now deprecated) SuperFences code block grouping behavior
+ > .highlight:first-child {
+
+ // Code block title - remove spacing and rounded borders
+ > .filename {
+ margin: 0;
+ border-top-left-radius: 0;
+ border-top-right-radius: 0;
+ }
+
+ // Code block with line numbers - unfortunately, these selectors need to
+ // be overly specific so they don't bleed into code blocks in annotations.
+ > .highlighttable {
+ margin: 0;
+
+ // Remove rounded borders on line numbers and titles
+ > tbody > tr > .filename span.filename,
+ > tbody > tr > .linenos {
+ margin: 0;
+ border-top-left-radius: 0;
+ border-top-right-radius: 0;
+ }
+
+ // Remove rounded borders on code blocks
+ > tbody > tr > .code > div > pre > code {
+ border-top-left-radius: 0;
+ border-top-right-radius: 0;
+ }
+ }
+
+ // Code block result container - adjust spacing
+ + .result {
+ margin-top: px2em(-2px);
+ }
+ }
+
+ // Adjust spacing for nested tabbed container
+ > .tabbed-set {
+ margin: 0;
+ }
+ }
+
+ // Tabbed button
+ .tabbed-button {
+ display: block;
+ align-self: center;
+ width: px2rem(18px);
+ height: px2rem(18px);
+ margin-top: px2rem(2px);
+ color: var(--md-default-fg-color--light);
+ border-radius: 100%;
+ cursor: pointer;
+ transition: background-color 250ms;
+ pointer-events: initial;
+
+ // Tabbed button on hover
+ &:hover {
+ color: var(--md-accent-fg-color);
+ background-color: var(--md-accent-fg-color--transparent);
+ }
+
+ // Tabbed button icon
+ &::after {
+ display: block;
+ width: 100%;
+ height: 100%;
+ background-color: currentcolor;
+ transition:
+ background-color 250ms,
+ transform 250ms;
+ mask-image: var(--md-tabbed-icon--prev);
+ mask-repeat: no-repeat;
+ mask-size: contain;
+ content: "";
+ }
+ }
+
+ // Tabbed control
+ .tabbed-control {
+ position: absolute;
+ display: flex;
+ justify-content: start;
+ width: px2rem(24px);
+ height: px2rem(38px);
+ background:
+ linear-gradient(
+ to right,
+ var(--md-default-bg-color) 60%,
+ transparent
+ );
+ transition: opacity 125ms;
+ pointer-events: none;
+
+ // Adjust for right-to-left languages
+ [dir="rtl"] & {
+ transform: rotate(180deg);
+ }
+
+ // Tabbed control is hidden
+ &[hidden] {
+ opacity: 0;
+ }
+
+ // Tabbed control next
+ &--next {
+ right: 0;
+ justify-content: end;
+ background:
+ linear-gradient(
+ to left,
+ var(--md-default-bg-color) 60%,
+ transparent
+ );
+
+ // Tabbed button icon content
+ .tabbed-button::after {
+ mask-image: var(--md-tabbed-icon--next);
+ }
+ }
+ }
+}
+
+// ----------------------------------------------------------------------------
+// Rules: top-level
+// ----------------------------------------------------------------------------
+
+// [mobile -]: Align with body copy
+@include break-to-device(mobile) {
+
+ // Top-level tabbed labels
+ .md-content__inner > .tabbed-set .tabbed-labels {
+ max-width: 100vw;
+ margin: 0 px2rem(-16px);
+ padding-inline-start: px2rem(16px);
+ scroll-padding-inline-start: px2rem(16px);
+
+ // Hack: some browsers ignore the right padding on flex containers,
+ // see https://bit.ly/3lsPS3S
+ &::after {
+ padding-inline-end: px2rem(16px);
+ content: "";
+ }
+
+ // Tabbed control previous
+ ~ .tabbed-control--prev {
+ width: px2rem(40px);
+ margin-inline-start: px2rem(-16px);
+ padding-inline-start: px2rem(16px);
+ }
+
+ // Tabbed control next
+ ~ .tabbed-control--next {
+ width: px2rem(40px);
+ margin-inline-end: px2rem(-16px);
+ padding-inline-end: px2rem(16px);
+ }
+ }
+}
+
+// ----------------------------------------------------------------------------
+// Placeholders: improve colocation for better compression
+// ----------------------------------------------------------------------------
+
+// Tab label placeholder
+%tabbed-label {
+
+ // [screen]: Show active state
+ @media screen {
+ color: var(--md-accent-fg-color);
+
+ // [no-js]: Show border (indicator is animated with JavaScript)
+ .no-js & {
+ border-color: var(--md-accent-fg-color);
+ }
+ }
+}
+
+// Tab label on keyboard focus placeholder
+%tabbed-label-focus-visible {
+ background-color: var(--md-accent-fg-color--transparent);
+}
+
+// Tab content placeholder
+%tabbed-content {
+ display: block;
+}
diff --git a/src/assets/stylesheets/main/extensions/pymdownx/_tasklist.scss b/src/assets/stylesheets/main/extensions/pymdownx/_tasklist.scss
@@ -0,0 +1,79 @@
+////
+/// Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+///
+/// Permission is hereby granted, free of charge, to any person obtaining a
+/// copy of this software and associated documentation files (the "Software"),
+/// to deal in the Software without restriction, including without limitation
+/// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+/// and/or sell copies of the Software, and to permit persons to whom the
+/// Software is furnished to do so, subject to the following conditions:
+///
+/// The above copyright notice and this permission notice shall be included in
+/// all copies or substantial portions of the Software.
+///
+/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+/// DEALINGS
+////
+
+// ----------------------------------------------------------------------------
+// Rules
+// ----------------------------------------------------------------------------
+
+// Tasklist variables
+:root {
+ --md-tasklist-icon:
+ svg-load("octicons/check-circle-fill-24.svg");
+ --md-tasklist-icon--checked:
+ svg-load("octicons/check-circle-fill-24.svg");
+}
+
+// ----------------------------------------------------------------------------
+
+// Scoped in typesetted content to match specificity of regular content
+.md-typeset {
+
+ // Tasklist item
+ .task-list-item {
+ position: relative;
+ list-style-type: none;
+
+ // Make checkbox items align with normal list items, but position
+ // everything in ems for correct layout at smaller font sizes
+ [type="checkbox"] {
+ position: absolute;
+ top: 0.45em;
+ inset-inline-start: -2em;
+ }
+ }
+
+ // Hide native checkbox, when custom classes are enabled
+ .task-list-control [type="checkbox"] {
+ z-index: -1;
+ opacity: 0;
+ }
+
+ // Tasklist indicator in unchecked state
+ .task-list-indicator::before {
+ position: absolute;
+ top: 0.15em;
+ inset-inline-start: px2em(-24px);
+ width: px2em(20px);
+ height: px2em(20px);
+ background-color: var(--md-default-fg-color--lightest);
+ mask-image: var(--md-tasklist-icon);
+ mask-repeat: no-repeat;
+ mask-size: contain;
+ content: "";
+ }
+
+ // Tasklist indicator in checked state
+ [type="checkbox"]:checked + .task-list-indicator::before {
+ background-color: $clr-green-a400;
+ mask-image: var(--md-tasklist-icon--checked);
+ }
+}
diff --git a/src/assets/stylesheets/main/integrations/_mermaid.scss b/src/assets/stylesheets/main/integrations/_mermaid.scss
@@ -0,0 +1,43 @@
+////
+/// Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+///
+/// Permission is hereby granted, free of charge, to any person obtaining a
+/// copy of this software and associated documentation files (the "Software"),
+/// to deal in the Software without restriction, including without limitation
+/// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+/// and/or sell copies of the Software, and to permit persons to whom the
+/// Software is furnished to do so, subject to the following conditions:
+///
+/// The above copyright notice and this permission notice shall be included in
+/// all copies or substantial portions of the Software.
+///
+/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+/// DEALINGS
+////
+
+// ----------------------------------------------------------------------------
+// Rules
+// ----------------------------------------------------------------------------
+
+// All definitions
+:root > * {
+ --md-mermaid-font-family: var(--md-text-font-family), sans-serif;
+
+ // Colors
+ --md-mermaid-edge-color: var(--md-code-fg-color);
+ --md-mermaid-node-bg-color: var(--md-accent-fg-color--transparent);
+ --md-mermaid-node-fg-color: var(--md-accent-fg-color);
+ --md-mermaid-label-bg-color: var(--md-default-bg-color);
+ --md-mermaid-label-fg-color: var(--md-code-fg-color);
+}
+
+// Mermaid container
+.mermaid {
+ margin: 1em 0;
+ line-height: normal;
+}
diff --git a/src/assets/stylesheets/main/layout/_banner.scss b/src/assets/stylesheets/main/layout/_banner.scss
@@ -0,0 +1,63 @@
+////
+/// Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+///
+/// Permission is hereby granted, free of charge, to any person obtaining a
+/// copy of this software and associated documentation files (the "Software"),
+/// to deal in the Software without restriction, including without limitation
+/// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+/// and/or sell copies of the Software, and to permit persons to whom the
+/// Software is furnished to do so, subject to the following conditions:
+///
+/// The above copyright notice and this permission notice shall be included in
+/// all copies or substantial portions of the Software.
+///
+/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+/// DEALINGS
+////
+
+// ----------------------------------------------------------------------------
+// Rules
+// ----------------------------------------------------------------------------
+
+// Banner for announcements and warnings
+.md-banner {
+ overflow: auto;
+ color: var(--md-footer-fg-color);
+ background-color: var(--md-footer-bg-color);
+
+ // [print]: Hide banner
+ @media print {
+ display: none;
+ }
+
+ // Banner with warning
+ &--warning {
+ color: var(--md-default-fg-color);
+ background: var(--md-typeset-mark-color);
+ }
+
+ // Banner wrapper
+ &__inner {
+ margin: px2rem(12px) auto;
+ padding: 0 px2rem(16px);
+ font-size: px2rem(14px);
+ }
+
+ // Banner button
+ &__button {
+ float: right;
+ color: inherit;
+ cursor: pointer;
+ transition: opacity 250ms;
+
+ // Button on hover
+ &:hover {
+ opacity: 0.7;
+ }
+ }
+}
diff --git a/src/assets/stylesheets/main/layout/_base.scss b/src/assets/stylesheets/main/layout/_base.scss
@@ -0,0 +1,183 @@
+////
+/// Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+///
+/// Permission is hereby granted, free of charge, to any person obtaining a
+/// copy of this software and associated documentation files (the "Software"),
+/// to deal in the Software without restriction, including without limitation
+/// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+/// and/or sell copies of the Software, and to permit persons to whom the
+/// Software is furnished to do so, subject to the following conditions:
+///
+/// The above copyright notice and this permission notice shall be included in
+/// all copies or substantial portions of the Software.
+///
+/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+/// DEALINGS
+////
+
+// ----------------------------------------------------------------------------
+// Rules: base grid and containers
+// ----------------------------------------------------------------------------
+
+// Stretch container to viewport and set base `font-size`
+html {
+ height: 100%;
+ overflow-x: hidden;
+ // Hack: normally, we would set the base `font-size` to `62.5%`, so we can
+ // base all calculations on `10px`, but Chromium and Chrome define a minimal
+ // `font-size` of `12px` if the system language is set to Chinese. For this
+ // reason we just double the `font-size` and set it to `20px`.
+ //
+ // See https://github.com/squidfunk/mkdocs-material/issues/911
+ font-size: 125%;
+
+ // [screen medium +]: Set base `font-size` to `11px`
+ @include break-from-device(screen medium) {
+ font-size: 137.5%;
+ }
+
+ // [screen large +]: Set base `font-size` to `12px`
+ @include break-from-device(screen large) {
+ font-size: 150%;
+ }
+}
+
+// Stretch body to container - flexbox is used, so the footer will always be
+// aligned to the bottom of the viewport
+body {
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ width: 100%;
+ min-height: 100%;
+ // Hack: reset `font-size` to `10px`, so the spacing for all inline elements
+ // is correct again. Otherwise the spacing would be based on `20px`.
+ font-size: px2rem(10px);
+ background-color: var(--md-default-bg-color);
+
+ // [print]: Omit flexbox layout due to a Firefox bug (https://mzl.la/39DgR3m)
+ @media print {
+ display: block;
+ }
+
+ // Body in locked state
+ &[data-md-scrolllock] {
+
+ // [tablet portrait -]: Omit scroll bubbling
+ @include break-to-device(tablet portrait) {
+ position: fixed;
+ }
+ }
+}
+
+// ----------------------------------------------------------------------------
+
+// Grid container - this class is applied to wrapper elements within the
+// header, content area and footer, and makes sure that their width is limited
+// to `1220px`, and they are rendered centered if the screen is larger.
+.md-grid {
+ max-width: px2rem(1220px);
+ margin-inline: auto;
+}
+
+// Main container
+.md-container {
+ display: flex;
+ flex-direction: column;
+ flex-grow: 1;
+
+ // [print]: Omit flexbox layout due to a Firefox bug (https://mzl.la/39DgR3m)
+ @media print {
+ display: block;
+ }
+}
+
+// Main area - stretch to remaining space of container
+.md-main {
+ flex-grow: 1;
+
+ // Main area wrapper
+ &__inner {
+ display: flex;
+ height: 100%;
+ margin-top: px2rem(24px + 6px);
+ }
+}
+
+// Add ellipsis in case of overflowing text
+.md-ellipsis {
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+}
+
+// ----------------------------------------------------------------------------
+// Rules: navigational elements
+// ----------------------------------------------------------------------------
+
+// Toggle - this class is applied to checkbox elements, which are used to
+// implement the CSS-only drawer and navigation, as well as the search
+.md-toggle {
+ display: none;
+}
+
+// Option - this class is applied to radio elements, which are used to
+// implement the color palette toggle
+.md-option {
+ position: absolute;
+ width: 0;
+ height: 0;
+ opacity: 0;
+
+ // Option label for checked radio button
+ &:checked + label:not([hidden]) {
+ display: block;
+ }
+
+ // Show outline for keyboard devices
+ &.focus-visible + label {
+ outline-style: auto;
+ outline-color: var(--md-accent-fg-color);
+ }
+}
+
+// Skip link
+.md-skip {
+ position: fixed;
+ // Hack: if we don't set the negative `z-index`, the skip link will force the
+ // creation of new layers when code blocks are near the header on scrolling
+ z-index: -1;
+ margin: px2rem(10px);
+ padding: px2rem(6px) px2rem(10px);
+ color: var(--md-default-bg-color);
+ font-size: px2rem(12.8px);
+ background-color: var(--md-default-fg-color);
+ border-radius: px2rem(2px);
+ outline-color: var(--md-accent-fg-color);
+ transform: translateY(px2rem(8px));
+ opacity: 0;
+
+ // Show skip link on focus
+ &:focus {
+ z-index: 10;
+ transform: translateY(0);
+ opacity: 1;
+ transition:
+ transform 250ms cubic-bezier(0.4, 0, 0.2, 1),
+ opacity 175ms 75ms;
+ }
+}
+
+// ----------------------------------------------------------------------------
+// Rules: print styles
+// ----------------------------------------------------------------------------
+
+// Add margins to page
+@page {
+ margin: 25mm;
+}
diff --git a/src/assets/stylesheets/main/layout/_clipboard.scss b/src/assets/stylesheets/main/layout/_clipboard.scss
@@ -0,0 +1,101 @@
+////
+/// Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+///
+/// Permission is hereby granted, free of charge, to any person obtaining a
+/// copy of this software and associated documentation files (the "Software"),
+/// to deal in the Software without restriction, including without limitation
+/// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+/// and/or sell copies of the Software, and to permit persons to whom the
+/// Software is furnished to do so, subject to the following conditions:
+///
+/// The above copyright notice and this permission notice shall be included in
+/// all copies or substantial portions of the Software.
+///
+/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+/// DEALINGS
+////
+
+// ----------------------------------------------------------------------------
+// Rules
+// ----------------------------------------------------------------------------
+
+// Clipboard button variables
+:root {
+ --md-clipboard-icon: svg-load("material/content-copy.svg");
+}
+
+// ----------------------------------------------------------------------------
+
+// Clipboard button
+.md-clipboard {
+ position: absolute;
+ top: px2em(8px);
+ right: px2em(8px);
+ z-index: 1;
+ width: px2em(24px);
+ height: px2em(24px);
+ color: var(--md-default-fg-color--lightest);
+ border-radius: px2rem(2px);
+ outline-color: var(--md-accent-fg-color);
+ outline-offset: px2rem(2px);
+ cursor: pointer;
+ transition: color 250ms;
+
+ // [print]: Hide button
+ @media print {
+ display: none;
+ }
+
+ // Hide outline for pointer devices
+ &:not(.focus-visible) {
+ outline: none;
+ -webkit-tap-highlight-color: transparent;
+ }
+
+ // Darken color on code block hover
+ :hover > & {
+ color: var(--md-default-fg-color--light);
+ }
+
+ // Button on focus/hover
+ &:is(:focus, :hover) {
+ color: var(--md-accent-fg-color);
+ }
+
+ // Button icon - the width and height are defined in `em`, so the size is
+ // automatically adjusted for nested code blocks (e.g. in admonitions)
+ &::after {
+ display: block;
+ width: px2em(18px);
+ height: px2em(18px);
+ margin: 0 auto;
+ background-color: currentcolor;
+ mask-image: var(--md-clipboard-icon);
+ mask-repeat: no-repeat;
+ mask-size: contain;
+ content: "";
+ }
+
+ // Inline clipboard button
+ &--inline {
+ cursor: pointer;
+
+ // Code block
+ code {
+ transition:
+ color 250ms,
+ background-color 250ms;
+ }
+
+ // Code block on focus/hover
+ &:is(:focus, :hover) code {
+ color: var(--md-accent-fg-color);
+ background-color: var(--md-accent-fg-color--transparent);
+ }
+ }
+}
diff --git a/src/assets/stylesheets/main/layout/_consent.scss b/src/assets/stylesheets/main/layout/_consent.scss
@@ -0,0 +1,127 @@
+////
+/// Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+///
+/// Permission is hereby granted, free of charge, to any person obtaining a
+/// copy of this software and associated documentation files (the "Software"),
+/// to deal in the Software without restriction, including without limitation
+/// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+/// and/or sell copies of the Software, and to permit persons to whom the
+/// Software is furnished to do so, subject to the following conditions:
+///
+/// The above copyright notice and this permission notice shall be included in
+/// all copies or substantial portions of the Software.
+///
+/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+/// DEALINGS
+////
+
+// ----------------------------------------------------------------------------
+// Keyframes
+// ----------------------------------------------------------------------------
+
+// Show consent
+@keyframes consent {
+ 0% {
+ transform: translateY(100%);
+ opacity: 0;
+ }
+
+ 100% {
+ transform: translateY(0);
+ opacity: 1;
+ }
+}
+
+// Show consent overlay
+@keyframes overlay {
+ 0% {
+ opacity: 0;
+ }
+
+ 100% {
+ opacity: 1;
+ }
+}
+
+// ----------------------------------------------------------------------------
+// Rules
+// ----------------------------------------------------------------------------
+
+// Consent
+.md-consent {
+
+ // Consent overlay
+ &__overlay {
+ position: fixed;
+ top: 0;
+ z-index: 5;
+ width: 100%;
+ height: 100%;
+ background-color: hsla(0, 0%, 0%, 0.54);
+ opacity: 1;
+ backdrop-filter: blur(px2rem(2px));
+ animation: overlay 250ms both;
+ }
+
+ // Consent wrapper
+ &__inner {
+ position: fixed;
+ bottom: 0;
+ z-index: 5;
+ width: 100%;
+ max-height: 100%;
+ padding: 0;
+ overflow: auto;
+ background-color: var(--md-default-bg-color);
+ border: 0;
+ border-radius: px2rem(2px);
+ box-shadow:
+ 0 0 px2rem(4px) rgba(0, 0, 0, 0.1),
+ 0 px2rem(4px) px2rem(8px) rgba(0, 0, 0, 0.2);
+ animation: consent 500ms cubic-bezier(0.1, 0.7, 0.1, 1) both;
+ }
+
+ // Consent form
+ &__form {
+ padding: px2rem(16px);
+ }
+
+ // Consent settings
+ &__settings {
+ display: none;
+ margin: 1em 0;
+
+ // Show settings
+ input:checked + & {
+ display: block;
+ }
+ }
+
+ // Consent controls
+ &__controls {
+ margin-bottom: px2rem(16px);
+
+ // Consent control button
+ .md-typeset & .md-button {
+ display: inline;
+
+ // [tablet +]: Align buttons horizontally
+ @include break-to-device(mobile) {
+ display: block;
+ width: 100%;
+ margin-top: px2rem(8px);
+ text-align: center;
+ }
+ }
+ }
+
+ // Ensure users realize that labels are clickaböe
+ label {
+ cursor: pointer;
+ }
+}
diff --git a/src/assets/stylesheets/main/layout/_content.scss b/src/assets/stylesheets/main/layout/_content.scss
@@ -0,0 +1,102 @@
+////
+/// Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+///
+/// Permission is hereby granted, free of charge, to any person obtaining a
+/// copy of this software and associated documentation files (the "Software"),
+/// to deal in the Software without restriction, including without limitation
+/// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+/// and/or sell copies of the Software, and to permit persons to whom the
+/// Software is furnished to do so, subject to the following conditions:
+///
+/// The above copyright notice and this permission notice shall be included in
+/// all copies or substantial portions of the Software.
+///
+/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+/// DEALINGS
+////
+
+// ----------------------------------------------------------------------------
+// Rules
+// ----------------------------------------------------------------------------
+
+// Content area
+.md-content {
+ flex-grow: 1;
+ // Hack: we must use `min-width: 0`, so the content area is capped by the
+ // dimensions of its parent. Otherwise, long code blocks might lead to a
+ // wider content area which will overflow. See https://bit.ly/3bP3f8k
+ min-width: 0;
+
+ // Content wrapper
+ &__inner {
+ margin: 0 px2rem(16px) px2rem(24px);
+ padding-top: px2rem(12px);
+
+ // [screen +]: Adjust spacing between content area and sidebars
+ @include break-from-device(screen) {
+
+ // Sidebar with navigation is visible
+ .md-sidebar--primary:not([hidden]) ~ .md-content > & {
+ margin-inline-start: px2rem(24px);
+ }
+
+ // Sidebar with table of contents is visible
+ .md-sidebar--secondary:not([hidden]) ~ .md-content > & {
+ margin-inline-end: px2rem(24px);
+ }
+ }
+
+ // Hack: add pseudo element for spacing, as the overflow of the content
+ // container may not be hidden due to an imminent offset error on targets
+ &::before {
+ display: block;
+ height: px2rem(8px);
+ content: "";
+ }
+
+ // Adjust spacing on last child
+ > :last-child {
+ margin-bottom: 0;
+ }
+ }
+
+ // Button inside of the content area - these buttons are meant for actions on
+ // a document-level, i.e. linking to related source code files, printing etc.
+ &__button {
+ float: right;
+ margin: px2rem(8px) 0;
+ margin-inline-start: px2rem(8px);
+ padding: 0;
+
+ // [print]: Hide buttons
+ @media print {
+ display: none;
+ }
+
+ // Adjust for right-to-left languages
+ [dir="rtl"] & {
+ float: left;
+ }
+
+ // Adjust default link color for icons
+ .md-typeset & {
+ color: var(--md-default-fg-color--lighter);
+ }
+
+ // Align with body copy located next to icon
+ svg {
+ display: inline;
+ vertical-align: top;
+
+ // Adjust for right-to-left languages
+ [dir="rtl"] & {
+ transform: scaleX(-1);
+ }
+ }
+ }
+}
diff --git a/src/assets/stylesheets/main/layout/_dialog.scss b/src/assets/stylesheets/main/layout/_dialog.scss
@@ -0,0 +1,65 @@
+////
+/// Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+///
+/// Permission is hereby granted, free of charge, to any person obtaining a
+/// copy of this software and associated documentation files (the "Software"),
+/// to deal in the Software without restriction, including without limitation
+/// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+/// and/or sell copies of the Software, and to permit persons to whom the
+/// Software is furnished to do so, subject to the following conditions:
+///
+/// The above copyright notice and this permission notice shall be included in
+/// all copies or substantial portions of the Software.
+///
+/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+/// DEALINGS
+////
+
+// ----------------------------------------------------------------------------
+// Rules
+// ----------------------------------------------------------------------------
+
+// Dialog
+.md-dialog {
+ position: fixed;
+ inset-inline-end: px2rem(16px);
+ bottom: px2rem(16px);
+ z-index: 4;
+ min-width: px2rem(222px);
+ padding: px2rem(8px) px2rem(12px);
+ background-color: var(--md-default-fg-color);
+ border-radius: px2rem(2px);
+ box-shadow: var(--md-shadow-z3);
+ transform: translateY(100%);
+ opacity: 0;
+ transition:
+ transform 0ms 400ms,
+ opacity 400ms;
+ pointer-events: none;
+
+ // [print]: Hide dialog
+ @media print {
+ display: none;
+ }
+
+ // Active dialog
+ &--active {
+ transform: translateY(0);
+ opacity: 1;
+ transition:
+ transform 400ms cubic-bezier(0.075, 0.85, 0.175, 1),
+ opacity 400ms;
+ pointer-events: initial;
+ }
+
+ // Dialog wrapper
+ &__inner {
+ color: var(--md-default-bg-color);
+ font-size: px2rem(14px);
+ }
+}
diff --git a/src/assets/stylesheets/main/layout/_feedback.scss b/src/assets/stylesheets/main/layout/_feedback.scss
@@ -0,0 +1,110 @@
+////
+/// Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+///
+/// Permission is hereby granted, free of charge, to any person obtaining a
+/// copy of this software and associated documentation files (the "Software"),
+/// to deal in the Software without restriction, including without limitation
+/// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+/// and/or sell copies of the Software, and to permit persons to whom the
+/// Software is furnished to do so, subject to the following conditions:
+///
+/// The above copyright notice and this permission notice shall be included in
+/// all copies or substantial portions of the Software.
+///
+/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+/// DEALINGS
+////
+
+// ----------------------------------------------------------------------------
+// Rules
+// ----------------------------------------------------------------------------
+
+// Was this page helpful?
+.md-feedback {
+ margin: 2em 0 1em;
+ text-align: center;
+
+ // Feedback fieldset
+ fieldset {
+ margin: 0;
+ padding: 0;
+ border: none;
+ }
+
+ // Feedback title
+ &__title {
+ margin: 1em auto;
+ font-weight: 700;
+ }
+
+ // Feedback wrapper
+ &__inner {
+ position: relative;
+ }
+
+ // Feedback list
+ &__list {
+ position: relative;
+ display: flex;
+ flex-wrap: wrap;
+ align-content: baseline;
+ justify-content: center;
+
+ // Feedback icon on hover
+ &:hover .md-icon:not(:disabled) {
+ color: var(--md-default-fg-color--lighter);
+ }
+
+ // Adjust height after submission
+ :disabled & {
+ min-height: px2rem(36px);
+ }
+ }
+
+ // Feedback icon
+ &__icon {
+ flex-shrink: 0;
+ margin: 0 px2rem(2px);
+ color: var(--md-default-fg-color--light);
+ cursor: pointer;
+ transition: color 125ms;
+
+ // Feedback icon on hover
+ &:not(:disabled).md-icon:hover {
+ color: var(--md-accent-fg-color);
+ }
+
+ // Feedback icon after submit
+ &:disabled {
+ color: var(--md-default-fg-color--lightest);
+ pointer-events: none;
+ }
+ }
+
+ // Feedback note
+ &__note {
+ position: relative;
+ transform: translateY(px2rem(8px));
+ opacity: 0;
+ transition:
+ transform 400ms cubic-bezier(0.1, 0.7, 0.1, 1),
+ opacity 150ms;
+
+ // Feedback note value
+ > * {
+ max-width: px2rem(320px);
+ margin: 0 auto;
+ }
+
+ // Show after submission
+ :disabled & {
+ transform: translateY(0);
+ opacity: 1;
+ }
+ }
+}
diff --git a/src/assets/stylesheets/main/layout/_footer.scss b/src/assets/stylesheets/main/layout/_footer.scss
@@ -0,0 +1,202 @@
+////
+/// Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+///
+/// Permission is hereby granted, free of charge, to any person obtaining a
+/// copy of this software and associated documentation files (the "Software"),
+/// to deal in the Software without restriction, including without limitation
+/// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+/// and/or sell copies of the Software, and to permit persons to whom the
+/// Software is furnished to do so, subject to the following conditions:
+///
+/// The above copyright notice and this permission notice shall be included in
+/// all copies or substantial portions of the Software.
+///
+/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+/// DEALINGS
+////
+
+// ----------------------------------------------------------------------------
+// Rules
+// ----------------------------------------------------------------------------
+
+// Footer
+.md-footer {
+ color: var(--md-footer-fg-color);
+ background-color: var(--md-footer-bg-color);
+
+ // [print]: Hide footer
+ @media print {
+ display: none;
+ }
+
+ // Footer wrapper
+ &__inner {
+ justify-content: space-between;
+ padding: px2rem(4px);
+ overflow: auto;
+
+ // Footer is visible
+ &:not([hidden]) {
+ display: flex;
+ }
+ }
+
+ // Footer link to previous and next page
+ &__link {
+ display: flex;
+ // Hack: some browsers induce ellipsis on flex children that are set to
+ // `overflow: hidden` and `text-overflow: ellipsis`. Enforcing growth by
+ // a tiny factor seems to get rid of the ellipsis and renders the text as
+ // it should - see https://bit.ly/2ZUCXQ8
+ flex-grow: 0.01;
+ padding-top: px2rem(28px);
+ padding-bottom: px2rem(8px);
+ overflow: hidden;
+ outline-color: var(--md-accent-fg-color);
+ transition: opacity 250ms;
+
+ // Footer link on focus/hover
+ &:is(:focus, :hover) {
+ opacity: 0.7;
+ }
+
+ // Adjust for right-to-left languages
+ [dir="rtl"] & svg {
+ transform: scaleX(-1);
+ }
+
+ // Footer link to previous page
+ &--prev {
+
+ // [mobile -]: Adjust width to 25/75 and hide title
+ @include break-to-device(mobile) {
+
+ // Hide footer title
+ .md-footer__title {
+ display: none;
+ }
+ }
+ }
+
+ // Footer link to next page
+ &--next {
+ margin-inline-start: auto;
+ text-align: right;
+
+ // Adjust for right-to-left languages
+ [dir="rtl"] & {
+ text-align: left;
+ }
+ }
+ }
+
+ // Footer title
+ &__title {
+ position: relative;
+ flex-grow: 1;
+ max-width: calc(100% - #{px2rem(48px)});
+ padding: 0 px2rem(20px);
+ font-size: px2rem(18px);
+ line-height: px2rem(48px);
+ white-space: nowrap;
+ }
+
+ // Footer link button
+ &__button {
+ margin: px2rem(4px);
+ padding: px2rem(8px);
+ }
+
+ // Footer link direction (i.e. prev and next)
+ &__direction {
+ position: absolute;
+ inset-inline: 0;
+ margin-top: px2rem(-20px);
+ padding: 0 px2rem(20px);
+ font-size: px2rem(12.8px);
+ opacity: 0.7;
+ }
+}
+
+// Footer metadata
+.md-footer-meta {
+ background-color: var(--md-footer-bg-color--dark);
+
+ // Footer metadata wrapper
+ &__inner {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: space-between;
+ padding: px2rem(4px);
+ }
+
+ // Lighten color for non-hovered text links
+ html &.md-typeset a {
+ color: var(--md-footer-fg-color--light);
+
+ // Text link on focus/hover
+ &:is(:focus, :hover) {
+ color: var(--md-footer-fg-color);
+ }
+ }
+}
+
+// ----------------------------------------------------------------------------
+
+// Copyright and theme information
+.md-copyright {
+ width: 100%;
+ margin: auto px2rem(12px);
+ padding: px2rem(8px) 0;
+ color: var(--md-footer-fg-color--lighter);
+ font-size: px2rem(12.8px);
+
+ // [tablet portrait +]: Show copyright and social links in one line
+ @include break-from-device(tablet portrait) {
+ width: auto;
+ }
+
+ // Footer copyright highlight - this is the upper part of the copyright and
+ // theme information, which will include a darker color than the theme link
+ &__highlight {
+ color: var(--md-footer-fg-color--light);
+ }
+}
+
+// ----------------------------------------------------------------------------
+
+// Social links
+.md-social {
+ margin: 0 px2rem(8px);
+ padding: px2rem(4px) 0 px2rem(12px);
+
+ // [tablet portrait +]: Show copyright and social links in one line
+ @include break-from-device(tablet portrait) {
+ padding: px2rem(12px) 0;
+ }
+
+ // Footer social link
+ &__link {
+ display: inline-block;
+ width: px2rem(32px);
+ height: px2rem(32px);
+ text-align: center;
+
+ // Adjust line-height to match height for correct alignment
+ &::before {
+ line-height: 1.9;
+ }
+
+ // Fill icon with current color
+ svg {
+ max-height: px2rem(16px);
+ vertical-align: -25%;
+ fill: currentcolor;
+ }
+ }
+}
diff --git a/src/assets/stylesheets/main/layout/_form.scss b/src/assets/stylesheets/main/layout/_form.scss
@@ -0,0 +1,83 @@
+////
+/// Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+///
+/// Permission is hereby granted, free of charge, to any person obtaining a
+/// copy of this software and associated documentation files (the "Software"),
+/// to deal in the Software without restriction, including without limitation
+/// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+/// and/or sell copies of the Software, and to permit persons to whom the
+/// Software is furnished to do so, subject to the following conditions:
+///
+/// The above copyright notice and this permission notice shall be included in
+/// all copies or substantial portions of the Software.
+///
+/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+/// DEALINGS
+////
+
+// ----------------------------------------------------------------------------
+// Rules
+// ----------------------------------------------------------------------------
+
+// Scoped in typesetted content to match specificity of regular content
+.md-typeset {
+
+ // Form button
+ .md-button {
+ display: inline-block;
+ padding: px2em(10px) px2em(32px);
+ color: var(--md-primary-fg-color);
+ font-weight: 700;
+ border: px2rem(2px) solid currentcolor;
+ border-radius: px2rem(2px);
+ cursor: pointer;
+ transition:
+ color 125ms,
+ background-color 125ms,
+ border-color 125ms;
+
+ // Primary button
+ &--primary {
+ color: var(--md-primary-bg-color);
+ background-color: var(--md-primary-fg-color);
+ border-color: var(--md-primary-fg-color);
+ }
+
+ // Button on focus/hover
+ &:is(:focus, :hover) {
+ color: var(--md-accent-bg-color);
+ background-color: var(--md-accent-fg-color);
+ border-color: var(--md-accent-fg-color);
+ }
+ }
+
+ // Form input
+ .md-input {
+ height: px2rem(36px);
+ padding: 0 px2rem(12px);
+ font-size: px2rem(16px);
+ border-bottom: px2rem(2px) solid var(--md-default-fg-color--lighter);
+ border-start-start-radius: px2rem(2px);
+ border-start-end-radius: px2rem(2px);
+ box-shadow: var(--md-shadow-z1);
+ transition:
+ border 250ms,
+ box-shadow 250ms;
+
+ // Input on focus/hover
+ &:is(:focus, :hover) {
+ border-bottom-color: var(--md-accent-fg-color);
+ box-shadow: var(--md-shadow-z2);
+ }
+
+ // Stretch to full width
+ &--stretch {
+ width: 100%;
+ }
+ }
+}
diff --git a/src/assets/stylesheets/main/layout/_header.scss b/src/assets/stylesheets/main/layout/_header.scss
@@ -0,0 +1,262 @@
+////
+/// Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+///
+/// Permission is hereby granted, free of charge, to any person obtaining a
+/// copy of this software and associated documentation files (the "Software"),
+/// to deal in the Software without restriction, including without limitation
+/// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+/// and/or sell copies of the Software, and to permit persons to whom the
+/// Software is furnished to do so, subject to the following conditions:
+///
+/// The above copyright notice and this permission notice shall be included in
+/// all copies or substantial portions of the Software.
+///
+/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+/// DEALINGS
+////
+
+// ----------------------------------------------------------------------------
+// Rules
+// ----------------------------------------------------------------------------
+
+// Header - by default, the header will be sticky and stay always on top of the
+// viewport. If this behavior is not desired, just set `position: static`.
+.md-header {
+ position: sticky;
+ top: 0;
+ inset-inline: 0;
+ z-index: 4;
+ display: block;
+ color: var(--md-primary-bg-color);
+ background-color: var(--md-primary-fg-color);
+ // Hack: reduce jitter by adding a transparent box shadow of the same size
+ // so the size of the layer doesn't change during animation
+ box-shadow:
+ 0 0 px2rem(4px) rgba(0, 0, 0, 0),
+ 0 px2rem(4px) px2rem(8px) rgba(0, 0, 0, 0);
+
+ // [print]: Hide header
+ @media print {
+ display: none;
+ }
+
+ // Header is hidden
+ &[hidden] {
+ transform: translateY(-100%);
+ transition:
+ transform 250ms cubic-bezier(0.8, 0, 0.6, 1),
+ box-shadow 250ms;
+ }
+
+ // Header in shadow state, i.e. shadow is visible
+ &--shadow {
+ box-shadow:
+ 0 0 px2rem(4px) rgba(0, 0, 0, 0.1),
+ 0 px2rem(4px) px2rem(8px) rgba(0, 0, 0, 0.2);
+ transition:
+ transform 250ms cubic-bezier(0.1, 0.7, 0.1, 1),
+ box-shadow 250ms;
+ }
+
+ // Header wrapper
+ &__inner {
+ display: flex;
+ align-items: center;
+ padding: 0 px2rem(4px);
+ }
+
+ // Header button
+ &__button {
+ position: relative;
+ z-index: 1;
+ margin: px2rem(4px);
+ padding: px2rem(8px);
+ color: currentcolor;
+ vertical-align: middle;
+ outline-color: var(--md-accent-fg-color);
+ cursor: pointer;
+ transition: opacity 250ms;
+
+ // Button on hover
+ &:hover {
+ opacity: 0.7;
+ }
+
+ // Header button is visible
+ &:not([hidden]) {
+ display: inline-block;
+ }
+
+ // Hide outline for pointer devices
+ &:not(.focus-visible) {
+ outline: none;
+ -webkit-tap-highlight-color: transparent;
+ }
+
+ // Button with logo, pointing to `config.site_url`
+ &.md-logo {
+ margin: px2rem(4px);
+ padding: px2rem(8px);
+
+ // [tablet -]: Hide button
+ @include break-to-device(tablet) {
+ display: none;
+ }
+
+ // Image or icon
+ :is(img, svg) {
+ display: block;
+ width: auto;
+ height: px2rem(24px);
+ fill: currentcolor;
+ }
+ }
+
+ // Button for search
+ &[for="__search"] {
+
+ // [tablet landscape +]: Hide button
+ @include break-from-device(tablet landscape) {
+ display: none;
+ }
+
+ // [no-js]: Hide button
+ .no-js & {
+ display: none;
+ }
+
+ // Adjust for right-to-left languages
+ [dir="rtl"] & svg {
+ transform: scaleX(-1);
+ }
+ }
+
+ // Button for drawer
+ &[for="__drawer"] {
+
+ // [screen +]: Hide button
+ @include break-from-device(screen) {
+ display: none;
+ }
+ }
+ }
+
+ // Header topic
+ &__topic {
+ position: absolute;
+ display: flex;
+ max-width: 100%;
+ white-space: nowrap;
+ transition:
+ transform 400ms cubic-bezier(0.1, 0.7, 0.1, 1),
+ opacity 150ms;
+
+ // Second header topic - title of the current page
+ & + & {
+ z-index: -1;
+ transform: translateX(px2rem(25px));
+ opacity: 0;
+ transition:
+ transform 400ms cubic-bezier(1, 0.7, 0.1, 0.1),
+ opacity 150ms;
+ pointer-events: none;
+
+ // Adjust for right-to-left languages
+ [dir="rtl"] & {
+ transform: translateX(px2rem(-25px));
+ }
+ }
+
+ // Adjust font weight of site title
+ &:first-child {
+ font-weight: 700;
+ }
+ }
+
+ // Header title
+ &__title {
+ flex-grow: 1;
+ height: px2rem(48px);
+ margin-inline-end: px2rem(8px);
+ margin-inline-start: px2rem(20px);
+ font-size: px2rem(18px);
+ line-height: px2rem(48px);
+
+ // Header title in active state, i.e. page title is visible
+ &--active .md-header__topic {
+ z-index: -1;
+ transform: translateX(px2rem(-25px));
+ opacity: 0;
+ transition:
+ transform 400ms cubic-bezier(1, 0.7, 0.1, 0.1),
+ opacity 150ms;
+ pointer-events: none;
+
+ // Adjust for right-to-left languages
+ [dir="rtl"] & {
+ transform: translateX(px2rem(25px));
+ }
+
+ // Second header topic - title of the current page
+ + .md-header__topic {
+ z-index: 0;
+ transform: translateX(0);
+ opacity: 1;
+ transition:
+ transform 400ms cubic-bezier(0.1, 0.7, 0.1, 1),
+ opacity 150ms;
+ pointer-events: initial;
+ }
+ }
+
+ // Add ellipsis in case of overflowing text
+ > .md-header__ellipsis {
+ position: relative;
+ width: 100%;
+ height: 100%;
+ }
+ }
+
+ // Header option
+ &__option {
+ display: flex;
+ flex-shrink: 0;
+ max-width: 100%;
+ white-space: nowrap;
+ transition:
+ max-width 0ms 250ms,
+ opacity 250ms 250ms;
+
+ // Hide toggle when search is active
+ [data-md-toggle="search"]:checked ~ .md-header & {
+ max-width: 0;
+ opacity: 0;
+ transition:
+ max-width 0ms,
+ opacity 0ms;
+ }
+ }
+
+ // Repository information container
+ &__source {
+ display: none;
+
+ // [tablet landscape +]: Show repository information
+ @include break-from-device(tablet landscape) {
+ display: block;
+ width: px2rem(234px);
+ max-width: px2rem(234px);
+ margin-inline-start: px2rem(20px);
+ }
+
+ // [screen +]: Adjust spacing of search bar
+ @include break-from-device(screen) {
+ margin-inline-start: px2rem(28px);
+ }
+ }
+}
diff --git a/src/assets/stylesheets/main/layout/_nav.scss b/src/assets/stylesheets/main/layout/_nav.scss
@@ -0,0 +1,642 @@
+////
+/// Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+///
+/// Permission is hereby granted, free of charge, to any person obtaining a
+/// copy of this software and associated documentation files (the "Software"),
+/// to deal in the Software without restriction, including without limitation
+/// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+/// and/or sell copies of the Software, and to permit persons to whom the
+/// Software is furnished to do so, subject to the following conditions:
+///
+/// The above copyright notice and this permission notice shall be included in
+/// all copies or substantial portions of the Software.
+///
+/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+/// DEALINGS
+////
+
+// ----------------------------------------------------------------------------
+// Rules
+// ----------------------------------------------------------------------------
+
+// Navigation variables
+:root {
+ --md-nav-icon--prev: svg-load("material/arrow-left.svg");
+ --md-nav-icon--next: svg-load("material/chevron-right.svg");
+ --md-toc-icon: svg-load("material/table-of-contents.svg");
+}
+
+// ----------------------------------------------------------------------------
+
+// Navigation
+.md-nav {
+ font-size: px2rem(14px);
+ line-height: 1.3;
+
+ // Navigation title
+ &__title {
+ display: block;
+ padding: 0 px2rem(12px);
+ overflow: hidden;
+ font-weight: 700;
+ text-overflow: ellipsis;
+
+ // Navigaton button
+ .md-nav__button {
+ display: none;
+
+ // Stretch images based on height, as it's the smaller dimension
+ img {
+ width: auto;
+ height: 100%;
+ }
+
+ // Button with logo, pointing to `config.site_url`
+ &.md-logo {
+
+ // Image or icon
+ :is(img, svg) {
+ display: block;
+ width: auto;
+ max-width: 100%;
+ height: px2rem(48px);
+ object-fit: contain;
+ fill: currentcolor;
+ }
+ }
+ }
+ }
+
+ // Navigation list
+ &__list {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ }
+
+ // Navigation item
+ &__item {
+ padding: 0 px2rem(12px);
+
+ // Navigation item on level 2
+ & & {
+ padding-inline-end: 0;
+ }
+ }
+
+ // Navigation link
+ &__link {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-top: 0.625em;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ cursor: pointer;
+ transition: color 125ms;
+ scroll-snap-align: start;
+
+ // Navigation link that was passed
+ &--passed {
+ color: var(--md-default-fg-color--light);
+ }
+
+ // Active link
+ .md-nav__item &--active {
+ color: var(--md-typeset-a-color);
+ }
+
+ // Stretch section index link to full width
+ .md-nav__item &--index [href] {
+ width: 100%;
+ }
+
+ // Navigation link on focus/hover
+ &:is(:focus, :hover) {
+ color: var(--md-accent-fg-color);
+ }
+
+ // Show outline for keyboard devices
+ &.focus-visible {
+ outline-color: var(--md-accent-fg-color);
+ outline-offset: px2rem(4px);
+ }
+
+ // Navigation link for table of contents
+ .md-nav--primary &[for="__toc"] {
+ display: none;
+
+ // Table of contents icon
+ .md-icon::after {
+ display: block;
+ width: 100%;
+ height: 100%;
+ mask-image: var(--md-toc-icon);
+ background-color: currentcolor;
+ }
+
+ // Hide table of contents
+ ~ .md-nav {
+ display: none;
+ }
+ }
+
+ // Navigation link children (for section indexes)
+ > * {
+ display: flex;
+ cursor: pointer;
+ }
+ }
+
+ // Navigation icon
+ &__icon {
+ flex-shrink: 0;
+ }
+
+ // Repository information container
+ &__source {
+ display: none;
+ }
+
+ // [tablet -]: Layered navigation
+ @include break-to-device(tablet) {
+
+ // Primary and nested navigation
+ &--primary,
+ &--primary & {
+ position: absolute;
+ top: 0;
+ inset-inline: 0;
+ z-index: 1;
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ background-color: var(--md-default-bg-color);
+ }
+
+ // Primary navigation
+ &--primary {
+
+ // Navigation title and item
+ :is(.md-nav__title, .md-nav__item) {
+ font-size: px2rem(16px);
+ line-height: 1.5;
+ }
+
+ // Navigation title
+ .md-nav__title {
+ position: relative;
+ height: px2rem(112px);
+ padding: px2rem(60px) px2rem(16px) px2rem(4px);
+ color: var(--md-default-fg-color--light);
+ line-height: px2rem(48px);
+ white-space: nowrap;
+ background-color: var(--md-default-fg-color--lightest);
+ cursor: pointer;
+
+ // Navigation icon
+ .md-nav__icon {
+ position: absolute;
+ top: px2rem(8px);
+ inset-inline-start: px2rem(8px);
+ display: block;
+ width: px2rem(24px);
+ height: px2rem(24px);
+ margin: px2rem(4px);
+
+ // Navigation icon in link to previous level
+ &::after {
+ display: block;
+ width: 100%;
+ height: 100%;
+ background-color: currentcolor;
+ mask-image: var(--md-nav-icon--prev);
+ mask-repeat: no-repeat;
+ mask-size: contain;
+ content: "";
+ }
+ }
+
+ // Navigation list
+ ~ .md-nav__list {
+ overflow-y: auto;
+ background-color: var(--md-default-bg-color);
+ box-shadow:
+ 0 px2rem(1px) 0 var(--md-default-fg-color--lightest) inset;
+ scroll-snap-type: y mandatory;
+ touch-action: pan-y;
+
+ // Omit border on first child
+ > :first-child {
+ border-top: 0;
+ }
+ }
+
+ // Top-level navigation title
+ &[for="__drawer"] {
+ color: var(--md-primary-bg-color);
+ font-weight: 700;
+ background-color: var(--md-primary-fg-color);
+ }
+
+ // Button with logo, pointing to `config.site_url`
+ .md-logo {
+ position: absolute;
+ top: px2rem(4px);
+ inset-inline: px2rem(4px);
+ display: block;
+ margin: px2rem(4px);
+ padding: px2rem(8px);
+ }
+ }
+
+ // Navigation list
+ .md-nav__list {
+ flex: 1;
+ }
+
+ // Navigation item
+ .md-nav__item {
+ padding: 0;
+ border-top: px2rem(1px) solid var(--md-default-fg-color--lightest);
+
+ // Navigation link in active navigation
+ &--active > .md-nav__link {
+ color: var(--md-typeset-a-color);
+
+ // Navigation link on focus/hover
+ &:is(:focus, :hover) {
+ color: var(--md-accent-fg-color);
+ }
+ }
+ }
+
+ // Navigation link
+ .md-nav__link {
+ margin-top: 0;
+ padding: px2rem(12px) px2rem(16px);
+
+ // Navigation icon
+ .md-nav__icon {
+ width: px2rem(24px);
+ height: px2rem(24px);
+ margin-inline-end: px2rem(-4px);
+ font-size: px2rem(24px);
+
+ // Navigation icon in link to next level
+ &::after {
+ display: block;
+ width: 100%;
+ height: 100%;
+ background-color: currentcolor;
+ mask-image: var(--md-nav-icon--next);
+ mask-repeat: no-repeat;
+ mask-size: contain;
+ content: "";
+ }
+ }
+ }
+
+ // Flip icon vertically
+ .md-nav__icon {
+
+ // Adjust for right-to-left languages
+ [dir="rtl"] &::after {
+ transform: scale(-1);
+ }
+ }
+
+ // Table of contents contained in primary navigation
+ .md-nav--secondary {
+
+ // Navigation on level 2-6
+ .md-nav {
+ position: static;
+ background-color: transparent;
+
+ // Navigation link on level 3
+ .md-nav__link {
+ padding-inline-start: px2rem(28px);
+ }
+
+ // Navigation link on level 4
+ .md-nav .md-nav__link {
+ padding-inline-start: px2rem(40px);
+ }
+
+ // Navigation link on level 5
+ .md-nav .md-nav .md-nav__link {
+ padding-inline-start: px2rem(52px);
+ }
+
+ // Navigation link on level 6
+ .md-nav .md-nav .md-nav .md-nav__link {
+ padding-inline-start: px2rem(64px);
+ }
+ }
+ }
+ }
+
+ // Table of contents
+ &--secondary {
+ background-color: transparent;
+ }
+
+ // Toggle for nested navigation
+ &__toggle ~ & {
+ display: flex;
+ transform: translateX(100%);
+ opacity: 0;
+ transition:
+ transform 250ms cubic-bezier(0.8, 0, 0.6, 1),
+ opacity 125ms 50ms;
+
+ // Adjust for right-to-left languages
+ [dir="rtl"] & {
+ transform: translateX(-100%);
+ }
+ }
+
+ // Show nested navigation when toggle is active
+ &__toggle:checked ~ & {
+ transform: translateX(0);
+ opacity: 1;
+ transition:
+ transform 250ms cubic-bezier(0.4, 0, 0.2, 1),
+ opacity 125ms 125ms;
+
+ // Navigation list
+ > .md-nav__list {
+ // Hack: promote to own layer to reduce jitter
+ backface-visibility: hidden;
+ }
+ }
+ }
+
+ // [tablet portrait -]: Layered navigation with table of contents
+ @include break-to-device(tablet portrait) {
+
+ // Show link to table of contents
+ &--primary &__link[for="__toc"] {
+ display: flex;
+
+ // Show table of contents icon
+ .md-icon::after {
+ content: "";
+ }
+
+ // Hide navigation link to current page
+ + .md-nav__link {
+ display: none;
+ }
+
+ // Show table of contents
+ ~ .md-nav {
+ display: flex;
+ }
+ }
+
+ // Repository information container
+ &__source {
+ display: block;
+ padding: 0 px2rem(4px);
+ color: var(--md-primary-bg-color);
+ background-color: var(--md-primary-fg-color--dark);
+ }
+ }
+
+ // [tablet landscape]: Layered navigation with table of contents
+ @include break-at-device(tablet landscape) {
+
+ // Show link to integrated table of contents
+ &--integrated &__link[for="__toc"] {
+ display: flex;
+
+ // Show table of contents icon
+ .md-icon::after {
+ content: "";
+ }
+
+ // Hide navigation link to current page
+ + .md-nav__link {
+ display: none;
+ }
+
+ // Show table of contents
+ ~ .md-nav {
+ display: flex;
+ }
+ }
+ }
+
+ // [tablet landscape +]: Tree-like table of contents
+ @include break-from-device(tablet landscape) {
+
+ // Navigation title
+ &--secondary &__title {
+
+ // Adjust snapping behavior
+ &[for="__toc"] {
+ scroll-snap-align: start;
+ }
+
+ // Hide navigation icon
+ .md-nav__icon {
+ display: none;
+ }
+ }
+ }
+
+ // [screen +]: Tree-like navigation
+ @include break-from-device(screen) {
+ transition: max-height 250ms cubic-bezier(0.86, 0, 0.07, 1);
+
+ // Navigation title
+ &--primary &__title {
+
+ // Adjust snapping behavior
+ &[for="__drawer"] {
+ scroll-snap-align: start;
+ }
+
+ // Hide navigation icon
+ .md-nav__icon {
+ display: none;
+ }
+ }
+
+ // Hide toggle for nested navigation
+ &__toggle ~ & {
+ display: none;
+ }
+
+ // Show nested navigation when toggle is active or indeterminate
+ &__toggle:is(:checked, :indeterminate) ~ & {
+ display: block;
+ }
+
+ // Hide navigation title in nested navigation
+ &__item--nested > & > &__title {
+ display: none;
+ }
+
+ // Navigation section
+ &__item--section {
+ display: block;
+ margin: 1.25em 0;
+
+ // Adjust spacing on last child
+ &:last-child {
+ margin-bottom: 0;
+ }
+
+ // Show navigation link as title
+ > .md-nav__link {
+ font-weight: 700;
+ pointer-events: none;
+
+ // Make navigation link clickable
+ &--index [href] {
+ pointer-events: initial;
+ }
+
+ // Hide naviation icon
+ .md-nav__icon {
+ display: none;
+ }
+ }
+
+ // Navigation
+ > .md-nav {
+ display: block;
+
+ // Adjust spacing on next level item
+ > .md-nav__list > .md-nav__item {
+ padding: 0;
+ }
+ }
+ }
+
+ // Navigation icon
+ &__icon {
+ float: right;
+ width: px2rem(18px);
+ height: px2rem(18px);
+ border-radius: 100%;
+ transition:
+ background-color 250ms,
+ transform 250ms;
+
+ // Adjust for right-to-left languages
+ [dir="rtl"] & {
+ float: left;
+ transform: rotate(180deg);
+ }
+
+ // Navigation icon on hover
+ &:hover {
+ background-color: var(--md-accent-fg-color--transparent);
+ }
+
+ // Navigation icon content
+ &::after {
+ display: inline-block;
+ width: 100%;
+ height: 100%;
+ vertical-align: px2rem(-2px);
+ background-color: currentcolor;
+ mask-image: var(--md-nav-icon--next);
+ mask-repeat: no-repeat;
+ mask-size: contain;
+ content: "";
+ }
+
+ // Navigation icon - rotate icon when toggle is active or indeterminate
+ .md-nav__item--nested .md-nav__toggle:checked ~ .md-nav__link &,
+ .md-nav__item--nested .md-nav__toggle:indeterminate ~ .md-nav__link & {
+ transform: rotate(90deg);
+ }
+ }
+
+ // Modifier for when navigation tabs are rendered
+ &--lifted {
+
+ // Hide nested level 0 navigation items and site title
+ > .md-nav__list > .md-nav__item--nested,
+ > .md-nav__title {
+ display: none;
+ }
+
+ // Hide level 0 navigation items
+ > .md-nav__list > .md-nav__item {
+ display: none;
+
+ // Active parent navigation item
+ &--active {
+ display: block;
+ padding: 0;
+
+ // Show navigation link as title
+ > .md-nav__link {
+ margin-top: 0;
+ padding: 0 px2rem(12px);
+ font-weight: 700;
+ pointer-events: none;
+
+ // Make navigation link clickable
+ &--index [href] {
+ pointer-events: initial;
+ }
+
+ // Hide naviation icon
+ .md-nav__icon {
+ display: none;
+ }
+ }
+ }
+ }
+
+ // Hack: Always show active navigation tab on breakpoint screen, despite
+ // of checkbox being checked or not. Fixes #1655.
+ .md-nav[data-md-level="1"] {
+ display: block;
+
+ // Adjust spacing for level 1 navigation items
+ > .md-nav__list > .md-nav__item {
+ padding-inline-end: px2rem(12px);
+ }
+ }
+ }
+
+ // Modifier for when table of contents is rendered in primary navigation
+ &--integrated > .md-nav__list > .md-nav__item--active {
+
+ // Add spacing to container for non-nested navigation items
+ &:not(.md-nav__item--nested) {
+ padding: 0 px2rem(12px);
+
+ // Remove padding as it's given by container
+ > .md-nav__link {
+ padding: 0;
+ }
+ }
+
+ // Show integrated table of contents
+ .md-nav--secondary {
+ display: block;
+ margin-bottom: 1.25em;
+ border-inline-start: px2rem(1px) solid var(--md-primary-fg-color);
+
+ // Hide table of contents title
+ > .md-nav__title {
+ display: none;
+ }
+ }
+ }
+ }
+}
diff --git a/src/assets/stylesheets/main/layout/_search.scss b/src/assets/stylesheets/main/layout/_search.scss
@@ -0,0 +1,713 @@
+////
+/// Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+///
+/// Permission is hereby granted, free of charge, to any person obtaining a
+/// copy of this software and associated documentation files (the "Software"),
+/// to deal in the Software without restriction, including without limitation
+/// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+/// and/or sell copies of the Software, and to permit persons to whom the
+/// Software is furnished to do so, subject to the following conditions:
+///
+/// The above copyright notice and this permission notice shall be included in
+/// all copies or substantial portions of the Software.
+///
+/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+/// DEALINGS
+////
+
+// ----------------------------------------------------------------------------
+// Rules
+// ----------------------------------------------------------------------------
+
+// Search variables
+:root {
+ --md-search-result-icon: svg-load("material/file-search-outline.svg");
+}
+
+// ----------------------------------------------------------------------------
+
+// Search
+.md-search {
+ position: relative;
+
+ // [tablet landscape +]: Header-embedded search
+ @include break-from-device(tablet landscape) {
+ padding: px2rem(4px) 0;
+ }
+
+ // [no-js]: Hide search
+ .no-js & {
+ display: none;
+ }
+
+ // Search overlay
+ &__overlay {
+ z-index: 1;
+ opacity: 0;
+
+ // [tablet portrait -]: Search modal
+ @include break-to-device(tablet portrait) {
+ position: absolute;
+ top: px2rem(-20px);
+ inset-inline-start: px2rem(-44px);
+ width: px2rem(40px);
+ height: px2rem(40px);
+ overflow: hidden;
+ background-color: var(--md-default-bg-color);
+ border-radius: px2rem(20px);
+ transform-origin: center;
+ transition:
+ transform 300ms 100ms,
+ opacity 200ms 200ms;
+ pointer-events: none;
+
+ // Show overlay when search is active
+ [data-md-toggle="search"]:checked ~ .md-header & {
+ opacity: 1;
+ transition:
+ transform 400ms,
+ opacity 100ms;
+ }
+ }
+
+ // [tablet landscape +]: Header-embedded search
+ @include break-from-device(tablet landscape) {
+ position: fixed;
+ top: 0;
+ inset-inline-start: 0;
+ width: 0;
+ height: 0;
+ background-color: hsla(0, 0%, 0%, 0.54);
+ cursor: pointer;
+ transition:
+ width 0ms 250ms,
+ height 0ms 250ms,
+ opacity 250ms;
+
+ // Show overlay when search is active
+ [data-md-toggle="search"]:checked ~ .md-header & {
+ width: 100%;
+ // Hack: when the header is translated upon scrolling, a new layer is
+ // induced, which means that the height will now refer to the height of
+ // the header, albeit positioning is fixed. This should be mitigated
+ // in all cases when setting the height to 2x the viewport.
+ height: 200vh;
+ opacity: 1;
+ transition:
+ width 0ms,
+ height 0ms,
+ opacity 250ms;
+ }
+ }
+
+ // Adjust appearance when search is active
+ [data-md-toggle="search"]:checked ~ .md-header & {
+
+ // [mobile portrait -]: Scale up 45 times
+ @include break-to-device(mobile portrait) {
+ transform: scale(45);
+ }
+
+ // [mobile landscape]: Scale up 60 times
+ @include break-at-device(mobile landscape) {
+ transform: scale(60);
+ }
+
+ // [tablet portrait]: Scale up 75 times
+ @include break-at-device(tablet portrait) {
+ transform: scale(75);
+ }
+ }
+ }
+
+ // Search wrapper
+ &__inner {
+ // Hack: promote to own layer to reduce jitter
+ backface-visibility: hidden;
+
+ // [tablet portrait -]: Search modal
+ @include break-to-device(tablet portrait) {
+ position: fixed;
+ top: 0;
+ inset-inline-start: 0;
+ z-index: 2;
+ width: 0;
+ height: 0;
+ overflow: hidden;
+ transform: translateX(5%);
+ opacity: 0;
+ transition:
+ width 0ms 300ms,
+ height 0ms 300ms,
+ transform 150ms 150ms cubic-bezier(0.4, 0, 0.2, 1),
+ opacity 150ms 150ms;
+
+ // Adjust for right-to-left languages
+ [dir="rtl"] & {
+ transform: translateX(-5%);
+ }
+
+ // Adjust appearance when search is active
+ [data-md-toggle="search"]:checked ~ .md-header & {
+ width: 100%;
+ height: 100%;
+ transform: translateX(0);
+ opacity: 1;
+ transition:
+ width 0ms 0ms,
+ height 0ms 0ms,
+ transform 150ms 150ms cubic-bezier(0.1, 0.7, 0.1, 1),
+ opacity 150ms 150ms;
+ }
+ }
+
+ // [tablet landscape +]: Header-embedded search
+ @include break-from-device(tablet landscape) {
+ position: relative;
+ float: right;
+ width: px2rem(234px);
+ padding: px2rem(2px) 0;
+ transition: width 250ms cubic-bezier(0.1, 0.7, 0.1, 1);
+
+ // Adjust for right-to-left languages
+ [dir="rtl"] & {
+ float: left;
+ }
+ }
+
+ // Adjust appearance when search is active
+ [data-md-toggle="search"]:checked ~ .md-header & {
+
+ // [tablet landscape]: Omit overlaying header title
+ @include break-at-device(tablet landscape) {
+ width: px2rem(468px);
+ }
+
+ // [screen +]: Match width of content area
+ @include break-from-device(screen) {
+ width: px2rem(688px);
+ }
+ }
+ }
+
+ // Search form
+ &__form {
+ position: relative;
+ z-index: 2;
+ height: px2rem(48px);
+ background-color: var(--md-default-bg-color);
+ box-shadow: 0 0 px2rem(12px) transparent;
+ transition:
+ color 250ms,
+ background-color 250ms;
+
+ // [tablet landscape +]: Header-embedded search
+ @include break-from-device(tablet landscape) {
+ height: px2rem(36px);
+ background-color: hsla(0, 0%, 0%, 0.26);
+ border-radius: px2rem(2px);
+
+ // Search form on hover
+ &:hover {
+ background-color: hsla(0, 0%, 100%, 0.12);
+ }
+ }
+
+ // Adjust appearance when search is active
+ [data-md-toggle="search"]:checked ~ .md-header & {
+ color: var(--md-default-fg-color);
+ background-color: var(--md-default-bg-color);
+ border-radius: px2rem(2px) px2rem(2px) 0 0;
+ box-shadow: 0 0 px2rem(12px) hsla(0, 0%, 0%, 0.07);
+ }
+ }
+
+ // Search input
+ &__input {
+ position: relative;
+ z-index: 2;
+ width: 100%;
+ height: 100%;
+ padding-inline: px2rem(72px) px2rem(44px);
+ font-size: px2rem(18px);
+ text-overflow: ellipsis;
+ background: transparent;
+
+ // Search placeholder
+ &::placeholder {
+ transition: color 250ms;
+ }
+
+ // Search icon and placeholder
+ ~ .md-search__icon,
+ &::placeholder {
+ color: var(--md-default-fg-color--light);
+ }
+
+ // Remove the "x" rendered by Internet Explorer
+ &::-ms-clear {
+ display: none;
+ }
+
+ // [tablet portrait -]: Search modal
+ @include break-to-device(tablet portrait) {
+ width: 100%;
+ height: px2rem(48px);
+ font-size: px2rem(18px);
+ }
+
+ // [tablet landscape +]: Header-embedded search
+ @include break-from-device(tablet landscape) {
+ padding-inline-start: px2rem(44px);
+ color: inherit;
+ font-size: px2rem(16px);
+
+ // Search placeholder
+ &::placeholder {
+ color: var(--md-primary-bg-color--light);
+ }
+
+ // Search icon
+ + .md-search__icon {
+ color: var(--md-primary-bg-color);
+ }
+
+ // Adjust appearance when search is active
+ [data-md-toggle="search"]:checked ~ .md-header & {
+ text-overflow: clip;
+
+ // Search icon and placeholder
+ + .md-search__icon,
+ &::placeholder {
+ color: var(--md-default-fg-color--light);
+ }
+ }
+ }
+ }
+
+ // Search icon
+ &__icon {
+ display: inline-block;
+ width: px2rem(24px);
+ height: px2rem(24px);
+ cursor: pointer;
+ transition:
+ color 250ms,
+ opacity 250ms;
+
+ // Search icon on hover
+ &:hover {
+ opacity: 0.7;
+ }
+
+ // Search focus button
+ &[for="__search"] {
+ position: absolute;
+ top: px2rem(6px);
+ inset-inline-start: px2rem(10px);
+ z-index: 2;
+
+ // Adjust for right-to-left languages
+ [dir="rtl"] & svg {
+ transform: scaleX(-1);
+ }
+
+ // [tablet portrait -]: Search modal
+ @include break-to-device(tablet portrait) {
+ top: px2rem(12px);
+ inset-inline-start: px2rem(16px);
+
+ // Hide the magnifying glass
+ svg:first-child {
+ display: none;
+ }
+ }
+
+ // [tablet landscape +]: Header-embedded search
+ @include break-from-device(tablet landscape) {
+ pointer-events: none;
+
+ // Hide the back arrow
+ svg:last-child {
+ display: none;
+ }
+ }
+ }
+ }
+
+ // Search options
+ &__options {
+ position: absolute;
+ top: px2rem(6px);
+ inset-inline-end: px2rem(10px);
+ z-index: 2;
+ pointer-events: none;
+
+ // [tablet portrait -]: Search modal
+ @include break-to-device(tablet portrait) {
+ top: px2rem(12px);
+ inset-inline-end: px2rem(16px);
+ }
+
+ // Search option buttons
+ > * {
+ margin-inline-start: px2rem(4px);
+ color: var(--md-default-fg-color--light);
+ transform: scale(0.75);
+ opacity: 0;
+ transition:
+ transform 150ms cubic-bezier(0.1, 0.7, 0.1, 1),
+ opacity 150ms;
+
+ // Hide outline for pointer devices
+ &:not(.focus-visible) {
+ outline: none;
+ -webkit-tap-highlight-color: transparent;
+ }
+
+ // Show reset button when search is active and input non-empty
+ [data-md-toggle="search"]:checked ~ .md-header
+ .md-search__input:valid ~ & {
+ transform: scale(1);
+ opacity: 1;
+ pointer-events: initial;
+
+ // Search focus icon
+ &:hover {
+ opacity: 0.7;
+ }
+ }
+ }
+ }
+
+ // Search suggestions
+ &__suggest {
+ position: absolute;
+ top: 0;
+ display: flex;
+ align-items: center;
+ width: 100%;
+ height: 100%;
+ padding-inline: px2rem(72px) px2rem(44px);
+ color: var(--md-default-fg-color--lighter);
+ font-size: px2rem(18px);
+ white-space: nowrap;
+ opacity: 0;
+ transition: opacity 50ms;
+
+ // [tablet landscape +]: Header-embedded search
+ @include break-from-device(tablet landscape) {
+ padding-inline-start: px2rem(44px);
+ font-size: px2rem(16px);
+ }
+
+ // Show suggestions when search is active
+ [data-md-toggle="search"]:checked ~ .md-header & {
+ opacity: 1;
+ transition: opacity 300ms 100ms;
+ }
+ }
+
+ // Search output
+ &__output {
+ position: absolute;
+ z-index: 1;
+ width: 100%;
+ overflow: hidden;
+ border-end-start-radius: px2rem(2px);
+ border-end-end-radius: px2rem(2px);
+
+ // [tablet portrait -]: Search modal
+ @include break-to-device(tablet portrait) {
+ top: px2rem(48px);
+ bottom: 0;
+ }
+
+ // [tablet landscape +]: Header-embedded search
+ @include break-from-device(tablet landscape) {
+ top: px2rem(38px);
+ opacity: 0;
+ transition: opacity 400ms;
+
+ // Show output when search is active
+ [data-md-toggle="search"]:checked ~ .md-header & {
+ box-shadow: var(--md-shadow-z3);
+ opacity: 1;
+ }
+ }
+ }
+
+ // Search scroll wrapper
+ &__scrollwrap {
+ height: 100%;
+ overflow-y: auto;
+ background-color: var(--md-default-bg-color);
+ // Hack: promote to own layer to reduce jitter
+ backface-visibility: hidden;
+ // Hack: Chrome 88+ has weird overscroll behavior. Overall, scroll snapping
+ // seems to be something that is not ready for prime time on some browsers.
+ // scroll-snap-type: y mandatory;
+ touch-action: pan-y;
+
+ // Mitigiate excessive repaints on non-retina devices
+ @media (max-resolution: 1dppx) {
+ transform: translateZ(0);
+ }
+
+ // [tablet landscape]: Set fixed width to omit unnecessary reflow
+ @include break-at-device(tablet landscape) {
+ width: px2rem(468px);
+ }
+
+ // [screen +]: Set fixed width to omit unnecessary reflow
+ @include break-from-device(screen) {
+ width: px2rem(688px);
+ }
+
+ // [tablet landscape +]: Limit height to viewport
+ @include break-from-device(tablet landscape) {
+ max-height: 0;
+ scrollbar-width: thin;
+ scrollbar-color: var(--md-default-fg-color--lighter) transparent;
+
+ // Show scroll wrapper when search is active
+ [data-md-toggle="search"]:checked ~ .md-header & {
+ max-height: 75vh;
+ }
+
+ // Search scroll wrapper on hover
+ &:hover {
+ scrollbar-color: var(--md-accent-fg-color) transparent;
+ }
+
+ // Webkit scrollbar
+ &::-webkit-scrollbar {
+ width: px2rem(4px);
+ height: px2rem(4px);
+ }
+
+ // Webkit scrollbar thumb
+ &::-webkit-scrollbar-thumb {
+ background-color: var(--md-default-fg-color--lighter);
+
+ // Webkit scrollbar thumb on hover
+ &:hover {
+ background-color: var(--md-accent-fg-color);
+ }
+ }
+ }
+ }
+}
+
+// Search result
+.md-search-result {
+ color: var(--md-default-fg-color);
+ word-break: break-word;
+
+ // Search result metadata
+ &__meta {
+ padding: 0 px2rem(16px);
+ color: var(--md-default-fg-color--light);
+ font-size: px2rem(12.8px);
+ line-height: px2rem(36px);
+ background-color: var(--md-default-fg-color--lightest);
+ scroll-snap-align: start;
+
+ // [tablet landscape +]: Adjust spacing
+ @include break-from-device(tablet landscape) {
+ padding-inline-start: px2rem(44px);
+ }
+ }
+
+ // Search result list
+ &__list {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ // Hack: omit accidental text selection on fast toggle of more button
+ user-select: none;
+ }
+
+ // Search result item
+ &__item {
+ box-shadow: 0 px2rem(-1px) var(--md-default-fg-color--lightest);
+
+ // Omit border on first child
+ &:first-child {
+ box-shadow: none;
+ }
+ }
+
+ // Search result link
+ &__link {
+ display: block;
+ outline: none;
+ transition: background-color 250ms;
+ scroll-snap-align: start;
+
+ // Search result link on focus/hover
+ &:is(:focus, :hover) {
+ background-color: var(--md-accent-fg-color--transparent);
+ }
+
+ // Adjust spacing on last child of last link
+ &:last-child p:last-child {
+ margin-bottom: px2rem(12px);
+ }
+ }
+
+ // Search result more link
+ &__more summary {
+ display: block;
+ padding: px2em(12px) px2rem(16px);
+ color: var(--md-typeset-a-color);
+ font-size: px2rem(12.8px);
+ outline: none;
+ cursor: pointer;
+ transition:
+ color 250ms,
+ background-color 250ms;
+ scroll-snap-align: start;
+
+ // [tablet landscape +]: Adjust spacing
+ @include break-from-device(tablet landscape) {
+ padding-inline-start: px2rem(44px);
+ }
+
+ // Search result more link on focus/hover
+ &:is(:focus, :hover) {
+ color: var(--md-accent-fg-color);
+ background-color: var(--md-accent-fg-color--transparent);
+ }
+
+ // Hide native details marker - modern
+ &::marker {
+ display: none;
+ }
+
+ // Hide native details marker - legacy, must be split into a seprate rule,
+ // so older browsers don't consider the selector list as invalid
+ &::-webkit-details-marker {
+ display: none;
+ }
+
+ // Adjust transparency of less relevant results
+ ~ * > * {
+ opacity: 0.65;
+ }
+ }
+
+ // Search result article
+ &__article {
+ position: relative;
+ padding: 0 px2rem(16px);
+ overflow: hidden;
+
+ // [tablet landscape +]: Adjust spacing
+ @include break-from-device(tablet landscape) {
+ padding-inline-start: px2rem(44px);
+ }
+
+ // Search result article document
+ &--document {
+
+ // Search result title
+ .md-search-result__title {
+ margin: px2rem(11px) 0;
+ font-weight: 400;
+ font-size: px2rem(16px);
+ line-height: 1.4;
+ }
+ }
+ }
+
+ // Search result icon
+ &__icon {
+ position: absolute;
+ inset-inline-start: 0;
+ width: px2rem(24px);
+ height: px2rem(24px);
+ margin: px2rem(10px);
+ color: var(--md-default-fg-color--light);
+
+ // [tablet portrait -]: Hide icon
+ @include break-to-device(tablet portrait) {
+ display: none;
+ }
+
+ // Search result icon content
+ &::after {
+ display: inline-block;
+ width: 100%;
+ height: 100%;
+ background-color: currentcolor;
+ mask-image: var(--md-search-result-icon);
+ mask-repeat: no-repeat;
+ mask-size: contain;
+ content: "";
+
+ // Adjust for right-to-left languages
+ [dir="rtl"] & {
+ transform: scaleX(-1);
+ }
+ }
+ }
+
+ // Search result title
+ &__title {
+ margin: 0.5em 0;
+ font-weight: 700;
+ font-size: px2rem(12.8px);
+ line-height: 1.6;
+ }
+
+ // Search result teaser
+ &__teaser {
+ display: -webkit-box;
+ max-height: px2rem(40px);
+ margin: 0.5em 0;
+ overflow: hidden;
+ color: var(--md-default-fg-color--light);
+ font-size: px2rem(12.8px);
+ line-height: 1.6;
+ text-overflow: ellipsis;
+ -webkit-box-orient: vertical;
+ -webkit-line-clamp: 2;
+
+ // [mobile -]: Adjust number of lines
+ @include break-to-device(mobile) {
+ max-height: px2rem(60px);
+ -webkit-line-clamp: 3;
+ }
+
+ // [tablet landscape]: Adjust number of lines
+ @include break-at-device(tablet landscape) {
+ max-height: px2rem(60px);
+ -webkit-line-clamp: 3;
+ }
+
+ // Search term highlighting
+ mark {
+ text-decoration: underline;
+ background-color: transparent;
+ }
+ }
+
+ // Search result terms
+ &__terms {
+ margin: 0.5em 0;
+ font-size: px2rem(12.8px);
+ font-style: italic;
+ }
+
+ // Search term highlighting
+ mark {
+ color: var(--md-accent-fg-color);
+ background-color: transparent;
+ }
+}
diff --git a/src/assets/stylesheets/main/layout/_select.scss b/src/assets/stylesheets/main/layout/_select.scss
@@ -0,0 +1,115 @@
+////
+/// Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+///
+/// Permission is hereby granted, free of charge, to any person obtaining a
+/// copy of this software and associated documentation files (the "Software"),
+/// to deal in the Software without restriction, including without limitation
+/// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+/// and/or sell copies of the Software, and to permit persons to whom the
+/// Software is furnished to do so, subject to the following conditions:
+///
+/// The above copyright notice and this permission notice shall be included in
+/// all copies or substantial portions of the Software.
+///
+/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+/// DEALINGS
+////
+
+// ----------------------------------------------------------------------------
+// Rules
+// ----------------------------------------------------------------------------
+
+// Selection
+.md-select {
+ position: relative;
+ z-index: 1;
+
+ // Selection tooltip
+ &__inner {
+ position: absolute;
+ top: calc(100% - #{px2rem(4px)});
+ left: 50%;
+ max-height: 0;
+ margin-top: px2rem(4px);
+ color: var(--md-default-fg-color);
+ background-color: var(--md-default-bg-color);
+ border-radius: px2rem(2px);
+ box-shadow: var(--md-shadow-z2);
+ transform: translate3d(-50%, px2rem(6px), 0);
+ opacity: 0;
+ transition:
+ transform 250ms 375ms,
+ opacity 250ms 250ms,
+ max-height 0ms 500ms;
+
+ // Selection bubble on parent focus/hover
+ .md-select:is(:focus-within, :hover) & {
+ max-height: px2rem(200px);
+ transform: translate3d(-50%, 0, 0);
+ opacity: 1;
+ transition:
+ transform 250ms cubic-bezier(0.1, 0.7, 0.1, 1),
+ opacity 250ms,
+ max-height 0ms;
+ }
+
+ // Selection bubble handle
+ &::after {
+ position: absolute;
+ top: 0;
+ left: 50%;
+ width: 0;
+ height: 0;
+ margin-top: px2rem(-4px);
+ margin-left: px2rem(-4px);
+ border: px2rem(4px) solid transparent;
+ border-top: 0;
+ border-bottom-color: var(--md-default-bg-color);
+ content: "";
+ }
+ }
+
+ // Selection list
+ &__list {
+ max-height: inherit;
+ margin: 0;
+ padding: 0;
+ overflow: auto;
+ font-size: px2rem(16px);
+ list-style-type: none;
+ border-radius: px2rem(2px);
+ }
+
+ // Selection item
+ &__item {
+ line-height: px2rem(36px);
+ }
+
+ // Selection link
+ &__link {
+ display: block;
+ width: 100%;
+ padding-inline: px2rem(12px) px2rem(24px);
+ outline: none;
+ cursor: pointer;
+ transition:
+ background-color 250ms,
+ color 250ms;
+ scroll-snap-align: start;
+
+ // Link on focus/hover
+ &:is(:focus, :hover) {
+ color: var(--md-accent-fg-color);
+ }
+
+ // Link on focus
+ &:focus {
+ background-color: var(--md-default-fg-color--lightest);
+ }
+ }
+}
diff --git a/src/assets/stylesheets/main/layout/_sidebar.scss b/src/assets/stylesheets/main/layout/_sidebar.scss
@@ -0,0 +1,181 @@
+////
+/// Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+///
+/// Permission is hereby granted, free of charge, to any person obtaining a
+/// copy of this software and associated documentation files (the "Software"),
+/// to deal in the Software without restriction, including without limitation
+/// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+/// and/or sell copies of the Software, and to permit persons to whom the
+/// Software is furnished to do so, subject to the following conditions:
+///
+/// The above copyright notice and this permission notice shall be included in
+/// all copies or substantial portions of the Software.
+///
+/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+/// DEALINGS
+////
+
+// ----------------------------------------------------------------------------
+// Rules
+// ----------------------------------------------------------------------------
+
+// Sidebar
+.md-sidebar {
+ position: sticky;
+ top: px2rem(48px);
+ flex-shrink: 0;
+ align-self: flex-start;
+ width: px2rem(242px);
+ padding: px2rem(24px) 0;
+
+ // [print]: Hide sidebar
+ @media print {
+ display: none;
+ }
+
+ // [tablet -]: Show navigation as drawer
+ @include break-to-device(tablet) {
+
+ // Primary sidebar with navigation
+ &--primary {
+ position: fixed;
+ top: 0;
+ inset-inline-start: px2rem(-242px);
+ z-index: 5;
+ display: block;
+ width: px2rem(242px);
+ height: 100%;
+ background-color: var(--md-default-bg-color);
+ transform: translateX(0);
+ transition:
+ transform 250ms cubic-bezier(0.4, 0, 0.2, 1),
+ box-shadow 250ms;
+
+ // Show sidebar when drawer is active
+ [data-md-toggle="drawer"]:checked ~ .md-container & {
+ box-shadow: var(--md-shadow-z3);
+ transform: translateX(px2rem(242px));
+
+ // Adjust for right-to-left languages
+ [dir="rtl"] & {
+ transform: translateX(px2rem(-242px));
+ }
+ }
+
+ // Stretch scroll wrapper for primary sidebar
+ .md-sidebar__scrollwrap {
+ position: absolute;
+ inset: 0;
+ margin: 0;
+ scroll-snap-type: none;
+ overflow: hidden;
+ }
+ }
+ }
+
+ // [screen +]: Show navigation as sidebar
+ @include break-from-device(screen) {
+ height: 0;
+
+ // [no-js]: Switch to native sticky behavior
+ .no-js & {
+ height: auto;
+ }
+ }
+
+ // Secondary sidebar with table of contents
+ &--secondary {
+ display: none;
+ order: 2;
+
+ // [tablet landscape +]: Show table of contents as sidebar
+ @include break-from-device(tablet landscape) {
+ height: 0;
+
+ // [no-js]: Switch to native sticky behavior
+ .no-js & {
+ height: auto;
+ }
+
+ // Sidebar is visible
+ &:not([hidden]) {
+ display: block;
+ }
+
+ // Ensure smooth scrolling on iOS
+ .md-sidebar__scrollwrap {
+ touch-action: pan-y;
+ }
+ }
+ }
+
+ // Sidebar scroll wrapper
+ &__scrollwrap {
+ margin: 0 px2rem(4px);
+ overflow-y: auto;
+ // Hack: promote to own layer to reduce jitter
+ backface-visibility: hidden;
+ // Hack: Chrome 81+ exhibits a strange bug, where it scrolls the container
+ // to the bottom if `scroll-snap-type` is set on the initial render. For
+ // this reason, we disable scroll snapping until this is resolved (#1667).
+ // scroll-snap-type: y mandatory;
+ scrollbar-width: thin;
+ scrollbar-color: var(--md-default-fg-color--lighter) transparent;
+
+ // Sidebar scroll wrapper on hover
+ &:hover {
+ scrollbar-color: var(--md-accent-fg-color) transparent;
+ }
+
+ // Webkit scrollbar
+ &::-webkit-scrollbar {
+ width: px2rem(4px);
+ height: px2rem(4px);
+ }
+
+ // Webkit scrollbar thumb
+ &::-webkit-scrollbar-thumb {
+ background-color: var(--md-default-fg-color--lighter);
+
+ // Webkit scrollbar thumb on hover
+ &:hover {
+ background-color: var(--md-accent-fg-color);
+ }
+ }
+ }
+}
+
+// [tablet -]: Show overlay on active drawer
+@include break-to-device(tablet) {
+
+ // Drawer overlay
+ .md-overlay {
+ position: fixed;
+ top: 0;
+ z-index: 5;
+ width: 0;
+ height: 0;
+ background-color: hsla(0, 0%, 0%, 0.54);
+ opacity: 0;
+ transition:
+ width 0ms 250ms,
+ height 0ms 250ms,
+ opacity 250ms;
+
+ // Show overlay when drawer is active
+ [data-md-toggle="drawer"]:checked ~ & {
+ width: 100%;
+ height: 100%;
+ opacity: 1;
+ transition:
+ width 0ms,
+ height 0ms,
+ opacity 250ms;
+ }
+ }
+}
diff --git a/src/assets/stylesheets/main/layout/_source.scss b/src/assets/stylesheets/main/layout/_source.scss
@@ -0,0 +1,181 @@
+////
+/// Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+///
+/// Permission is hereby granted, free of charge, to any person obtaining a
+/// copy of this software and associated documentation files (the "Software"),
+/// to deal in the Software without restriction, including without limitation
+/// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+/// and/or sell copies of the Software, and to permit persons to whom the
+/// Software is furnished to do so, subject to the following conditions:
+///
+/// The above copyright notice and this permission notice shall be included in
+/// all copies or substantial portions of the Software.
+///
+/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+/// DEALINGS
+////
+
+// ----------------------------------------------------------------------------
+// Keyframes
+// ----------------------------------------------------------------------------
+
+// Show repository facts
+@keyframes facts {
+ 0% {
+ height: 0;
+ }
+
+ 100% {
+ height: px2rem(13px);
+ }
+}
+
+// Show repository fact
+@keyframes fact {
+ 0% {
+ transform: translateY(100%);
+ opacity: 0;
+ }
+
+ 50% {
+ opacity: 0;
+ }
+
+ 100% {
+ transform: translateY(0%);
+ opacity: 1;
+ }
+}
+
+// ----------------------------------------------------------------------------
+// Rules
+// ----------------------------------------------------------------------------
+
+// Repository information variables
+:root {
+ --md-source-forks-icon: svg-load("octicons/repo-forked-16.svg");
+ --md-source-repositories-icon: svg-load("octicons/repo-16.svg");
+ --md-source-stars-icon: svg-load("octicons/star-16.svg");
+ --md-source-version-icon: svg-load("octicons/tag-16.svg");
+}
+
+// ----------------------------------------------------------------------------
+
+// Repository information
+.md-source {
+ display: block;
+ font-size: px2rem(13px);
+ line-height: 1.2;
+ white-space: nowrap;
+ outline-color: var(--md-accent-fg-color);
+ // Hack: promote to own layer to reduce jitter
+ backface-visibility: hidden;
+ transition: opacity 250ms;
+
+ // Repository information on hover
+ &:hover {
+ opacity: 0.7;
+ }
+
+ // Repository icon
+ &__icon {
+ display: inline-block;
+ width: px2rem(40px);
+ height: px2rem(48px);
+ vertical-align: middle;
+
+ // Align with margin only (as opposed to normal button alignment)
+ svg {
+ margin-top: px2rem(12px);
+ margin-inline-start: px2rem(12px);
+ }
+
+ // Adjust spacing if icon is present
+ + .md-source__repository {
+ margin-inline-start: px2rem(-40px);
+ padding-inline-start: px2rem(40px);
+ }
+ }
+
+ // Repository name
+ &__repository {
+ display: inline-block;
+ max-width: calc(100% - #{px2rem(24px)});
+ margin-inline-start: px2rem(12px);
+ overflow: hidden;
+ text-overflow: ellipsis;
+ vertical-align: middle;
+ }
+
+ // Repository facts
+ &__facts {
+ display: flex;
+ gap: px2rem(8px);
+ width: 100%;
+ margin: px2rem(2px) 0 0;
+ padding: 0;
+ overflow: hidden;
+ font-size: px2rem(11px);
+ list-style-type: none;
+ opacity: 0.75;
+
+ // Show after the data was loaded
+ .md-source__repository--active & {
+ animation: facts 250ms ease-in;
+ }
+ }
+
+ // Repository fact
+ &__fact {
+ overflow: hidden;
+ text-overflow: ellipsis;
+
+ // Show after the data was loaded
+ .md-source__repository--active & {
+ animation: fact 400ms ease-out;
+ }
+
+ // Repository fact icon
+ &::before {
+ display: inline-block;
+ width: px2rem(12px);
+ height: px2rem(12px);
+ margin-inline-end: px2rem(2px);
+ vertical-align: text-top;
+ background-color: currentcolor;
+ mask-repeat: no-repeat;
+ mask-size: contain;
+ content: "";
+ }
+
+ // Adjust spacing for 2nd+ fact
+ &:nth-child(1n+2) {
+ flex-shrink: 0;
+ }
+
+ // Repository fact: version
+ &--version::before {
+ mask-image: var(--md-source-version-icon);
+ }
+
+ // Repository fact: stars
+ &--stars::before {
+ mask-image: var(--md-source-stars-icon);
+ }
+
+ // Repository fact: forks
+ &--forks::before {
+ mask-image: var(--md-source-forks-icon);
+ }
+
+ // Repository fact: repositories
+ &--repositories::before {
+ mask-image: var(--md-source-repositories-icon);
+ }
+ }
+}
diff --git a/src/assets/stylesheets/main/layout/_tabs.scss b/src/assets/stylesheets/main/layout/_tabs.scss
@@ -0,0 +1,110 @@
+////
+/// Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+///
+/// Permission is hereby granted, free of charge, to any person obtaining a
+/// copy of this software and associated documentation files (the "Software"),
+/// to deal in the Software without restriction, including without limitation
+/// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+/// and/or sell copies of the Software, and to permit persons to whom the
+/// Software is furnished to do so, subject to the following conditions:
+///
+/// The above copyright notice and this permission notice shall be included in
+/// all copies or substantial portions of the Software.
+///
+/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+/// DEALINGS
+////
+
+// ----------------------------------------------------------------------------
+// Rules
+// ----------------------------------------------------------------------------
+
+// Navigation tabs
+.md-tabs {
+ // Must be higher than the z-index of the back-to-top button, or the button
+ // will overlay the navigation tabs bar when scrolling up fast.
+ z-index: 3;
+ display: block;
+ width: 100%;
+ overflow: auto;
+ color: var(--md-primary-bg-color);
+ line-height: 1.3;
+ background-color: var(--md-primary-fg-color);
+
+ // [print]: Hide tabs
+ @media print {
+ display: none;
+ }
+
+ // [tablet -]: Hide tabs
+ @include break-to-device(tablet) {
+ display: none;
+ }
+
+ // Navigation tabs are hidden
+ &[hidden] {
+ pointer-events: none;
+ }
+
+ // Navigation tabs list
+ &__list {
+ margin: 0;
+ margin-inline-start: px2rem(4px);
+ padding: 0;
+ white-space: nowrap;
+ list-style: none;
+ contain: content;
+ }
+
+ // Navigation tabs item
+ &__item {
+ display: inline-block;
+ height: px2rem(48px);
+ padding-inline: px2rem(12px);
+ }
+
+ // Navigation tabs link - could be defined as block elements and aligned via
+ // line height, but this would imply more repaints when scrolling
+ &__link {
+ display: block;
+ margin-top: px2rem(16px);
+ font-size: px2rem(14px);
+ outline-color: var(--md-accent-fg-color);
+ outline-offset: px2rem(4px);
+ // Hack: save a repaint when tabs are appearing on scrolling up
+ backface-visibility: hidden;
+ opacity: 0.7;
+ transition:
+ transform 400ms cubic-bezier(0.1, 0.7, 0.1, 1),
+ opacity 250ms;
+
+ // Active link and link on focus/hover
+ &--active,
+ &:is(:focus, :hover) {
+ color: inherit;
+ opacity: 1;
+ }
+
+ // Delay transitions by a small amount
+ @for $i from 2 through 16 {
+ .md-tabs__item:nth-child(#{$i}) & {
+ transition-delay: 20ms * ($i - 1);
+ }
+ }
+
+ // Hide tabs upon scrolling - disable transition to minimizes repaints
+ // while scrolling down, while scrolling up seems to be okay
+ .md-tabs[hidden] & {
+ transform: translateY(50%);
+ opacity: 0;
+ transition:
+ transform 0ms 100ms,
+ opacity 100ms;
+ }
+ }
+}
diff --git a/src/assets/stylesheets/main/layout/_tag.scss b/src/assets/stylesheets/main/layout/_tag.scss
@@ -0,0 +1,65 @@
+////
+/// Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+///
+/// Permission is hereby granted, free of charge, to any person obtaining a
+/// copy of this software and associated documentation files (the "Software"),
+/// to deal in the Software without restriction, including without limitation
+/// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+/// and/or sell copies of the Software, and to permit persons to whom the
+/// Software is furnished to do so, subject to the following conditions:
+///
+/// The above copyright notice and this permission notice shall be included in
+/// all copies or substantial portions of the Software.
+///
+/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+/// DEALINGS
+////
+
+// ----------------------------------------------------------------------------
+// Rules
+// ----------------------------------------------------------------------------
+
+// Tag list
+.md-tags {
+ margin-bottom: px2em(12px);
+}
+
+// Tag
+.md-tag {
+ display: inline-block;
+ margin-inline-end: 0.5em;
+ margin-bottom: 0.5em;
+ padding: px2em(4px, 12.8px) px2em(12px, 12.8px);
+ font-weight: 700;
+ font-size: px2rem(12.8px);
+ line-height: 1.6;
+ background: var(--md-default-fg-color--lightest);
+ border-radius: px2rem(8px);
+
+ // Linked tag
+ &[href] {
+ color: inherit;
+ outline: none;
+ -webkit-tap-highlight-color: transparent;
+ transition:
+ color 125ms,
+ background-color 125ms;
+
+ // Linked tag on focus/hover
+ &:focus,
+ &:hover {
+ color: var(--md-accent-bg-color);
+ background-color: var(--md-accent-fg-color);
+ }
+ }
+
+ // Tag inside headline
+ [id] > & {
+ vertical-align: text-top;
+ }
+}
diff --git a/src/assets/stylesheets/main/layout/_tooltip.scss b/src/assets/stylesheets/main/layout/_tooltip.scss
@@ -0,0 +1,253 @@
+////
+/// Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+///
+/// Permission is hereby granted, free of charge, to any person obtaining a
+/// copy of this software and associated documentation files (the "Software"),
+/// to deal in the Software without restriction, including without limitation
+/// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+/// and/or sell copies of the Software, and to permit persons to whom the
+/// Software is furnished to do so, subject to the following conditions:
+///
+/// The above copyright notice and this permission notice shall be included in
+/// all copies or substantial portions of the Software.
+///
+/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+/// DEALINGS
+////
+
+// ----------------------------------------------------------------------------
+// Keyframes
+// ----------------------------------------------------------------------------
+
+// Continuous pulse animation
+@keyframes pulse {
+ 0% {
+ box-shadow: 0 0 0 0 var(--md-default-fg-color--lightest);
+ transform: scale(0.95);
+ }
+
+ 75% {
+ box-shadow: 0 0 0 px2em(10px) transparent;
+ transform: scale(1);
+ }
+
+ 100% {
+ box-shadow: 0 0 0 0 transparent;
+ transform: scale(0.95);
+ }
+}
+
+// ----------------------------------------------------------------------------
+// Rules
+// ----------------------------------------------------------------------------
+
+// Tooltip variables
+:root {
+ --md-tooltip-width: #{px2rem(400px)};
+}
+
+// ----------------------------------------------------------------------------
+
+// Tooltip
+.md-tooltip {
+ position: absolute;
+ top: var(--md-tooltip-y);
+ left:
+ clamp(
+ var(--md-tooltip-0, #{px2rem(0px)}) + #{px2rem(16px)},
+ var(--md-tooltip-x),
+ 100vw +
+ var(--md-tooltip-0, #{px2rem(0px)}) + #{px2rem(16px)} -
+ var(--md-tooltip-width) -
+ 2 * #{px2rem(16px)}
+ );
+ // Hack: set an explicit `z-index` so we can transition it to ensure that any
+ // following elements are not overlaying the tooltip during the transition.
+ z-index: 0;
+ width: var(--md-tooltip-width);
+ max-width: calc(100vw - 2 * #{px2rem(16px)});
+ max-height: 0;
+ color: var(--md-default-fg-color);
+ background-color: var(--md-default-bg-color);
+ border-radius: px2rem(2px);
+ box-shadow: var(--md-shadow-z2);
+ transform: translateY(px2rem(-8px));
+ // Hack: promote to own layer to reduce jitter
+ backface-visibility: hidden;
+ opacity: 0;
+ transition:
+ transform 0ms 250ms,
+ opacity 250ms,
+ max-height 0ms 250ms,
+ z-index 250ms;
+
+ // Tooltip on parent focus
+ :focus-within > & {
+ max-height: 1000%;
+ transform: translateY(0);
+ opacity: 1;
+ transition:
+ transform 250ms cubic-bezier(0.1, 0.7, 0.1, 1),
+ opacity 250ms,
+ max-height 250ms,
+ z-index 0ms;
+ }
+
+ // Show outline for keyboard devices
+ .focus-visible > & {
+ outline: var(--md-accent-fg-color) auto;
+ }
+
+ // Tooltip wrapper
+ &__inner {
+ padding: px2rem(16px);
+ font-size: px2rem(12.8px);
+
+ // Adjust spacing on first child
+ &.md-typeset > :first-child {
+ margin-top: 0;
+ }
+
+ // Adjust spacing on last child
+ &.md-typeset > :last-child {
+ margin-bottom: 0;
+ }
+ }
+}
+
+// ----------------------------------------------------------------------------
+
+// Annotation
+.md-annotation {
+ white-space: normal;
+ outline: none;
+
+ // Adjust for right-to-left languages
+ [dir="rtl"] & {
+ direction: rtl;
+ }
+
+ // Annotation is not hidden (e.g. when copying)
+ &:not([hidden]) {
+ display: inline-block;
+ // Hack: ensure that the line height doesn't exceed the line height of the
+ // hosting line, because it will lead to dancing pixels.
+ line-height: 1.325;
+ }
+
+ // Promote children to top on focus
+ &:focus-within > * {
+ z-index: 2;
+ }
+
+ // Annotation wrapper (= tooltip)
+ &__inner {
+ top: calc(var(--md-tooltip-y) + 1.2ch);
+ font-family: var(--md-text-font-family);
+
+ // Annotation tooltip when not focused
+ :not(:focus-within) > & {
+ user-select: none;
+ pointer-events: none;
+ }
+ }
+
+ // Annotation index
+ &__index {
+ position: relative;
+ z-index: 0;
+ margin: 0 1ch;
+ color: hsla(0, 0%, 100%, 1);
+ cursor: pointer;
+ transition: z-index 250ms;
+ user-select: none;
+
+ // Annotation marker – the marker must be positioned absolutely behind
+ // the index, because it shouldn't impact the rendering of a code block.
+ // Otherwise, small rounding differences in browsers can sometimes mess up
+ // alignment of text following an annotation.
+ &::after {
+ position: absolute;
+ left: -0.126em;
+ z-index: -1;
+ // Hack: the first property is used as a fallback for older browsers
+ // which don't support the min/max/clamp math functions.
+ width: calc(100% + 1.2ch);
+ width: max(2.2ch, 100% + 1.2ch);
+ height: 2.2ch;
+ margin: 0 -0.4ch;
+ padding: 0 0.4ch;
+ background-color: var(--md-default-fg-color--lighter);
+ border-radius: 2ch;
+ transition:
+ color 250ms,
+ background-color 250ms;
+ content: "";
+
+ // [reduced motion]: Disable animation
+ @media not all and (prefers-reduced-motion) {
+
+ // Annotation marker is visible
+ [data-md-visible] > & {
+ animation: pulse 2000ms infinite;
+ }
+ }
+
+ // Annotation marker on focus/hover
+ :is(:focus-within, :hover) > & {
+ background-color: var(--md-accent-fg-color);
+ }
+
+ // Annotation marker on focus
+ :focus-within > & {
+ transition:
+ color 250ms,
+ background-color 250ms;
+ animation: none;
+ }
+ }
+
+ // Annotation marker
+ [data-md-annotation-id] {
+ display: inline-block;
+ line-height: 90%;
+
+ // Annotation marker content
+ &::before {
+ display: inline-block;
+ padding-bottom: 0.1em;
+ vertical-align: 0.065em;
+ transform: scale(1.15);
+ transition: transform 400ms cubic-bezier(0.1, 0.7, 0.1, 1);
+ content: attr(data-md-annotation-id);
+
+ // [not print]: if we're not in print mode, show a `+` sign instead of
+ // the original numbers, as context is already given by the position.
+ @media not print {
+ content: "+";
+
+ // Annotation marker content on focus
+ :focus-within > & {
+ transform: scale(1.25) rotate(45deg);
+ }
+ }
+ }
+ }
+
+ // Annotation index on focus/hover
+ :is(:focus-within, :hover) > & {
+ color: var(--md-accent-bg-color);
+ }
+
+ // Annotation index on focus
+ :focus-within > & {
+ transition: none;
+ animation: none;
+ }
+ }
+}
diff --git a/src/assets/stylesheets/main/layout/_top.scss b/src/assets/stylesheets/main/layout/_top.scss
@@ -0,0 +1,82 @@
+////
+/// Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+///
+/// Permission is hereby granted, free of charge, to any person obtaining a
+/// copy of this software and associated documentation files (the "Software"),
+/// to deal in the Software without restriction, including without limitation
+/// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+/// and/or sell copies of the Software, and to permit persons to whom the
+/// Software is furnished to do so, subject to the following conditions:
+///
+/// The above copyright notice and this permission notice shall be included in
+/// all copies or substantial portions of the Software.
+///
+/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+/// DEALINGS
+////
+
+// ----------------------------------------------------------------------------
+// Rules
+// ----------------------------------------------------------------------------
+
+// Back-to-top button
+.md-top {
+ position: fixed;
+ top: px2rem(48px + 16px);
+ z-index: 2;
+ display: block;
+ margin-inline-start: 50%;
+ padding: px2rem(8px) px2rem(16px);
+ color: var(--md-default-fg-color--light);
+ font-size: px2rem(14px);
+ background-color: var(--md-default-bg-color);
+ border-radius: px2rem(32px);
+ outline: none;
+ box-shadow: var(--md-shadow-z2);
+ transform: translate(-50%, 0);
+ transition:
+ color 125ms,
+ background-color 125ms,
+ transform 125ms cubic-bezier(0.4, 0, 0.2, 1),
+ opacity 125ms;
+
+ // [print]: Hide back-to-top button
+ @media print {
+ display: none;
+ }
+
+ // Adjust for right-to-left languages
+ [dir="rtl"] & {
+ transform: translate(50%, 0);
+ }
+
+ // Back-to-top button is hidden
+ &[hidden] {
+ transform: translate(-50%, px2rem(4px));
+ opacity: 0;
+ transition-duration: 0ms;
+ pointer-events: none;
+
+ // Adjust for right-to-left languages
+ [dir="rtl"] & {
+ transform: translate(50%, px2rem(4px));
+ }
+ }
+
+ // Back-to-top button on focus/hover
+ &:is(:focus, :hover) {
+ color: var(--md-accent-bg-color);
+ background-color: var(--md-accent-fg-color);
+ }
+
+ // Inline icon
+ svg {
+ display: inline-block;
+ vertical-align: -0.5em;
+ }
+}
diff --git a/src/assets/stylesheets/main/layout/_version.scss b/src/assets/stylesheets/main/layout/_version.scss
@@ -0,0 +1,149 @@
+////
+/// Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+///
+/// Permission is hereby granted, free of charge, to any person obtaining a
+/// copy of this software and associated documentation files (the "Software"),
+/// to deal in the Software without restriction, including without limitation
+/// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+/// and/or sell copies of the Software, and to permit persons to whom the
+/// Software is furnished to do so, subject to the following conditions:
+///
+/// The above copyright notice and this permission notice shall be included in
+/// all copies or substantial portions of the Software.
+///
+/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+/// DEALINGS
+////
+
+// ----------------------------------------------------------------------------
+// Keyframes
+// ----------------------------------------------------------------------------
+
+// See https://github.com/squidfunk/mkdocs-material/issues/2429
+@keyframes hoverfix {
+ 0% {
+ pointer-events: none;
+ }
+}
+
+// ----------------------------------------------------------------------------
+// Rules
+// ----------------------------------------------------------------------------
+
+// Version selection variables
+:root {
+ --md-version-icon: svg-load("fontawesome/solid/caret-down.svg");
+}
+
+// ----------------------------------------------------------------------------
+
+// Version selection
+.md-version {
+ flex-shrink: 0;
+ height: px2rem(48px);
+ font-size: px2rem(16px);
+
+ // Current selection
+ &__current {
+ position: relative;
+ // Hack: in general, we would use `vertical-align` to align the version at
+ // the bottom with the title, but since the list uses absolute positioning,
+ // this won't work consistently. Furthermore, we would need to use inline
+ // positioning to align the links, which looks jagged.
+ top: px2rem(1px);
+ margin-inline: px2rem(28px) px2rem(8px);
+ color: inherit;
+ outline: none;
+ cursor: pointer;
+
+ // Version selection icon
+ &::after {
+ display: inline-block;
+ width: px2rem(8px);
+ height: px2rem(12px);
+ margin-inline-start: px2rem(8px);
+ background-color: currentcolor;
+ mask-image: var(--md-version-icon);
+ mask-repeat: no-repeat;
+ content: "";
+ }
+ }
+
+ // Version selection list
+ &__list {
+ position: absolute;
+ top: px2rem(3px);
+ z-index: 3;
+ max-height: 0;
+ margin: px2rem(4px) px2rem(16px);
+ padding: 0;
+ overflow: auto;
+ color: var(--md-default-fg-color);
+ list-style-type: none;
+ background-color: var(--md-default-bg-color);
+ border-radius: px2rem(2px);
+ box-shadow: var(--md-shadow-z2);
+ opacity: 0;
+ transition:
+ max-height 0ms 500ms,
+ opacity 250ms 250ms;
+ scroll-snap-type: y mandatory;
+
+ // Version selection list on parent focus/hover
+ .md-version:is(:focus-within, :hover) & {
+ max-height: px2rem(200px);
+ opacity: 1;
+ transition:
+ max-height 0ms,
+ opacity 250ms;
+ }
+
+ // Fix hover on touch devices
+ @media (pointer: coarse) {
+
+ // Switch off on hover
+ .md-version:hover & {
+ animation: hoverfix 250ms forwards;
+ }
+
+ // Enable on focus
+ .md-version:focus-within & {
+ animation: none;
+ }
+ }
+ }
+
+ // Version selection item
+ &__item {
+ line-height: px2rem(36px);
+ }
+
+ // Version selection link
+ &__link {
+ display: block;
+ width: 100%;
+ padding-inline: px2rem(12px) px2rem(24px);
+ white-space: nowrap;
+ outline: none;
+ cursor: pointer;
+ transition:
+ color 250ms,
+ background-color 250ms;
+ scroll-snap-align: start;
+
+ // Link on focus/hover
+ &:is(:focus, :hover) {
+ color: var(--md-accent-fg-color);
+ }
+
+ // Link on focus
+ &:focus {
+ background-color: var(--md-default-fg-color--lightest);
+ }
+ }
+}
diff --git a/src/assets/stylesheets/palette.scss b/src/assets/stylesheets/palette.scss
@@ -0,0 +1,40 @@
+////
+/// Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+///
+/// Permission is hereby granted, free of charge, to any person obtaining a
+/// copy of this software and associated documentation files (the "Software"),
+/// to deal in the Software without restriction, including without limitation
+/// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+/// and/or sell copies of the Software, and to permit persons to whom the
+/// Software is furnished to do so, subject to the following conditions:
+///
+/// The above copyright notice and this permission notice shall be included in
+/// all copies or substantial portions of the Software.
+///
+/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+/// DEALINGS
+////
+
+// ----------------------------------------------------------------------------
+// Dependencies
+// ----------------------------------------------------------------------------
+
+@import "material-color";
+
+// ----------------------------------------------------------------------------
+// Local imports
+// ----------------------------------------------------------------------------
+
+@import "utilities/break";
+@import "utilities/convert";
+
+@import "config";
+
+@import "palette/scheme";
+@import "palette/accent";
+@import "palette/primary";
diff --git a/src/assets/stylesheets/palette/_accent.scss b/src/assets/stylesheets/palette/_accent.scss
@@ -0,0 +1,61 @@
+////
+/// Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+///
+/// Permission is hereby granted, free of charge, to any person obtaining a
+/// copy of this software and associated documentation files (the "Software"),
+/// to deal in the Software without restriction, including without limitation
+/// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+/// and/or sell copies of the Software, and to permit persons to whom the
+/// Software is furnished to do so, subject to the following conditions:
+///
+/// The above copyright notice and this permission notice shall be included in
+/// all copies or substantial portions of the Software.
+///
+/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+/// DEALINGS
+////
+
+// ----------------------------------------------------------------------------
+// Rules
+// ----------------------------------------------------------------------------
+
+// Define accent colors
+@each $name, $color in (
+ "red": $clr-red-a400,
+ "pink": $clr-pink-a400,
+ "purple": $clr-purple-a200,
+ "deep-purple": $clr-deep-purple-a200,
+ "indigo": $clr-indigo-a200,
+ "blue": $clr-blue-a200,
+ "light-blue": $clr-light-blue-a700,
+ "cyan": $clr-cyan-a700,
+ "teal": $clr-teal-a700,
+ "green": $clr-green-a700,
+ "light-green": $clr-light-green-a700,
+ "lime": $clr-lime-a700,
+ "yellow": $clr-yellow-a700,
+ "amber": $clr-amber-a700,
+ "orange": $clr-orange-a400,
+ "deep-orange": $clr-deep-orange-a200
+) {
+
+ // Color palette
+ [data-md-color-accent="#{$name}"] {
+ --md-accent-fg-color: hsla(#{hex2hsl($color)}, 1);
+ --md-accent-fg-color--transparent: hsla(#{hex2hsl($color)}, 0.1);
+
+ // Inverted text for lighter shades
+ @if index("lime" "yellow" "amber" "orange", $name) {
+ --md-accent-bg-color: hsla(0, 0%, 0%, 0.87);
+ --md-accent-bg-color--light: hsla(0, 0%, 0%, 0.54);
+ } @else {
+ --md-accent-bg-color: hsla(0, 0%, 100%, 1);
+ --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7);
+ }
+ }
+}
diff --git a/src/assets/stylesheets/palette/_primary.scss b/src/assets/stylesheets/palette/_primary.scss
@@ -0,0 +1,193 @@
+////
+/// Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+///
+/// Permission is hereby granted, free of charge, to any person obtaining a
+/// copy of this software and associated documentation files (the "Software"),
+/// to deal in the Software without restriction, including without limitation
+/// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+/// and/or sell copies of the Software, and to permit persons to whom the
+/// Software is furnished to do so, subject to the following conditions:
+///
+/// The above copyright notice and this permission notice shall be included in
+/// all copies or substantial portions of the Software.
+///
+/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+/// DEALINGS
+////
+
+@use "sass:list";
+
+// ----------------------------------------------------------------------------
+// Rules
+// ----------------------------------------------------------------------------
+
+// Define primary colors
+@each $name, $colors in (
+ "red": $clr-red-400 $clr-red-300 $clr-red-600,
+ "pink": $clr-pink-500 $clr-pink-400 $clr-pink-700,
+ "purple": $clr-purple-400 $clr-purple-300 $clr-purple-600,
+ "deep-purple": $clr-deep-purple-400 $clr-deep-purple-300 $clr-deep-purple-500,
+ "indigo": $clr-indigo-500 $clr-indigo-400 $clr-indigo-700,
+ "blue": $clr-blue-500 $clr-blue-400 $clr-blue-700,
+ "light-blue": $clr-light-blue-500 $clr-light-blue-400 $clr-light-blue-700,
+ "cyan": $clr-cyan-500 $clr-cyan-400 $clr-cyan-700,
+ "teal": $clr-teal-500 $clr-teal-400 $clr-teal-700,
+ "green": $clr-green-500 $clr-green-400 $clr-green-700,
+ "light-green": $clr-light-green-500 $clr-light-green-400 $clr-light-green-700,
+ "lime": $clr-lime-500 $clr-lime-400 $clr-lime-700,
+ "yellow": $clr-yellow-500 $clr-yellow-400 $clr-yellow-700,
+ "amber": $clr-amber-500 $clr-amber-400 $clr-amber-700,
+ "orange": $clr-orange-400 $clr-orange-400 $clr-orange-600,
+ "deep-orange": $clr-deep-orange-400 $clr-deep-orange-300 $clr-deep-orange-600,
+ "brown": $clr-brown-500 $clr-brown-400 $clr-brown-700,
+ "grey": $clr-grey-600 $clr-grey-500 $clr-grey-700,
+ "blue-grey": $clr-blue-grey-600 $clr-blue-grey-500 $clr-blue-grey-700
+) {
+
+ // Color palette
+ [data-md-color-primary="#{$name}"] {
+ --md-primary-fg-color: hsl(#{hex2hsl(list.nth($colors, 1))});
+ --md-primary-fg-color--light: hsl(#{hex2hsl(list.nth($colors, 2))});
+ --md-primary-fg-color--dark: hsl(#{hex2hsl(list.nth($colors, 3))});
+
+ // Inverted text for lighter shades
+ @if index("lime" "yellow" "amber" "orange", $name) {
+ --md-primary-bg-color: hsla(0, 0%, 0%, 0.87);
+ --md-primary-bg-color--light: hsla(0, 0%, 0%, 0.54);
+ } @else {
+ --md-primary-bg-color: hsla(0, 0%, 100%, 1);
+ --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);
+ }
+
+ // Typeset color shades
+ @if index("grey" "blue-grey", $name) {
+ --md-typeset-a-color: hsl(#{hex2hsl($clr-indigo-500)});
+ }
+ }
+}
+
+// ----------------------------------------------------------------------------
+
+// Adjust link colors for light primary colors
+@each $name, $color in (
+ "light-green": hsl(88, 58%, 43%),
+ "lime": hsl(66, 88%, 32%),
+ "yellow": hsl(54, 100%, 36%),
+ "amber": hsl(45, 100%, 41%),
+ "orange": hsl(36, 100%, 45%)
+) {
+ [data-md-color-primary="#{$name}"]:not([data-md-color-scheme="slate"]) {
+ --md-typeset-a-color: #{$color};
+ }
+}
+
+// ----------------------------------------------------------------------------
+// Rules: white
+// ----------------------------------------------------------------------------
+
+// Define primary colors for white
+[data-md-color-primary="white"] {
+ --md-primary-fg-color: hsla(0, 0%, 100%, 1);
+ --md-primary-fg-color--light: hsla(0, 0%, 100%, 0.7);
+ --md-primary-fg-color--dark: hsla(0, 0%, 0%, 0.07);
+ --md-primary-bg-color: hsla(0, 0%, 0%, 0.87);
+ --md-primary-bg-color--light: hsla(0, 0%, 0%, 0.54);
+
+ // Typeset `a` color shades
+ --md-typeset-a-color: hsl(#{hex2hsl($clr-indigo-500)});
+
+ // [tablet portrait +]: Header-embedded search
+ @include break-from-device(tablet landscape) {
+
+ // Search form
+ .md-search__form {
+ background-color: hsla(0, 0%, 0%, 0.07);
+
+ // Search form on hover
+ &:hover {
+ background-color: hsla(0, 0%, 0%, 0.32);
+ }
+ }
+
+ // Search icon
+ .md-search__input + .md-search__icon {
+ color: hsla(0, 0%, 0%, 0.87);
+ }
+ }
+
+ // [screen +]: Add bottom border for tabs
+ @include break-from-device(screen) {
+
+ // Navigation tabs
+ .md-tabs {
+ border-bottom: px2rem(1px) solid hsla(0, 0%, 0%, 0.07);
+ }
+ }
+}
+
+// ----------------------------------------------------------------------------
+// Rules: black
+// ----------------------------------------------------------------------------
+
+// Define primary colors for black
+[data-md-color-primary="black"] {
+ --md-primary-fg-color: hsla(0, 0%, 0%, 1);
+ --md-primary-fg-color--light: hsla(0, 0%, 0%, 0.54);
+ --md-primary-fg-color--dark: hsla(0, 0%, 0%, 1);
+ --md-primary-bg-color: hsla(0, 0%, 100%, 1);
+ --md-primary-bg-color--light: hsla(0, 0%, 100%, 0.7);
+
+ // Typeset `a` color shades
+ --md-typeset-a-color: hsl(#{hex2hsl($clr-indigo-500)});
+
+ // Header
+ .md-header {
+ background-color: hsla(0, 0%, 0%, 1);
+ }
+
+ // [tablet portrait -]: Layered navigation
+ @include break-to-device(tablet portrait) {
+
+ // Repository information container
+ .md-nav__source {
+ background-color: hsla(0, 0%, 0%, 0.87);
+ }
+ }
+
+ // [tablet landscape +]: Header-embedded search
+ @include break-from-device(tablet landscape) {
+
+ // Search form
+ .md-search__form {
+ background-color: hsla(0, 0%, 100%, 0.12);
+
+ // Search form on hover
+ &:hover {
+ background-color: hsla(0, 0%, 100%, 0.3);
+ }
+ }
+ }
+
+ // [tablet -]: Layered navigation
+ @include break-to-device(tablet) {
+
+ // Site title in main navigation
+ html & .md-nav--primary .md-nav__title[for="__drawer"] {
+ background-color: hsla(0, 0%, 0%, 1);
+ }
+ }
+
+ // [screen +]: Set background color for tabs
+ @include break-from-device(screen) {
+
+ // Navigation tabs
+ .md-tabs {
+ background-color: hsla(0, 0%, 0%, 1);
+ }
+ }
+}
diff --git a/src/assets/stylesheets/palette/_scheme.scss b/src/assets/stylesheets/palette/_scheme.scss
@@ -0,0 +1,152 @@
+////
+/// Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+///
+/// Permission is hereby granted, free of charge, to any person obtaining a
+/// copy of this software and associated documentation files (the "Software"),
+/// to deal in the Software without restriction, including without limitation
+/// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+/// and/or sell copies of the Software, and to permit persons to whom the
+/// Software is furnished to do so, subject to the following conditions:
+///
+/// The above copyright notice and this permission notice shall be included in
+/// all copies or substantial portions of the Software.
+///
+/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+/// DEALINGS
+////
+
+// ----------------------------------------------------------------------------
+// Rules
+// ----------------------------------------------------------------------------
+
+// Only use dark mode on screens
+@media screen {
+
+ // Slate theme, i.e. dark mode
+ [data-md-color-scheme="slate"] {
+
+ // Slate's hue in the range [0,360] - change this variable to alter the tone
+ // of the theme, e.g. to make it more redish or greenish. This is a slate-
+ // specific variable, but the same approach may be adapted to custom themes.
+ --md-hue: 232;
+
+ // Default color shades
+ --md-default-fg-color: hsla(var(--md-hue), 75%, 95%, 1);
+ --md-default-fg-color--light: hsla(var(--md-hue), 75%, 90%, 0.62);
+ --md-default-fg-color--lighter: hsla(var(--md-hue), 75%, 90%, 0.32);
+ --md-default-fg-color--lightest: hsla(var(--md-hue), 75%, 90%, 0.12);
+ --md-default-bg-color: hsla(var(--md-hue), 15%, 21%, 1);
+ --md-default-bg-color--light: hsla(var(--md-hue), 15%, 21%, 0.54);
+ --md-default-bg-color--lighter: hsla(var(--md-hue), 15%, 21%, 0.26);
+ --md-default-bg-color--lightest: hsla(var(--md-hue), 15%, 21%, 0.07);
+
+ // Code color shades
+ --md-code-fg-color: hsla(var(--md-hue), 18%, 86%, 1);
+ --md-code-bg-color: hsla(var(--md-hue), 15%, 15%, 1);
+
+ // Code highlighting color shades
+ --md-code-hl-color: hsla(#{hex2hsl($clr-blue-a200)}, 0.15);
+ --md-code-hl-number-color: hsla(6, 74%, 63%, 1);
+ --md-code-hl-special-color: hsla(340, 83%, 66%, 1);
+ --md-code-hl-function-color: hsla(291, 57%, 65%, 1);
+ --md-code-hl-constant-color: hsla(250, 62%, 70%, 1);
+ --md-code-hl-keyword-color: hsla(219, 66%, 64%, 1);
+ --md-code-hl-string-color: hsla(150, 58%, 44%, 1);
+ --md-code-hl-name-color: var(--md-code-fg-color);
+ --md-code-hl-operator-color: var(--md-default-fg-color--light);
+ --md-code-hl-punctuation-color: var(--md-default-fg-color--light);
+ --md-code-hl-comment-color: var(--md-default-fg-color--light);
+ --md-code-hl-generic-color: var(--md-default-fg-color--light);
+ --md-code-hl-variable-color: var(--md-default-fg-color--light);
+
+ // Typeset color shades
+ --md-typeset-color: var(--md-default-fg-color);
+
+ // Typeset `a` color shades
+ --md-typeset-a-color: var(--md-primary-fg-color);
+
+ // Typeset `mark` color shades
+ --md-typeset-mark-color: hsla(#{hex2hsl($clr-blue-a200)}, 0.3);
+
+ // Typeset `kbd` color shades
+ --md-typeset-kbd-color: hsla(var(--md-hue), 15%, 94%, 0.12);
+ --md-typeset-kbd-accent-color: hsla(var(--md-hue), 15%, 94%, 0.2);
+ --md-typeset-kbd-border-color: hsla(var(--md-hue), 15%, 14%, 1);
+
+ // Typeset `table` color shades
+ --md-typeset-table-color: hsla(var(--md-hue), 75%, 95%, 0.12);
+
+ // Admonition color shades
+ --md-admonition-fg-color: var(--md-default-fg-color);
+ --md-admonition-bg-color: var(--md-default-bg-color);
+
+ // Footer color shades
+ --md-footer-bg-color: hsla(var(--md-hue), 15%, 12%, 0.87);
+ --md-footer-bg-color--dark: hsla(var(--md-hue), 15%, 10%, 1);
+
+ // Shadow depth 1
+ --md-shadow-z1:
+ 0 #{px2rem(4px)} #{px2rem(10px)} hsla(0, 0%, 0%, 0.2),
+ 0 0 #{px2rem(1px)} hsla(0, 0%, 0%, 0.1);
+
+ // Shadow depth 2
+ --md-shadow-z2:
+ 0 #{px2rem(4px)} #{px2rem(10px)} hsla(0, 0%, 0%, 0.3),
+ 0 0 #{px2rem(1px)} hsla(0, 0%, 0%, 0.25);
+
+ // Shadow depth 3
+ --md-shadow-z3:
+ 0 #{px2rem(4px)} #{px2rem(10px)} hsla(0, 0%, 0%, 0.4),
+ 0 0 #{px2rem(1px)} hsla(0, 0%, 0%, 0.35);
+
+ // Hide images for light mode
+ img[src$="#only-light"],
+ img[src$="#gh-light-mode-only"] {
+ display: none;
+ }
+
+ // Show images for dark mode
+ img[src$="#only-dark"],
+ img[src$="#gh-dark-mode-only"] {
+ display: initial;
+ }
+ }
+
+ // --------------------------------------------------------------------------
+
+ // Adjust link colors for dark primary colors
+ @each $name, $color in (
+ "pink": hsl(340, 81%, 63%),
+ "purple": hsl(291, 43%, 63%),
+ "deep-purple": hsl(262, 63%, 70%),
+ "indigo": hsl(219, 56%, 63%),
+ "teal": hsl(174, 100%, 40%),
+ "green": hsl(122, 39%, 60%),
+ "deep-orange": hsl(14, 100%, 73%),
+ "brown": hsl(16, 45%, 60%),
+
+ // Set neutral colors to indigo
+ "grey": hsl(219, 56%, 63%),
+ "blue-grey": hsl(219, 56%, 63%),
+ "white": hsl(219, 56%, 63%),
+ "black": hsl(219, 56%, 63%)
+ ) {
+ [data-md-color-scheme="slate"][data-md-color-primary="#{$name}"] {
+ --md-typeset-a-color: #{$color};
+ }
+ }
+
+ // --------------------------------------------------------------------------
+
+ // Switching in progress - disable all transitions temporarily
+ [data-md-color-switching] *,
+ [data-md-color-switching] *::before,
+ [data-md-color-switching] *::after {
+ transition-duration: 0ms !important; // stylelint-disable-line
+ }
+}
diff --git a/src/assets/stylesheets/utilities/_break.scss b/src/assets/stylesheets/utilities/_break.scss
@@ -0,0 +1,219 @@
+////
+/// Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+///
+/// Permission is hereby granted, free of charge, to any person obtaining a
+/// copy of this software and associated documentation files (the "Software"),
+/// to deal in the Software without restriction, including without limitation
+/// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+/// and/or sell copies of the Software, and to permit persons to whom the
+/// Software is furnished to do so, subject to the following conditions:
+///
+/// The above copyright notice and this permission notice shall be included in
+/// all copies or substantial portions of the Software.
+///
+/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+/// DEALINGS
+////
+
+@use "sass:list";
+@use "sass:map";
+@use "sass:math";
+
+// ----------------------------------------------------------------------------
+// Variables
+// ----------------------------------------------------------------------------
+
+///
+/// Device-specific breakpoints
+///
+/// @example
+/// $break-devices: (
+/// mobile: (
+/// portrait: 220px 479px,
+/// landscape: 480px 719px
+/// ),
+/// tablet: (
+/// portrait: 720px 959px,
+/// landscape: 960px 1219px
+/// ),
+/// screen: (
+/// small: 1220px 1599px,
+/// medium: 1600px 1999px,
+/// large: 2000px
+/// )
+/// );
+///
+$break-devices: () !default;
+
+// ----------------------------------------------------------------------------
+// Helpers
+// ----------------------------------------------------------------------------
+
+///
+/// Choose minimum and maximum device widths
+///
+@function break-select-min-max($devices) {
+ $min: 1000000;
+ $max: 0;
+ @each $key, $value in $devices {
+ @while type-of($value) == map {
+ $value: break-select-min-max($value);
+ }
+ @if type-of($value) == list {
+ @each $number in $value {
+ @if type-of($number) == number {
+ $min: math.min($number, $min);
+ @if $max {
+ $max: math.max($number, $max);
+ }
+ } @else {
+ @error "Invalid number: #{$number}";
+ }
+ }
+ } @else if type-of($value) == number {
+ $min: math.min($value, $min);
+ $max: null;
+ } @else {
+ @error "Invalid value: #{$value}";
+ }
+ }
+ @return $min, $max;
+}
+
+///
+/// Select minimum and maximum widths for a device breakpoint
+///
+@function break-select-device($device) {
+ $current: $break-devices;
+ @for $n from 1 through length($device) {
+ @if type-of($current) == map {
+ $current: map.get($current, list.nth($device, $n));
+ } @else {
+ @error "Invalid device map: #{$devices}";
+ }
+ }
+ @if type-of($current) == list or type-of($current) == number {
+ $current: (default: $current);
+ }
+ @return break-select-min-max($current);
+}
+
+// ----------------------------------------------------------------------------
+// Mixins
+// ----------------------------------------------------------------------------
+
+///
+/// A minimum-maximum media query breakpoint
+///
+@mixin break-at($breakpoint) {
+ @if type-of($breakpoint) == number {
+ @media screen and (min-width: $breakpoint) {
+ @content;
+ }
+ } @else if type-of($breakpoint) == list {
+ $min: list.nth($breakpoint, 1);
+ $max: list.nth($breakpoint, 2);
+ @if type-of($min) == number and type-of($max) == number {
+ @media screen and (min-width: $min) and (max-width: $max) {
+ @content;
+ }
+ } @else {
+ @error "Invalid breakpoint: #{$breakpoint}";
+ }
+ } @else {
+ @error "Invalid breakpoint: #{$breakpoint}";
+ }
+}
+
+///
+/// An orientation media query breakpoint
+///
+@mixin break-at-orientation($breakpoint) {
+ @if type-of($breakpoint) == string {
+ @media screen and (orientation: $breakpoint) {
+ @content;
+ }
+ } @else {
+ @error "Invalid breakpoint: #{$breakpoint}";
+ }
+}
+
+///
+/// A maximum-aspect-ratio media query breakpoint
+///
+@mixin break-at-ratio($breakpoint) {
+ @if type-of($breakpoint) == number {
+ @media screen and (max-aspect-ratio: $breakpoint) {
+ @content;
+ }
+ } @else {
+ @error "Invalid breakpoint: #{$breakpoint}";
+ }
+}
+
+///
+/// A minimum-maximum media query device breakpoint
+///
+@mixin break-at-device($device) {
+ @if type-of($device) == string {
+ $device: $device,;
+ }
+ @if type-of($device) == list {
+ $breakpoint: break-select-device($device);
+ @if list.nth($breakpoint, 2) {
+ $min: list.nth($breakpoint, 1);
+ $max: list.nth($breakpoint, 2);
+
+ @media screen and (min-width: $min) and (max-width: $max) {
+ @content;
+ }
+ } @else {
+ @error "Invalid device: #{$device}";
+ }
+ } @else {
+ @error "Invalid device: #{$device}";
+ }
+}
+
+///
+/// A minimum media query device breakpoint
+///
+@mixin break-from-device($device) {
+ @if type-of($device) == string {
+ $device: $device,;
+ }
+ @if type-of($device) == list {
+ $breakpoint: break-select-device($device);
+ $min: list.nth($breakpoint, 1);
+
+ @media screen and (min-width: $min) {
+ @content;
+ }
+ } @else {
+ @error "Invalid device: #{$device}";
+ }
+}
+
+///
+/// A maximum media query device breakpoint
+///
+@mixin break-to-device($device) {
+ @if type-of($device) == string {
+ $device: $device,;
+ }
+ @if type-of($device) == list {
+ $breakpoint: break-select-device($device);
+ $max: list.nth($breakpoint, 2);
+
+ @media screen and (max-width: $max) {
+ @content;
+ }
+ } @else {
+ @error "Invalid device: #{$device}";
+ }
+}
diff --git a/src/assets/stylesheets/utilities/_convert.scss b/src/assets/stylesheets/utilities/_convert.scss
@@ -0,0 +1,79 @@
+////
+/// Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+///
+/// Permission is hereby granted, free of charge, to any person obtaining a
+/// copy of this software and associated documentation files (the "Software"),
+/// to deal in the Software without restriction, including without limitation
+/// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+/// and/or sell copies of the Software, and to permit persons to whom the
+/// Software is furnished to do so, subject to the following conditions:
+///
+/// The above copyright notice and this permission notice shall be included in
+/// all copies or substantial portions of the Software.
+///
+/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+/// DEALINGS
+////
+
+@use "sass:math";
+
+// ----------------------------------------------------------------------------
+// Helpers
+// ----------------------------------------------------------------------------
+
+///
+/// Strip units from a number
+///
+@function strip-units($number) {
+ @return math.div($number, ($number * 0 + 1));
+}
+
+///
+/// Convert color in HEX to HSL
+///
+/// Note, that we need to strip the `deg` units from the `hue` value, as they
+/// were added in Color Level 4, which not all browsers support.
+///
+@function hex2hsl($color) {
+ @return
+ round(strip-units(hue($color))),
+ round(saturation($color)),
+ round(lightness($color));
+}
+
+// ----------------------------------------------------------------------------
+
+///
+/// Convert font size in px to em
+///
+@function px2em($size, $base: 16px) {
+ @if unit($size) == px {
+ @if unit($base) == px {
+ @return math.div($size, $base) * 1em;
+ } @else {
+ @error "Invalid base: #{$base} - unit must be 'px'";
+ }
+ } @else {
+ @error "Invalid size: #{$size} - unit must be 'px'";
+ }
+}
+
+///
+/// Convert font size in px to rem
+///
+@function px2rem($size, $base: 20px) {
+ @if unit($size) == px {
+ @if unit($base) == px {
+ @return math.div($size, $base) * 1rem;
+ } @else {
+ @error "Invalid base: #{$base} - unit must be 'px'";
+ }
+ } @else {
+ @error "Invalid size: #{$size} - unit must be 'px'";
+ }
+}
diff --git a/src/base.html b/src/base.html
@@ -0,0 +1,412 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+{% import "partials/language.html" as lang with context %}
+
+<!doctype html>
+<html lang="{{ lang.t('language') }}" class="no-js">
+ <head>
+
+ <!-- Meta tags -->
+ {% block site_meta %}
+ <meta charset="utf-8" />
+ <meta name="viewport" content="width=device-width,initial-scale=1" />
+
+ <!-- Page description -->
+ {% if page.meta and page.meta.description %}
+ <meta name="description" content="{{ page.meta.description }}" />
+ {% elif config.site_description %}
+ <meta name="description" content="{{ config.site_description }}" />
+ {% endif %}
+
+ <!-- Page author -->
+ {% if page.meta and page.meta.author %}
+ <meta name="author" content="{{ page.meta.author }}" />
+ {% elif config.site_author %}
+ <meta name="author" content="{{ config.site_author }}" />
+ {% endif %}
+
+ <!-- Canonical -->
+ {% if page.canonical_url %}
+ <link rel="canonical" href="{{ page.canonical_url }}" />
+ {% endif %}
+
+ <!-- Favicon -->
+ <link rel="icon" href="{{ config.theme.favicon | url }}" />
+
+ <!-- Generator banner -->
+ <meta
+ name="generator"
+ content="mkdocs-{{ mkdocs_version }}, $md-name$-$md-version$"
+ />
+ {% endblock %}
+
+ <!-- Site title -->
+ {% block htmltitle %}
+ {% if page.meta and page.meta.title %}
+ <title>{{ page.meta.title }} - {{ config.site_name }}</title>
+ {% elif page.title and not page.is_homepage %}
+ <title>{{ page.title | striptags }} - {{ config.site_name }}</title>
+ {% else %}
+ <title>{{ config.site_name }}</title>
+ {% endif %}
+ {% endblock %}
+
+ <!-- Theme-related style sheets -->
+ {% block styles %}
+ <link rel="stylesheet" href="{{ 'assets/stylesheets/main.css' | url }}" />
+
+ <!-- Extra color palette -->
+ {% if config.theme.palette %}
+ {% set palette = config.theme.palette %}
+ <link
+ rel="stylesheet"
+ href="{{ 'assets/stylesheets/palette.css' | url }}"
+ />
+
+ <!-- Theme-color meta tag for Android -->
+ {% if palette.primary %}
+ {% import "partials/palette.html" as map %}
+ {% set primary = map.primary(
+ palette.primary | replace(" ", "-") | lower
+ ) %}
+ <meta name="theme-color" content="{{ primary }}" />
+ {% endif %}
+ {% endif %}
+
+ <!-- Custom icons -->
+ {% include "partials/icons.html" %}
+ {% endblock %}
+
+ <!-- JavaScript libraries -->
+ {% block libs %}{% endblock %}
+
+ <!-- Webfonts -->
+ {% block fonts %}
+
+ <!-- Load fonts from Google -->
+ {% if config.theme.font != false %}
+ {% set text = config.theme.font.text | d("Roboto", true) %}
+ {% set code = config.theme.font.code | d("Roboto Mono", true) %}
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
+ <link
+ rel="stylesheet"
+ href="https://fonts.googleapis.com/css?family={{
+ text | replace(' ', '+') + ':300,300i,400,400i,700,700i%7C' +
+ code | replace(' ', '+') + ':400,400i,700,700i'
+ }}&display=fallback"
+ />
+ <style>
+ :root {
+ --md-text-font: "{{ text }}";
+ --md-code-font: "{{ code }}";
+ }
+ </style>
+ {% endif %}
+ {% endblock %}
+
+ <!-- Custom style sheets -->
+ {% for path in config["extra_css"] %}
+ <link rel="stylesheet" href="{{ path | url }}" />
+ {% endfor %}
+
+ <!-- Helper functions for inline scripts -->
+ {% include "partials/javascripts/base.html" %}
+
+ <!-- Analytics -->
+ {% block analytics %}
+ {% include "partials/integrations/analytics.html" %}
+ {% endblock %}
+
+ <!-- Custom front matter -->
+ {% block extrahead %}{% endblock %}
+ </head>
+
+ <!-- Set text direction and color palette, if defined -->
+ {% set direction = config.theme.direction or lang.t('direction') %}
+ {% if config.theme.palette %}
+ {% set palette = config.theme.palette %}
+ {% if not palette is mapping %}
+ {% set palette = palette | first %}
+ {% endif %}
+ {% set scheme = palette.scheme | replace(" ", "-") | lower %}
+ {% set primary = palette.primary | replace(" ", "-") | lower %}
+ {% set accent = palette.accent | replace(" ", "-") | lower %}
+ <body
+ dir="{{ direction }}"
+ data-md-color-scheme="{{ scheme }}"
+ data-md-color-primary="{{ primary }}"
+ data-md-color-accent="{{ accent }}"
+ >
+ {% else %}
+ <body dir="{{ direction }}">
+ {% endif %}
+ {% set features = config.theme.features or [] %}
+
+ <!-- User preference: color palette -->
+ {% if not config.theme.palette is mapping %}
+ {% include "partials/javascripts/palette.html" %}
+ {% endif %}
+
+ <!--
+ State toggles - we need to set autocomplete="off" in order to reset the
+ drawer on back button invocation in some browsers
+ -->
+ <input
+ class="md-toggle"
+ data-md-toggle="drawer"
+ type="checkbox"
+ id="__drawer"
+ autocomplete="off"
+ />
+ <input
+ class="md-toggle"
+ data-md-toggle="search"
+ type="checkbox"
+ id="__search"
+ autocomplete="off"
+ />
+
+ <!-- Overlay for expanded drawer -->
+ <label class="md-overlay" for="__drawer"></label>
+
+ <!-- Skip to content -->
+ <div data-md-component="skip">
+ {% if page.toc | first is defined %}
+ {% set skip = page.toc | first %}
+ <a href="{{ skip.url | url }}" class="md-skip">
+ {{ lang.t('skip.link.title') }}
+ </a>
+ {% endif %}
+ </div>
+
+ <!-- Announcement bar -->
+ <div data-md-component="announce">
+ {% if self.announce() %}
+ <aside class="md-banner">
+ <div class="md-banner__inner md-grid md-typeset">
+
+ <!-- Button to dismiss announcement -->
+ {% if "announce.dismiss" in features %}
+ <button
+ class="md-banner__button md-icon"
+ aria-label="{{ lang.t('announce.dismiss') }}"
+ >
+ {% include ".icons/material/close.svg" %}
+ </button>
+ {% endif %}
+
+ <!-- Announcement bar content -->
+ {% block announce %}{% endblock %}
+ </div>
+ {% if "announce.dismiss" in features %}
+ {% include "partials/javascripts/announce.html" %}
+ {% endif %}
+ </aside>
+ {% endif %}
+ </div>
+
+ <!-- Version warning -->
+ {% if config.extra.version %}
+ <div data-md-component="outdated" hidden>
+ {% if self.outdated() %}
+ <aside class="md-banner md-banner--warning">
+ <div class="md-banner__inner md-grid md-typeset">
+ {% block outdated %}{% endblock %}
+ </div>
+ {% include "partials/javascripts/outdated.html" %}
+ </aside>
+ {% endif %}
+ </div>
+ {% endif %}
+
+ <!-- Header -->
+ {% block header %}
+ {% include "partials/header.html" %}
+ {% endblock %}
+
+ <!-- Container -->
+ <div class="md-container" data-md-component="container">
+
+ <!-- Hero teaser -->
+ {% block hero %}{% endblock %}
+
+ <!-- Navigation tabs (collapsing) -->
+ {% block tabs %}
+ {% if not "navigation.tabs.sticky" in features %}
+ {% if "navigation.tabs" in features %}
+ {% include "partials/tabs.html" %}
+ {% endif %}
+ {% endif %}
+ {% endblock %}
+
+ <!-- Main area -->
+ <main class="md-main" data-md-component="main">
+ <div class="md-main__inner md-grid">
+
+ <!-- Sidebars -->
+ {% block site_nav %}
+
+ <!-- Navigation -->
+ {% if nav %}
+ {% if page.meta and page.meta.hide %}
+ {% set hidden = "hidden" if "navigation" in page.meta.hide %}
+ {% endif %}
+ <div
+ class="md-sidebar md-sidebar--primary"
+ data-md-component="sidebar"
+ data-md-type="navigation"
+ {{ hidden }}
+ >
+ <div class="md-sidebar__scrollwrap">
+ <div class="md-sidebar__inner">
+ {% include "partials/nav.html" %}
+ </div>
+ </div>
+ </div>
+ {% endif %}
+
+ <!-- Table of contents -->
+ {% if not "toc.integrate" in features %}
+ {% if page.meta and page.meta.hide %}
+ {% set hidden = "hidden" if "toc" in page.meta.hide %}
+ {% endif %}
+ <div
+ class="md-sidebar md-sidebar--secondary"
+ data-md-component="sidebar"
+ data-md-type="toc"
+ {{ hidden }}
+ >
+ <div class="md-sidebar__scrollwrap">
+ <div class="md-sidebar__inner">
+ {% include "partials/toc.html" %}
+ </div>
+ </div>
+ </div>
+ {% endif %}
+ {% endblock %}
+
+ <!-- Page content -->
+ <div class="md-content" data-md-component="content">
+ <article class="md-content__inner md-typeset">
+ {% block content %}
+ {% include "partials/content.html" %}
+ {% endblock %}
+ </article>
+
+ <!-- User preference: content -->
+ {% include "partials/javascripts/content.html" %}
+ </div>
+ </div>
+
+ <!-- Back-to-top button -->
+ {% if "navigation.top" in features %}
+ <a
+ href="#"
+ class="md-top md-icon"
+ data-md-component="top"
+ hidden
+ >
+ {% include ".icons/material/arrow-up.svg" %}
+ {{ lang.t('top.title') }}
+ </a>
+ {% endif %}
+ </main>
+
+ <!-- Footer -->
+ {% block footer %}
+ {% include "partials/footer.html" %}
+ {% endblock %}
+ </div>
+
+ <!-- Dialog -->
+ <div class="md-dialog" data-md-component="dialog">
+ <div class="md-dialog__inner md-typeset"></div>
+ </div>
+
+ <!-- Consent -->
+ {% if config.extra.consent %}
+ <div class="md-consent" data-md-component="consent" id="__consent" hidden>
+ <div class="md-consent__overlay"></div>
+ <aside class="md-consent__inner">
+ <form class="md-consent__form md-grid md-typeset" name="consent">
+ {% include "partials/consent.html" %}
+ </form>
+ </aside>
+ </div>
+
+ <!-- User preference: consent -->
+ {% include "partials/javascripts/consent.html" %}
+ {% endif %}
+
+ <!-- Theme-related configuration -->
+ {% block config %}
+ {%- set app = {
+ "base": base_url,
+ "features": features,
+ "translations": {},
+ "search": "assets/javascripts/workers/search.js" | url
+ } -%}
+
+ <!-- Versioning -->
+ {%- if config.extra.version -%}
+ {%- set _ = app.update({ "version": config.extra.version }) -%}
+ {%- endif -%}
+
+ <!-- Translations -->
+ {%- set translations = app.translations -%}
+ {%- for key in [
+ "clipboard.copy",
+ "clipboard.copied",
+ "search.config.lang",
+ "search.config.pipeline",
+ "search.config.separator",
+ "search.placeholder",
+ "search.result.placeholder",
+ "search.result.none",
+ "search.result.one",
+ "search.result.other",
+ "search.result.more.one",
+ "search.result.more.other",
+ "search.result.term.missing",
+ "select.version.title"
+ ] -%}
+ {%- set _ = translations.update({ key: lang.t(key) }) -%}
+ {%- endfor -%}
+
+ <!-- Configuration -->
+ <script id="__config" type="application/json">
+ {{- app | tojson -}}
+ </script>
+ {% endblock %}
+
+ <!-- Theme-related JavaScript -->
+ {% block scripts %}
+ <script src="{{ 'assets/javascripts/bundle.js' | url }}"></script>
+
+ <!-- Custom JavaScript -->
+ {% for path in config["extra_javascript"] %}
+ <script src="{{ path | url }}"></script>
+ {% endfor %}
+ {% endblock %}
+ </body>
+</html>
diff --git a/src/main.html b/src/main.html
@@ -0,0 +1,23 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+{% extends "base.html" %}
diff --git a/src/mkdocs_theme.yml b/src/mkdocs_theme.yml
@@ -0,0 +1,68 @@
+# Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to
+# deal in the Software without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+# sell copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+# Language for theme localization
+language: en
+
+# Text direction (can be ltr or rtl), default: ltr
+direction:
+
+# Feature flags for functionality that alters behavior significantly, and thus
+# may be a matter of taste
+features: []
+
+# Sets the primary and accent color palettes as defined in the Material Design
+# documentation - possible values can be looked up in the getting started guide
+palette:
+
+ # Primary color used for header, sidebar and links, default: indigo
+ primary:
+
+ # Accent color for highlighting user interaction, default: indigo
+ accent:
+
+# Fonts used by Material, automatically loaded from Google Fonts - see the site
+# for a list of available fonts
+font:
+
+ # Default font for text
+ text: Roboto
+
+ # Fixed-width font for code listings
+ code: Roboto Mono
+
+# From Material 5.x on, icons are inlined into the HTML and CSS as SVGs. Some
+# icons that are part of the HTML can be configured and replaced
+icon:
+
+# Favicon to be rendered
+favicon: assets/images/favicon.png
+
+# Material includes the search in the header as a partial, not as a separate
+# template, so it's correct that search.html is missing
+include_search_page: false
+
+# Material doesn't use MkDocs search functionality but provides its own. For
+# this reason, only the search index needs to be built
+search_index_only: true
+
+# Static pages to build
+static_templates:
+ - 404.html
diff --git a/src/overrides/assets/javascripts/bundle.ts b/src/overrides/assets/javascripts/bundle.ts
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import { merge, switchMap } from "rxjs"
+
+import {
+ getComponentElements,
+ mountIconSearch,
+ mountSponsorship
+} from "./components"
+import { setupAnalytics } from "./integrations"
+
+/* ----------------------------------------------------------------------------
+ * Application
+ * ------------------------------------------------------------------------- */
+
+/* Set up extra analytics events */
+setupAnalytics()
+
+/* Set up extra component observables */
+const component$ = document$
+ .pipe(
+ switchMap(() => merge(
+
+ /* Icon search */
+ ...getComponentElements("iconsearch")
+ .map(el => mountIconSearch(el)),
+
+ /* Sponsorship */
+ ...getComponentElements("sponsorship")
+ .map(el => mountSponsorship(el))
+ ))
+ )
+
+/* Subscribe to all components */
+component$.subscribe()
diff --git a/src/overrides/assets/javascripts/components/_/index.ts b/src/overrides/assets/javascripts/components/_/index.ts
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import { getElement, getElements } from "~/browser"
+
+/* ----------------------------------------------------------------------------
+ * Types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Component type
+ */
+export type ComponentType =
+ | "iconsearch" /* Icon search */
+ | "iconsearch-query" /* Icon search input */
+ | "iconsearch-result" /* Icon search results */
+ | "sponsorship" /* Sponsorship */
+ | "sponsorship-count" /* Sponsorship count */
+ | "sponsorship-total" /* Sponsorship total */
+
+/**
+ * Component
+ *
+ * @template T - Component type
+ * @template U - Reference type
+ */
+export type Component<
+ T extends {} = {},
+ U extends HTMLElement = HTMLElement
+> =
+ T & {
+ ref: U /* Component reference */
+ }
+
+/* ----------------------------------------------------------------------------
+ * Helper types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Component type map
+ */
+interface ComponentTypeMap {
+ "iconsearch": HTMLElement /* Icon search */
+ "iconsearch-query": HTMLInputElement /* Icon search input */
+ "iconsearch-result": HTMLElement /* Icon search results */
+ "sponsorship": HTMLElement /* Sponsorship */
+ "sponsorship-count": HTMLElement /* Sponsorship count */
+ "sponsorship-total": HTMLElement /* Sponsorship total */
+}
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Retrieve the element for a given component or throw a reference error
+ *
+ * @template T - Component type
+ *
+ * @param type - Component type
+ * @param node - Node of reference
+ *
+ * @returns Element
+ */
+export function getComponentElement<T extends ComponentType>(
+ type: T, node: ParentNode = document
+): ComponentTypeMap[T] {
+ return getElement(`[data-mdx-component=${type}]`, node)
+}
+
+/**
+ * Retrieve all elements for a given component
+ *
+ * @template T - Component type
+ *
+ * @param type - Component type
+ * @param node - Node of reference
+ *
+ * @returns Elements
+ */
+export function getComponentElements<T extends ComponentType>(
+ type: T, node: ParentNode = document
+): ComponentTypeMap[T][] {
+ return getElements(`[data-mdx-component=${type}]`, node)
+}
diff --git a/src/overrides/assets/javascripts/components/iconsearch/_/index.ts b/src/overrides/assets/javascripts/components/iconsearch/_/index.ts
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import { Observable, merge } from "rxjs"
+
+import { configuration } from "~/_"
+import { requestJSON } from "~/browser"
+
+import { Component, getComponentElement } from "../../_"
+import {
+ IconSearchQuery,
+ mountIconSearchQuery
+} from "../query"
+import {
+ IconSearchResult,
+ mountIconSearchResult
+} from "../result"
+
+/* ----------------------------------------------------------------------------
+ * Types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Icon category
+ */
+export interface IconCategory {
+ base: string /* Category base URL */
+ data: Record<string, string> /* Category data */
+}
+
+/**
+ * Icon search index
+ */
+export interface IconSearchIndex {
+ icons: IconCategory /* Icons */
+ emojis: IconCategory /* Emojis */
+}
+
+/* ------------------------------------------------------------------------- */
+
+/**
+ * Icon search
+ */
+export type IconSearch =
+ | IconSearchQuery
+ | IconSearchResult
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Mount icon search
+ *
+ * @param el - Icon search element
+ *
+ * @returns Icon search component observable
+ */
+export function mountIconSearch(
+ el: HTMLElement
+): Observable<Component<IconSearch>> {
+ const config = configuration()
+ const index$ = requestJSON<IconSearchIndex>(
+ new URL("overrides/assets/javascripts/iconsearch_index.json", config.base)
+ )
+
+ /* Retrieve query and result components */
+ const query = getComponentElement("iconsearch-query", el)
+ const result = getComponentElement("iconsearch-result", el)
+
+ /* Create and return component */
+ const query$ = mountIconSearchQuery(query)
+ const result$ = mountIconSearchResult(result, { index$, query$ })
+ return merge(query$, result$)
+}
diff --git a/src/overrides/assets/javascripts/components/iconsearch/index.ts b/src/overrides/assets/javascripts/components/iconsearch/index.ts
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+export * from "./_"
+export * from "./query"
+export * from "./result"
diff --git a/src/overrides/assets/javascripts/components/iconsearch/query/index.ts b/src/overrides/assets/javascripts/components/iconsearch/query/index.ts
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import {
+ Observable,
+ combineLatest,
+ delay,
+ distinctUntilChanged,
+ filter,
+ fromEvent,
+ map,
+ merge,
+ startWith,
+ withLatestFrom
+} from "rxjs"
+
+import { watchElementFocus } from "~/browser"
+
+import { Component } from "../../_"
+
+/* ----------------------------------------------------------------------------
+ * Types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Icon search query
+ */
+export interface IconSearchQuery {
+ value: string /* Query value */
+ focus: boolean /* Query focus */
+}
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Mount icon search query
+ *
+ * @param el - Icon search query element
+ *
+ * @returns Icon search query component observable
+ */
+export function mountIconSearchQuery(
+ el: HTMLInputElement
+): Observable<Component<IconSearchQuery, HTMLInputElement>> {
+
+ /* Intercept focus and input events */
+ const focus$ = watchElementFocus(el)
+ const value$ = merge(
+ fromEvent(el, "keyup"),
+ fromEvent(el, "focus").pipe(delay(1))
+ )
+ .pipe(
+ map(() => el.value),
+ startWith(el.value),
+ distinctUntilChanged(),
+ )
+
+ /* Log search on blur */
+ focus$
+ .pipe(
+ filter(active => !active),
+ withLatestFrom(value$)
+ )
+ .subscribe(([, value]) => {
+ const path = document.location.pathname
+ if (typeof ga === "function" && value.length)
+ ga("send", "pageview", `${path}?q=[icon]+${value}`)
+ })
+
+ /* Combine into single observable */
+ return combineLatest([value$, focus$])
+ .pipe(
+ map(([value, focus]) => ({ ref: el, value, focus })),
+ )
+}
diff --git a/src/overrides/assets/javascripts/components/iconsearch/result/index.ts b/src/overrides/assets/javascripts/components/iconsearch/result/index.ts
@@ -0,0 +1,237 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import { filter as search } from "fuzzaldrin-plus"
+import {
+ Observable,
+ Subject,
+ bufferCount,
+ combineLatest,
+ distinctUntilKeyChanged,
+ filter,
+ finalize,
+ map,
+ merge,
+ of,
+ switchMap,
+ tap,
+ withLatestFrom,
+ zipWith
+} from "rxjs"
+
+import {
+ getElement,
+ watchElementBoundary
+} from "~/browser"
+import { round } from "~/utilities"
+
+import { Icon, renderIconSearchResult } from "_/templates"
+
+import { Component } from "../../_"
+import { IconSearchIndex } from "../_"
+import { IconSearchQuery } from "../query"
+
+/* ----------------------------------------------------------------------------
+ * Types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Icon search result
+ */
+export interface IconSearchResult {
+ data: Icon[] /* Search result data */
+}
+
+/* ----------------------------------------------------------------------------
+ * Helper types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Watch options
+ */
+interface WatchOptions {
+ index$: Observable<IconSearchIndex> /* Search index observable */
+ query$: Observable<IconSearchQuery> /* Search query observable */
+}
+
+/**
+ * Mount options
+ */
+interface MountOptions {
+ index$: Observable<IconSearchIndex> /* Search index observable */
+ query$: Observable<IconSearchQuery> /* Search query observable */
+}
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Watch icon search result
+ *
+ * @param el - Icon search result element
+ * @param options - Options
+ *
+ * @returns Icon search result observable
+ */
+export function watchIconSearchResult(
+ el: HTMLElement, { index$, query$ }: WatchOptions
+): Observable<IconSearchResult> {
+ switch (el.getAttribute("data-mdx-mode")) {
+
+ case "file":
+ return combineLatest([
+ query$.pipe(distinctUntilKeyChanged("value")),
+ index$
+ .pipe(
+ map(({ icons }) => Object.values(icons.data)
+ .map(icon => icon.replace(/\.svg$/, ""))
+ )
+ )
+ ])
+ .pipe(
+ map(([{ value }, data]) => search(data, value)),
+ switchMap(files => index$.pipe(
+ map(({ icons }) => ({
+ data: files.map<Icon>(shortcode => {
+ return {
+ shortcode,
+ url: [
+ icons.base,
+ shortcode,
+ ".svg"
+ ].join("")
+ }
+ })
+ }))
+ ))
+ )
+
+ default:
+ return combineLatest([
+ query$.pipe(distinctUntilKeyChanged("value")),
+ index$
+ .pipe(
+ map(({ icons, emojis }) => [
+ ...Object.keys(icons.data),
+ ...Object.keys(emojis.data)
+ ])
+ )
+ ])
+ .pipe(
+ map(([{ value }, data]) => search(data, value)),
+ switchMap(shortcodes => index$.pipe(
+ map(({ icons, emojis }) => ({
+ data: shortcodes.map<Icon>(shortcode => {
+ const category =
+ shortcode in icons.data
+ ? icons
+ : emojis
+ return {
+ shortcode,
+ url: [
+ category.base,
+ category.data[shortcode]
+ ].join("")
+ }
+ })
+ }))
+ ))
+ )
+ }
+}
+
+/**
+ * Mount icon search result
+ *
+ * @param el - Icon search result element
+ * @param options - Options
+ *
+ * @returns Icon search result component observable
+ */
+export function mountIconSearchResult(
+ el: HTMLElement, { index$, query$ }: MountOptions
+): Observable<Component<IconSearchResult, HTMLElement>> {
+ const push$ = new Subject<IconSearchResult>()
+ const boundary$ = watchElementBoundary(el)
+ .pipe(
+ filter(Boolean)
+ )
+
+ /* Update search result metadata */
+ const meta = getElement(":scope > :first-child", el)
+ push$
+ .pipe(
+ withLatestFrom(query$)
+ )
+ .subscribe(([{ data }, { value }]) => {
+ if (value) {
+ switch (data.length) {
+
+ /* No results */
+ case 0:
+ meta.textContent = "No matches"
+ break
+
+ /* One result */
+ case 1:
+ meta.textContent = "1 match"
+ break
+
+ /* Multiple result */
+ default:
+ meta.textContent = `${round(data.length)} matches`
+ }
+ } else {
+ meta.textContent = "Type to start searching"
+ }
+ })
+
+ /* Update icon search result list */
+ const file = el.getAttribute("data-mdx-mode") === "file"
+ const list = getElement(":scope > :last-child", el)
+ push$
+ .pipe(
+ tap(() => list.innerHTML = ""),
+ switchMap(({ data }) => merge(
+ of(...data.slice(0, 10)),
+ of(...data.slice(10))
+ .pipe(
+ bufferCount(10),
+ zipWith(boundary$),
+ switchMap(([chunk]) => chunk)
+ )
+ )),
+ withLatestFrom(query$)
+ )
+ .subscribe(([result, { value }]) => list.appendChild(
+ renderIconSearchResult(result, value, file)
+ ))
+
+ /* Create and return component */
+ return watchIconSearchResult(el, { query$, index$ })
+ .pipe(
+ tap(state => push$.next(state)),
+ finalize(() => push$.complete()),
+ map(state => ({ ref: el, ...state }))
+ )
+}
diff --git a/src/overrides/assets/javascripts/components/index.ts b/src/overrides/assets/javascripts/components/index.ts
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+export * from "./_"
+export * from "./iconsearch"
+export * from "./sponsorship"
diff --git a/src/overrides/assets/javascripts/components/sponsorship/index.ts b/src/overrides/assets/javascripts/components/sponsorship/index.ts
@@ -0,0 +1,149 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import { Observable, map } from "rxjs"
+
+import { getElement, requestJSON } from "~/browser"
+
+import { renderPrivateSponsor, renderPublicSponsor } from "_/templates"
+
+import { Component, getComponentElement } from "../_"
+
+/* ----------------------------------------------------------------------------
+ * Types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Sponsor type
+ */
+export type SponsorType =
+ | "user" /* Sponsor is a user */
+ | "organization" /* Sponsor is an organization */
+
+/**
+ * Sponsor visibility
+ */
+export type SponsorVisibility =
+ | "public" /* Sponsor is a user */
+ | "private" /* Sponsor is an organization */
+
+/* ------------------------------------------------------------------------- */
+
+/**
+ * Sponsor user
+ */
+export interface SponsorUser {
+ type: SponsorType /* Sponsor type */
+ name: string /* Sponsor login name */
+ image: string /* Sponsor image URL */
+ url: string /* Sponsor URL */
+}
+
+/* ------------------------------------------------------------------------- */
+
+/**
+ * Public sponsor
+ */
+export interface PublicSponsor {
+ type: "public" /* Sponsor visibility */
+ user: SponsorUser /* Sponsor user */
+}
+
+/**
+ * Private sponsor
+ */
+export interface PrivateSponsor {
+ type: "private" /* Sponsor visibility */
+}
+
+/* ------------------------------------------------------------------------- */
+
+/**
+ * Sponsor
+ */
+export type Sponsor =
+ | PublicSponsor
+ | PrivateSponsor
+
+/* ------------------------------------------------------------------------- */
+
+/**
+ * Sponsorship
+ */
+export interface Sponsorship {
+ sponsors: Sponsor[] /* Sponsors */
+ total: number /* Total amount */
+}
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Mount sponsorship
+ *
+ * @param el - Sponsorship element
+ *
+ * @returns Sponsorship component observable
+ */
+export function mountSponsorship(
+ el: HTMLElement
+): Observable<Component<Sponsorship>> {
+ const sponsorship$ = requestJSON<Sponsorship>(
+ "https://3if8u9o552.execute-api.us-east-1.amazonaws.com/_/"
+ )
+
+ /* Retrieve adjacent components */
+ const count = getComponentElement("sponsorship-count")
+ const total = getComponentElement("sponsorship-total")
+
+ /* Render sponsorship */
+ sponsorship$.subscribe(sponsorship => {
+ el.removeAttribute("hidden")
+
+ /* Render public sponsors with avatar and links */
+ const list = getElement(":scope > :first-child", el)
+ for (const sponsor of sponsorship.sponsors)
+ if (sponsor.type === "public")
+ list.appendChild(renderPublicSponsor(sponsor.user))
+
+ /* Render combined private sponsors */
+ list.appendChild(renderPrivateSponsor(
+ sponsorship.sponsors.filter(({ type }) => (
+ type === "private"
+ )).length
+ ))
+
+ /* Render sponsorship count and total */
+ count.innerText = `${sponsorship.sponsors.length}`
+ total.innerText = `$ ${sponsorship.total
+ .toString()
+ .replace(/\B(?=(\d{3})+(?!\d))/g, ",")
+ } a month`
+ })
+
+ // /* Create and return component */
+ return sponsorship$
+ .pipe(
+ map(state => ({ ref: el, ...state }))
+ )
+}
diff --git a/src/overrides/assets/javascripts/integrations/analytics/index.ts b/src/overrides/assets/javascripts/integrations/analytics/index.ts
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import { fromEvent } from "rxjs"
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Set up extra analytics events
+ */
+export function setupAnalytics(): void {
+ const { origin } = new URL(location.href)
+ fromEvent(document.body, "click")
+ .subscribe(ev => {
+ if (ev.target instanceof HTMLElement) {
+ const el = ev.target.closest("a")
+ if (el && el.origin !== origin)
+ ga("send", "event", "outbound", "click", el.href)
+ }
+ })
+}
diff --git a/src/overrides/assets/javascripts/integrations/index.ts b/src/overrides/assets/javascripts/integrations/index.ts
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+export * from "./analytics"
diff --git a/src/overrides/assets/javascripts/templates/iconsearch/index.tsx b/src/overrides/assets/javascripts/templates/iconsearch/index.tsx
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import { wrap } from "fuzzaldrin-plus"
+
+import { translation } from "~/_"
+import { h } from "~/utilities"
+
+/* ----------------------------------------------------------------------------
+ * Types
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Icon
+ */
+export interface Icon {
+ shortcode: string /* Icon shortcode */
+ url: string /* Icon URL */
+}
+
+/* ----------------------------------------------------------------------------
+ * Helper functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Highlight an icon search result
+ *
+ * @param icon - Icon
+ * @param query - Search query
+ *
+ * @returns Highlighted result
+ */
+function highlight(icon: Icon, query: string): string {
+ return wrap(icon.shortcode, query, {
+ wrap: {
+ tagOpen: "<b>",
+ tagClose: "</b>"
+ }
+ })
+}
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Render an icon search result
+ *
+ * @param icon - Icon
+ * @param query - Search query
+ * @param file - Render as file
+ *
+ * @returns Element
+ */
+export function renderIconSearchResult(
+ icon: Icon, query: string, file?: boolean
+): HTMLElement {
+ return (
+ <li class="mdx-iconsearch-result__item">
+ <span class="twemoji">
+ <img src={icon.url} />
+ </span>
+ <button
+ class="md-clipboard--inline"
+ title={translation("clipboard.copy")}
+ data-clipboard-text={file ? icon.shortcode : `:${icon.shortcode}:`}
+ >
+ <code>{
+ file
+ ? highlight(icon, query)
+ : `:${highlight(icon, query)}:`
+ }</code>
+ </button>
+ </li>
+ )
+}
diff --git a/src/overrides/assets/javascripts/templates/index.ts b/src/overrides/assets/javascripts/templates/index.ts
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+export * from "./iconsearch"
+export * from "./sponsorship"
diff --git a/src/overrides/assets/javascripts/templates/sponsorship/index.tsx b/src/overrides/assets/javascripts/templates/sponsorship/index.tsx
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+import { h } from "~/utilities"
+
+import { SponsorUser } from "_/components"
+
+/* ----------------------------------------------------------------------------
+ * Functions
+ * ------------------------------------------------------------------------- */
+
+/**
+ * Render public sponsor
+ *
+ * @param user - Sponsor user
+ *
+ * @returns Element
+ */
+export function renderPublicSponsor(
+ user: SponsorUser
+): HTMLElement {
+ const title = `@${user.name}`
+ return (
+ <a href={user.url} title={title} class="mdx-sponsorship__item">
+ <img src={user.image} />
+ </a>
+ )
+}
+
+/**
+ * Render private sponsor
+ *
+ * @param count - Number of private sponsors
+ *
+ * @returns Element
+ */
+export function renderPrivateSponsor(
+ count: number
+): HTMLElement {
+ return (
+ <a
+ href="https://github.com/sponsors/squidfunk"
+ class="mdx-sponsorship__item mdx-sponsorship__item--private"
+ >
+ +{count}
+ </a>
+ )
+}
diff --git a/src/overrides/assets/stylesheets/main.scss b/src/overrides/assets/stylesheets/main.scss
@@ -0,0 +1,46 @@
+////
+/// Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+///
+/// Permission is hereby granted, free of charge, to any person obtaining a
+/// copy of this software and associated documentation files (the "Software"),
+/// to deal in the Software without restriction, including without limitation
+/// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+/// and/or sell copies of the Software, and to permit persons to whom the
+/// Software is furnished to do so, subject to the following conditions:
+///
+/// The above copyright notice and this permission notice shall be included in
+/// all copies or substantial portions of the Software.
+///
+/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+/// DEALINGS
+////
+
+// ----------------------------------------------------------------------------
+// Dependencies
+// ----------------------------------------------------------------------------
+
+@import "material-color";
+@import "material-shadows";
+
+// ----------------------------------------------------------------------------
+// Local imports
+// ----------------------------------------------------------------------------
+
+@import "utilities/break";
+@import "utilities/convert";
+
+@import "config";
+
+@import "main/typeset";
+
+@import "main/layout/banner";
+@import "main/layout/hero";
+@import "main/layout/iconsearch";
+@import "main/layout/sponsorship";
+
+@import "main/shame";
diff --git a/src/overrides/assets/stylesheets/main/_shame.scss b/src/overrides/assets/stylesheets/main/_shame.scss
@@ -0,0 +1,25 @@
+////
+/// Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+///
+/// Permission is hereby granted, free of charge, to any person obtaining a
+/// copy of this software and associated documentation files (the "Software"),
+/// to deal in the Software without restriction, including without limitation
+/// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+/// and/or sell copies of the Software, and to permit persons to whom the
+/// Software is furnished to do so, subject to the following conditions:
+///
+/// The above copyright notice and this permission notice shall be included in
+/// all copies or substantial portions of the Software.
+///
+/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+/// DEALINGS
+////
+
+// ----------------------------------------------------------------------------
+// Nothing to see here, move along
+// ----------------------------------------------------------------------------
diff --git a/src/overrides/assets/stylesheets/main/_typeset.scss b/src/overrides/assets/stylesheets/main/_typeset.scss
@@ -0,0 +1,165 @@
+////
+/// Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+///
+/// Permission is hereby granted, free of charge, to any person obtaining a
+/// copy of this software and associated documentation files (the "Software"),
+/// to deal in the Software without restriction, including without limitation
+/// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+/// and/or sell copies of the Software, and to permit persons to whom the
+/// Software is furnished to do so, subject to the following conditions:
+///
+/// The above copyright notice and this permission notice shall be included in
+/// all copies or substantial portions of the Software.
+///
+/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+/// DEALINGS
+////
+
+// ----------------------------------------------------------------------------
+// Keyframes
+// ----------------------------------------------------------------------------
+
+// Pumping heart animation
+@keyframes heart {
+ 0%,
+ 40%,
+ 80%,
+ 100% {
+ transform: scale(1);
+ }
+
+ 20%,
+ 60% {
+ transform: scale(1.15);
+ }
+}
+
+// ----------------------------------------------------------------------------
+// Rules
+// ----------------------------------------------------------------------------
+
+// Scoped in typesetted content to match specificity of regular content
+.md-typeset {
+
+ // Twitter icon
+ .twitter {
+ color: #00acee;
+ }
+
+ // Insiders video
+ .mdx-video {
+ width: auto;
+
+ // Insiders video container
+ &__inner {
+ position: relative;
+ width: 100%;
+ height: 0;
+ padding-bottom: 56.138%;
+ }
+
+ // Insiders video iframe
+ iframe {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ border: none;
+ }
+ }
+
+ // Pumping heart
+ .mdx-heart {
+ animation: heart 1000ms infinite;
+ }
+
+ // Insiders color (for links, etc.)
+ .mdx-insiders {
+ color: $clr-pink-500;
+ }
+
+ // Switch buttons
+ .mdx-switch button {
+ cursor: pointer;
+ transition: opacity 250ms;
+
+ // Button on focus/hover
+ &:focus,
+ &:hover {
+ opacity: 0.75;
+ }
+
+ // Code block
+ > code {
+ display: block;
+ color: var(--md-primary-bg-color);
+ background-color: var(--md-primary-fg-color);
+ }
+ }
+
+ // Deprecation
+ .mdx-deprecated {
+ opacity: 0.5;
+ transition: opacity 250ms;
+
+ // Deprecation on focus/hover
+ &:focus-within,
+ &:hover {
+ opacity: 1;
+ }
+ }
+
+ // Two-column layout
+ .mdx-columns {
+
+ // Column
+ ol,
+ ul {
+ columns: 2;
+
+ // [mobile portrait -]: Reset columns on mobile
+ @include break-to-device(mobile portrait) {
+ columns: initial;
+ }
+ }
+
+ // Column item
+ li {
+ break-inside: avoid;
+ }
+ }
+
+ // Blog author
+ .mdx-author {
+ display: flex;
+ font-size: px2rem(13.6px);
+
+ // Blog author image
+ img {
+ height: px2rem(40px);
+ border-radius: 100%;
+ }
+
+ // Blog author content
+ p {
+
+ // TODO: refactor, use dedicated classes
+ &:first-child {
+ flex-shrink: 0;
+ margin-right: px2rem(16px);
+ }
+
+ // Blog metadata
+ > span {
+ display: block;
+ }
+ }
+ }
+}
diff --git a/src/overrides/assets/stylesheets/main/layout/_banner.scss b/src/overrides/assets/stylesheets/main/layout/_banner.scss
@@ -0,0 +1,46 @@
+////
+/// Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+///
+/// Permission is hereby granted, free of charge, to any person obtaining a
+/// copy of this software and associated documentation files (the "Software"),
+/// to deal in the Software without restriction, including without limitation
+/// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+/// and/or sell copies of the Software, and to permit persons to whom the
+/// Software is furnished to do so, subject to the following conditions:
+///
+/// The above copyright notice and this permission notice shall be included in
+/// all copies or substantial portions of the Software.
+///
+/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+/// DEALINGS
+////
+
+// ----------------------------------------------------------------------------
+// Rules
+// ----------------------------------------------------------------------------
+
+// Banner for announcements and warnings
+.md-banner {
+
+ // Text link, also on focus/hover
+ a,
+ a:focus,
+ a:hover {
+ color: currentcolor;
+ }
+
+ // Don't wrap name of blog article
+ strong {
+ white-space: nowrap;
+ }
+
+ // Twitter icon
+ .twitter {
+ margin-inline-start: 0.2em;
+ }
+}
diff --git a/src/overrides/assets/stylesheets/main/layout/_hero.scss b/src/overrides/assets/stylesheets/main/layout/_hero.scss
@@ -0,0 +1,124 @@
+////
+/// Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+///
+/// Permission is hereby granted, free of charge, to any person obtaining a
+/// copy of this software and associated documentation files (the "Software"),
+/// to deal in the Software without restriction, including without limitation
+/// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+/// and/or sell copies of the Software, and to permit persons to whom the
+/// Software is furnished to do so, subject to the following conditions:
+///
+/// The above copyright notice and this permission notice shall be included in
+/// all copies or substantial portions of the Software.
+///
+/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+/// DEALINGS
+////
+
+// ----------------------------------------------------------------------------
+// Rules
+// ----------------------------------------------------------------------------
+
+// Landing page container
+.mdx-container {
+ padding-top: px2rem(20px);
+ background:
+ url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1123 258'><path d='M1124,2c0,0 0,256 0,256l-1125,0l0,-48c0,0 16,5 55,5c116,0 197,-92 325,-92c121,0 114,46 254,46c140,0 214,-167 572,-166Z' style='fill: hsla(0, 0%, 100%, 1)' /></svg>") no-repeat bottom,
+ linear-gradient(
+ to bottom,
+ var(--md-primary-fg-color),
+ hsla(280, 67%, 55%, 1) 99%,
+ var(--md-default-bg-color) 99%
+ );
+
+ // Adjust background for slate theme
+ [data-md-color-scheme="slate"] & {
+ background:
+ url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1123 258'><path d='M1124,2c0,0 0,256 0,256l-1125,0l0,-48c0,0 16,5 55,5c116,0 197,-92 325,-92c121,0 114,46 254,46c140,0 214,-167 572,-166Z' style='fill: hsla(232, 15%, 21%, 1)' /></svg>") no-repeat bottom,
+ linear-gradient(
+ to bottom,
+ var(--md-primary-fg-color),
+ hsla(280, 67%, 55%, 1) 99%,
+ var(--md-default-bg-color) 99%
+ );
+ }
+}
+
+// Landing page hero
+.mdx-hero {
+ margin: 0 px2rem(16px);
+ color: var(--md-primary-bg-color);
+
+ // Hero headline
+ h1 {
+ margin-bottom: px2rem(20px);
+ color: currentcolor;
+ font-weight: 700;
+
+ // [mobile portrait -]: Larger hero headline
+ @include break-to-device(mobile portrait) {
+ font-size: px2rem(28px);
+ }
+ }
+
+ // Hero content
+ &__content {
+ padding-bottom: px2rem(120px);
+ }
+
+ // [tablet landscape +]: Columnar display
+ @include break-from-device(tablet landscape) {
+ display: flex;
+ align-items: stretch;
+
+ // Adjust spacing and set dimensions
+ &__content {
+ max-width: px2rem(380px);
+ margin-top: px2rem(70px);
+ padding-bottom: 14vw;
+ }
+
+ // Hero image
+ &__image {
+ order: 1;
+ width: px2rem(760px);
+ transform: translateX(#{px2rem(80px)});
+ }
+ }
+
+ // [screen +]: Columnar display and adjusted spacing
+ @include break-from-device(screen) {
+
+ // Hero image
+ &__image {
+ transform: translateX(#{px2rem(160px)});
+ }
+ }
+
+ // Button
+ .md-button {
+ margin-top: px2rem(10px);
+ margin-right: px2rem(10px);
+ color: var(--md-primary-bg-color);
+
+ // Button on focus/hover
+ &:focus,
+ &:hover {
+ color: var(--md-accent-bg-color);
+ background-color: var(--md-accent-fg-color);
+ border-color: var(--md-accent-fg-color);
+ }
+
+ // Primary button
+ &--primary {
+ color: hsla(280, 37%, 48%, 1);
+ background-color: var(--md-primary-bg-color);
+ border-color: var(--md-primary-bg-color);
+ }
+ }
+}
diff --git a/src/overrides/assets/stylesheets/main/layout/_iconsearch.scss b/src/overrides/assets/stylesheets/main/layout/_iconsearch.scss
@@ -0,0 +1,137 @@
+////
+/// Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+///
+/// Permission is hereby granted, free of charge, to any person obtaining a
+/// copy of this software and associated documentation files (the "Software"),
+/// to deal in the Software without restriction, including without limitation
+/// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+/// and/or sell copies of the Software, and to permit persons to whom the
+/// Software is furnished to do so, subject to the following conditions:
+///
+/// The above copyright notice and this permission notice shall be included in
+/// all copies or substantial portions of the Software.
+///
+/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+/// DEALINGS
+////
+
+// ----------------------------------------------------------------------------
+// Rules
+// ----------------------------------------------------------------------------
+
+// Scoped in typesetted content to match specificity of regular content
+.md-typeset {
+
+ // Icon search
+ .mdx-iconsearch {
+ position: relative;
+ background-color: var(--md-default-bg-color);
+ border-radius: px2rem(2px);
+ box-shadow: var(--md-shadow-z1);
+ transition: box-shadow 125ms;
+
+ // Icon search on focus/hover
+ &:focus-within,
+ &:hover {
+ box-shadow: var(--md-shadow-z2);
+ }
+
+ // Icon search input
+ .md-input {
+ background: var(--md-default-bg-color);
+ box-shadow: none;
+
+ // Slate theme, i.e. dark mode
+ [data-md-color-scheme="slate"] & {
+ background: var(--md-code-bg-color);
+ }
+ }
+ }
+
+ // Icon search result
+ .mdx-iconsearch-result {
+ max-height: 50vh;
+ overflow-y: auto;
+ // Hack: promote to own layer to reduce jitter
+ backface-visibility: hidden;
+ touch-action: pan-y;
+ scrollbar-width: thin;
+ scrollbar-color: var(--md-default-fg-color--lighter) transparent;
+
+ // Icon search result inside tooltip
+ .md-tooltip & {
+ max-height: px2rem(205px);
+ }
+
+ // Webkit scrollbar
+ &::-webkit-scrollbar {
+ width: px2rem(4px);
+ height: px2rem(4px);
+ }
+
+ // Webkit scrollbar thumb
+ &::-webkit-scrollbar-thumb {
+ background-color: var(--md-default-fg-color--lighter);
+
+ // Webkit scrollbar thumb on hover
+ &:hover {
+ background-color: var(--md-accent-fg-color);
+ }
+ }
+
+ // Icon search result metadata
+ &__meta {
+ position: absolute;
+ top: px2rem(8px);
+ right: px2rem(12px);
+ color: var(--md-default-fg-color--lighter);
+ font-size: px2rem(12.8px);
+ }
+
+ // Icon search result list
+ &__list {
+ margin: 0;
+ // Hack: necessary because of increased specificity due to the PostCSS
+ // plugin which prefixes this with `[dir=...]` selectors.
+ margin-inline-start: 0;
+ padding: 0;
+ list-style: none;
+ }
+
+ // Icon search result item
+ &__item {
+ margin: 0;
+ // Hack: necessary because of increased specificity due to the PostCSS
+ // plugin which prefixes this with `[dir=...]` selectors.
+ margin-inline-start: 0;
+ padding: px2rem(4px) px2rem(12px);
+ border-bottom: px2rem(1px) solid var(--md-default-fg-color--lightest);
+
+ // Omit border on last child
+ &:last-child {
+ border-bottom: none;
+ }
+
+ // Item content
+ > * {
+ margin-right: px2rem(12px);
+ }
+
+ // Set icon dimensions to fit
+ img {
+ width: px2rem(18px);
+ height: px2rem(18px);
+
+ // Slate theme, i.e. dark mode
+ [data-md-color-scheme="slate"] &[src*="squidfunk"] {
+ filter: invert(1); /* stylelint-disable-line */
+ }
+ }
+ }
+ }
+}
diff --git a/src/overrides/assets/stylesheets/main/layout/_sponsorship.scss b/src/overrides/assets/stylesheets/main/layout/_sponsorship.scss
@@ -0,0 +1,129 @@
+////
+/// Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+///
+/// Permission is hereby granted, free of charge, to any person obtaining a
+/// copy of this software and associated documentation files (the "Software"),
+/// to deal in the Software without restriction, including without limitation
+/// the rights to use, copy, modify, merge, publish, distribute, sublicense,
+/// and/or sell copies of the Software, and to permit persons to whom the
+/// Software is furnished to do so, subject to the following conditions:
+///
+/// The above copyright notice and this permission notice shall be included in
+/// all copies or substantial portions of the Software.
+///
+/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+/// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+/// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+/// DEALINGS
+////
+
+// ----------------------------------------------------------------------------
+// Rules
+// ----------------------------------------------------------------------------
+
+// Scoped in typesetted content to match specificity of regular content
+.md-typeset {
+
+ // Premium sponsors
+ .mdx-premium {
+
+ // Paragraphs
+ p {
+ margin: 2em 0;
+ text-align: center;
+ }
+
+ // Premium sponsor image
+ img {
+ height: px2rem(65px);
+ }
+
+ // Premium sponsor list
+ p:last-child {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: center;
+
+ // Premium sponsor link
+ > a {
+ display: block;
+ flex-shrink: 0;
+ }
+ }
+ }
+
+ // Sponsorship
+ .mdx-sponsorship {
+
+ // Sponsorship list
+ &__list {
+ margin: 2em 0;
+
+ // Clearfix, because we can't use overflow: auto
+ &::after {
+ display: block;
+ clear: both;
+ content: "";
+ }
+ }
+
+ // Sponsorship item
+ &__item {
+ display: block;
+ float: left;
+ width: px2rem(32px);
+ height: px2rem(32px);
+ margin: px2rem(4px);
+ overflow: hidden;
+ border-radius: 100%;
+ transform: scale(1);
+ transition:
+ color 125ms,
+ transform 125ms;
+
+ // Sponsor item on focus/hover
+ &:focus,
+ &:hover {
+ transform: scale(1.1);
+
+ // Sponsor avatar
+ img {
+ filter: grayscale(0%);
+ }
+ }
+
+ // Private sponsor
+ &--private {
+ color: var(--md-default-fg-color--lighter);
+ font-weight: 700;
+ font-size: px2rem(12px);
+ line-height: px2rem(32px);
+ text-align: center;
+ background: var(--md-default-fg-color--lightest);
+ }
+
+ // Sponsor avatar
+ img {
+ display: block;
+ width: 100%;
+ height: auto;
+ filter: grayscale(100%) opacity(75%);
+ transition: filter 125ms;
+ }
+ }
+ }
+
+ // Sponsorship button
+ .mdx-sponsorship-button {
+ font-weight: 400;
+ }
+
+ // Sponsorship count and total
+ .mdx-sponsorship-count,
+ .mdx-sponsorship-total {
+ font-weight: 700;
+ }
+}
diff --git a/src/overrides/blog.html b/src/overrides/blog.html
@@ -0,0 +1,76 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+{% extends "overrides/main.html" %}
+
+<!-- Content -->
+{% block content %}
+ {{ super() }}
+
+ <!-- Giscus - generated by https://giscus.app -->
+ <h2 id="__comments">{{ lang.t("meta.comments") }}</h2>
+ <script
+ src="https://giscus.app/client.js"
+ data-repo="squidfunk/mkdocs-material"
+ data-repo-id="MDEwOlJlcG9zaXRvcnk1MDYxNzQyOA=="
+ data-category="_"
+ data-category-id="DIC_kwDOAwRcVM4CAtJY"
+ data-mapping="pathname"
+ data-reactions-enabled="1"
+ data-emit-metadata="1"
+ data-theme="light"
+ data-lang="en"
+ crossorigin="anonymous"
+ async
+ >
+ </script>
+
+ <!-- Synchronize Giscus theme with palette -->
+ <script>
+ var giscus = document.querySelector("script[src*=giscus]")
+
+ /* Set palette on initial load */
+ var palette = __md_get("__palette")
+ if (palette && typeof palette.color === "object") {
+ var theme = palette.color.scheme === "slate" ? "dark" : "light"
+ giscus.setAttribute("data-theme", theme)
+ }
+
+ /* Register event handlers after documented loaded */
+ document.addEventListener("DOMContentLoaded", function() {
+ var ref = document.querySelector("[data-md-component=palette]")
+ ref.addEventListener("change", function() {
+ var palette = __md_get("__palette")
+ if (palette && typeof palette.color === "object") {
+ var theme = palette.color.scheme === "slate" ? "dark" : "light"
+
+ /* Instruct Giscus to change theme */
+ var frame = document.querySelector(".giscus-frame")
+ frame.contentWindow.postMessage(
+ { giscus: { setConfig: { theme } } },
+ "https://giscus.app"
+ )
+ }
+ })
+ })
+ </script>
+{% endblock %}
diff --git a/src/overrides/home.html b/src/overrides/home.html
@@ -0,0 +1,106 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+{% extends "overrides/main.html" %}
+
+<!-- Render hero under tabs -->
+{% block tabs %}
+ {{ super() }}
+
+ <!-- Additional styles for landing page -->
+ <style>
+
+ /* Application header should be static for the landing page */
+ .md-header {
+ position: initial;
+ }
+
+ /* Remove spacing, as we cannot hide it completely */
+ .md-main__inner {
+ margin: 0;
+ }
+
+ /* Hide main content for now */
+ .md-content {
+ display: none;
+ }
+
+ /* Hide table of contents */
+ @media screen and (min-width: 60em) {
+ .md-sidebar--secondary {
+ display: none;
+ }
+ }
+
+ /* Hide navigation */
+ @media screen and (min-width: 76.25em) {
+ .md-sidebar--primary {
+ display: none;
+ }
+ }
+ </style>
+
+ <!-- Hero for landing page -->
+ <section class="mdx-container">
+ <div class="md-grid md-typeset">
+ <div class="mdx-hero">
+
+ <!-- Hero image -->
+ <div class="mdx-hero__image">
+ <img
+ src="assets/images/illustration.png"
+ alt=""
+ width="1659"
+ height="1200"
+ draggable="false"
+ >
+ </div>
+
+ <!-- Hero content -->
+ <div class="mdx-hero__content">
+ <h1>Technical documentation that just works</h1>
+ <p>{{ config.site_description }}. Set up in 5 minutes.</p>
+ <a
+ href="{{ page.next_page.url | url }}"
+ title="{{ page.next_page.title | e }}"
+ class="md-button md-button--primary"
+ >
+ Quick start
+ </a>
+ <a
+ href="{{ 'insiders/' | url }}"
+ title="Material for MkDocs Insiders"
+ class="md-button"
+ >
+ Get Insiders
+ </a>
+ </div>
+ </div>
+ </div>
+ </section>
+{% endblock %}
+
+<!-- Content -->
+{% block content %}{% endblock %}
+
+<!-- Application footer -->
+{% block footer %}{% endblock %}
diff --git a/src/overrides/main.html b/src/overrides/main.html
@@ -0,0 +1,57 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+{% extends "base.html" %}
+
+<!-- Custom front matter -->
+{% block extrahead %}
+
+ <!-- Extra style sheets (can't be set in mkdocs.yml due to content hash) -->
+ <link
+ rel="stylesheet"
+ href="{{ 'overrides/assets/stylesheets/main.css' | url }}"
+ />
+{% endblock %}
+
+<!-- Announcement bar -->
+{% block announce %}
+ <a href="https://twitter.com/teamseshisma">
+ For updates follow <strong>@teamseshisma</strong> on
+ <span class="twemoji twitter">
+ {% include ".icons/fontawesome/brands/twitter.svg" %}
+ </span>
+ <strong>Twitter</strong>
+ </a>
+{% endblock %}
+
+<!-- Content -->
+{% block content %}
+ {% include "overrides/partials/content.html" %}
+{% endblock %}
+
+<!-- Theme-related JavaScript -->
+{% block scripts %}
+ {{ super() }}
+
+ <!-- Extra JavaScript (can't be set in mkdocs.yml due to content hash) -->
+ <script src="{{ 'overrides/assets/javascripts/bundle.js' | url }}"></script>
+{% endblock %}
diff --git a/src/overrides/partials/content.html b/src/overrides/partials/content.html
@@ -0,0 +1,68 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Edit and view button -->
+{% if page.edit_url %}
+ {% set edit = "https://github.com/squidfunk/mkdocs-material/edit" %}
+ {% set view = "https://raw.githubusercontent.com/squidfunk/mkdocs-material" %}
+ <a
+ href="{{ page.edit_url }}"
+ title="{{ lang.t('edit.link.title') }}"
+ class="md-content__button md-icon"
+ >
+ {% include ".icons/material/file-edit-outline.svg" %}
+ </a>
+ <a
+ href="{{ page.edit_url | replace(edit, view) }}"
+ title="View source of this page"
+ class="md-content__button md-icon"
+ >
+ {% include ".icons/material/file-eye-outline.svg" %}
+ </a>
+{% endif %}
+
+<!-- Tags -->
+{% if "tags" in config.plugins %}
+ {% include "partials/tags.html" %}
+{% endif %}
+
+<!--
+ Hack: check whether the content contains a h1 headline. If it doesn't, the
+ page title (or respectively site name) is used as the main headline.
+-->
+{% if not "\x3ch1" in page.content %}
+ <h1>{{ page.title | d(config.site_name, true)}}</h1>
+{% endif %}
+
+<!-- Markdown content -->
+{{ page.content }}
+
+<!-- Source file information -->
+{% if page.meta and (
+ page.meta.git_revision_date_localized or
+ page.meta.revision_date
+) %}
+ {% include "partials/source-file.html" %}
+{% endif %}
+
+<!-- Was this page helpful? -->
+{% include "partials/feedback.html" %}
diff --git a/src/partials/consent.html b/src/partials/consent.html
@@ -0,0 +1,91 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+{% import "partials/language.html" as lang with context %}
+
+<!-- Determine cookies (default to analytics, if present) -->
+{% set cookies = config.extra.consent.cookies %}
+{% if config.extra.analytics and not cookies %}
+ {% set cookies = { "analytics": "Google Analytics" } %}
+{% endif %}
+
+<!-- Determine actions -->
+{% set actions = config.extra.consent.actions %}
+{% if not actions %}
+ {% set actions = ["accept", "manage"] %}
+{% endif %}
+
+<!-- Consent title -->
+<h4>{{ config.extra.consent.title }}</h4>
+<p>{{ config.extra.consent.description }}</p>
+
+<!-- Consent settings -->
+<input type="checkbox" class="md-toggle" id="__settings" />
+<div class="md-consent__settings">
+ <ul class="task-list">
+ {% for type in cookies %}
+ {% if cookies[type] is string %}
+ {% set name = cookies[type] %}
+ {% set checked = "checked" %}
+ {% else %}
+ {% set name = cookies[type].name %}
+ {% if cookies[type].checked %}
+ {% set checked = "checked" %}
+ {% endif %}
+ {% endif %}
+ <li class="task-list-item">
+ <label class="task-list-control">
+ <input type="checkbox" name="{{ type }}" {{ checked }}>
+ <span class="task-list-indicator"></span>
+ {{ name }}
+ <label>
+ </li>
+ {% endfor %}
+ </ul>
+</div>
+
+<!-- Consent controls -->
+<div class="md-consent__controls">
+ {% for action in actions %}
+
+ <!-- Button to accept cookies -->
+ {% if action == "accept" %}
+ <button class="md-button md-button--primary">
+ {{- lang.t("consent.accept") -}}
+ </button>
+ {% endif %}
+
+ <!-- Button to reject cookies -->
+ {% if action == "reject" %}
+ <button type="reset" class="md-button md-button--primary">
+ {{- lang.t("consent.reject") -}}
+ </button>
+ {% endif %}
+
+ <!-- Button to manage settings -->
+ {% if action == "manage" %}
+ <label class="md-button" for="__settings">
+ {{- lang.t("consent.manage") -}}
+ </label>
+ {% endif %}
+ {% endfor %}
+</div>
diff --git a/src/partials/content.html b/src/partials/content.html
@@ -0,0 +1,59 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Edit button -->
+{% if page.edit_url %}
+ <a
+ href="{{ page.edit_url }}"
+ title="{{ lang.t('edit.link.title') }}"
+ class="md-content__button md-icon"
+ >
+ {% include ".icons/material/pencil.svg" %}
+ </a>
+{% endif %}
+
+<!-- Tags -->
+{% if "tags" in config.plugins %}
+ {% include "partials/tags.html" %}
+{% endif %}
+
+<!--
+ Hack: check whether the content contains a h1 headline. If it doesn't, the
+ page title (or respectively site name) is used as the main headline.
+-->
+{% if not "\x3ch1" in page.content %}
+ <h1>{{ page.title | d(config.site_name, true)}}</h1>
+{% endif %}
+
+<!-- Markdown content -->
+{{ page.content }}
+
+<!-- Source file information -->
+{% if page.meta and (
+ page.meta.git_revision_date_localized or
+ page.meta.revision_date
+) %}
+ {% include "partials/source-file.html" %}
+{% endif %}
+
+<!-- Was this page helpful? -->
+{% include "partials/feedback.html" %}
diff --git a/src/partials/copyright.html b/src/partials/copyright.html
@@ -0,0 +1,33 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Copyright and theme information -->
+<div class="md-copyright">
+ {% if config.copyright %}
+ <div class="md-copyright__highlight">
+ {{ config.copyright }}
+ </div>
+ {% endif %}
+ {% if not config.extra.generator == false %}
+ Made for Sound-Engineers around the world
+ {% endif %}
+</div>
diff --git a/src/partials/feedback.html b/src/partials/feedback.html
@@ -0,0 +1,85 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Determine feedback configuration -->
+{% if config.extra.analytics %}
+ {% set feedback = config.extra.analytics.feedback %}
+{% endif %}
+
+<!-- Determine whether to show feedback -->
+{% if page and page.meta and page.meta.hide %}
+ {% if "feedback" in page.meta.hide %}
+ {% set feedback = None %}
+ {% endif %}
+{% endif %}
+
+<!-- Was this page helpful? -->
+{% if feedback %}
+ <form class="md-feedback" name="feedback" hidden>
+ <fieldset>
+ <legend class="md-feedback__title">
+ {{ feedback.title }}
+ </legend>
+ <div class="md-feedback__inner">
+
+ <!-- Feedback ratings -->
+ <div class="md-feedback__list">
+ {% for rating in feedback.ratings %}
+ <button
+ class="md-feedback__icon md-icon"
+ type="submit"
+ title="{{ rating.name }}"
+ data-md-value="{{ rating.data }}"
+ >
+ {% include ".icons/" ~ rating.icon ~ ".svg" %}
+ </button>
+ {% endfor %}
+ </div>
+
+ <!-- Feedback rating notes (shown after submission) -->
+ <div class="md-feedback__note">
+ {% for rating in feedback.ratings %}
+ <div data-md-value="{{ rating.data }}" hidden>
+ {% set url = "/" ~ page.url %}
+
+ <!-- Determine title -->
+ {% if page and page.meta and page.meta.title %}
+ {% set title = page.meta.title | urlencode %}
+ {% else %}
+ {% set title = page.title | urlencode %}
+ {% endif %}
+
+ <!-- Legacy, deprecated, removed in next major version -->
+ {% if "{}" in rating.note %}
+ {{ rating.note.format(url, title) }}
+
+ <!-- Replace {url} and {title} placeholders in note -->
+ {% else %}
+ {{ rating.note.format(url = url, title = title) }}
+ {% endif %}
+ </div>
+ {% endfor %}
+ </div>
+ </div>
+ </fieldset>
+ </form>
+{% endif %}
diff --git a/src/partials/footer.html b/src/partials/footer.html
@@ -0,0 +1,96 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Footer -->
+<footer class="md-footer">
+
+ <!-- Link to previous and/or next page -->
+ {% if page.previous_page or page.next_page %}
+ {% if page.meta and page.meta.hide %}
+ {% set hidden = "hidden" if "footer" in page.meta.hide %}
+ {% endif %}
+ <nav
+ class="md-footer__inner md-grid"
+ aria-label="{{ lang.t('footer.title') }}"
+ {{ hidden }}
+ >
+
+ <!-- Link to previous page -->
+ {% if page.previous_page %}
+ {% set direction = lang.t("footer.previous") %}
+ <a
+ href="{{ page.previous_page.url | url }}"
+ class="md-footer__link md-footer__link--prev"
+ aria-label="{{ direction }}: {{ page.previous_page.title | e }}"
+ rel="prev"
+ >
+ <div class="md-footer__button md-icon">
+ {% include ".icons/material/arrow-left.svg" %}
+ </div>
+ <div class="md-footer__title">
+ <div class="md-ellipsis">
+ <span class="md-footer__direction">
+ {{ direction }}
+ </span>
+ {{ page.previous_page.title }}
+ </div>
+ </div>
+ </a>
+ {% endif %}
+
+ <!-- Link to next page -->
+ {% if page.next_page %}
+ {% set direction = lang.t("footer.next") %}
+ <a
+ href="{{ page.next_page.url | url }}"
+ class="md-footer__link md-footer__link--next"
+ aria-label="{{ direction }}: {{ page.next_page.title | e }}"
+ rel="next"
+ >
+ <div class="md-footer__title">
+ <div class="md-ellipsis">
+ <span class="md-footer__direction">
+ {{ direction }}
+ </span>
+ {{ page.next_page.title }}
+ </div>
+ </div>
+ <div class="md-footer__button md-icon">
+ {% include ".icons/material/arrow-right.svg" %}
+ </div>
+ </a>
+ {% endif %}
+ </nav>
+ {% endif %}
+
+ <!-- Further information -->
+ <div class="md-footer-meta md-typeset">
+ <div class="md-footer-meta__inner md-grid">
+ {% include "partials/copyright.html" %}
+
+ <!-- Social links -->
+ {% if config.extra.social %}
+ {% include "partials/social.html" %}
+ {% endif %}
+ </div>
+ </div>
+</footer>
diff --git a/src/partials/header.html b/src/partials/header.html
@@ -0,0 +1,161 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Determine class according to configuration -->
+{% set class = "md-header" %}
+{% if "navigation.tabs.sticky" in features %}
+ {% set class = class ~ " md-header--lifted" %}
+{% endif %}
+
+<!-- Header -->
+<header class="{{ class }}" data-md-component="header">
+ <nav
+ class="md-header__inner md-grid"
+ aria-label="{{ lang.t('header.title') }}"
+ >
+
+ <!-- Link to home -->
+ <a
+ href="{{ config.extra.homepage | d(nav.homepage.url, true) | url }}"
+ title="{{ config.site_name | e }}"
+ class="md-header__button md-logo"
+ aria-label="{{ config.site_name }}"
+ data-md-component="logo"
+ >
+ {% include "partials/logo.html" %}
+ </a>
+
+ <!-- Button to open drawer -->
+ <label class="md-header__button md-icon" for="__drawer">
+ {% include ".icons/material/menu" ~ ".svg" %}
+ </label>
+
+ <!-- Header title -->
+ <div class="md-header__title" data-md-component="header-title">
+ <div class="md-header__ellipsis">
+ <div class="md-header__topic">
+ <span class="md-ellipsis">
+ {{ config.site_name }}
+ </span>
+ </div>
+ <div class="md-header__topic" data-md-component="header-topic">
+ <span class="md-ellipsis">
+ {% if page.meta and page.meta.title %}
+ {{ page.meta.title }}
+ {% else %}
+ {{ page.title }}
+ {% endif %}
+ </span>
+ </div>
+ </div>
+ </div>
+
+ <!-- Color palette -->
+ {% if not config.theme.palette is mapping %}
+ <form class="md-header__option" data-md-component="palette">
+ {% for option in config.theme.palette %}
+ {% set primary = option.primary | replace(" ", "-") | lower %}
+ {% set accent = option.accent | replace(" ", "-") | lower %}
+ <input
+ class="md-option"
+ data-md-color-media="{{ option.media }}"
+ data-md-color-scheme="{{ option.scheme }}"
+ data-md-color-primary="{{ primary }}"
+ data-md-color-accent="{{ accent }}"
+ {% if option.toggle %}
+ aria-label="{{ option.toggle.name }}"
+ {% else %}
+ aria-hidden="true"
+ {% endif %}
+ type="radio"
+ name="__palette"
+ id="__palette_{{ loop.index }}"
+ />
+ {% if option.toggle %}
+ <label
+ class="md-header__button md-icon"
+ title="{{ option.toggle.name }}"
+ for="__palette_{{ loop.index0 or loop.length }}"
+ hidden
+ >
+ {% include ".icons/" ~ option.toggle.icon ~ ".svg" %}
+ </label>
+ {% endif %}
+ {% endfor %}
+ </form>
+ {% endif %}
+
+ <!-- Site language selector -->
+ {% if config.extra.alternate %}
+ <div class="md-header__option"></form>
+ <div class="md-select">
+ {% set icon = config.theme.icon.alternate or "material/translate" %}
+ <button
+ class="md-header__button md-icon"
+ aria-label="{{ lang.t('select.language.title') }}"
+ >
+ {% include ".icons/" ~ icon ~ ".svg" %}
+ </button>
+ <div class="md-select__inner">
+ <ul class="md-select__list">
+ {% for alt in config.extra.alternate %}
+ <li class="md-select__item">
+ <a
+ href="{{ alt.link | url }}"
+ hreflang="{{ alt.lang }}"
+ class="md-select__link"
+ >
+ {{ alt.name }}
+ </a>
+ </li>
+ {% endfor %}
+ </ul>
+ </div>
+ </div>
+ </div>
+ {% endif %}
+
+ <!-- Button to open search modal -->
+ {% if "search" in config["plugins"] %}
+ <label class="md-header__button md-icon" for="__search">
+ {% include ".icons/material/magnify.svg" %}
+ </label>
+
+ <!-- Search interface -->
+ {% include "partials/search.html" %}
+ {% endif %}
+
+ <!-- Repository information -->
+ {% if config.repo_url %}
+ <div class="md-header__source">
+ {% include "partials/source.html" %}
+ </div>
+ {% endif %}
+ </nav>
+
+ <!-- Navigation tabs (sticky) -->
+ {% if "navigation.tabs.sticky" in features %}
+ {% if "navigation.tabs" in features %}
+ {% include "partials/tabs.html" %}
+ {% endif %}
+ {% endif %}
+</header>
diff --git a/src/partials/icons.html b/src/partials/icons.html
@@ -0,0 +1,37 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Custom admonition icons -->
+{% if config.theme.icon.admonition %}
+ {% set style = ["\x3cstyle\x3e:root{"] %}
+ {% for type, icon in config.theme.icon.admonition.items() %}
+ {% import ".icons/" ~ icon ~ ".svg" as icon %}
+ {% set _ = style.append(
+ "--md-admonition-icon--" ~ type ~ ":" ~
+ "url('data:image/svg+xml;charset=utf-8," ~
+ icon | replace("\n", "") ~
+ "');"
+ ) %}
+ {% endfor %}
+ {% set _ = style.append("}\x3c/style\x3e") %}
+ {{ style | join }}
+{% endif %}
diff --git a/src/partials/integrations/analytics.html b/src/partials/integrations/analytics.html
@@ -0,0 +1,49 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Determine analytics provider -->
+{% if config.extra.analytics %}
+ {% set provider = config.extra.analytics.provider %}
+{% endif %}
+
+<!-- Set up analytics provider -->
+{% if provider %}
+ {% include "partials/integrations/analytics/" ~ provider ~ ".html" %}
+
+ <!-- Consent necessary -->
+ {% if config.extra.consent %}
+ <script>
+ if (typeof __md_analytics !== "undefined") {
+ var consent = __md_get("__consent")
+ if (consent && consent.analytics)
+ __md_analytics()
+ }
+ </script>
+
+ <!-- Consent unnecessary -->
+ {% else %}
+ <script>
+ if (typeof __md_analytics !== "undefined")
+ __md_analytics()
+ </script>
+ {% endif %}
+{% endif %}
diff --git a/src/partials/integrations/analytics/google.html b/src/partials/integrations/analytics/google.html
@@ -0,0 +1,168 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Determine analytics property -->
+{% if config.extra.analytics %}
+ {% set property = config.extra.analytics.property | d("", true) %}
+{% endif %}
+
+<!-- Google Analytics 4 (G-XXXXXXXXXX) -->
+{% if property.startswith("G-") %}
+ <script id="__analytics">
+ function __md_analytics() {
+ window.dataLayer = window.dataLayer || []
+ function gtag() { dataLayer.push(arguments) }
+
+ /* Set up integration and send page view */
+ gtag("js", new Date())
+ gtag("config", "{{ property }}")
+
+ /* Register event handlers after documented loaded */
+ document.addEventListener("DOMContentLoaded", function() {
+
+ /* Set up search tracking */
+ if (document.forms.search) {
+ var query = document.forms.search.query
+ query.addEventListener("blur", function() {
+ if (this.value)
+ gtag("event", "search", { search_term: this.value })
+ })
+ }
+
+ /* Set up feedback, i.e. "Was this page helpful?" */
+ if (document.forms.feedback) {
+ var feedback = document.forms.feedback
+ for (var button of feedback.querySelectorAll("[type=submit]")) {
+ button.addEventListener("click", function(ev) {
+ ev.preventDefault()
+
+ /* Retrieve and send data */
+ var page = document.location.pathname
+ var data = this.getAttribute("data-md-value")
+ gtag("event", "feedback", { page, data })
+
+ /* Disable form and show note, if given */
+ feedback.firstElementChild.disabled = true
+ var note = feedback.querySelector(
+ ".md-feedback__note [data-md-value='" + data + "']"
+ )
+ if (note)
+ note.hidden = false
+ })
+
+ /* Show feedback */
+ feedback.hidden = false
+ }
+ }
+
+ /* Send page view on location change */
+ if (typeof location$ !== "undefined")
+ location$.subscribe(function(url) {
+ gtag("config", "{{ property }}", {
+ page_path: url.pathname
+ })
+ })
+ })
+
+ /* Create script tag */
+ var script = document.createElement("script")
+ script.async = true
+ script.src = "https://www.googletagmanager.com/gtag/js?id={{ property }}"
+
+ /* Inject script tag */
+ var container = document.getElementById("__analytics")
+ container.insertAdjacentElement("afterEnd", script)
+ }
+ </script>
+
+<!-- Universal Analytics (UA-XXXXXXXX-X) -->
+{% elif property.startswith("UA-") %}
+ <script id="__analytics">
+ function __md_analytics() {
+ window.ga = window.ga || function() {
+ (ga.q = ga.q || []).push(arguments)
+ }
+ ga.l = +new Date()
+
+ /* Set up integration and send page view */
+ ga("create", "{{ property }}", "auto")
+ ga("set", "anonymizeIp", true)
+ ga("send", "pageview")
+
+ /* Register event handlers after documented loaded */
+ document.addEventListener("DOMContentLoaded", function() {
+
+ /* Set up search tracking */
+ if (document.forms.search) {
+ var query = document.forms.search.query
+ query.addEventListener("blur", function() {
+ if (this.value) {
+ var page = document.location.pathname;
+ ga("send", "pageview", page + "?q=" + this.value)
+ }
+ })
+ }
+
+ /* Set up feedback, i.e. "Was this page helpful?" */
+ if (document.forms.feedback) {
+ var feedback = document.forms.feedback
+ for (var button of feedback.querySelectorAll("[type=submit]")) {
+ button.addEventListener("click", function(ev) {
+ ev.preventDefault()
+
+ /* Retrieve and send data */
+ var page = document.location.pathname
+ var data = this.getAttribute("data-md-value")
+ ga("send", "event", "feedback", "click", page, data)
+
+ /* Disable form and show note, if given */
+ feedback.firstElementChild.disabled = true
+ var note = feedback.querySelector(
+ ".md-feedback__note [data-md-value='" + data + "']"
+ )
+ if (note)
+ note.hidden = false
+ })
+
+ /* Show feedback */
+ feedback.hidden = false
+ }
+ }
+
+ /* Send page view on location change */
+ if (typeof location$ !== "undefined")
+ location$.subscribe(function(url) {
+ ga("send", "pageview", url.pathname)
+ })
+ })
+
+ /* Create script tag */
+ var script = document.createElement("script")
+ script.async = true
+ script.src = "https://www.google-analytics.com/analytics.js"
+
+ /* Inject script tag */
+ var container = document.getElementById("__analytics")
+ container.insertAdjacentElement("afterEnd", script)
+ }
+ </script>
+{% endif %}
diff --git a/src/partials/javascripts/announce.html b/src/partials/javascripts/announce.html
@@ -0,0 +1,31 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Announcement bar -->
+<script>
+ var el = document.querySelector("[data-md-component=announce]")
+ if (el) {
+ var content = el.querySelector(".md-typeset")
+ if (__md_hash(content.innerHTML) === __md_get("__announce"))
+ el.hidden = true
+ }
+</script>
diff --git a/src/partials/javascripts/base.html b/src/partials/javascripts/base.html
@@ -0,0 +1,48 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!--
+ A collection of functions used from within some partials to allow the usage
+ of state saved in local or session storage, e.g. to model preferences.
+-->
+<script>
+
+ /* Compute base path once to integrate with instant loading */
+ __md_scope = new URL("{{ config.extra.scope | d(base_url) }}", location)
+
+ /* Compute hash from the given string - see https://bit.ly/3pvPjXG */
+ __md_hash = v => [...v].reduce((h, c) => (h << 5) - h + c.charCodeAt(0), 0)
+
+ /* Fetch the value for a key from the given storage */
+ __md_get = (key, storage = localStorage, scope = __md_scope) => (
+ JSON.parse(storage.getItem(scope.pathname + "." + key))
+ )
+
+ /* Persist a key-value pair in the given storage */
+ __md_set = (key, value, storage = localStorage, scope = __md_scope) => {
+ try {
+ storage.setItem(scope.pathname + "." + key, JSON.stringify(value))
+ } catch (err) {
+ /* Uncritical, just swallow */
+ }
+ }
+</script>
diff --git a/src/partials/javascripts/consent.html b/src/partials/javascripts/consent.html
@@ -0,0 +1,62 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- User-preference: consent -->
+<script>
+ var consent = __md_get("__consent")
+ if (consent) {
+ for (var input of document.forms.consent.elements)
+ if (input.name)
+ input.checked = consent[input.name] || false
+
+ /* Show consent with a small delay, but not if browsing locally */
+ } else if (location.protocol !== "file:") {
+ setTimeout(function() {
+ var el = document.querySelector("[data-md-component=consent]")
+ el.hidden = false
+ }, 250)
+ }
+
+ /* Intercept submission of consent form */
+ var form = document.forms.consent
+ for (var action of ["submit", "reset"])
+ form.addEventListener(action, function(ev) {
+ ev.preventDefault()
+
+ /* Reject all cookies */
+ if (ev.type === "reset")
+ for (var input of document.forms.consent.elements)
+ if (input.name)
+ input.checked = false
+
+ /* Grab and serialize form data */
+ console.log(new FormData(form))
+ __md_set("__consent", Object.fromEntries(
+ Array.from(new FormData(form).keys())
+ .map(function(key) { return [key, true] })
+ ))
+
+ /* Remove anchor to omit consent from reappearing and reload */
+ location.hash = '';
+ location.reload()
+ })
+</script>
diff --git a/src/partials/javascripts/content.html b/src/partials/javascripts/content.html
@@ -0,0 +1,39 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- User-preference: link content tabs -->
+{% if "content.tabs.link" in features %}
+ <script>
+ var tabs = __md_get("__tabs")
+ if (Array.isArray(tabs))
+ main: for (var set of document.querySelectorAll(".tabbed-set")) {
+ var labels = set.querySelector(".tabbed-labels")
+ for (var tab of tabs)
+ for (var label of labels.getElementsByTagName("label"))
+ if (label.innerText.trim() === tab) {
+ var input = document.getElementById(label.htmlFor)
+ input.checked = true
+ continue main
+ }
+ }
+ </script>
+{% endif %}
diff --git a/src/partials/javascripts/outdated.html b/src/partials/javascripts/outdated.html
@@ -0,0 +1,29 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Version warning -->
+<script>
+ var el = document.querySelector("[data-md-component=outdated]")
+ var outdated = __md_get("__outdated", sessionStorage)
+ if (outdated === true && el)
+ el.hidden = false
+</script>
diff --git a/src/partials/javascripts/palette.html b/src/partials/javascripts/palette.html
@@ -0,0 +1,29 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- User preference: color palette -->
+<script>
+ var palette = __md_get("__palette")
+ if (palette && typeof palette.color === "object")
+ for (var key of Object.keys(palette.color))
+ document.body.setAttribute("data-md-color-" + key, palette.color[key])
+</script>
diff --git a/src/partials/language.html b/src/partials/language.html
@@ -0,0 +1,28 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Import translations for given language and fallback -->
+{% import "partials/languages/" ~ config.theme.language ~ ".html" as lang %}
+{% import "partials/languages/en.html" as fallback %}
+
+<!-- Re-export translations -->
+{% macro t(key) %}{{ lang.t(key) or fallback.t(key) }}{% endmacro %}
diff --git a/src/partials/languages/af.html b/src/partials/languages/af.html
@@ -0,0 +1,44 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Translations: Afrikaans -->
+{% macro t(key) %}{{ {
+ "language": "af",
+ "clipboard.copy": "Kopieer na knipbord",
+ "clipboard.copied": "gekopieer na knipbord",
+ "edit.link.title": "Wysig hierdie bladsy",
+ "footer.previous": "Vorige",
+ "footer.next": "Volgende",
+ "meta.comments": "Kommentaar",
+ "meta.source": "Bron",
+ "search.config.lang": "nl",
+ "search.placeholder": "Soek",
+ "search.result.placeholder": "Tik om te begin soek",
+ "search.result.none": "Geen ooreenstemmende dokumente",
+ "search.result.one": "1 ooreenstemmende dokument",
+ "search.result.other": "# ooreenstemmende dokumente",
+ "skip.link.title": "Slaan oor na inhoud",
+ "source.link.title": "Slaan oor na inhoud",
+ "source.file.date.updated": "Laaste opdatering",
+ "source.file.date.created": "Geskep",
+ "toc.title": "Inhoudsopgawe"
+}[key] }}{% endmacro %}
diff --git a/src/partials/languages/ar.html b/src/partials/languages/ar.html
@@ -0,0 +1,45 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Translations: Arabic -->
+{% macro t(key) %}{{ {
+ "language": "ar",
+ "direction": "rtl",
+ "clipboard.copy": "نسخ إلى الحافظة",
+ "clipboard.copied": "تم النسخ الى الحافظة",
+ "edit.link.title": "عدل الصفحة",
+ "footer.previous": "السابقة",
+ "footer.next": "التالية",
+ "meta.comments": "التعليقات",
+ "meta.source": "المصدر",
+ "search.config.pipeline": " ",
+ "search.placeholder": "بحث",
+ "search.result.placeholder": "اكتب لبدء البحث",
+ "search.result.none": "لا توجد نتائج",
+ "search.result.one": "نتائج البحث مستند واحد",
+ "search.result.other": "نتائج البحث # مستندات",
+ "skip.link.title": "انتقل إلى المحتوى",
+ "source.link.title": "اذهب إلى المصدر",
+ "source.file.date.updated": "اخر تحديث",
+ "source.file.date.created": "خلقت",
+ "toc.title": "جدول المحتويات"
+}[key] }}{% endmacro %}
diff --git a/src/partials/languages/bg.html b/src/partials/languages/bg.html
@@ -0,0 +1,51 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Translations: Bulgarian -->
+{% macro t(key) %}{{ {
+ "language": "bg",
+ "clipboard.copy": "Копирай",
+ "clipboard.copied": "Копирано",
+ "edit.link.title": "Редактирай тази страница",
+ "footer.previous": "Предишна",
+ "footer.next": "Следваща",
+ "footer.title": "Долен колонтитул",
+ "header.title": "Горен колонтитул",
+ "meta.comments": "Коментари",
+ "meta.source": "Код",
+ "nav.title": "Навигация",
+ "search.config.lang": "ru",
+ "search.placeholder": "Търси",
+ "search.reset": "Изчисти",
+ "search.result.placeholder": "Започнете да пишете, за да търсите",
+ "search.result.none": "Няма резултати",
+ "search.result.one": "1 резултат",
+ "search.result.other": "# резултата",
+ "search.result.more.one": "още 1 на тази страница",
+ "search.result.more.other": "още # на тази страница",
+ "skip.link.title": "Към съдържанието",
+ "source.link.title": "Към хранилището",
+ "source.file.date.updated": "Последна промяна",
+ "source.file.date.created": "Създаден",
+ "tabs.title": "Табове",
+ "toc.title": "Съдържание"
+}[key] }}{% endmacro %}
diff --git a/src/partials/languages/bn.html b/src/partials/languages/bn.html
@@ -0,0 +1,49 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Translations: Bengali (Bangla) -->
+{% macro t(key) %}{{ {
+ "language": "bn",
+ "clipboard.copy": "ক্লিপবোর্ডে কপি করুন",
+ "clipboard.copied": "ক্লিপবোর্ডে কপি হয়েছে",
+ "edit.link.title": "এই পেজ এডিট করুন",
+ "footer.previous": "পূর্ববর্তী",
+ "footer.next": "পরে",
+ "footer.title": "ফুটার",
+ "header.title": "হেডার",
+ "meta.comments": "কমেন্ট",
+ "meta.source": "সোর্স",
+ "nav.title": "ন্যাভিগেশন",
+ "search.config.pipeline": " ",
+ "search.placeholder": "সার্চ",
+ "search.reset": "মুছে ফেলুন",
+ "search.result.placeholder": "সার্চ টাইপ করুন",
+ "search.result.none": "কিছু পাওয়া যায়নি",
+ "search.result.one": "১ টা ডকুমেন্ট",
+ "search.result.other": "# টা ডকুমেন্ট",
+ "skip.link.title": "কনটেন্টে যান",
+ "source.link.title": "রিপোজিটরিতে যান",
+ "source.file.date.updated": "শেষ আপডেট",
+ "source.file.date.created": "তৈরি হয়েছে",
+ "tabs.title": "ট্যাব",
+ "toc.title": "টেবিল অফ কনটেন্ট"
+}[key] }}{% endmacro %}
diff --git a/src/partials/languages/ca.html b/src/partials/languages/ca.html
@@ -0,0 +1,43 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Translations: Catalan -->
+{% macro t(key) %}{{ {
+ "language": "ca",
+ "clipboard.copy": "Còpia al porta-retalls",
+ "clipboard.copied": "Copiat al porta-retalls",
+ "edit.link.title": "Edita aquesta pàgina",
+ "footer.previous": "Anterior",
+ "footer.next": "Següent",
+ "meta.comments": "Comentaris",
+ "meta.source": "Codi font",
+ "search.placeholder": "Cerca",
+ "search.result.placeholder": "Escriu per a començar a cercar",
+ "search.result.none": "Cap document coincideix",
+ "search.result.one": "1 document coincident",
+ "search.result.other": "# documents coincidents",
+ "skip.link.title": "Salta el contingut",
+ "source.link.title": "Ves al repositori",
+ "source.file.date.updated": "Darrera actualització",
+ "source.file.date.created": "Creada",
+ "toc.title": "Taula de continguts"
+}[key] }}{% endmacro %}
diff --git a/src/partials/languages/cs.html b/src/partials/languages/cs.html
@@ -0,0 +1,43 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Translations: Czech -->
+{% macro t(key) %}{{ {
+ "language": "cs",
+ "clipboard.copy": "Kopírovat do schránky",
+ "clipboard.copied": "Zkopírováno do schránky",
+ "edit.link.title": "Upravit tuto stránku",
+ "footer.previous": "Předchozí",
+ "footer.next": "Další",
+ "meta.comments": "Komentáře",
+ "meta.source": "Zdroj",
+ "search.placeholder": "Hledat",
+ "search.result.placeholder": "Pište co se má vyhledat",
+ "search.result.none": "Nenalezeny žádné dokumenty",
+ "search.result.one": "Nalezený dokument: 1",
+ "search.result.other": "Nalezené dokumenty: #",
+ "skip.link.title": "Přeskočit obsah",
+ "source.link.title": "Přejít do repozitáře",
+ "source.file.date.updated": "Poslední aktualizace",
+ "source.file.date.created": "Vytvořeno",
+ "toc.title": "Obsah"
+}[key] }}{% endmacro %}
diff --git a/src/partials/languages/da.html b/src/partials/languages/da.html
@@ -0,0 +1,44 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Translations: Danish -->
+{% macro t(key) %}{{ {
+ "language": "da",
+ "clipboard.copy": "Kopiér til udklipsholderen",
+ "clipboard.copied": "Kopieret til udklipsholderen",
+ "edit.link.title": "Redigér denne side",
+ "footer.previous": "Forrige",
+ "footer.next": "Næste",
+ "meta.comments": "Kommentarer",
+ "meta.source": "Kilde",
+ "search.config.lang": "da",
+ "search.placeholder": "Søg",
+ "search.result.placeholder": "Indtast søgeord",
+ "search.result.none": "Ingen resultater fundet",
+ "search.result.one": "1 resultat",
+ "search.result.other": "# resultater",
+ "skip.link.title": "Gå til indholdet",
+ "source.link.title": "Åbn arkiv",
+ "source.file.date.updated": "Sidste ændring",
+ "source.file.date.created": "Oprettet",
+ "toc.title": "Indholdsfortegnelse"
+}[key] }}{% endmacro %}
diff --git a/src/partials/languages/de.html b/src/partials/languages/de.html
@@ -0,0 +1,58 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Translations: German -->
+{% macro t(key) %}{{ {
+ "language": "de",
+ "announce.dismiss": "Nicht mehr anzeigen",
+ "clipboard.copy": "In Zwischenablage kopieren",
+ "clipboard.copied": "In Zwischenablage kopiert",
+ "consent.accept": "Akzeptieren",
+ "consent.manage": "Einstellungen",
+ "consent.reject": "Ablehnen",
+ "edit.link.title": "Seite editieren",
+ "footer.previous": "Zurück",
+ "footer.next": "Weiter",
+ "meta.comments": "Kommentare",
+ "meta.source": "Quellcode",
+ "search.config.lang": "de",
+ "search.placeholder": "Suche",
+ "search.share": "Teilen",
+ "search.reset": "Zurücksetzen",
+ "search.result.initializer": "Suche wird initialisiert",
+ "search.result.placeholder": "Suchbegriff eingeben",
+ "search.result.none": "Keine Suchergebnisse",
+ "search.result.one": "1 Suchergebnis",
+ "search.result.other": "# Suchergebnisse",
+ "search.result.more.one": "1 weiteres Suchergebnis auf dieser Seite",
+ "search.result.more.other": "# weitere Suchergebnisse auf dieser Seite",
+ "search.result.term.missing": "Es fehlt",
+ "search.title": "Suche",
+ "select.language.title": "Sprache wechseln",
+ "select.version.title": "Version auswählen",
+ "skip.link.title": "Zum Inhalt",
+ "source.link.title": "Quellcode",
+ "source.file.date.updated": "Letztes Update",
+ "source.file.date.created": "Erstellt",
+ "toc.title": "Inhaltsverzeichnis",
+ "top.title": "Zurück zum Seitenanfang"
+}[key] }}{% endmacro %}
diff --git a/src/partials/languages/el.html b/src/partials/languages/el.html
@@ -0,0 +1,58 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Translations: Greek -->
+{% macro t(key) %}{{ {
+ "language": "el",
+ "clipboard.copy": "Αντιγραφή στο πρόχειρο",
+ "clipboard.copied": "Αντιγράφηκε στο πρόχειρο",
+ "edit.link.title": "Επεξεργασία αυτής της σελίδας",
+ "footer.previous": "Προηγούμενο",
+ "footer.next": "Επόμενο",
+ "footer.title": "Υποσέλιδο",
+ "header.title": "Κεφαλίδα",
+ "meta.comments": "Σχόλια",
+ "meta.source": "Πηγή",
+ "nav.title": "Πλοήγηση",
+ "search.config.pipeline": "stopWordFilter",
+ "search.placeholder": "Αναζήτηση",
+ "search.share": "Διαμοίραση",
+ "search.reset": "Καθαρισμός",
+ "search.result.initializer": "Αρχικοποίηση αναζήτησης",
+ "search.result.placeholder": "Πληκτρολογήστε για να αρχίσει η αναζήτηση",
+ "search.result.none": "Δεν βρέθηκαν αντίστοιχα αρχεία",
+ "search.result.one": "1 έγγραφο ταιριάζει",
+ "search.result.other": "# έγγραφα ταιριάζουν",
+ "search.result.more.one": "1 ακόμα σε αυτήν τη σελίδα",
+ "search.result.more.other": "# ακόμα σε αυτήν τη σελίδα",
+ "search.result.term.missing": "Λείπει",
+ "search.title": "Αναζήτηση",
+ "select.language.title": "Επιλογή γλώσσας",
+ "select.version.title": "Επιλογή έκδοσης",
+ "skip.link.title": "Μετάβαση στο περιεχόμενο",
+ "source.link.title": "Μετάβαση στο αποθετήριο",
+ "source.file.date.updated": "τελευταία ενημέρωση",
+ "source.file.date.created": "Δημιουργήθηκε",
+ "tabs.title": "Καρτέλες",
+ "toc.title": "Πίνακας περιεχομένων",
+ "top.title": "Επιστροφή στην αρχή"
+}[key] }}{% endmacro %}
diff --git a/src/partials/languages/en.html b/src/partials/languages/en.html
@@ -0,0 +1,65 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Translations: English -->
+{% macro t(key) %}{{ {
+ "language": "en",
+ "direction": "ltr",
+ "announce.dismiss": "Don't show this again",
+ "clipboard.copy": "Copy to clipboard",
+ "clipboard.copied": "Copied to clipboard",
+ "consent.accept": "Accept",
+ "consent.manage": "Manage settings",
+ "consent.reject": "Reject",
+ "edit.link.title": "Edit this page",
+ "footer.previous": "Previous",
+ "footer.next": "Next",
+ "footer.title": "Footer",
+ "header.title": "Header",
+ "meta.comments": "Comments",
+ "meta.source": "Source",
+ "nav.title": "Navigation",
+ "search.config.lang": "en",
+ "search.config.pipeline": "trimmer, stopWordFilter",
+ "search.config.separator": "[\\s\\-]+",
+ "search.placeholder": "Search",
+ "search.share": "Share",
+ "search.reset": "Clear",
+ "search.result.initializer": "Initializing search",
+ "search.result.placeholder": "Type to start searching",
+ "search.result.none": "No matching documents",
+ "search.result.one": "1 matching document",
+ "search.result.other": "# matching documents",
+ "search.result.more.one": "1 more on this page",
+ "search.result.more.other": "# more on this page",
+ "search.result.term.missing": "Missing",
+ "search.title": "Search",
+ "select.language.title": "Select language",
+ "select.version.title": "Select version",
+ "skip.link.title": "Skip to content",
+ "source.link.title": "Go to repository",
+ "source.file.date.updated": "Last update",
+ "source.file.date.created": "Created",
+ "tabs.title": "Tabs",
+ "toc.title": "Table of contents",
+ "top.title": "Back to top"
+}[key] }}{% endmacro %}
diff --git a/src/partials/languages/eo.html b/src/partials/languages/eo.html
@@ -0,0 +1,49 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Translations: Esperanto -->
+{% macro t(key) %}{{ {
+ "language": "eo",
+ "clipboard.copy": "Kopii al tondujo",
+ "clipboard.copied": "Kopiado al klipo",
+ "edit.link.title": "Redakti ĉi tiun paĝon",
+ "footer.previous": "Antaŭa",
+ "footer.next": "Sekva",
+ "footer.title": "Piedlinio",
+ "header.title": "Kaplinio",
+ "meta.comments": "Komentoj",
+ "meta.source": "Fontkodo",
+ "nav.title": "Navigado",
+ "search.config.lang": "es",
+ "search.placeholder": "Serĉo",
+ "search.reset": "Klara",
+ "search.result.placeholder": "Tajpu por komenci serĉadon",
+ "search.result.none": "Neniuj kongruaj dokumentoj",
+ "search.result.one": "1 kongrua dokumento",
+ "search.result.other": "# kongruaj dokumentoj",
+ "skip.link.title": "Saltu al enhavo",
+ "source.link.title": "Iru al deponejo",
+ "source.file.date.updated": "Lasta ĝisdatigo",
+ "source.file.date.created": "Kreita",
+ "tabs.title": "Langetoj",
+ "toc.title": "Enhavtabelo"
+}[key] }}{% endmacro %}
diff --git a/src/partials/languages/es.html b/src/partials/languages/es.html
@@ -0,0 +1,58 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Translations: Spanish -->
+{% macro t(key) %}{{ {
+ "language": "es",
+ "clipboard.copy": "Copiar al portapapeles",
+ "clipboard.copied": "Copiado al portapapeles",
+ "consent.accept": "Aceptar",
+ "consent.manage": "Gestionar cookies",
+ "edit.link.title": "Editar esta página",
+ "footer.previous": "Anterior",
+ "footer.next": "Siguiente",
+ "footer.title": "Pie",
+ "header.title": "Cabecera",
+ "meta.comments": "Comentarios",
+ "meta.source": "Fuente",
+ "nav.title": "Navegación",
+ "search.config.lang": "es",
+ "search.placeholder": "Búsqueda",
+ "search.reset": "Limpiar",
+ "search.result.initializer": "Inicializando búsqueda",
+ "search.result.placeholder": "Teclee para comenzar búsqueda",
+ "search.result.none": "No se encontraron documentos",
+ "search.result.one": "1 documento encontrado",
+ "search.result.other": "# documentos encontrados",
+ "search.result.more.one": "1 más en esta página",
+ "search.result.more.other": "# más en esta página",
+ "search.result.term.missing": "Falta",
+ "select.language.title": "Seleccionar idioma",
+ "select.version.title": "Seleccionar versión",
+ "skip.link.title": "Saltar a contenido",
+ "source.link.title": "Ir al repositorio",
+ "source.file.date.updated": "Última actualización",
+ "source.file.date.created": "Creado",
+ "tabs.title": "Pestañas",
+ "toc.title": "Tabla de contenidos",
+ "top.title": "Volver al principio"
+}[key] }}{% endmacro %}
diff --git a/src/partials/languages/et.html b/src/partials/languages/et.html
@@ -0,0 +1,43 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Translations: Estonian -->
+{% macro t(key) %}{{ {
+ "language": "et",
+ "clipboard.copy": "Kopeeri lõikelauale",
+ "clipboard.copied": "Kopeeritud",
+ "edit.link.title": "Muuda seda lehte",
+ "footer.previous": "Eelmine",
+ "footer.next": "Järgmine",
+ "meta.comments": "Kommentaarid",
+ "meta.source": "Lähtekood",
+ "search.placeholder": "Otsi",
+ "search.result.placeholder": "Otsimiseks kirjuta siia",
+ "search.result.none": "Otsingule ei leitud ühtegi vastet",
+ "search.result.one": "Leiti üks tulemus",
+ "search.result.other": "Leiti # tulemust",
+ "skip.link.title": "Keri sisuni",
+ "source.link.title": "Ava repositooriumis",
+ "source.file.date.updated": "Viimane uuendus",
+ "source.file.date.created": "Loodud",
+ "toc.title": "Sisukord"
+}[key] }}{% endmacro %}
diff --git a/src/partials/languages/fa.html b/src/partials/languages/fa.html
@@ -0,0 +1,45 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Translations: Persian (Farsi) -->
+{% macro t(key) %}{{ {
+ "language": "fa",
+ "direction": "rtl",
+ "clipboard.copy": "کپی کردن",
+ "clipboard.copied": "کپی شد",
+ "edit.link.title": "این صفحه را ویرایش کنید",
+ "footer.previous": "قبلی",
+ "footer.next": "بعدی",
+ "meta.comments": "نظرات",
+ "meta.source": "منبع",
+ "search.config.pipeline": " ",
+ "search.placeholder": "جستجو",
+ "search.result.placeholder": "برای شروع جستجو تایپ کنید",
+ "search.result.none": "سندی یافت نشد",
+ "search.result.one": "1 سند یافت شد",
+ "search.result.other": "# سند یافت شد",
+ "skip.link.title": "پرش به محتویات",
+ "source.link.title": "رفتن به مخزن",
+ "source.file.date.updated": "اخرین بروزرسانی",
+ "source.file.date.created": "ایجاد شده",
+ "toc.title": "فهرست موضوعات"
+}[key] }}{% endmacro %}
diff --git a/src/partials/languages/fi.html b/src/partials/languages/fi.html
@@ -0,0 +1,44 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Translations: Finnish -->
+{% macro t(key) %}{{ {
+ "language": "fi",
+ "clipboard.copy": "Kopioi leikepöydälle",
+ "clipboard.copied": "Kopioitu leikepöydälle",
+ "edit.link.title": "Muokkaa tätä sivua",
+ "footer.previous": "Edellinen",
+ "footer.next": "Seuraava",
+ "meta.comments": "Kommentit",
+ "meta.source": "Lähdekodi",
+ "search.config.lang": "fi",
+ "search.placeholder": "Hae",
+ "search.result.placeholder": "Kirjoita aloittaaksesi haun",
+ "search.result.none": "Ei täsmääviä dokumentteja",
+ "search.result.one": "1 täsmäävä dokumentti",
+ "search.result.other": "# täsmäävää dokumenttia",
+ "skip.link.title": "Hyppää sisältöön",
+ "source.link.title": "Mene repositoryyn",
+ "source.file.date.updated": "Viimeisin päivitys",
+ "source.file.date.created": "Luotu",
+ "toc.title": "Sisällysluettelo"
+}[key] }}{% endmacro %}
diff --git a/src/partials/languages/fr.html b/src/partials/languages/fr.html
@@ -0,0 +1,59 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Translations: French -->
+{% macro t(key) %}{{ {
+ "language": "fr",
+ "clipboard.copy": "Copier dans le presse-papier",
+ "clipboard.copied": "Copié dans le presse-papier",
+ "consent.accept": "Accepter",
+ "consent.manage": "Paramétrer vos choix",
+ "consent.reject": "Refuser",
+ "edit.link.title": "Editer cette page",
+ "footer.previous": "Précédent",
+ "footer.next": "Suivant",
+ "footer.title": "Pied de page",
+ "header.title": "En-tête",
+ "meta.comments": "Commentaires",
+ "meta.source": "Source",
+ "nav.title": "Navigation",
+ "search.config.lang": "fr",
+ "search.placeholder": "Rechercher",
+ "search.reset": "Effacer",
+ "search.result.initializer": "Initialisation de la recherche",
+ "search.result.placeholder": "Taper pour démarrer la recherche",
+ "search.result.none": "Aucun document trouvé",
+ "search.result.one": "1 document trouvé",
+ "search.result.other": "# documents trouvés",
+ "search.result.more.one": "1 de plus sur cette page",
+ "search.result.more.other": "# de plus sur cette page",
+ "search.result.term.missing": "Non trouvé",
+ "select.language.title": "Sélectionner la langue",
+ "select.version.title": "Sélectionner la version",
+ "skip.link.title": "Aller au contenu",
+ "source.link.title": "Aller au dépôt",
+ "source.file.date.updated": "Dernière mise à jour",
+ "source.file.date.created": "Créé",
+ "tabs.title": "Onglets",
+ "toc.title": "Table des matières",
+ "top.title": "Retour en haut de la page"
+}[key] }}{% endmacro %}
diff --git a/src/partials/languages/gl.html b/src/partials/languages/gl.html
@@ -0,0 +1,56 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Translations: Galician -->
+{% macro t(key) %}{{ {
+ "language": "gl",
+ "clipboard.copy": "Copiar no cortapapeis",
+ "clipboard.copied": "Copiado no cortapapeis",
+ "edit.link.title": "Editar esta páxina",
+ "footer.previous": "Anterior",
+ "footer.next": "Seguinte",
+ "footer.title": "Pé",
+ "header.title": "Cabeceira",
+ "meta.comments": "Comentarios",
+ "meta.source": "Fonte",
+ "nav.title": "Navegación",
+ "search.config.lang": "es",
+ "search.placeholder": "Procura",
+ "search.reset": "Limpar",
+ "search.result.initializer": "Inicializando procura",
+ "search.result.placeholder": "Insira un termo",
+ "search.result.none": "Sen resultados",
+ "search.result.one": "1 resultado atopado",
+ "search.result.other": "# resultados atopados",
+ "search.result.more.one": "1 máis nesta páxina",
+ "search.result.more.other": "# máis nesta páxina",
+ "search.result.term.missing": "Falta",
+ "select.language.title": "Seleccionar idioma",
+ "select.version.title": "Seleccionar version",
+ "skip.link.title": "Ir ao contido",
+ "source.link.title": "Ir ao repositorio",
+ "source.file.date.updated": "Última actualización",
+ "source.file.date.created": "Creada",
+ "tabs.title": "Pestanas",
+ "toc.title": "Táboa de contidos",
+ "top.title": "Volver ao principio"
+}[key] }}{% endmacro %}
diff --git a/src/partials/languages/he.html b/src/partials/languages/he.html
@@ -0,0 +1,63 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Translations: Hebrew -->
+{% macro t(key) %}{{ {
+ "language": "he",
+ "direction": "rtl",
+ "announce.dismiss": "לא להציג את זה שוב",
+ "clipboard.copy": "העתקה ללוח",
+ "clipboard.copied": "הועתק ללוח",
+ "consent.accept": "לקבל",
+ "consent.manage": "לנהל הגדרות",
+ "consent.reject": "לדחות",
+ "edit.link.title": "עריכת הדף הזה",
+ "footer.previous": "הקודם",
+ "footer.next": "הבא",
+ "footer.title": "כותרת תחתונה",
+ "header.title": "כותרת עליונה",
+ "meta.comments": "הערות",
+ "meta.source": "מקור",
+ "nav.title": "ניווט",
+ "search.config.pipeline": " ",
+ "search.placeholder": "חיפוש",
+ "search.share": "שיתוף",
+ "search.reset": "ניקוי",
+ "search.result.initializer": "אתחול חיפוש",
+ "search.result.placeholder": "יש להקליד כדי להתחיל לחפש",
+ "search.result.none": "אין מסמכים תואמים",
+ "search.result.one": "1 מסמך תואם",
+ "search.result.other": "# מסמך תואם",
+ "search.result.more.one": "עוד אחד בדף הזה",
+ "search.result.more.other": "עוד # בדף הזה",
+ "search.result.term.missing": "חסר",
+ "search.title": "חיפוש",
+ "select.language.title": "בחירת שפה",
+ "select.version.title": "בחירת גרסה",
+ "skip.link.title": "לדלג לתוכן",
+ "source.link.title": "לעבור אל המאגר",
+ "source.file.date.updated": "עדכון אחרון",
+ "source.file.date.created": "נוצר",
+ "tabs.title": "לשוניות",
+ "toc.title": "תוכן העניינים",
+ "top.title": "חזרה למעלה"
+}[key] }}{% endmacro %}
diff --git a/src/partials/languages/hi.html b/src/partials/languages/hi.html
@@ -0,0 +1,44 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Translations: Hindi -->
+{% macro t(key) %}{{ {
+ "language": "hi",
+ "clipboard.copy": "क्लिपबोर्ड पर कॉपी करें",
+ "clipboard.copied": "क्लिपबोर्ड पर कॉपी कर दिया गया",
+ "edit.link.title": "इस पृष्ठ को संपादित करें",
+ "footer.previous": "पिछला",
+ "footer.next": "आगामी",
+ "meta.comments": "टिप्पणियाँ",
+ "meta.source": "स्रोत",
+ "search.config.lang": "hi",
+ "search.placeholder": "खोज",
+ "search.result.placeholder": "खोज शुरू करने के लिए टाइप करें",
+ "search.result.none": "कोई मिलान डॉक्यूमेंट नहीं",
+ "search.result.one": "1 मिलान डॉक्यूमेंट",
+ "search.result.other": "# मिलान डाक्यूमेंट्स",
+ "skip.link.title": "विषय पर बढ़ें",
+ "source.link.title": "रिपॉजिटरी पर जाएं",
+ "source.file.date.updated": "आखिरी अपडेट",
+ "source.file.date.created": "बनाया था",
+ "toc.title": "विषय - सूची"
+}[key] }}{% endmacro %}
diff --git a/src/partials/languages/hr.html b/src/partials/languages/hr.html
@@ -0,0 +1,61 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Translations: Croatian -->
+{% macro t(key) %}{{ {
+ "language": "hr",
+ "announce.dismiss": "Ne prikazuj ovo opet",
+ "clipboard.copy": "Kopirajte u međuspremnik",
+ "clipboard.copied": "Kopirano u međuspremnik",
+ "consent.accept": "Prihvati",
+ "consent.manage": "Upravljaj postavkama",
+ "consent.reject": "Odbij",
+ "edit.link.title": "Uredi stranicu",
+ "footer.previous": "Prethodno",
+ "footer.next": "Sljedeće",
+ "footer.title": "Podnožje",
+ "header.title": "Zaglavlje",
+ "meta.comments": "Komentari",
+ "meta.source": "Izvor",
+ "nav.title": "Navigacija",
+ "search.placeholder": "Pretraživanje",
+ "search.share": "Podijeli",
+ "search.reset": "Očisti",
+ "search.result.initializer": "Inicijaliziranje pretraživanja",
+ "search.result.placeholder": "Unesite pojam pretraživanja",
+ "search.result.none": "Ništa nije pronađeno",
+ "search.result.one": "1 rezultat pretraživanja",
+ "search.result.other": "# rezultata pretraživanja",
+ "search.result.more.one": "1 rezultat na ovoj stranici",
+ "search.result.more.other": "# rezultata na ovoj stranici",
+ "search.result.term.missing": "Nedostaje",
+ "search.title": "Pretraživanje",
+ "select.language.title": "Odabir jezika",
+ "select.version.title": "Odabir verzije",
+ "skip.link.title": "Preskočite na sadržaj",
+ "source.link.title": "Idite u repozitorij",
+ "source.file.date.updated": "Zadnje ažuriranje",
+ "source.file.date.created": "Stvoreno",
+ "tabs.title": "Kartice",
+ "toc.title": "Sadržaj",
+ "top.title": "Vratite se na vrh"
+}[key] }}{% endmacro %}
diff --git a/src/partials/languages/hu.html b/src/partials/languages/hu.html
@@ -0,0 +1,53 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Translations: Hungarian -->
+{% macro t(key) %}{{ {
+ "language": "hu",
+ "clipboard.copy": "Másolás vágólapra",
+ "clipboard.copied": "Vágólapra másolva",
+ "edit.link.title": "Oldal szerkesztése",
+ "footer.previous": "Előző",
+ "footer.next": "Következő",
+ "footer.title": "Élőláb",
+ "header.title": "Élőfej",
+ "meta.comments": "Hozzászólások",
+ "meta.source": "Forrás",
+ "nav.title": "Navigáció",
+ "search.config.lang": "hu",
+ "search.placeholder": "Keresés",
+ "search.reset": "Törlés",
+ "search.result.initializer": "Keresés inicializálása",
+ "search.result.placeholder": "Kereséshez írj ide valamit",
+ "search.result.none": "Nincs találat",
+ "search.result.one": "1 egyező dokumentum",
+ "search.result.other": "# egyező dokumentum",
+ "search.result.more.one": "1 további találat az oldalon",
+ "search.result.more.other": "# további találat az oldalon",
+ "search.result.term.missing": "Üres",
+ "skip.link.title": "Kihagyás",
+ "source.link.title": "Főoldalra ugrás",
+ "source.file.date.updated": "Utolsó frissítés",
+ "source.file.date.created": "Létrehozva",
+ "tabs.title": "Lapok",
+ "toc.title": "Tartalomjegyzék"
+}[key] }}{% endmacro %}
diff --git a/src/partials/languages/hy.html b/src/partials/languages/hy.html
@@ -0,0 +1,58 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Translations: Armenian -->
+{% macro t(key) %}{{ {
+ "language": "hy",
+ "clipboard.copy": "Պատճենել",
+ "clipboard.copied": "Պատճենված է",
+ "edit.link.title": "Խմբագրել այս էջը",
+ "footer.previous": "Նախորդը",
+ "footer.next": "Հաջորդը",
+ "footer.title": "Վերջնագիր",
+ "header.title": "Գլխագիր",
+ "meta.comments": "Մեկնաբանությունները",
+ "meta.source": "Աղբյուր",
+ "nav.title": "Տեղորոշում",
+ "search.config.pipeline": " ",
+ "search.placeholder": "Փնտրել",
+ "search.share": "Կիսվել",
+ "search.reset": "Ջնջել",
+ "search.result.initializer": "Փնտրում",
+ "search.result.placeholder": "Մուտքագրեք փնտրելու համար",
+ "search.result.none": "Համապատասխանություններ չկան",
+ "search.result.one": "1 համապատասխանություն",
+ "search.result.other": "# համապատասխանություններ",
+ "search.result.more.one": "ևս 1-ը այս էջում",
+ "search.result.more.other": "ևս #-ը այս էջում",
+ "search.result.term.missing": "Բացակայում է",
+ "search.title": "Փնտրում",
+ "select.language.title": "Ընտրել լեզուն",
+ "select.version.title": "Ընտրել տարբերակը",
+ "skip.link.title": "Անցնել պարունակությանը",
+ "source.link.title": "Դեպի պահոց",
+ "source.file.date.updated": "Վերջին թարմացումը",
+ "source.file.date.created": "Ստեղծված է",
+ "tabs.title": "Ներդիրներ",
+ "toc.title": "Բովանդակություն",
+ "top.title": "Վերադառնալ սկիզբ"
+}[key] }}{% endmacro %}
diff --git a/src/partials/languages/id.html b/src/partials/languages/id.html
@@ -0,0 +1,43 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Translations: Indonesian -->
+{% macro t(key) %}{{ {
+ "language": "id",
+ "clipboard.copy": "Salin ke memori",
+ "clipboard.copied": "Tersalin ke memori",
+ "edit.link.title": "Ubah halaman ini",
+ "footer.previous": "Sebelumnya",
+ "footer.next": "Selanjutnya",
+ "meta.comments": "Komentar",
+ "meta.source": "Sumber",
+ "search.config.pipeline": " ",
+ "search.placeholder": "Cari",
+ "search.result.placeholder": "Ketik untuk mulai pencarian",
+ "search.result.none": "Tidak ada dokumen yang sesuai",
+ "search.result.one": "1 dokumen ditemukan",
+ "search.result.other": "# dokumen ditemukan",
+ "skip.link.title": "Lewati ke isi",
+ "source.link.title": "Menuju repositori",
+ "source.file.date.created": "Dibuat",
+ "toc.title": "Daftar isi"
+}[key] }}{% endmacro %}
diff --git a/src/partials/languages/is.html b/src/partials/languages/is.html
@@ -0,0 +1,50 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Translations: Icelandic -->
+{% macro t(key) %}{{ {
+ "language": "is",
+ "clipboard.copy": "Afrita í klemmuspjald",
+ "clipboard.copied": "Afritað í klemmuspjald",
+ "edit.link.title": "Ritvinna þessa síðu",
+ "footer.previous": "Fyrra",
+ "footer.next": "Næsta",
+ "footer.title": "Síðufótur",
+ "header.title": "Haus",
+ "meta.comments": "Athugasemdir",
+ "meta.source": "Grunnur",
+ "nav.title": "Valmynd",
+ "search.placeholder": "Leit",
+ "search.reset": "Hreinsa",
+ "search.result.placeholder": "Sláðu inn til að hefja leit",
+ "search.result.none": "Engin skjöl fundust",
+ "search.result.one": "1 skjal fannst",
+ "search.result.other": "# skjöl fundust",
+ "search.result.more.one": "1 til viðbótar á þessari síðu",
+ "search.result.more.other": "# til viðbótar á þessari síðu",
+ "skip.link.title": "Hoppa yfir í efni",
+ "source.link.title": "Fara í gagnahirslu (e. repository)",
+ "source.file.date.updated": "Síðasta uppfærsla",
+ "source.file.date.created": "Búið til",
+ "tabs.title": "Flipar",
+ "toc.title": "Efnisyfirlit"
+}[key] }}{% endmacro %}
diff --git a/src/partials/languages/it.html b/src/partials/languages/it.html
@@ -0,0 +1,58 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Translations: Italian -->
+{% macro t(key) %}{{ {
+ "language": "it",
+ "clipboard.copy": "Copia",
+ "clipboard.copied": "Copiato",
+ "edit.link.title": "Modifica",
+ "footer.previous": "Precedente",
+ "footer.next": "Prossimo",
+ "footer.title": "Piede",
+ "header.title": "Intestazione",
+ "meta.comments": "Commenti",
+ "meta.source": "Sorgente",
+ "nav.title": "Navigazione",
+ "search.config.lang": "it",
+ "search.placeholder": "Cerca",
+ "search.share": "Condividi",
+ "search.reset": "Cancella",
+ "search.result.initializer": "Inizializza la ricerca",
+ "search.result.placeholder": "Scrivi per iniziare a cercare",
+ "search.result.none": "Nessun documento trovato",
+ "search.result.one": "1 documento trovato",
+ "search.result.other": "# documenti trovati",
+ "search.result.more.one": "1 altro in questa pagina",
+ "search.result.more.other": "# altri in questa pagina",
+ "search.result.term.missing": "Non presente",
+ "search.title": "Cerca",
+ "select.language.title": "Seleziona la lingua",
+ "select.version.title": "Seleziona la versione",
+ "skip.link.title": "Vai al contenuto",
+ "source.link.title": "Apri repository",
+ "source.file.date.updated": "Ultimo aggiornamento",
+ "source.file.date.created": "Creata",
+ "tabs.title": "Tabs",
+ "toc.title": "Indice",
+ "top.title": "Torna su"
+}[key] }}{% endmacro %}
diff --git a/src/partials/languages/ja.html b/src/partials/languages/ja.html
@@ -0,0 +1,55 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Translations: Japanese -->
+{% macro t(key) %}{{ {
+ "language": "ja",
+ "clipboard.copy": "クリップボードへコピー",
+ "clipboard.copied": "コピーしました",
+ "edit.link.title": "編集",
+ "footer.previous": "前",
+ "footer.next": "次",
+ "footer.title": "フッター",
+ "header.title": "ヘッダー",
+ "meta.comments": "コメント",
+ "meta.source": "ソース",
+ "nav.title": "ナビゲーション",
+ "search.config.lang": "ja",
+ "search.config.pipeline": "trimmer, stemmer",
+ "search.config.separator": "[\\s\\- 、。,.]+",
+ "search.placeholder": "検索",
+ "search.reset": "クリア",
+ "search.result.initializer": "検索を初期化",
+ "search.result.placeholder": "検索キーワードを入力してください",
+ "search.result.none": "何も見つかりませんでした",
+ "search.result.one": "1件見つかりました",
+ "search.result.other": "#件見つかりました",
+ "search.result.more.one": "このページ内にもう1件見つかりました",
+ "search.result.more.other": "このページ内にあと#件見つかりました",
+ "search.result.term.missing": "検索に含まれない",
+ "skip.link.title": "コンテンツにスキップ",
+ "source.link.title": "リポジトリへ",
+ "source.file.date.updated": "最終更新日",
+ "source.file.date.created": "作成した",
+ "tabs.title": "タブ",
+ "toc.title": "目次"
+}[key] }}{% endmacro %}
diff --git a/src/partials/languages/ka.html b/src/partials/languages/ka.html
@@ -0,0 +1,49 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Translations: Georgian -->
+{% macro t(key) %}{{ {
+ "language": "ka",
+ "clipboard.copy": "კოპირება",
+ "clipboard.copied": "კოპირებულია",
+ "edit.link.title": "გვერდის რედარქირება",
+ "footer.previous": "წინა",
+ "footer.next": "შემდეგი",
+ "meta.comments": "კომენტარები",
+ "meta.source": "წყარო",
+ "nav.title": "ნავიგაცია",
+ "search.config.pipeline": " ",
+ "search.placeholder": "ძებნა",
+ "search.reset": "გასუფთავება",
+ "search.result.placeholder": "ჩაწერე ძებნის დასაწყებად",
+ "search.result.none": "დოკუმენტი ვერ მოიძებნა",
+ "search.result.one": "მოიძებნა 1 დოკუმენტი",
+ "search.result.other": "მოიძებნა # დოკუმენტი",
+ "search.result.more.one": "კიდევ 1 ამ გვერდზე",
+ "search.result.more.other": "კიდევ # ამ გვერდზე",
+ "skip.link.title": "კონტენტზე გადასვლა",
+ "source.link.title": "საცავში გადასვლა",
+ "source.file.date.updated": "ბოლო განახლება",
+ "source.file.date.created": "შეიქმნა",
+ "tabs.title": "ტაბები",
+ "toc.title": "სარჩევი"
+}[key] }}{% endmacro %}
diff --git a/src/partials/languages/kr.html b/src/partials/languages/kr.html
@@ -0,0 +1,54 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Translations: Korean -->
+{% macro t(key) %}{{ {
+ "language": "kr",
+ "clipboard.copy": "클립보드로 복사",
+ "clipboard.copied": "클립보드에 복사됨",
+ "edit.link.title": "이 페이지를 편집",
+ "footer.previous": "이전",
+ "footer.next": "다음",
+ "meta.comments": "댓글",
+ "meta.source": "출처",
+ "search.config.pipeline": " ",
+ "search.placeholder": "검색",
+ "search.share": "공유",
+ "search.reset": "지우기",
+ "search.result.initializer": "검색 초기화",
+ "search.result.placeholder": "검색어를 입력하세요",
+ "search.result.none": "검색어와 일치하는 문서가 없습니다",
+ "search.result.one": "1개의 일치하는 문서",
+ "search.result.other": "#개의 일치하는 문서",
+ "search.result.more.one": "이 문서에서 1개의 검색 결과 더 보기",
+ "search.result.more.other": "이 문서에서 #개의 검색 결과 더 보기",
+ "search.result.term.missing": "포함되지 않은 검색어",
+ "search.title": "검색",
+ "select.language.title": "언어설정",
+ "select.version.title": "버전 선택",
+ "skip.link.title": "콘텐츠로 이동",
+ "source.link.title": "저장소로 이동",
+ "source.file.date.updated": "마지막 업데이트",
+ "source.file.date.created": "작성일",
+ "toc.title": "목차",
+ "top.title": "맨위로"
+}[key] }}{% endmacro %}
diff --git a/src/partials/languages/lt.html b/src/partials/languages/lt.html
@@ -0,0 +1,58 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Translations: Lithuanian -->
+{% macro t(key) %}{{ {
+ "language": "lt",
+ "clipboard.copy": "Kopijuoti į iškarpinę",
+ "clipboard.copied": "Nukopijuota į iškarpinę",
+ "edit.link.title": "Redaguoti šį puslapį",
+ "footer.previous": "Ankstesnis",
+ "footer.next": "Sekantis",
+ "footer.title": "Poraštė",
+ "header.title": "Antraštė",
+ "meta.comments": "Komentarai",
+ "meta.source": "Išeitinis kodas",
+ "nav.title": "Navigacija",
+ "search.config.pipeline": " ",
+ "search.placeholder": "Paieška",
+ "search.share": "Dalintis",
+ "search.reset": "Išvalyti",
+ "search.result.initializer": "Paieškos inicijavimas",
+ "search.result.placeholder": "Įveskite norėdami pradėti paiešką",
+ "search.result.none": "Atitinkančių dokumentų nerasta",
+ "search.result.one": "1 atitinkantis dokumentas",
+ "search.result.other": "# atitinkantys dokumentai",
+ "search.result.more.one": "Dar 1 šiame puslapyje",
+ "search.result.more.other": "Dar # šiame puslapyje",
+ "search.result.term.missing": "Nerasta",
+ "search.title": "Paieška",
+ "select.language.title": "Pasirinkti kalbą",
+ "select.version.title": "Pasrinkti versiją",
+ "skip.link.title": "Pereiti prie turinio",
+ "source.link.title": "Eiti į saugyklą",
+ "source.file.date.updated": "Paskutinis atnaujinimas",
+ "source.file.date.created": "Sukurta",
+ "tabs.title": "Skirtukai",
+ "toc.title": "Turinys",
+ "top.title": "Grįžti į viršų"
+}[key] }}{% endmacro %}
diff --git a/src/partials/languages/lv.html b/src/partials/languages/lv.html
@@ -0,0 +1,55 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Translations: Latvian -->
+{% macro t(key) %}{{ {
+ "language": "lv",
+ "clipboard.copy": "Kopēt starpliktuvē",
+ "clipboard.copied": "Kopēts starpliktuvē",
+ "edit.link.title": "Rediģēt šo lapu",
+ "footer.previous": "Iepriekšējais",
+ "footer.next": "Nākamais",
+ "footer.title": "Kājene",
+ "header.title": "Galvene",
+ "meta.comments": "Komentārs",
+ "meta.source": "Avots",
+ "nav.title": "Navigācija",
+ "search.placeholder": "Meklēt",
+ "search.reset": "Notīrīt",
+ "search.result.initializer": "Notiek meklēšanas inicializācija",
+ "search.result.placeholder": "Ierakstiet, lai sāktu meklēšanu",
+ "search.result.none": "Nav atbilstošu dokumentu",
+ "search.result.one": "1 atbilstošs dokuments",
+ "search.result.other": "# atbilstoši dokumenti ",
+ "search.result.more.one": "1 šajā lapā",
+ "search.result.more.other": "# un vairāk šajā lapā",
+ "search.result.term.missing": "Trūkstošs",
+ "select.language.title": "Izvēlies valodu",
+ "select.version.title": "Izvēlies versiju",
+ "skip.link.title": "Pāriet uz saturu",
+ "source.link.title": "Doties uz repozitoriju",
+ "source.file.date.updated": "Pēdējoreiz atjaunots",
+ "source.file.date.created": "Izveidots",
+ "tabs.title": "Cilnes",
+ "toc.title": "Satura rādītājs",
+ "top.title": "Atpakaļ uz augšu"
+}[key] }}{% endmacro %}
diff --git a/src/partials/languages/mk.html b/src/partials/languages/mk.html
@@ -0,0 +1,56 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Translations: Macedonian -->
+{% macro t(key) %}{{ {
+ "language": "mk",
+ "clipboard.copy": "Копирај во таблата",
+ "clipboard.copied": "Копирано",
+ "edit.link.title": "Уредете ја оваа страница",
+ "footer.previous": "Претходно",
+ "footer.next": "Следно",
+ "footer.title": "Подножје",
+ "header.title": "Заглавје",
+ "meta.comments": "Коментари",
+ "meta.source": "Извор",
+ "nav.title": "Наслов за навигација",
+ "search.config.lang": "ru",
+ "search.placeholder": "Пребарување",
+ "search.reset": "Чисти",
+ "search.result.initializer": "Иницијализирање на пребарувањето",
+ "search.result.placeholder": "Напишете за да започнете со пребарување",
+ "search.result.none": "Нема соодветни документи",
+ "search.result.one": "1 документ што се совпаѓа",
+ "search.result.other": "# соодветни документи",
+ "search.result.more.one": "Уште 1 на оваа страница",
+ "search.result.more.other": "Уште # на оваа страница",
+ "search.result.term.missing": "Недостасува",
+ "select.language.title": "Изберете јазик",
+ "select.version.title": "Изберете верзија",
+ "skip.link.title": "Прескокнете до содржината",
+ "source.link.title": "Одете до складиштето",
+ "source.file.date.updated": "Последно ажурирање",
+ "source.file.date.created": "Создаден",
+ "tabs.title": "Јазичиња",
+ "toc.title": "Содржина",
+ "top.title": "Вратете се на почетокот"
+}[key] }}{% endmacro %}
diff --git a/src/partials/languages/mn.html b/src/partials/languages/mn.html
@@ -0,0 +1,51 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Translations: Mongolian -->
+{% macro t(key) %}{{ {
+ "language": "mn",
+ "clipboard.copy": "Хуулах",
+ "clipboard.copied": "Санах ойд хуулах",
+ "edit.link.title": "Хуудас засварлах",
+ "footer.previous": "Өмнөх",
+ "footer.next": "Дараах",
+ "footer.title": "Хөл",
+ "header.title": "Толгой",
+ "meta.comments": "Сэтгэгдэл",
+ "meta.source": "Эх үүсвэр",
+ "nav.title": "Чиглүүлэгч",
+ "search.config.lang": "ru",
+ "search.placeholder": "Хайлт",
+ "search.reset": "Цэвэрлэх",
+ "search.result.placeholder": "Хайлтын үгээ бичнэ үү",
+ "search.result.none": "Таарц илэрсэнгүй",
+ "search.result.one": "1 таарц илэрлээ",
+ "search.result.other": "# Тохирох баримт бичиг",
+ "search.result.more.one": "1 илүү хуудас байна",
+ "search.result.more.other": "# илүү хуудас байна",
+ "skip.link.title": "Агуулгыг алгасах",
+ "source.link.title": "Хадгалах сан руу очих",
+ "source.file.date.updated": "Сүүлийн шинэчлэлт",
+ "source.file.date.created": "Үүсгэсэн",
+ "tabs.title": "Табууд",
+ "toc.title": "Агуулга"
+}[key] }}{% endmacro %}
diff --git a/src/partials/languages/ms.html b/src/partials/languages/ms.html
@@ -0,0 +1,55 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Translations: Bahasa Malaysia -->
+{% macro t(key) %}{{ {
+ "language": "ms",
+ "clipboard.copy": "Salin ke papan keratan",
+ "clipboard.copied": "Disalin ke papan keratan",
+ "edit.link.title": "Edit halaman ini",
+ "footer.previous": "Sebelumnya",
+ "footer.next" : "Seterusnya",
+ "footer.title": "Pengaki",
+ "header.title": "Pengepala",
+ "meta.comments": "Komen",
+ "meta.source": "Sumber",
+ "nav.title": "Navigasi",
+ "search.placeholder": "Cari",
+ "search.reset": "Padam",
+ "search.result.initializer": "Siap carian",
+ "search.result.placeholder": "Taip untuk mula mencari",
+ "search.result.none": "Tiada dokumen yang sepadan",
+ "search.result.one": "1 dokumen yang sepadan",
+ "search.result.other": "# dokumen yang sepadan",
+ "search.result.more.one": "1 lagi di halaman ini",
+ "search.result.more.other": "# lagi di halaman ini",
+ "search.result.term.missing": "Hilang",
+ "select.language.title": "Pilih bahasa",
+ "select.version.title": "Pilih versi",
+ "skip.link.title": "Langkau tajuk talian",
+ "source.link.title": "tajuk talian asal",
+ "source.file.date.updated": "Tarikh fil dikemas kini",
+ "source.file.date.created": "tarikh fil asal dicipta",
+ "tabs.title": "Tab",
+ "toc.title": "Jadual kandungan",
+ "top.title": "Kembali ke atas"
+}[key] }}{% endmacro %}
diff --git a/src/partials/languages/my.html b/src/partials/languages/my.html
@@ -0,0 +1,49 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Translations: Burmese -->
+{% macro t(key) %}{{ {
+ "language": "my",
+ "clipboard.copy": "ကလစ်ဘုတ် သို့ ကူးယူရန်",
+ "clipboard.copied": "ကလစ်ဘုတ် သို့ ကူယူပြီး",
+ "edit.link.title": "ဤ စာမျက်နှာကို ပြင်ရန်",
+ "footer.previous": "နောက်သို့",
+ "footer.next": "ရှေ့သို့",
+ "footer.title": "အောက်ခြေ",
+ "header.title": "ခေါင်းပိုင်း",
+ "meta.comments": "မှတ်ချက်များ",
+ "meta.source": "ရင်းမြစ်",
+ "nav.title": "လမ်းညွှန်",
+ "search.config.pipeline": " ",
+ "search.placeholder": "ရှာရန်",
+ "search.reset": "ရှင်းလင်း",
+ "search.result.placeholder": "ရှာဖွေခြင်းစရန် စာရိုက်ပါ",
+ "search.result.none": "တူညီသော စာရွက်စာတမ်းများ မရှိပါ",
+ "search.result.one": "စာရွက်စာတမ်း ၁ ခု တူညီသည်",
+ "search.result.other": "စာရွက်စာတမ်း # ခု တူညီသည်",
+ "skip.link.title": "မာတိကာ သို့ သွားရန်",
+ "source.link.title": "repository သို့ သွားရန်",
+ "source.file.date.updated": "နောက်ဆုံး ထုတ်ပြန်ချက်",
+ "source.file.date.created": "နေပြည်တော်",
+ "tabs.title": "တက်များ",
+ "toc.title": "ပါဝင်အကြောင်းအရာများ"
+}[key] }}{% endmacro %}
diff --git a/src/partials/languages/nl.html b/src/partials/languages/nl.html
@@ -0,0 +1,44 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Translations: Dutch -->
+{% macro t(key) %}{{ {
+ "language": "nl",
+ "clipboard.copy": "Kopiëren naar klembord",
+ "clipboard.copied": "Gekopieerd naar klembord",
+ "edit.link.title": "Wijzig deze pagina",
+ "footer.previous": "Vorige",
+ "footer.next": "Volgende",
+ "meta.comments": "Reacties",
+ "meta.source": "Bron",
+ "search.config.lang": "nl",
+ "search.placeholder": "Zoeken",
+ "search.result.placeholder": "Typ om te beginnen met zoeken",
+ "search.result.none": "Geen overeenkomende documenten",
+ "search.result.one": "1 overeenkomende document",
+ "search.result.other": "# overeenkomende documenten",
+ "skip.link.title": "Ga naar inhoud",
+ "source.link.title": "Ga naar repository",
+ "source.file.date.updated": "Laatst geüpdatet",
+ "source.file.date.created": "Gecreëerd",
+ "toc.title": "Inhoudsopgave"
+}[key] }}{% endmacro %}
diff --git a/src/partials/languages/nn.html b/src/partials/languages/nn.html
@@ -0,0 +1,44 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Translations: Norwegian Nynorsk -->
+{% macro t(key) %}{{ {
+ "language": "nn",
+ "clipboard.copy": "Kopier til utklippstavla",
+ "clipboard.copied": "Kopiert til utklippstavla",
+ "edit.link.title": "Rediger denne sida",
+ "footer.previous": "Førre",
+ "footer.next": "Neste",
+ "meta.comments": "Kommentarar",
+ "meta.source": "Kjelde",
+ "search.config.lang": "no",
+ "search.placeholder": "Søk",
+ "search.result.placeholder": "Skriv søkeord",
+ "search.result.none": "Ingen treff",
+ "search.result.one": "1 treff",
+ "search.result.other": "# treff",
+ "skip.link.title": "Gå til innhald",
+ "source.link.title": "Gå til kjelde",
+ "source.file.date.updated": "Siste oppdatering",
+ "source.file.date.created": "Laget",
+ "toc.title": "Innhaldsliste"
+}[key] }}{% endmacro %}
diff --git a/src/partials/languages/no.html b/src/partials/languages/no.html
@@ -0,0 +1,44 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Translations: Norwegian Bokmål -->
+{% macro t(key) %}{{ {
+ "language": "no",
+ "clipboard.copy": "Kopier til utklippstavlen",
+ "clipboard.copied": "Kopiert til utklippstavlen",
+ "edit.link.title": "Rediger denne siden",
+ "footer.previous": "Forrige",
+ "footer.next": "Neste",
+ "meta.comments": "Kommentarer",
+ "meta.source": "Kilde",
+ "search.config.lang": "no",
+ "search.placeholder": "Søk",
+ "search.result.placeholder": "Skriv søkeord",
+ "search.result.none": "Ingen treff",
+ "search.result.one": "1 treff",
+ "search.result.other": "# treff",
+ "skip.link.title": "Gå til innhold",
+ "source.link.title": "Gå til kilde",
+ "source.file.date.updated": "Siste oppdatering",
+ "source.file.date.created": "Created",
+ "toc.title": "Innholdsfortegnelse"
+}[key] }}{% endmacro %}
diff --git a/src/partials/languages/pl.html b/src/partials/languages/pl.html
@@ -0,0 +1,53 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Translations: Polish -->
+{% macro t(key) %}{{ {
+ "language": "pl",
+ "clipboard.copy": "Kopiuj do schowka",
+ "clipboard.copied": "Skopiowane",
+ "edit.link.title": "Edytuj tę stronę",
+ "footer.previous": "Poprzednia strona",
+ "footer.next": "Następna strona",
+ "footer.title": "Stopka",
+ "header.title": "Nagłówek",
+ "meta.comments": "Komentarze",
+ "meta.source": "Kod źródłowy",
+ "search.config.pipeline": " ",
+ "nav.title": "Nawigacja",
+ "search.placeholder": "Szukaj",
+ "search.reset": "Wyczyść",
+ "search.result.initializer": "Inicjowanie wyszukiwania",
+ "search.result.placeholder": "Zacznij pisać, aby szukać",
+ "search.result.none": "Brak wyników wyszukiwania",
+ "search.result.one": "Wyniki wyszukiwania: 1",
+ "search.result.other": "Wyniki wyszukiwania: #",
+ "search.result.more.one": "1 więcej na tej stronie",
+ "search.result.more.other": "# więcej na tej stronie",
+ "search.result.term.missing": "Brak",
+ "skip.link.title": "Przejdź do treści",
+ "source.link.title": "Idź do repozytorium",
+ "source.file.date.updated": "Ostatnia aktualizacja",
+ "source.file.date.created": "Utworzony",
+ "tabs.title": "Zakładki",
+ "toc.title": "Spis treści"
+}[key] }}{% endmacro %}
diff --git a/src/partials/languages/pt-BR.html b/src/partials/languages/pt-BR.html
@@ -0,0 +1,57 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Translations: Portuguese (Brasilian) -->
+{% macro t(key) %}{{ {
+ "language": "pt",
+ "clipboard.copy": "Copiar para área de Transferência",
+ "clipboard.copied": "Copiado para área de Transferência",
+ "edit.link.title": "Editar esta página",
+ "footer.previous": "Anterior",
+ "footer.next": "Próximo",
+ "footer.title": "Rodapé",
+ "header.title": "Cabeçalho",
+ "meta.comments": "Comentários",
+ "meta.source": "Origem",
+ "nav.title": "Navegação",
+ "search.config.lang": "pt",
+ "search.placeholder": "Buscar",
+ "search.reset": "Limpar",
+ "search.result.initializer": "Inicializando busca",
+ "search.result.placeholder": "Digite para iniciar a busca",
+ "search.result.none": "Nenhum documento encontrado",
+ "search.result.one": "1 documento encontrado",
+ "search.result.other": "# documentos encontrados",
+ "search.result.more.one": "1 more on this page",
+ "search.result.more.other": "# more on this page",
+ "search.result.term.missing": "Perdido",
+ "search.title": "Pesquisar",
+ "select.language.title": "Selecione a linguagem",
+ "select.version.title": "Selecione a versão",
+ "skip.link.title": "Pular para conteúdo",
+ "source.link.title": "Ir para repositório",
+ "source.file.date.updated": "Ultima atualização",
+ "source.file.date.created": "Criado em",
+ "tabs.title": "Abas",
+ "toc.title": "Indice",
+ "top.title": "Voltar para o topo"
+}[key] }}{% endmacro %}
diff --git a/src/partials/languages/pt.html b/src/partials/languages/pt.html
@@ -0,0 +1,58 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Translations: Portuguese -->
+{% macro t(key) %}{{ {
+ "language": "pt",
+ "clipboard.copy": "Copiar para área de transferência",
+ "clipboard.copied": "Copiado para área de transferência",
+ "edit.link.title": "Editar esta página",
+ "footer.previous": "Anterior",
+ "footer.next": "Próximo",
+ "footer.title": "Rodapé",
+ "header.title": "Cabeçalho",
+ "meta.comments": "Comentários",
+ "meta.source": "Fonte",
+ "nav.title": "Navegação",
+ "search.config.lang": "pt",
+ "search.placeholder": "Buscar",
+ "search.share": "Compartilhar",
+ "search.reset": "Limpar",
+ "search.result.initializer": "Inicializando a pesquisa",
+ "search.result.placeholder": "Digite para iniciar a busca",
+ "search.result.none": "Nenhum resultado encontrado",
+ "search.result.one": "1 resultado encontrado",
+ "search.result.other": "# resultados encontrados",
+ "search.result.more.one": "Mais 1 nesta página",
+ "search.result.more.other": "Mais # nesta página",
+ "search.result.term.missing": "Ausente",
+ "search.title": "Pesquisar",
+ "select.language.title": "Selecione o idioma",
+ "select.version.title": "Selecione a versão",
+ "skip.link.title": "Ir para o conteúdo",
+ "source.link.title": "Ir ao repositório",
+ "source.file.date.updated": "Última atualização",
+ "source.file.date.created": "Criada",
+ "tabs.title": "Abas",
+ "toc.title": "Índice",
+ "top.title": "Voltar ao topo"
+}[key] }}{% endmacro %}
diff --git a/src/partials/languages/ro.html b/src/partials/languages/ro.html
@@ -0,0 +1,44 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Translations: Romanian -->
+{% macro t(key) %}{{ {
+ "language": "ro",
+ "clipboard.copy": "Copiază în clipboard",
+ "clipboard.copied": "Copiat în clipboard",
+ "edit.link.title": "Editeaza această pagină",
+ "footer.previous": "Anterior",
+ "footer.next": "Următor",
+ "meta.comments": "Comentarii",
+ "meta.source": "Sursă",
+ "search.config.lang": "ro",
+ "search.placeholder": "Căutare",
+ "search.result.placeholder": "Tastează pentru a începe căutarea",
+ "search.result.none": "Nu a fost găsit niciun document",
+ "search.result.one": "1 document găsit",
+ "search.result.other": "# documente găsite",
+ "skip.link.title": "Sari la conținut",
+ "source.link.title": "Accesează repository-ul",
+ "source.file.date.updated": "Ultima actualizare",
+ "source.file.date.created": "Creată",
+ "toc.title": "Cuprins"
+}[key] }}{% endmacro %}
diff --git a/src/partials/languages/ru.html b/src/partials/languages/ru.html
@@ -0,0 +1,58 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Translations: Russian -->
+{% macro t(key) %}{{ {
+ "language": "ru",
+ "clipboard.copy": "Копировать в буфер",
+ "clipboard.copied": "Скопировано в буфер",
+ "edit.link.title": "Редактировать страницу",
+ "footer.previous": "Назад",
+ "footer.next": "Вперед",
+ "footer.title": "Нижний колонтитул",
+ "header.title": "Верхний колонтитул",
+ "meta.comments": "Комментарии",
+ "meta.source": "Исходный код",
+ "nav.title": "Навигация",
+ "search.config.lang": "ru",
+ "search.placeholder": "Поиск",
+ "search.share": "Поделиться",
+ "search.reset": "Очистить",
+ "search.result.initializer": "Инициализация поиска",
+ "search.result.placeholder": "Начните печатать для поиска",
+ "search.result.none": "Совпадений не найдено",
+ "search.result.one": "Найдено 1 совпадение",
+ "search.result.other": "Найдено совпадений: #",
+ "search.result.more.one": "Ещё 1 на этой странице",
+ "search.result.more.other": "Ещё # на этой странице",
+ "search.result.term.missing": "Отсутствует",
+ "search.title": "Поиск",
+ "select.language.title": "Выберите язык",
+ "select.version.title": "Выберите версию",
+ "skip.link.title": "Перейти к содержанию",
+ "source.link.title": "Перейти к репозиторию",
+ "source.file.date.updated": "Последнее обновление",
+ "source.file.date.created": "Дата создания",
+ "tabs.title": "Вкладки",
+ "toc.title": "Содержание раздела",
+ "top.title": "К началу"
+}[key] }}{% endmacro %}
diff --git a/src/partials/languages/sh.html b/src/partials/languages/sh.html
@@ -0,0 +1,57 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Translations: Serbo-Croatian -->
+{% macro t(key) %}{{ {
+ "language": "sh",
+ "clipboard.copy": "Kopiraj u klipbord",
+ "clipboard.copied": "Iskopirano u klipbord",
+ "edit.link.title": "Ažuriraj stranicu",
+ "footer.previous": "Prethodno",
+ "footer.next": "Sledeće",
+ "footer.title": "Podnožje",
+ "header.title": "Zaglavlje",
+ "meta.comments": "Komentari",
+ "meta.source": "Izvor",
+ "nav.title": "Navigacija",
+ "search.placeholder": "Pretraga",
+ "search.share": "Deljenje",
+ "search.reset": "Očisti",
+ "search.result.initializer": "Inicijalizujem pretragu",
+ "search.result.placeholder": "Unesite pojam pretrage",
+ "search.result.none": "Ništa nije pronađeno",
+ "search.result.one": "1 rezultat pretrage",
+ "search.result.other": "# rezultata pretrage",
+ "search.result.more.one": "još 1 na ovoj strani",
+ "search.result.more.other": "još # na ovoj strani",
+ "search.result.term.missing": "Nedostaje",
+ "search.title": "Pretraga",
+ "select.language.title": "Izaberi jezik",
+ "select.version.title": "Izaberi verziju",
+ "skip.link.title": "Idi na tekst",
+ "source.link.title": "Idi u repozitorijum",
+ "source.file.date.updated": "Ažuriran",
+ "source.file.date.created": "Kreiran",
+ "tabs.title": "Tabovi",
+ "toc.title": "Sadržaj",
+ "top.title": "Nazad na vrh"
+}[key] }}{% endmacro %}
diff --git a/src/partials/languages/si.html b/src/partials/languages/si.html
@@ -0,0 +1,51 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Translations: Sinhalese -->
+{% macro t(key) %}{{ {
+ "language": "si",
+ "clipboard.copy": "කොපි කරන්න",
+ "clipboard.copied": "කොපි කළා",
+ "edit.link.title": "පිටුව සංස්කරණය",
+ "footer.previous": "පසුගිය",
+ "footer.next": "මීළඟ",
+ "footer.title": "පාදම",
+ "header.title": "ශීර්ෂය",
+ "meta.comments": "ප්රතිචාර",
+ "meta.source": "මූලාශ්රය",
+ "nav.title": "යාත්රණය",
+ "search.config.pipeline": " ",
+ "search.placeholder": "සොයන්න",
+ "search.reset": "මකන්න",
+ "search.result.placeholder": "සෙවීමට ටයිප් කරන්න",
+ "search.result.none": "කිසිවක් හමු නොවුණි",
+ "search.result.one": "1 ගැලපෙන ගොනුවක්",
+ "search.result.other": "ගැලපෙන ගොනු # ක්",
+ "search.result.more.one": "තව 1 ප්රතිඵලයක්",
+ "search.result.more.other": "තව ප්රතිඵල # ක්",
+ "skip.link.title": "අන්තර්ගතය වෙත යන්න",
+ "source.link.title": "රිපොසිටරියට යන්න",
+ "source.file.date.updated": "අවසන් යාවත්කාලීන වීම",
+ "source.file.date.created": "ٺاھيو ويو",
+ "tabs.title": "ටැබ්ස්",
+ "toc.title": "පටුන"
+}[key] }}{% endmacro %}
diff --git a/src/partials/languages/sk.html b/src/partials/languages/sk.html
@@ -0,0 +1,43 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Translations: Slovak -->
+{% macro t(key) %}{{ {
+ "language": "sk",
+ "clipboard.copy": "Kopírovať do schránky",
+ "clipboard.copied": "Skopírované do schránky",
+ "edit.link.title": "Upraviť túto stránku",
+ "footer.previous": "Späť",
+ "footer.next": "Ďalej",
+ "meta.comments": "Komentáre",
+ "meta.source": "Zdroj",
+ "search.placeholder": "Hľadať",
+ "search.result.placeholder": "Pre vyhľadávanie začni písať",
+ "search.result.none": "Žiadne vyhovujúce dokumenty",
+ "search.result.one": "Vyhovujúci dokument: 1",
+ "search.result.other": "Vyhovujúce dokumenty: #",
+ "skip.link.title": "Preskočiť na obsah",
+ "source.link.title": "Zobraziť repozitár",
+ "source.file.date.updated": "Posledná aktualizácia",
+ "source.file.date.created": "Vytvorené",
+ "toc.title": "Obsah"
+}[key] }}{% endmacro %}
diff --git a/src/partials/languages/sl.html b/src/partials/languages/sl.html
@@ -0,0 +1,43 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Translations: Slovenian -->
+{% macro t(key) %}{{ {
+ "language": "sl",
+ "clipboard.copy": "Kopiraj v odložišče",
+ "clipboard.copied": "Kopirano v odložišče",
+ "edit.link.title": "Uredi stran",
+ "footer.previous": "Prejšnja stran",
+ "footer.next": "Naslednja stran",
+ "meta.comments": "Komentarji",
+ "meta.source": "Izvorna koda",
+ "search.placeholder": "Išči",
+ "search.result.placeholder": "Vpiši iskalni niz",
+ "search.result.none": "Ni zadetkov",
+ "search.result.one": "1 zadetek",
+ "search.result.other": "# zadetkov",
+ "skip.link.title": "Skoči na vsebino",
+ "source.link.title": "Pojdi na repozitorij",
+ "source.file.date.updated": "Zadnja posodobitev",
+ "source.file.date.created": "Ustvarjeno",
+ "toc.title": "Kazalo"
+}[key] }}{% endmacro %}
diff --git a/src/partials/languages/sr.html b/src/partials/languages/sr.html
@@ -0,0 +1,57 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Translations: Serbian -->
+{% macro t(key) %}{{ {
+ "language": "sr",
+ "clipboard.copy": "Копирај у клипборд",
+ "clipboard.copied": "Ископирано у клипборд",
+ "edit.link.title": "Ажурирај страницу",
+ "footer.previous": "Претходно",
+ "footer.next": "Следеће",
+ "footer.title": "Подножје",
+ "header.title": "Заглавље",
+ "meta.comments": "Коментари",
+ "meta.source": "Извор",
+ "nav.title": "Навигација",
+ "search.placeholder": "Претрага",
+ "search.share": "Дељење",
+ "search.reset": "Очисти",
+ "search.result.initializer": "Иницијализујем претрагу",
+ "search.result.placeholder": "Унесите појам претраге",
+ "search.result.none": "Ништа није пронађено",
+ "search.result.one": "1 резултат претраге",
+ "search.result.other": "# резултата претраге",
+ "search.result.more.one": "још 1 на овој страни",
+ "search.result.more.other": "још # на овој страни",
+ "search.result.term.missing": "Недостаје",
+ "search.title": "Претрага",
+ "select.language.title": "Изабери језик",
+ "select.version.title": "Изабери верзију",
+ "skip.link.title": "Иди на текст",
+ "source.link.title": "Иди у репозиторијум",
+ "source.file.date.updated": "Ажуриран",
+ "source.file.date.created": "Креиран",
+ "tabs.title": "Табови",
+ "toc.title": "Садржај",
+ "top.title": "Назад на врх"
+}[key] }}{% endmacro %}
diff --git a/src/partials/languages/sv.html b/src/partials/languages/sv.html
@@ -0,0 +1,60 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Translations: Swedish -->
+{% macro t(key) %}{{ {
+ "language": "sv",
+ "clipboard.copy": "Kopiera till urklipp",
+ "clipboard.copied": "Kopierat till urklipp",
+ "consent.accept": "Acceptera",
+ "consent.manage": "Hantera inställningar",
+ "edit.link.title": "Redigera sidan",
+ "footer.previous": "Föregående",
+ "footer.next": "Nästa",
+ "footer.title": "Sidfot",
+ "header.title": "Sidhuvud",
+ "meta.comments": "Kommentarer",
+ "meta.source": "Källa",
+ "nav.title": "Navigation",
+ "search.config.lang": "sv",
+ "search.placeholder": "Sök",
+ "search.share": "Dela",
+ "search.reset": "Rensa",
+ "search.result.initializer": "Initialiserar sök",
+ "search.result.placeholder": "Skriv sökord",
+ "search.result.none": "Inga sökresultat",
+ "search.result.one": "1 sökresultat",
+ "search.result.other": "# sökresultat",
+ "search.result.more.one": "1 till på denna sidan",
+ "search.result.more.other": "# till på denna sidan",
+ "search.result.term.missing": "Saknas",
+ "search.title": "Sök",
+ "select.language.title": "Välj språk",
+ "select.version.title": "Välj version",
+ "skip.link.title": "Gå till innehållet",
+ "source.link.title": "Gå till datakatalog",
+ "source.file.date.updated": "Senaste uppdaterad",
+ "source.file.date.created": "Skapad",
+ "tabs.title": "Flikar",
+ "toc.title": "Innehållsförteckning",
+ "top.title": "Tillbaka till toppen"
+}[key] }}{% endmacro %}
diff --git a/src/partials/languages/th.html b/src/partials/languages/th.html
@@ -0,0 +1,44 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Translations: Thai -->
+{% macro t(key) %}{{ {
+ "language": "th",
+ "clipboard.copy": "คัดลอก",
+ "clipboard.copied": "คัดลอกแล้ว",
+ "edit.link.title": "แก้ไขหน้านี้",
+ "footer.previous": "ก่อนหน้า",
+ "footer.next": "ต่อไป",
+ "meta.comments": "ความคิดเห็น",
+ "meta.source": "แหล่งที่มา",
+ "search.config.lang": "th",
+ "search.placeholder": "ค้นหา",
+ "search.result.placeholder": "พิมพ์เพื่อเริ่มค้นหา",
+ "search.result.none": "ไม่พบเอกสารที่ตรงกัน",
+ "search.result.one": "พบเอกสารที่ตรงกัน",
+ "search.result.other": "พบ # เอกสารที่ตรงกัน",
+ "skip.link.title": "ข้ามไปที่เนื้อหา",
+ "source.link.title": "ไปที่ Repository",
+ "source.file.date.updated": "สร้าง",
+ "source.file.date.created": "สร้าง",
+ "toc.title": "สารบัญ"
+}[key] }}{% endmacro %}
diff --git a/src/partials/languages/tl.html b/src/partials/languages/tl.html
@@ -0,0 +1,57 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Translations: Tagalog -->
+{% macro t(key) %}{{ {
+ "language": "tl",
+ "clipboard.copy": "Kopyahin sa clipboard",
+ "clipboard.copied": "Nakopya mula sa clipboard",
+ "edit.link.title": "I-edit ang pahinang ito",
+ "footer.previous": "Nakaraan",
+ "footer.next": "Susunod",
+ "footer.title": "Lagdang Pangwakas",
+ "header.title": "Pamuhatan",
+ "meta.comments": "Mga Komento",
+ "meta.source": "Pinagmulan",
+ "nav.title": "Nabigasyon",
+ "search.placeholder": "Hanapin",
+ "search.share": "Ibahagi",
+ "search.reset": "Tanggalin",
+ "search.result.initializer": "Sinisimulan ang paghahanap",
+ "search.result.placeholder": "Mag-type upang simulan ang paghahanap",
+ "search.result.none": "Walang nahanap na dokumento",
+ "search.result.one": "1 magkatugmang dokumento",
+ "search.result.other": "# magkatugmang mga dokumento",
+ "search.result.more.one": "1 meron sa pahina na ito",
+ "search.result.more.other": "# meron sa pahina na ito",
+ "search.result.term.missing": "Nawawala",
+ "search.title": "Hanapin",
+ "select.language.title": "Pumili ng lenguwahe",
+ "select.version.title": "Pumili ng bersyon",
+ "skip.link.title": "I-skip tungo sa nilalaman",
+ "source.link.title": "Pumunta sa repository",
+ "source.file.date.updated": "Huling update",
+ "source.file.date.created": "Nagawa",
+ "tabs.title": "Mga tala",
+ "toc.title": "Talaan ng nilalaman",
+ "top.title": "Bumalik sa taas"
+}[key] }}{% endmacro %}
diff --git a/src/partials/languages/tr.html b/src/partials/languages/tr.html
@@ -0,0 +1,44 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Translations: Turkish -->
+{% macro t(key) %}{{ {
+ "language": "tr",
+ "clipboard.copy": "Kopyala",
+ "clipboard.copied": "Kopyalandı",
+ "edit.link.title": "Düzenle",
+ "footer.previous": "Önceki",
+ "footer.next": "Sonraki",
+ "meta.comments": "Yorumlar",
+ "meta.source": "Kaynak",
+ "search.config.lang": "tr",
+ "search.placeholder": "Ara",
+ "search.result.placeholder": "Aramaya başlamak için yazın",
+ "search.result.none": "Eşleşen doküman bulunamadı",
+ "search.result.one": "1 doküman bulundu",
+ "search.result.other": "# doküman bulundu",
+ "skip.link.title": "Ana içeriğe geç",
+ "source.link.title": "Depoya git",
+ "source.file.date.updated": "Son Güncelleme",
+ "source.file.date.created": "Oluşturuldu",
+ "toc.title": "İçindekiler"
+}[key] }}{% endmacro %}
diff --git a/src/partials/languages/uk.html b/src/partials/languages/uk.html
@@ -0,0 +1,44 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Translations: Ukrainian -->
+{% macro t(key) %}{{ {
+ "language": "uk",
+ "clipboard.copy": "Скопіювати в буфер",
+ "clipboard.copied": "Скопійовано в буфер",
+ "edit.link.title": "Редагувати сторінку",
+ "footer.previous": "Назад",
+ "footer.next": "Вперед",
+ "meta.comments": "Коментарі",
+ "meta.source": "Вихідний код",
+ "search.config.lang": "ru",
+ "search.placeholder": "Пошук",
+ "search.result.placeholder": "Розпочніть писати для пошуку",
+ "search.result.none": "Збігів не знайдено",
+ "search.result.one": "Знайдено 1 збіг",
+ "search.result.other": "Знайдено # збігів",
+ "skip.link.title": "Перейти до змісту",
+ "source.link.title": "Перейти до репозиторію",
+ "source.file.date.updated": "Останнє оновлення",
+ "source.file.date.created": "Створено",
+ "toc.title": "Зміст"
+}[key] }}{% endmacro %}
diff --git a/src/partials/languages/ur.html b/src/partials/languages/ur.html
@@ -0,0 +1,59 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Translations: Urdu -->
+{% macro t(key) %}{{ {
+ "language": "ur",
+ "direction": "rtl",
+ "clipboard.copy": "کلِپ بورڈ میں نقل کریں",
+ "clipboard.copied": "کلِپ بورڈ میں نقل کر دیا گیا",
+ "edit.link.title": "اس صفحے میں ترمیم کریں",
+ "footer.previous": "پچھلا",
+ "footer.next": "اگلا",
+ "footer.title": "ذیلی تحریر",
+ "header.title": "سر تحریر",
+ "meta.comments": "تبصرے",
+ "meta.source": "ذریعہ",
+ "nav.title": "رہنمائی",
+ "search.config.pipeline": " ",
+ "search.placeholder": "تلاش کریں",
+ "search.share": "اشتراک کریں",
+ "search.reset": "صاف کریں",
+ "search.result.initializer": "تلاش کا آغاز ہو رہا ہے",
+ "search.result.placeholder": "تلاش شروع کرنے کے لئے ٹائپ کریں",
+ "search.result.none": "کوئی ملتی جلتی دستاویزات نہیں",
+ "search.result.one": "۱ ملتی جلتی دستاویز",
+ "search.result.other": "# ملتی جلتی دستاویزات",
+ "search.result.more.one": "اِس صفحے پر مزید ۱",
+ "search.result.more.other": "اِس صفحے پر مزید #",
+ "search.result.term.missing": "گمشدہ",
+ "search.title": "تلاش",
+ "select.language.title": "زبان کا انتخاب کریں",
+ "select.version.title": "ورژن کا انتخاب کریں",
+ "skip.link.title": "براہِ راست مواد پر جائیں",
+ "source.link.title": "ریپازٹری پر جائیں",
+ "source.file.date.updated": "آخری بار تجدید",
+ "source.file.date.created": "تخلیق",
+ "tabs.title": "ٹیبز",
+ "toc.title": "فہرست",
+ "top.title": "واپس اوپر جائیں"
+}[key] }}{% endmacro %}
diff --git a/src/partials/languages/uz.html b/src/partials/languages/uz.html
@@ -0,0 +1,58 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Translations: Uzbek -->
+{% macro t(key) %}{{ {
+ "language": "uz",
+ "clipboard.copy": "Buferga nusxalash",
+ "clipboard.copied": "Buferga nusxalandi",
+ "edit.link.title": "Ushbu sahifani tahrirlash",
+ "footer.previous": "Oldingi sahifa",
+ "footer.next": "Keyingi sahifa",
+ "footer.title": "Pastgi qism",
+ "header.title": "Sarlavha",
+ "meta.comments": "Izohlar",
+ "meta.source": "Manba",
+ "nav.title": "Navigatsiya",
+ "search.config.lang": "tr",
+ "search.placeholder": "Qidirish",
+ "search.share": "Ulashish",
+ "search.reset": "Tozalash",
+ "search.result.initializer": "Qidiruv ishga tushirilmoqda",
+ "search.result.placeholder": "Qidiruvni boshlash uchun kiriting",
+ "search.result.none": "Mos natijalar yo'q",
+ "search.result.one": "1 ta mos natija",
+ "search.result.other": "# ta mos keladigan natijalar",
+ "search.result.more.one": "Ushbu sahifada yana 1 ta natija",
+ "search.result.more.other": "Bu sahifada yana # ta natija",
+ "search.result.term.missing": "To'ldirilmagan",
+ "search.title": "Qidirish",
+ "select.language.title": "Tilni tanlang",
+ "select.version.title": "Versiyani tanlang",
+ "skip.link.title": "Tarkibga o'tish",
+ "source.link.title": "Repozitoriyga o'tish",
+ "source.file.date.updated": "Oxirgi yangilanish",
+ "source.file.date.created": "Yaratildi",
+ "tabs.title": "Yorliqlar",
+ "toc.title": "Mundarija",
+ "top.title": "Yuqoriga qaytish"
+}[key] }}{% endmacro %}
diff --git a/src/partials/languages/vi.html b/src/partials/languages/vi.html
@@ -0,0 +1,44 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Translations: Vietnamese -->
+{% macro t(key) %}{{ {
+ "language": "vi",
+ "clipboard.copy": "Sao chép vào bộ nhớ",
+ "clipboard.copied": "Sao chép xong",
+ "edit.link.title": "Chỉnh sửa",
+ "footer.previous": "Trước",
+ "footer.next": "Sau",
+ "meta.comments": "Bình luận",
+ "meta.source": "Mã nguồn",
+ "search.config.lang": "vi",
+ "search.placeholder": "Tìm kiếm",
+ "search.result.placeholder": "Nhập để bắt đầu tìm kiếm",
+ "search.result.none": "Không tìm thấy tài liệu liên quan",
+ "search.result.one": "1 tài liệu liên quan",
+ "search.result.other": "# tài liệu liên quan",
+ "skip.link.title": "Vào thẳng nội dung",
+ "source.link.title": "Đến kho lưu trữ mã nguồn",
+ "source.file.date.updated": "Cập nhật cuối cùng",
+ "source.file.date.created": "Tạo",
+ "toc.title": "Mục lục"
+}[key] }}{% endmacro %}
diff --git a/src/partials/languages/zh-Hant.html b/src/partials/languages/zh-Hant.html
@@ -0,0 +1,47 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Translations: Chinese (Traditional) -->
+{% macro t(key) %}{{ {
+ "language": "zh-Hant",
+ "clipboard.copy": "拷貝",
+ "clipboard.copied": "已拷貝",
+ "edit.link.title": "編輯此頁",
+ "footer.previous": "上一頁",
+ "footer.next": "下一頁",
+ "meta.comments": "評論",
+ "meta.source": "來源",
+ "search.config.lang": "ja",
+ "search.config.pipeline": "trimmer, stemmer",
+ "search.config.separator": "[\\s\\-,。]+",
+ "search.placeholder": "搜尋",
+ "search.result.initializer": "正在初始化搜尋引擎",
+ "search.result.placeholder": "鍵入以開始檢索",
+ "search.result.none": "沒有找到符合條件的結果",
+ "search.result.one": "找到 1 个符合條件的結果",
+ "search.result.other": "# 個符合條件的結果",
+ "skip.link.title": "跳轉至",
+ "source.link.title": "前往倉庫",
+ "source.file.date.updated": "最後更新",
+ "source.file.date.created": "建立日期",
+ "toc.title": "目錄"
+}[key] }}{% endmacro %}
diff --git a/src/partials/languages/zh-TW.html b/src/partials/languages/zh-TW.html
@@ -0,0 +1,53 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Translations: Chinese (Taiwanese) -->
+{% macro t(key) %}{{ {
+ "language": "zh-Hant",
+ "announce.dismiss": "不再顯示此訊息",
+ "clipboard.copy": "複製",
+ "clipboard.copied": "已複製",
+ "consent.accept": "同意",
+ "consent.manage": "管理設定",
+ "consent.reject": "拒絕",
+ "edit.link.title": "編輯此頁",
+ "footer.previous": "上一頁",
+ "footer.next": "下一頁",
+ "meta.comments": "留言",
+ "meta.source": "來源",
+ "search.config.lang": "ja",
+ "search.config.pipeline": "trimmer, stemmer",
+ "search.config.separator": "[\\s\\- 、。,.?;]+",
+ "search.placeholder": "搜尋",
+ "search.result.initializer": "正在初始化搜尋引擎",
+ "search.result.placeholder": "打字進行搜尋",
+ "search.result.none": "沒有符合的項目",
+ "search.result.one": "找到 1 個符合的項目",
+ "search.result.other": "找到 # 個符合的項目",
+ "search.result.more.one": "此頁尚有 1 個符合的項目",
+ "search.result.more.other": "此頁尚有 # 個符合的項目",
+ "skip.link.title": "跳轉到",
+ "source.link.title": "前往倉庫",
+ "source.file.date.updated": "最後更新",
+ "source.file.date.created": "建立日期",
+ "toc.title": "目錄"
+}[key] }}{% endmacro %}
diff --git a/src/partials/languages/zh.html b/src/partials/languages/zh.html
@@ -0,0 +1,64 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Translations: Chinese (Simplified) -->
+{% macro t(key) %}{{ {
+ "language": "zh",
+ "announce.dismiss": "不再显示此消息",
+ "clipboard.copy": "复制",
+ "clipboard.copied": "已复制",
+ "consent.accept": "同意",
+ "consent.manage": "管理设定",
+ "consent.reject": "拒绝",
+ "edit.link.title": "编辑此页",
+ "footer.previous": "上一页",
+ "footer.next": "下一页",
+ "footer.title": "页脚",
+ "header.title": "页眉",
+ "meta.comments": "评论",
+ "meta.source": "来源",
+ "nav.title": "导航栏",
+ "search.config.lang": "ja",
+ "search.config.pipeline": "trimmer, stemmer",
+ "search.config.separator": "[\\s\\-,。]+",
+ "search.placeholder": "搜索",
+ "search.share": "分享",
+ "search.reset": "清空当前内容",
+ "search.result.initializer": "正在初始化搜索引擎",
+ "search.result.placeholder": "键入以开始搜索",
+ "search.result.none": "没有找到符合条件的结果",
+ "search.result.one": "找到 1 个符合条件的结果",
+ "search.result.other": "# 个符合条件的结果",
+ "search.result.more.one": "在该页上还有 1 个符合条件的结果",
+ "search.result.more.other": "在该页上还有 # 个符合条件的结果",
+ "search.result.term.missing": "缺少",
+ "search.title": "查找",
+ "select.language.title": "选择当前语言",
+ "select.version.title": "选择当前版本",
+ "skip.link.title": "跳转至",
+ "source.link.title": "前往仓库",
+ "source.file.date.updated": "最后更新",
+ "source.file.date.created": "创建日期",
+ "tabs.title": "标签",
+ "toc.title": "目录",
+ "top.title": "回到页面顶部"
+}[key] }}{% endmacro %}
diff --git a/src/partials/logo.html b/src/partials/logo.html
@@ -0,0 +1,29 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Logo -->
+{% if config.theme.logo %}
+ <img src="{{ config.theme.logo | url }}" alt="logo" />
+{% else %}
+ {% set icon = config.theme.icon.logo or "material/library" %}
+ {% include ".icons/" ~ icon ~ ".svg" %}
+{% endif %}
diff --git a/src/partials/nav-item.html b/src/partials/nav-item.html
@@ -0,0 +1,170 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Wrap everything with a macro to reduce file roundtrips (see #2213) -->
+{% macro render(nav_item, path, level) %}
+
+ <!-- Determine class according to state -->
+ {% set class = "md-nav__item" %}
+ {% if nav_item.active %}
+ {% set class = class ~ " md-nav__item--active" %}
+ {% endif %}
+
+ <!-- Main navigation item with nested items -->
+ {% if nav_item.children %}
+
+ <!-- Determine whether to render item as a section -->
+ {% if "navigation.sections" in features and level == 1 + (
+ "navigation.tabs" in features
+ ) %}
+ {% set class = class ~ " md-nav__item--section" %}
+ {% endif %}
+
+ <!-- Render item with nested items -->
+ <li class="{{ class }} md-nav__item--nested">
+
+ <!-- Active checkbox expands items contained within nested section -->
+ {% set checked = "checked" if nav_item.active %}
+ {% if "navigation.expand" in features and not checked %}
+ <input
+ class="md-nav__toggle md-toggle md-toggle--indeterminate"
+ data-md-toggle="{{ path }}"
+ type="checkbox"
+ id="{{ path }}"
+ checked
+ />
+ {% else %}
+ <input
+ class="md-nav__toggle md-toggle"
+ data-md-toggle="{{ path }}"
+ type="checkbox"
+ id="{{ path }}"
+ {{ checked }}
+ />
+ {% endif %}
+
+ <!-- Determine all nested items that are index pages -->
+ {% set indexes = [] %}
+ {% if "navigation.indexes" in features %}
+ {% for nav_item in nav_item.children %}
+ {% if nav_item.is_index and not index is defined %}
+ {% set _ = indexes.append(nav_item) %}
+ {% endif %}
+ {% endfor %}
+ {% endif %}
+
+ <!-- Render toggle to expand nested items -->
+ {% if not indexes %}
+ <label class="md-nav__link" for="{{ path }}">
+ {{ nav_item.title }}
+ <span class="md-nav__icon md-icon"></span>
+ </label>
+
+ <!-- Render link to index page + toggle -->
+ {% else %}
+ {% set index = indexes | first %}
+ {% set class = "md-nav__link--active" if index == page %}
+ <div class="md-nav__link md-nav__link--index {{ class }}">
+ <a href="{{ index.url | url }}">{{ nav_item.title }}</a>
+
+ <!-- Only render toggle if there's at least one more page -->
+ {% if nav_item.children | length > 1 %}
+ <label for="{{ path }}">
+ <span class="md-nav__icon md-icon"></span>
+ </label>
+ {% endif %}
+ </div>
+ {% endif %}
+
+ <!-- Render nested navigation -->
+ <nav
+ class="md-nav"
+ aria-label="{{ nav_item.title }}"
+ data-md-level="{{ level }}"
+ >
+ <label class="md-nav__title" for="{{ path }}">
+ <span class="md-nav__icon md-icon"></span>
+ {{ nav_item.title }}
+ </label>
+ <ul class="md-nav__list" data-md-scrollfix>
+
+ <!-- Render nested item list -->
+ {% for nav_item in nav_item.children %}
+ {% if not indexes or nav_item != indexes | first %}
+ {{ render(nav_item, path ~ "_" ~ loop.index, level + 1) }}
+ {% endif %}
+ {% endfor %}
+ </ul>
+ </nav>
+ </li>
+
+ <!-- Currently active page -->
+ {% elif nav_item == page %}
+ <li class="{{ class }}">
+ {% set toc = page.toc %}
+
+ <!-- Active checkbox expands items contained within nested section -->
+ <input
+ class="md-nav__toggle md-toggle"
+ data-md-toggle="toc"
+ type="checkbox"
+ id="__toc"
+ />
+
+ <!-- Hack: see partials/toc.html for more information -->
+ {% set first = toc | first %}
+ {% if first and first.level == 1 %}
+ {% set toc = first.children %}
+ {% endif %}
+
+ <!-- Render table of contents, if not empty -->
+ {% if toc %}
+ <label class="md-nav__link md-nav__link--active" for="__toc">
+ {{ nav_item.title }}
+ <span class="md-nav__icon md-icon"></span>
+ </label>
+ {% endif %}
+ <a
+ href="{{ nav_item.url | url }}"
+ class="md-nav__link md-nav__link--active"
+ >
+ {{ nav_item.title }}
+ </a>
+
+ <!-- Show table of contents -->
+ {% if toc %}
+ {% include "partials/toc.html" %}
+ {% endif %}
+ </li>
+
+ <!-- Main navigation item -->
+ {% else %}
+ <li class="{{ class }}">
+ <a href="{{ nav_item.url | url }}" class="md-nav__link">
+ {{ nav_item.title }}
+ </a>
+ </li>
+ {% endif %}
+{% endmacro %}
+
+<!-- Render current and nested navigation items -->
+{{ render(nav_item, path, level) }}
diff --git a/src/partials/nav.html b/src/partials/nav.html
@@ -0,0 +1,68 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Determine class according to configuration -->
+{% set class = "md-nav md-nav--primary" %}
+{% if "navigation.tabs" in features %}
+ {% set class = class ~ " md-nav--lifted" %}
+{% endif %}
+{% if "toc.integrate" in features %}
+ {% set class = class ~ " md-nav--integrated" %}
+{% endif %}
+
+<!-- Main navigation -->
+<nav
+ class="{{ class }}"
+ aria-label="{{ lang.t('nav.title') }}"
+ data-md-level="0"
+>
+
+ <!-- Site title -->
+ <label class="md-nav__title" for="__drawer">
+ <a
+ href="{{ config.extra.homepage | d(nav.homepage.url, true) | url }}"
+ title="{{ config.site_name | e }}"
+ class="md-nav__button md-logo"
+ aria-label="{{ config.site_name }}"
+ data-md-component="logo"
+ >
+ {% include "partials/logo.html" %}
+ </a>
+ {{ config.site_name }}
+ </label>
+
+ <!-- Repository information -->
+ {% if config.repo_url %}
+ <div class="md-nav__source">
+ {% include "partials/source.html" %}
+ </div>
+ {% endif %}
+
+ <!-- Render item list -->
+ <ul class="md-nav__list" data-md-scrollfix>
+ {% for nav_item in nav %}
+ {% set path = "__nav_" ~ loop.index %}
+ {% set level = 1 %}
+ {% include "partials/nav-item.html" %}
+ {% endfor %}
+ </ul>
+</nav>
diff --git a/src/partials/palette.html b/src/partials/palette.html
@@ -0,0 +1,66 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Primary colors -->
+{% macro primary(key) %}{{ {
+ "red": "#ef5552",
+ "pink": "#e92063",
+ "purple": "#ab47bd",
+ "deep-purple": "#7e56c2",
+ "indigo": "#4051b5",
+ "blue": "#2094f3",
+ "light-blue": "#02a6f2",
+ "cyan": "#00bdd6",
+ "teal": "#009485",
+ "green": "#4cae4f",
+ "light-green": "#8bc34b",
+ "lime": "#cbdc38",
+ "yellow": "#ffec3d",
+ "amber": "#ffc105",
+ "orange": "#ffa724",
+ "deep-orange": "#ff6e42",
+ "brown": "#795649",
+ "grey": "#757575",
+ "blue-grey": "#546d78",
+ "black": "#000000",
+ "white": "#ffffff"
+}[key] }}{% endmacro %}
+
+<!-- Accent colors -->
+{% macro accent(key) %}{{ {
+ "red": "#ff1a47",
+ "pink": "#f50056",
+ "purple": "#df41fb",
+ "deep-purple": "#7c4dff",
+ "indigo": "#526cfe",
+ "blue": "#4287ff",
+ "light-blue": "#0091eb",
+ "cyan": "#00bad6",
+ "teal": "#00bda4",
+ "green": "#00c753",
+ "light-green": "#63de17",
+ "lime": "#b0eb00",
+ "yellow": "#ffd500",
+ "amber": "#ffaa00",
+ "orange": "#ff9100",
+ "deep-orange": "#ff6e42"
+}[key] }}{% endmacro %}
diff --git a/src/partials/search.html b/src/partials/search.html
@@ -0,0 +1,103 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Search interface -->
+<div class="md-search" data-md-component="search" role="dialog">
+ <label class="md-search__overlay" for="__search"></label>
+ <div class="md-search__inner" role="search">
+ <form class="md-search__form" name="search">
+
+ <!-- Search input -->
+ <input
+ type="text"
+ class="md-search__input"
+ name="query"
+ aria-label="{{ lang.t('search.placeholder') }}"
+ placeholder="{{ lang.t('search.placeholder') }}"
+ autocapitalize="off"
+ autocorrect="off"
+ autocomplete="off"
+ spellcheck="false"
+ data-md-component="search-query"
+ required
+ />
+
+ <!-- Button to open search -->
+ <label class="md-search__icon md-icon" for="__search">
+ {% include ".icons/material/magnify.svg" %}
+ {% include ".icons/material/arrow-left.svg" %}
+ </label>
+
+ <!-- Search options -->
+ <nav
+ class="md-search__options"
+ aria-label="{{ lang.t('search.title') }}"
+ >
+
+ <!-- Button to share search -->
+ {% if "search.share" in features %}
+ <a
+ href="javascript:void(0)"
+ class="md-search__icon md-icon"
+ aria-label="{{ lang.t('search.share') }}"
+ data-clipboard
+ data-clipboard-text=""
+ data-md-component="search-share"
+ tabindex="-1"
+ >
+ {% include ".icons/material/share-variant.svg" %}
+ </a>
+ {% endif %}
+
+ <!-- Button to reset search -->
+ <button
+ type="reset"
+ class="md-search__icon md-icon"
+ aria-label="{{ lang.t('search.reset') }}"
+ tabindex="-1"
+ >
+ {% include ".icons/material/close.svg" %}
+ </button>
+ </nav>
+
+ <!-- Search suggestions -->
+ {% if "search.suggest" in features %}
+ <div
+ class="md-search__suggest"
+ data-md-component="search-suggest"
+ ></div>
+ {% endif %}
+ </form>
+ <div class="md-search__output">
+ <div class="md-search__scrollwrap" data-md-scrollfix>
+
+ <!-- Search results -->
+ <div class="md-search-result" data-md-component="search-result">
+ <div class="md-search-result__meta">
+ {{ lang.t("search.result.initializer") }}
+ </div>
+ <ol class="md-search-result__list"></ol>
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
diff --git a/src/partials/social.html b/src/partials/social.html
@@ -0,0 +1,40 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Social links -->
+<div class="md-social">
+ {% for social in config.extra.social %}
+ {% set title = social.name %}
+ {% if not title and "//" in social.link %}
+ {% set _, url = social.link.split("//") %}
+ {% set title = url.split("/")[0] %}
+ {% endif %}
+ <a
+ href="{{ social.link }}"
+ target="_blank" rel="noopener"
+ title="{{ title | e }}"
+ class="md-social__link"
+ >
+ {% include ".icons/" ~ social.icon ~ ".svg" %}
+ </a>
+ {% endfor %}
+</div>
diff --git a/src/partials/source-file.html b/src/partials/source-file.html
@@ -0,0 +1,44 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Source file information -->
+<hr />
+<div class="md-source-file">
+ <small>
+
+ <!-- mkdocs-git-revision-date-localized-plugin -->
+ {% if page.meta.git_revision_date_localized %}
+ {{ lang.t("source.file.date.updated") }}:
+ {{ page.meta.git_revision_date_localized }}
+ {% if page.meta.git_creation_date_localized %}
+ <br />
+ {{ lang.t("source.file.date.created") }}:
+ {{ page.meta.git_creation_date_localized }}
+ {% endif %}
+
+ <!-- mkdocs-git-revision-date-plugin -->
+ {% elif page.meta.revision_date %}
+ {{ lang.t("source.file.date.updated") }}:
+ {{ page.meta.revision_date }}
+ {% endif %}
+ </small>
+</div>
diff --git a/src/partials/source.html b/src/partials/source.html
@@ -0,0 +1,37 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Repository information -->
+<a
+ href="{{ config.repo_url }}"
+ title="{{ lang.t('source.link.title') }}"
+ class="md-source"
+ data-md-component="source"
+>
+ <div class="md-source__icon md-icon">
+ {% set icon = config.theme.icon.repo or "fontawesome/brands/git-alt" %}
+ {% include ".icons/" ~ icon ~ ".svg" %}
+ </div>
+ <div class="md-source__repository">
+ {{ config.repo_name }}
+ </div>
+</a>
diff --git a/src/partials/tabs-item.html b/src/partials/tabs-item.html
@@ -0,0 +1,56 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Determine class according to state -->
+{% if not class %}
+ {% set class = "md-tabs__link" %}
+ {% if nav_item.active %}
+ {% set class = class ~ " md-tabs__link--active" %}
+ {% endif %}
+{% endif %}
+
+<!-- Main navigation item with nested items -->
+{% if nav_item.children %}
+ {% set title = title | d(nav_item.title) %}
+ {% set nav_item = nav_item.children | first %}
+
+ <!-- Recurse, if the first item has further nested items -->
+ {% if nav_item.children %}
+ {% include "partials/tabs-item.html" %}
+
+ <!-- Render item -->
+ {% else %}
+ <li class="md-tabs__item">
+ <a href="{{ nav_item.url | url }}" class="{{ class }}">
+ {{ title }}
+ </a>
+ </li>
+ {% endif %}
+
+<!-- Main navigation item -->
+{% else %}
+ <li class="md-tabs__item">
+ <a href="{{ nav_item.url | url }}" class="{{ class }}">
+ {{ nav_item.title }}
+ </a>
+ </li>
+{% endif %}
diff --git a/src/partials/tabs.html b/src/partials/tabs.html
@@ -0,0 +1,39 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Hack: unset variable, as we're using it recursively in tabs-item.html -->
+{% set class = "" %}
+
+<!-- Navigation tabs -->
+<nav
+ class="md-tabs"
+ aria-label="{{ lang.t('tabs.title') }}"
+ data-md-component="tabs"
+>
+ <div class="md-tabs__inner md-grid">
+ <ul class="md-tabs__list">
+ {% for nav_item in nav %}
+ {% include "partials/tabs-item.html" %}
+ {% endfor %}
+ </ul>
+ </div>
+</nav>
diff --git a/src/partials/tags.html b/src/partials/tags.html
@@ -0,0 +1,41 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Determine whether to show tags -->
+{% if page.meta and page.meta.hide %}
+ {% set hidden = "hidden" if "tags" in page.meta.hide %}
+{% endif %}
+
+<!-- Tags -->
+{% if tags %}
+ <nav class="md-tags" {{ hidden }}>
+ {% for tag in tags %}
+ {% if tag.url %}
+ <a href="{{ tag.url | url }}" class="md-tag">
+ {{ tag.name }}
+ </a>
+ {% else %}
+ <span class="md-tag">{{ tag.name }}</span>
+ {% endif %}
+ {% endfor %}
+ </nav>
+{% endif %}
diff --git a/src/partials/toc-item.html b/src/partials/toc-item.html
@@ -0,0 +1,39 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Table of contents item -->
+<li class="md-nav__item">
+ <a href="{{ toc_item.url }}" class="md-nav__link">
+ {{ toc_item.title }}
+ </a>
+
+ <!-- Table of contents list -->
+ {% if toc_item.children %}
+ <nav class="md-nav" aria-label="{{ toc_item.title }}">
+ <ul class="md-nav__list">
+ {% for toc_item in toc_item.children %}
+ {% include "partials/toc-item.html" %}
+ {% endfor %}
+ </ul>
+ </nav>
+ {% endif %}
+</li>
diff --git a/src/partials/toc.html b/src/partials/toc.html
@@ -0,0 +1,56 @@
+<!--
+ Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+-->
+
+<!-- Determine title -->
+{% set title = lang.t("toc.title") %}
+{% if config.mdx_configs.toc and config.mdx_configs.toc.title %}
+ {% set title = config.mdx_configs.toc.title %}
+{% endif %}
+
+<!-- Table of contents -->
+<nav class="md-nav md-nav--secondary" aria-label="{{ title }}">
+ {% set toc = page.toc %}
+
+ <!--
+ Hack: check whether the content contains a h1 headline. If it does, the
+ top-level anchor must be skipped, since it would be redundant to the link
+ to the current page that is located just above the anchor. Therefore we
+ directly continue with the children of the anchor.
+ -->
+ {% set first = toc | first %}
+ {% if first and first.level == 1 %}
+ {% set toc = first.children %}
+ {% endif %}
+
+ <!-- Table of contents title and list -->
+ {% if toc %}
+ <label class="md-nav__title" for="__toc">
+ <span class="md-nav__icon md-icon"></span>
+ {{ title }}
+ </label>
+ <ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
+ {% for toc_item in toc %}
+ {% include "partials/toc-item.html" %}
+ {% endfor %}
+ </ul>
+ {% endif %}
+</nav>
diff --git a/src/plugins/__init__.py b/src/plugins/__init__.py
diff --git a/src/plugins/search/__init__.py b/src/plugins/search/__init__.py
diff --git a/src/plugins/search/plugin.py b/src/plugins/search/plugin.py
@@ -0,0 +1,55 @@
+# Copyright (c) 2016-2021 Martin Donath <martin.donath@squidfunk.com>
+
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to
+# deal in the Software without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+# sell copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+from mkdocs.contrib.search import SearchPlugin as BasePlugin
+from mkdocs.contrib.search.search_index import SearchIndex as BaseIndex
+
+# -----------------------------------------------------------------------------
+# Class
+# -----------------------------------------------------------------------------
+
+# Search plugin with custom search index
+class SearchPlugin(BasePlugin):
+
+ # Override to use a custom search index
+ def on_pre_build(self, config):
+ super().on_pre_build(config)
+ self.search_index = SearchIndex(**self.config)
+
+# -----------------------------------------------------------------------------
+
+# Search index with support for additional fields
+class SearchIndex(BaseIndex):
+
+ # Override to add additional fields for each page
+ def add_entry_from_context(self, page):
+ index = len(self._entries)
+ super().add_entry_from_context(page)
+ entry = self._entries[index]
+
+ # Add document tags
+ if page.meta.get("tags"):
+ entry["tags"] = page.meta["tags"]
+
+ # Add document boost for search
+ if "search" in page.meta:
+ search = page.meta["search"]
+ if "boost" in search:
+ entry["boost"] = search["boost"]
diff --git a/src/plugins/tags/__init__.py b/src/plugins/tags/__init__.py
diff --git a/src/plugins/tags/plugin.py b/src/plugins/tags/plugin.py
@@ -0,0 +1,138 @@
+# Copyright (c) 2016-2022 Martin Donath <martin.donath@squidfunk.com>
+
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to
+# deal in the Software without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+# sell copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+import logging
+import os
+import sys
+
+from collections import defaultdict
+from markdown.extensions.toc import slugify
+from mkdocs import utils
+from mkdocs.commands.build import DuplicateFilter
+from mkdocs.config.config_options import Type
+from mkdocs.plugins import BasePlugin
+
+# -----------------------------------------------------------------------------
+# Class
+# -----------------------------------------------------------------------------
+
+# Tags plugin
+class TagsPlugin(BasePlugin):
+
+ # Configuration scheme
+ config_scheme = (
+ ("tags_file", Type(str, required = False)),
+ )
+
+ # Initialize plugin
+ def __init__(self):
+ self.tags = defaultdict(list)
+ self.tags_file = None
+ self.slugify = None
+
+ # Retrieve configuration for anchor generation
+ def on_config(self, config):
+ if "toc" in config["markdown_extensions"]:
+ toc = { "slugify": slugify, "separator": "-" }
+ if "toc" in config["mdx_configs"]:
+ toc = { **toc, **config["mdx_configs"]["toc"] }
+
+ # Partially apply slugify function
+ self.slugify = lambda value: (
+ toc["slugify"](str(value), toc["separator"])
+ )
+
+ # Hack: 2nd pass for tags index page
+ def on_nav(self, nav, files, **kwargs):
+ file = self.config.get("tags_file")
+ if file:
+ self.tags_file = files.get_file_from_path(file)
+ if not self.tags_file:
+ log.error(f"Configuration error: {file} doesn't exist.")
+ sys.exit()
+
+ # Add tags file to files
+ files.append(self.tags_file)
+
+ # Build and render tags index page
+ def on_page_markdown(self, markdown, page, **kwargs):
+ if page.file == self.tags_file:
+ return self.__render_tag_index(markdown)
+
+ # Add page to tags index
+ for tag in page.meta.get("tags", []):
+ self.tags[tag].append(page)
+
+ # Inject tags into page (after search and before minification)
+ def on_page_context(self, context, page, **kwargs):
+ if "tags" in page.meta:
+ context["tags"] = [
+ self.__render_tag(tag)
+ for tag in page.meta["tags"]
+ ]
+
+ # -------------------------------------------------------------------------
+
+ # Render tags index
+ def __render_tag_index(self, markdown):
+ if not "[TAGS]" in markdown:
+ markdown += "\n[TAGS]"
+
+ # Replace placeholder in Markdown with rendered tags index
+ return markdown.replace("[TAGS]", "\n".join([
+ self.__render_tag_links(*args)
+ for args in sorted(self.tags.items())
+ ]))
+
+ # Render the given tag and links to all pages with occurrences
+ def __render_tag_links(self, tag, pages):
+ content = [f"## <span class=\"md-tag\">{tag}</span>", ""]
+ for page in pages:
+ url = utils.get_relative_url(
+ page.file.src_path.replace(os.path.sep, "/"),
+ self.tags_file.src_path.replace(os.path.sep, "/")
+ )
+
+ # Ensure forward slashes, as we have to use the path of the source
+ # file which contains the operating system's path separator.
+ content.append("- [{}]({})".format(
+ page.meta.get("title", page.title),
+ url
+ ))
+
+ # Return rendered tag links
+ return "\n".join(content)
+
+ # Render the given tag, linking to the tags index (if enabled)
+ def __render_tag(self, tag):
+ if not self.tags_file or not self.slugify:
+ return dict(name = tag)
+ else:
+ url = self.tags_file.url
+ url += f"#{self.slugify(tag)}"
+ return dict(name = tag, url = url)
+
+# -----------------------------------------------------------------------------
+# Data
+# -----------------------------------------------------------------------------
+
+# Set up logging
+log = logging.getLogger("mkdocs")
+log.addFilter(DuplicateFilter())