diff options
| author | 2023-12-15 10:08:56 +0800 | |
|---|---|---|
| committer | 2023-12-15 10:08:56 +0800 | |
| commit | d697a1774ad8571e9314e3bd85aa141170587d19 (patch) | |
| tree | cd3c3d84e3288db1f8e574a0b4dfb85e2e40f764 /src/templates/assets/javascripts/components/content | |
| parent | 4943d5eff52a75caaccda6a1d84183032f06be26 (diff) | |
| parent | 4dafb0f0a81255193f2a44df5d203239325e2236 (diff) | |
| download | infini-d697a1774ad8571e9314e3bd85aa141170587d19.tar.gz infini-d697a1774ad8571e9314e3bd85aa141170587d19.zip | |
Merge branch 'master' into master
Diffstat (limited to 'src/templates/assets/javascripts/components/content')
13 files changed, 0 insertions, 2044 deletions
diff --git a/src/templates/assets/javascripts/components/content/_/index.ts b/src/templates/assets/javascripts/components/content/_/index.ts deleted file mode 100644 index 899a695c..00000000 --- a/src/templates/assets/javascripts/components/content/_/index.ts +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright (c) 2016-2023 Martin Donath <martin.donath@squidfunk.com> - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER 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 { Viewport, getElements } from "~/browser" - -import { Component } from "../../_" -import { - Annotation, - mountAnnotationBlock -} from "../annotation" -import { - CodeBlock, - mountCodeBlock -} from "../code" -import { - Details, - mountDetails -} from "../details" -import { - Mermaid, - mountMermaid -} from "../mermaid" -import { - DataTable, - mountDataTable -} from "../table" -import { - ContentTabs, - mountContentTabs -} from "../tabs" - -/* ---------------------------------------------------------------------------- - * Types - * ------------------------------------------------------------------------- */ - -/** - * Content - */ -export type Content = - | Annotation - | CodeBlock - | ContentTabs - | DataTable - | Details - | Mermaid - -/* ---------------------------------------------------------------------------- - * Helper types - * ------------------------------------------------------------------------- */ - -/** - * Mount options - */ -interface MountOptions { - viewport$: Observable<Viewport> /* Viewport observable */ - 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, { viewport$, target$, print$ }: MountOptions -): Observable<Component<Content>> { - return merge( - - /* Annotations */ - ...getElements(".annotate:not(.highlight)", el) - .map(child => mountAnnotationBlock(child, { target$, print$ })), - - /* Code blocks */ - ...getElements("pre:not(.mermaid) > code", el) - .map(child => mountCodeBlock(child, { target$, 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, { viewport$ })) - ) -} diff --git a/src/templates/assets/javascripts/components/content/annotation/_/index.ts b/src/templates/assets/javascripts/components/content/annotation/_/index.ts deleted file mode 100644 index c5138fa4..00000000 --- a/src/templates/assets/javascripts/components/content/annotation/_/index.ts +++ /dev/null @@ -1,272 +0,0 @@ -/* - * Copyright (c) 2016-2023 Martin Donath <martin.donath@squidfunk.com> - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER 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, - debounceTime, - defer, - delay, - endWith, - filter, - finalize, - fromEvent, - ignoreElements, - map, - merge, - switchMap, - take, - takeUntil, - tap, - throttleTime, - withLatestFrom -} from "rxjs" - -import { - ElementOffset, - getActiveElement, - getElementSize, - watchElementContentOffset, - watchElementFocus, - watchElementOffset, - watchElementVisibility -} from "~/browser" - -import { Component } from "../../../_" - -/* ---------------------------------------------------------------------------- - * Types - * ------------------------------------------------------------------------- */ - -/** - * Annotation - */ -export interface Annotation { - active: boolean /* Annotation is active */ - offset: ElementOffset /* Annotation offset */ -} - -/* ---------------------------------------------------------------------------- - * Helper types - * ------------------------------------------------------------------------- */ - -/** - * Mount options - */ -interface MountOptions { - target$: Observable<HTMLElement> /* Location target observable */ -} - -/* ---------------------------------------------------------------------------- - * 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]): ElementOffset => { - const { width, height } = getElementSize(el) - return ({ - x: x - scroll.x + width / 2, - y: y - scroll.y + height / 2 - }) - }) - ) - - /* 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 - * @param options - Options - * - * @returns Annotation component observable - */ -export function mountAnnotation( - el: HTMLElement, container: HTMLElement, { target$ }: MountOptions -): Observable<Component<Annotation>> { - const [tooltip, index] = Array.from(el.children) - - /* Mount component on subscription */ - return defer(() => { - const push$ = new Subject<Annotation>() - const done$ = push$.pipe(ignoreElements(), endWith(true)) - 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 */ - watchElementVisibility(el) - .pipe( - takeUntil(done$) - ) - .subscribe(visible => { - el.toggleAttribute("data-md-visible", visible) - }) - - /* Toggle tooltip presence to mitigate empty lines when copying */ - merge( - push$.pipe(filter(({ active }) => active)), - push$.pipe(debounceTime(250), filter(({ active }) => !active)) - ) - .subscribe({ - - /* Handle emission */ - next({ active }) { - if (active) - el.prepend(tooltip) - else - tooltip.remove() - }, - - /* Handle complete */ - complete() { - el.prepend(tooltip) - } - }) - - /* Toggle tooltip visibility */ - push$ - .pipe( - auditTime(16, animationFrameScheduler) - ) - .subscribe(({ active }) => { - tooltip.classList.toggle("md-tooltip--active", active) - }) - - /* Track relative origin of tooltip */ - push$ - .pipe( - throttleTime(125, animationFrameScheduler), - filter(() => !!el.offsetParent), - map(() => el.offsetParent!.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") - } - }) - - /* Allow to copy link without scrolling to anchor */ - fromEvent<MouseEvent>(index, "click") - .pipe( - takeUntil(done$), - filter(ev => !(ev.metaKey || ev.ctrlKey)) - ) - .subscribe(ev => { - ev.stopPropagation() - ev.preventDefault() - }) - - /* Allow to open link in new tab or blur on close */ - fromEvent<MouseEvent>(index, "mousedown") - .pipe( - takeUntil(done$), - withLatestFrom(push$) - ) - .subscribe(([ev, { active }]) => { - - /* Open in new tab */ - if (ev.button !== 0 || ev.metaKey || ev.ctrlKey) { - ev.preventDefault() - - /* Close annotation */ - } else if (active) { - ev.preventDefault() - - /* Focus parent annotation, if any */ - const parent = el.parentElement!.closest(".md-annotation") - if (parent instanceof HTMLElement) - parent.focus() - else - getActiveElement()?.blur() - } - }) - - /* Open and focus annotation on location target */ - target$ - .pipe( - takeUntil(done$), - filter(target => target === tooltip), - delay(125) - ) - .subscribe(() => el.focus()) - - /* 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/templates/assets/javascripts/components/content/annotation/block/index.ts b/src/templates/assets/javascripts/components/content/annotation/block/index.ts deleted file mode 100644 index c73b01fa..00000000 --- a/src/templates/assets/javascripts/components/content/annotation/block/index.ts +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (c) 2016-2023 Martin Donath <martin.donath@squidfunk.com> - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER 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, defer } from "rxjs" - -import { Component } from "../../../_" -import { Annotation } from "../_" -import { mountAnnotationList } from "../list" - -/* ---------------------------------------------------------------------------- - * Helper types - * ------------------------------------------------------------------------- */ - -/** - * Mount options - */ -interface MountOptions { - target$: Observable<HTMLElement> /* Location target observable */ - print$: Observable<boolean> /* Media print observable */ -} - -/* ---------------------------------------------------------------------------- - * Helper functions - * ------------------------------------------------------------------------- */ - -/** - * Find list element directly following a block - * - * @param el - Annotation block element - * - * @returns List element or nothing - */ -function findList(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 findList(sibling) - } - - /* Everything else */ - return undefined -} - -/* ---------------------------------------------------------------------------- - * Functions - * ------------------------------------------------------------------------- */ - -/** - * Mount annotation block - * - * @param el - Annotation block element - * @param options - Options - * - * @returns Annotation component observable - */ -export function mountAnnotationBlock( - el: HTMLElement, options: MountOptions -): Observable<Component<Annotation>> { - return defer(() => { - const list = findList(el) - return typeof list !== "undefined" - ? mountAnnotationList(list, el, options) - : EMPTY - }) -} diff --git a/src/templates/assets/javascripts/components/content/annotation/index.ts b/src/templates/assets/javascripts/components/content/annotation/index.ts deleted file mode 100644 index c593b723..00000000 --- a/src/templates/assets/javascripts/components/content/annotation/index.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2016-2023 Martin Donath <martin.donath@squidfunk.com> - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER 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 "./block" -export * from "./list" diff --git a/src/templates/assets/javascripts/components/content/annotation/list/index.ts b/src/templates/assets/javascripts/components/content/annotation/list/index.ts deleted file mode 100644 index 725dd583..00000000 --- a/src/templates/assets/javascripts/components/content/annotation/list/index.ts +++ /dev/null @@ -1,209 +0,0 @@ -/* - * Copyright (c) 2016-2023 Martin Donath <martin.donath@squidfunk.com> - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER 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, - endWith, - finalize, - ignoreElements, - merge, - share, - 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 { - target$: Observable<HTMLElement> /* Location target observable */ - print$: Observable<boolean> /* Media print observable */ -} - -/* ---------------------------------------------------------------------------- - * Helper functions - * ------------------------------------------------------------------------- */ - -/** - * Find all annotation hosts in the containing element - * - * @param container - Containing element - * - * @returns Annotation hosts - */ -function findHosts(container: HTMLElement): HTMLElement[] { - return container.tagName === "CODE" - ? getElements(".c, .c1, .cm", container) - : [container] -} - -/** - * Find all annotation markers in the containing element - * - * @param container - Containing element - * - * @returns Annotation markers - */ -function findMarkers(container: HTMLElement): Text[] { - const markers: Text[] = [] - for (const el of findHosts(container)) { - const nodes: Text[] = [] - - /* Find all text nodes in current element */ - const it = document.createNodeIterator(el, NodeFilter.SHOW_TEXT) - for (let node = it.nextNode(); node; node = it.nextNode()) - nodes.push(node as Text) - - /* Find all markers in each text node */ - for (let text of nodes) { - let match: RegExpExecArray | null - - /* Split text at marker and add to list */ - while ((match = /(\(\d+\))(!)?/.exec(text.textContent!))) { - const [, id, force] = match - if (typeof force === "undefined") { - const marker = text.splitText(match.index) - text = marker.splitText(id.length) - markers.push(marker) - - /* Replace entire text with marker */ - } else { - text.textContent = id - markers.push(text) - break - } - } - } - } - 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, { target$, print$ }: MountOptions -): Observable<Component<Annotation>> { - - /* Compute prefix for tooltip anchors */ - const parent = container.closest("[id]") - const prefix = parent?.id - - /* Find and replace all markers with empty annotations */ - const annotations = new Map<string, HTMLElement>() - for (const marker of findMarkers(container)) { - const [, id] = marker.textContent!.match(/\((\d+)\)/)! - if (getOptionalElement(`:scope > li:nth-child(${id})`, el)) { - annotations.set(id, renderAnnotation(id, prefix)) - marker.replaceWith(annotations.get(id)!) - } - } - - /* Keep list if there are no annotations to render */ - if (annotations.size === 0) - return EMPTY - - /* Mount component on subscription */ - return defer(() => { - const push$ = new Subject() - const done$ = push$.pipe(ignoreElements(), endWith(true)) - - /* Retrieve container pairs for swapping */ - const pairs: [HTMLElement, HTMLElement][] = [] - for (const [id, annotation] of annotations) - pairs.push([ - getElement(".md-typeset", annotation), - getElement(`:scope > li:nth-child(${id})`, el) - ]) - - /* Handle print mode - see https://bit.ly/3rgPdpt */ - print$.pipe(takeUntil(done$)) - .subscribe(active => { - el.hidden = !active - - /* Add class to discern list element */ - el.classList.toggle("md-annotation-list", active) - - /* Show annotations in code block or list (print) */ - for (const [inner, child] of pairs) - if (!active) - swap(child, inner) - else - swap(inner, child) - }) - - /* Create and return component */ - return merge(...[...annotations] - .map(([, annotation]) => ( - mountAnnotation(annotation, container, { target$ }) - )) - ) - .pipe( - finalize(() => push$.complete()), - share() - ) - }) -} diff --git a/src/templates/assets/javascripts/components/content/code/_/index.ts b/src/templates/assets/javascripts/components/content/code/_/index.ts deleted file mode 100644 index ccc09339..00000000 --- a/src/templates/assets/javascripts/components/content/code/_/index.ts +++ /dev/null @@ -1,238 +0,0 @@ -/* - * Copyright (c) 2016-2023 Martin Donath <martin.donath@squidfunk.com> - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER 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, - 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 { - target$: Observable<HTMLElement> /* Location target observable */ - print$: Observable<boolean> /* Media print observable */ -} - -/* ---------------------------------------------------------------------------- - * Data - * ------------------------------------------------------------------------- */ - -/** - * Global sequence number for code blocks - */ -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. - * - * Note that code blocks may be mounted eagerly or lazily. If they're mounted - * lazily (on first visibility), code annotation anchor links will not work, - * as they are evaluated on initial page load, and code annotations in general - * might feel a little bumpier. - * - * @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()) { - if (el.closest(".copy") || ( - feature("content.code.copy") && !el.closest(".no-copy") - )) { - 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( - 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 lazily */ - if (feature("content.lazy")) - return watchElementVisibility(el) - .pipe( - filter(visible => visible), - take(1), - switchMap(() => factory$) - ) - - /* Mount code block */ - return factory$ -} diff --git a/src/templates/assets/javascripts/components/content/code/index.ts b/src/templates/assets/javascripts/components/content/code/index.ts deleted file mode 100644 index 3f86e2b4..00000000 --- a/src/templates/assets/javascripts/components/content/code/index.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (c) 2016-2023 Martin Donath <martin.donath@squidfunk.com> - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER 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 "./_" diff --git a/src/templates/assets/javascripts/components/content/details/index.ts b/src/templates/assets/javascripts/components/content/details/index.ts deleted file mode 100644 index 17bfae45..00000000 --- a/src/templates/assets/javascripts/components/content/details/index.ts +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright (c) 2016-2023 Martin Donath <martin.donath@squidfunk.com> - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER 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 }) => { - el.toggleAttribute("open", action === "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/templates/assets/javascripts/components/content/index.ts b/src/templates/assets/javascripts/components/content/index.ts deleted file mode 100644 index a29d8b41..00000000 --- a/src/templates/assets/javascripts/components/content/index.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (c) 2016-2023 Martin Donath <martin.donath@squidfunk.com> - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER 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/templates/assets/javascripts/components/content/mermaid/index.css b/src/templates/assets/javascripts/components/content/mermaid/index.css deleted file mode 100644 index 3092b8ec..00000000 --- a/src/templates/assets/javascripts/components/content/mermaid/index.css +++ /dev/null @@ -1,430 +0,0 @@ -/* - * Copyright (c) 2016-2023 Martin Donath <martin.donath@squidfunk.com> - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER 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); - stroke-width: .05rem; -} - -/* 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 */ -g #flowchart-circleStart, -g #flowchart-circleEnd, -g #flowchart-crossStart, -g #flowchart-crossEnd, -g #flowchart-pointStart, -g #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 - * ------------------------------------------------------------------------- */ - -/* Attribute box */ -.attributeBoxEven, -.attributeBoxOdd { - fill: var(--md-mermaid-node-bg-color); - stroke: var(--md-mermaid-node-fg-color); -} - -/* 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-sequence-actor-bg-color); - stroke: var(--md-mermaid-sequence-actor-border-color); -} - -/* Sequence actor text */ -text.actor > tspan { - font-family: var(--md-mermaid-font-family); - fill: var(--md-mermaid-sequence-actor-fg-color); -} - -/* Sequence actor line */ -line { - stroke: var(--md-mermaid-sequence-actor-line-color); -} - -/* Sequence actor */ -.actor-man circle, -.actor-man line { - fill: var(--md-mermaid-sequence-actorman-bg-color); - stroke: var(--md-mermaid-sequence-actorman-line-color); -} - -/* Sequence message line */ -.messageLine0, -.messageLine1 { - stroke: var(--md-mermaid-sequence-message-line-color); -} - -/* Sequence note */ -.note { - fill: var(--md-mermaid-sequence-note-bg-color); - stroke: var(--md-mermaid-sequence-note-border-color); -} - -/* Sequence message, loop and note text */ -.messageText, -.loopText, -.loopText > tspan, -.noteText > tspan { - font-family: var(--md-mermaid-font-family) !important; - stroke: none; -} - -/* Sequence message text */ -.messageText { - fill: var(--md-mermaid-sequence-message-fg-color); -} - -/* Sequence loop text */ -.loopText, -.loopText > tspan { - fill: var(--md-mermaid-sequence-loop-fg-color); -} - -/* Sequence note text */ -.noteText > tspan { - fill: var(--md-mermaid-sequence-note-fg-color); -} - -/* Sequence arrow head */ -#arrowhead path { - fill: var(--md-mermaid-sequence-message-line-color); - stroke: none; -} - -/* Sequence loop line */ -.loopLine { - fill: var(--md-mermaid-sequence-loop-bg-color); - stroke: var(--md-mermaid-sequence-loop-border-color); -} - -/* Sequence label box */ -.labelBox { - fill: var(--md-mermaid-sequence-label-bg-color); - stroke: none; -} - -/* Sequence label text */ -.labelText, -.labelText > span { - font-family: var(--md-mermaid-font-family); - fill: var(--md-mermaid-sequence-label-fg-color); -} - -/* Sequence number */ -.sequenceNumber { - fill: var(--md-mermaid-sequence-number-fg-color); -} - -/* Sequence rectangle */ -rect.rect { - fill: var(--md-mermaid-sequence-box-bg-color); - stroke: none; -} - -/* Sequence rectangle text */ -rect.rect + text.text { - fill: var(--md-mermaid-sequence-box-fg-color); -} - -/* Sequence diagram markers */ -defs #sequencenumber { - fill: var(--md-mermaid-sequence-number-bg-color) !important; -} diff --git a/src/templates/assets/javascripts/components/content/mermaid/index.ts b/src/templates/assets/javascripts/components/content/mermaid/index.ts deleted file mode 100644 index 3f6480fd..00000000 --- a/src/templates/assets/javascripts/components/content/mermaid/index.ts +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright (c) 2016-2023 Martin Donath <martin.donath@squidfunk.com> - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER 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.4.3/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, - sequence: { - actorFontSize: "16px", // Hack: mitigate https://bit.ly/3y0NEi3 - messageFontSize: "16px", - noteFontSize: "16px" - } - })), - map(() => undefined), - shareReplay(1) - ) - - /* Render diagram */ - mermaid$.subscribe(() => { - el.classList.add("mermaid") // Hack: mitigate https://bit.ly/3CiN6Du - const id = `__mermaid_${sequence++}` - - /* Create host element to replace code block */ - const host = h("div", { class: "mermaid" }) - const text = el.textContent - - /* Render and inject diagram */ - mermaid.mermaidAPI.render(id, text, (svg: string, fn: Function) => { - - /* Create a shadow root and inject diagram */ - const shadow = host.attachShadow({ mode: "closed" }) - shadow.innerHTML = svg - - /* Replace code block with diagram and bind functions */ - el.replaceWith(host) - fn?.(shadow) - }) - }) - - /* Create and return component */ - return mermaid$ - .pipe( - map(() => ({ ref: el })) - ) -} diff --git a/src/templates/assets/javascripts/components/content/table/index.ts b/src/templates/assets/javascripts/components/content/table/index.ts deleted file mode 100644 index c318e7a6..00000000 --- a/src/templates/assets/javascripts/components/content/table/index.ts +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (c) 2016-2023 Martin Donath <martin.donath@squidfunk.com> - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER 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/templates/assets/javascripts/components/content/tabs/index.ts b/src/templates/assets/javascripts/components/content/tabs/index.ts deleted file mode 100644 index f57447e2..00000000 --- a/src/templates/assets/javascripts/components/content/tabs/index.ts +++ /dev/null @@ -1,265 +0,0 @@ -/* - * Copyright (c) 2016-2023 Martin Donath <martin.donath@squidfunk.com> - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER 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, - endWith, - finalize, - fromEvent, - ignoreElements, - map, - merge, - skip, - startWith, - subscribeOn, - takeUntil, - tap, - withLatestFrom -} from "rxjs" - -import { feature } from "~/_" -import { - Viewport, - 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 */ -} - -/* ---------------------------------------------------------------------------- - * Helper types - * ------------------------------------------------------------------------- */ - -/** - * Mount options - */ -interface MountOptions { - viewport$: Observable<Viewport> /* Viewport observable */ -} - -/* ---------------------------------------------------------------------------- - * 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 - * @param options - Options - * - * @returns Content tabs component observable - */ -export function mountContentTabs( - el: HTMLElement, { viewport$ }: MountOptions -): 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(ignoreElements(), endWith(true)) - 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), - withLatestFrom(viewport$) - ) - .subscribe(([{ active }, { offset }]) => { - const tab = active.innerText.trim() - if (active.hasAttribute("data-md-switching")) { - active.removeAttribute("data-md-switching") - - /* Determine viewport offset of active tab */ - } else { - const y = el.offsetTop - offset.y - - /* Passively activate other tabs */ - 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 !== active && - label.innerText.trim() === tab - ) { - label.setAttribute("data-md-switching", "") - input.click() - break - } - } - - /* Bring active tab into view */ - window.scrollTo({ - top: el.offsetTop - y - }) - - /* Persist active tabs in local storage */ - const tabs = __md_get<string[]>("__tabs") || [] - __md_set("__tabs", [...new Set([tab, ...tabs])]) - } - }) - - /* Pause media (audio, video) on switch - see https://bit.ly/3Bk6cel */ - push$.pipe(takeUntil(done$)) - .subscribe(() => { - for (const media of getElements<HTMLAudioElement>("audio, video", el)) - media.pause() - }) - - /* Create and return component */ - return watchContentTabs(el) - .pipe( - tap(state => push$.next(state)), - finalize(() => push$.complete()), - map(state => ({ ref: el, ...state })) - ) - }) - .pipe( - subscribeOn(asyncScheduler) - ) -} |
