Refactored project structure

This commit is contained in:
squidfunk 2021-11-14 16:09:09 +01:00
parent ad1b964aaa
commit 88ba609ee1
52 changed files with 721 additions and 459 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -34,7 +34,7 @@
{% endif %} {% endif %}
{% endblock %} {% endblock %}
{% block styles %} {% block styles %}
<link rel="stylesheet" href="{{ 'assets/stylesheets/main.b5f74394.min.css' | url }}"> <link rel="stylesheet" href="{{ 'assets/stylesheets/main.b29cf17d.min.css' | url }}">
{% if config.theme.palette %} {% if config.theme.palette %}
{% set palette = config.theme.palette %} {% set palette = config.theme.palette %}
<link rel="stylesheet" href="{{ 'assets/stylesheets/palette.9204c3b2.min.css' | url }}"> <link rel="stylesheet" href="{{ 'assets/stylesheets/palette.9204c3b2.min.css' | url }}">
@ -211,7 +211,7 @@
</script> </script>
{% endblock %} {% endblock %}
{% block scripts %} {% block scripts %}
<script src="{{ 'assets/javascripts/bundle.0d86bc28.min.js' | url }}"></script> <script src="{{ 'assets/javascripts/bundle.4fa4ff07.min.js' | url }}"></script>
{% for path in config["extra_javascript"] %} {% for path in config["extra_javascript"] %}
<script src="{{ path | url }}"></script> <script src="{{ path | url }}"></script>
{% endfor %} {% endfor %}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -16,5 +16,5 @@
{% endblock %} {% endblock %}
{% block scripts %} {% block scripts %}
{{ super() }} {{ super() }}
<script src="{{ 'overrides/assets/javascripts/bundle.2a83b894.min.js' | url }}"></script> <script src="{{ 'overrides/assets/javascripts/bundle.5ee92cf1.min.js' | url }}"></script>
{% endblock %} {% endblock %}

View File

@ -20,7 +20,7 @@
* IN THE SOFTWARE. * IN THE SOFTWARE.
*/ */
import { getElementOrThrow, getLocation } from "~/browser" import { getElement, getLocation } from "~/browser"
/* ---------------------------------------------------------------------------- /* ----------------------------------------------------------------------------
* Types * Types
@ -99,7 +99,7 @@ export interface Config {
/** /**
* Retrieve global configuration and make base URL absolute * Retrieve global configuration and make base URL absolute
*/ */
const script = getElementOrThrow("#__config") const script = getElement("#__config")
const config: Config = JSON.parse(script.textContent!) const config: Config = JSON.parse(script.textContent!)
config.base = `${new URL(config.base, getLocation())}` config.base = `${new URL(config.base, getLocation())}`

View File

@ -23,8 +23,7 @@
import { import {
ReplaySubject, ReplaySubject,
Subject, Subject,
fromEvent, fromEvent
mapTo
} from "rxjs" } from "rxjs"
/* ---------------------------------------------------------------------------- /* ----------------------------------------------------------------------------
@ -40,12 +39,9 @@ import {
* @returns Document subject * @returns Document subject
*/ */
export function watchDocument(): Subject<Document> { export function watchDocument(): Subject<Document> {
const document$ = new ReplaySubject<Document>() const document$ = new ReplaySubject<Document>(1)
fromEvent(document, "DOMContentLoaded") fromEvent(document, "DOMContentLoaded", { once: true })
.pipe( .subscribe(() => document$.next(document))
mapTo(document)
)
.subscribe(document$)
/* Return document */ /* Return document */
return document$ return document$

View File

@ -24,72 +24,6 @@
* Functions * Functions
* ------------------------------------------------------------------------- */ * ------------------------------------------------------------------------- */
/**
* Retrieve an element matching the query selector
*
* @template T - Element type
*
* @param selector - Query selector
* @param node - Node of reference
*
* @returns Element or nothing
*/
export function getElement<T extends keyof HTMLElementTagNameMap>(
selector: T, node?: ParentNode
): HTMLElementTagNameMap[T] | undefined
export function getElement<T extends HTMLElement>(
selector: string, node?: ParentNode
): T | undefined
export function getElement<T extends HTMLElement>(
selector: string, node: ParentNode = document
): T | undefined {
return node.querySelector<T>(selector) || undefined
}
/**
* Retrieve an element matching a query selector or throw a reference error
*
* @template T - Element type
*
* @param selector - Query selector
* @param node - Node of reference
*
* @returns Element
*/
export function getElementOrThrow<T extends keyof HTMLElementTagNameMap>(
selector: T, node?: ParentNode
): HTMLElementTagNameMap[T]
export function getElementOrThrow<T extends HTMLElement>(
selector: string, node?: ParentNode
): T
export function getElementOrThrow<T extends HTMLElement>(
selector: string, node: ParentNode = document
): T {
const el = getElement<T>(selector, node)
if (typeof el === "undefined")
throw new ReferenceError(
`Missing element: expected "${selector}" to be present`
)
/* Return element */
return el
}
/**
* Retrieve the currently active element
*
* @returns Element or nothing
*/
export function getActiveElement(): HTMLElement | undefined {
return document.activeElement instanceof HTMLElement
? document.activeElement
: undefined
}
/** /**
* Retrieve all elements matching the query selector * Retrieve all elements matching the query selector
* *
@ -114,16 +48,73 @@ export function getElements<T extends HTMLElement>(
return Array.from(node.querySelectorAll<T>(selector)) 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
}
/* ------------------------------------------------------------------------- */ /* ------------------------------------------------------------------------- */
/** /**
* Replace an element with the given list of nodes * Retrieve an optional element matching the query selector
* *
* @param el - Element * @template T - Element type
* @param nodes - Replacement nodes *
* @param selector - Query selector
* @param node - Node of reference
*
* @returns Element or nothing
*/ */
export function replaceElement( export function getOptionalElement<T extends keyof HTMLElementTagNameMap>(
el: HTMLElement, ...nodes: Node[] selector: T, node?: ParentNode
): void { ): HTMLElementTagNameMap[T] | undefined
el.replaceWith(...nodes)
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
} }

View File

@ -25,3 +25,4 @@ export * from "./focus"
export * from "./offset" export * from "./offset"
export * from "./selection" export * from "./selection"
export * from "./size" export * from "./size"
export * from "./visibility"

View File

@ -0,0 +1,77 @@
/*
* 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.
*/
import {
Observable,
fromEvent,
map,
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 fromEvent(window, "resize")
.pipe(
map(() => getElementOffset(el)),
startWith(getElementOffset(el))
)
}

View File

@ -0,0 +1,71 @@
/*
* 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.
*/
import {
Observable,
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(
map(() => getElementContentOffset(el)),
startWith(getElementContentOffset(el))
)
}

View File

@ -20,95 +20,5 @@
* IN THE SOFTWARE. * IN THE SOFTWARE.
*/ */
import { export * from "./_"
Observable, export * from "./content"
distinctUntilChanged,
fromEvent,
map,
merge,
startWith
} from "rxjs"
import {
getElementContentSize,
getElementSize
} from "../size"
/* ----------------------------------------------------------------------------
* 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.scrollLeft,
y: el.scrollTop
}
}
/* ------------------------------------------------------------------------- */
/**
* Watch element offset
*
* @param el - Element
*
* @returns Element offset observable
*/
export function watchElementOffset(
el: HTMLElement
): Observable<ElementOffset> {
return merge(
fromEvent(el, "scroll"),
fromEvent(window, "resize")
)
.pipe(
map(() => getElementOffset(el)),
startWith(getElementOffset(el))
)
}
/**
* Watch element threshold
*
* This function returns an observable which emits whether the bottom scroll
* offset of an elements is within a certain threshold.
*
* @param el - Element
* @param threshold - Threshold
*
* @returns Element threshold observable
*/
export function watchElementThreshold(
el: HTMLElement, threshold = 16
): Observable<boolean> {
return watchElementOffset(el)
.pipe(
map(({ y }) => {
const visible = getElementSize(el)
const content = getElementContentSize(el)
return y >= (
content.height - visible.height - threshold
)
}),
distinctUntilChanged()
)
}

View File

@ -0,0 +1,138 @@
/*
* 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.
*/
import {
NEVER,
Observable,
Subject,
defer,
filter,
finalize,
map,
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 => NEVER.pipe(startWith(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))
)
}

View File

@ -0,0 +1,41 @@
/*
* 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.
*/
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
}
}

View File

@ -20,133 +20,5 @@
* IN THE SOFTWARE. * IN THE SOFTWARE.
*/ */
import { export * from "./_"
NEVER, export * from "./content"
Observable,
Subject,
defer,
filter,
finalize,
map,
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(resize => NEVER.pipe(startWith(resize))
.pipe(
finalize(() => resize.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
}
}
/**
* Retrieve element content size, i.e. including overflowing content
*
* @param el - Element
*
* @returns Element size
*/
export function getElementContentSize(el: HTMLElement): ElementSize {
return {
width: el.scrollWidth,
height: el.scrollHeight
}
}
/* ------------------------------------------------------------------------- */
/**
* 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))
)
}

View File

@ -0,0 +1,131 @@
/*
* 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.
*/
import {
NEVER,
Observable,
Subject,
defer,
distinctUntilChanged,
filter,
finalize,
map,
of,
shareReplay,
startWith,
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: 1
})
))
.pipe(
switchMap(observer => NEVER.pipe(startWith(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 threshold 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()
)
}

View File

@ -29,7 +29,7 @@ import {
startWith startWith
} from "rxjs" } from "rxjs"
import { getElement } from "~/browser" import { getOptionalElement } from "~/browser"
import { h } from "~/utilities" import { h } from "~/utilities"
/* ---------------------------------------------------------------------------- /* ----------------------------------------------------------------------------
@ -86,7 +86,7 @@ export function watchLocationHash(): Observable<string> {
export function watchLocationTarget(): Observable<HTMLElement> { export function watchLocationTarget(): Observable<HTMLElement> {
return watchLocationHash() return watchLocationHash()
.pipe( .pipe(
map(id => getElement(`[id="${id}"]`)!), map(id => getOptionalElement(`[id="${id}"]`)!),
filter(el => typeof el !== "undefined") filter(el => typeof el !== "undefined")
) )
} }

View File

@ -27,7 +27,7 @@ import {
startWith startWith
} from "rxjs" } from "rxjs"
import { getElementOrThrow } from "../element" import { getElement } from "../element"
/* ---------------------------------------------------------------------------- /* ----------------------------------------------------------------------------
* Types * Types
@ -48,8 +48,8 @@ export type Toggle =
* Toggle map * Toggle map
*/ */
const toggles: Record<Toggle, HTMLInputElement> = { const toggles: Record<Toggle, HTMLInputElement> = {
drawer: getElementOrThrow("[data-md-toggle=drawer]"), drawer: getElement("[data-md-toggle=drawer]"),
search: getElementOrThrow("[data-md-toggle=search]") search: getElement("[data-md-toggle=search]")
} }
/* ---------------------------------------------------------------------------- /* ----------------------------------------------------------------------------

View File

@ -30,6 +30,7 @@ import {
import { Header } from "~/components" import { Header } from "~/components"
import { getElementOffset } from "../../element"
import { import {
ViewportOffset, ViewportOffset,
watchViewportOffset watchViewportOffset
@ -102,10 +103,7 @@ export function watchViewportAt(
/* Compute element offset */ /* Compute element offset */
const offset$ = combineLatest([size$, header$]) const offset$ = combineLatest([size$, header$])
.pipe( .pipe(
map((): ViewportOffset => ({ map(() => getElementOffset(el))
x: el.offsetLeft,
y: el.offsetTop
}))
) )
/* Compute relative viewport, return hot observable */ /* Compute relative viewport, return hot observable */

View File

@ -54,8 +54,8 @@ export interface ViewportOffset {
*/ */
export function getViewportOffset(): ViewportOffset { export function getViewportOffset(): ViewportOffset {
return { return {
x: Math.max(0, pageXOffset), x: Math.max(0, scrollX),
y: Math.max(0, pageYOffset) y: Math.max(0, scrollY)
} }
} }

View File

@ -37,7 +37,7 @@ import {
import { configuration, feature } from "./_" import { configuration, feature } from "./_"
import { import {
at, at,
getElement, getOptionalElement,
requestJSON, requestJSON,
setToggle, setToggle,
watchDocument, watchDocument,
@ -96,6 +96,7 @@ const keyboard$ = watchKeyboard()
const viewport$ = watchViewport() const viewport$ = watchViewport()
const tablet$ = watchMedia("(min-width: 960px)") const tablet$ = watchMedia("(min-width: 960px)")
const screen$ = watchMedia("(min-width: 1220px)") const screen$ = watchMedia("(min-width: 1220px)")
const hover$ = watchMedia("(hover)")
const print$ = watchPrint() const print$ = watchPrint()
/* Retrieve search index, if search is enabled */ /* Retrieve search index, if search is enabled */
@ -139,7 +140,7 @@ keyboard$
/* Go to previous page */ /* Go to previous page */
case "p": case "p":
case ",": case ",":
const prev = getElement("[href][rel=prev]") const prev = getOptionalElement("[href][rel=prev]")
if (typeof prev !== "undefined") if (typeof prev !== "undefined")
prev.click() prev.click()
break break
@ -147,7 +148,7 @@ keyboard$
/* Go to next page */ /* Go to next page */
case "n": case "n":
case ".": case ".":
const next = getElement("[href][rel=next]") const next = getOptionalElement("[href][rel=next]")
if (typeof next !== "undefined") if (typeof next !== "undefined")
next.click() next.click()
break break
@ -197,7 +198,7 @@ const content$ = defer(() => merge(
/* Content */ /* Content */
...getComponentElements("content") ...getComponentElements("content")
.map(el => mountContent(el, { target$, viewport$, print$ })), .map(el => mountContent(el, { target$, viewport$, hover$, print$ })),
/* Search highlighting */ /* Search highlighting */
...getComponentElements("content") ...getComponentElements("content")
@ -250,8 +251,9 @@ window.location$ = location$ /* Location subject */
window.target$ = target$ /* Location target observable */ window.target$ = target$ /* Location target observable */
window.keyboard$ = keyboard$ /* Keyboard observable */ window.keyboard$ = keyboard$ /* Keyboard observable */
window.viewport$ = viewport$ /* Viewport observable */ window.viewport$ = viewport$ /* Viewport observable */
window.tablet$ = tablet$ /* Tablet observable */ window.tablet$ = tablet$ /* Media tablet observable */
window.screen$ = screen$ /* Screen observable */ window.screen$ = screen$ /* Media screen observable */
window.print$ = print$ /* Print observable */ window.hover$ = hover$ /* Media hover observable */
window.print$ = print$ /* Media print observable */
window.alert$ = alert$ /* Alert subject */ window.alert$ = alert$ /* Alert subject */
window.component$ = component$ /* Component observable */ window.component$ = component$ /* Component observable */

View File

@ -20,7 +20,7 @@
* IN THE SOFTWARE. * IN THE SOFTWARE.
*/ */
import { getElementOrThrow, getElements } from "~/browser" import { getElement, getElements } from "~/browser"
/* ---------------------------------------------------------------------------- /* ----------------------------------------------------------------------------
* Types * Types
@ -114,7 +114,7 @@ interface ComponentTypeMap {
export function getComponentElement<T extends ComponentType>( export function getComponentElement<T extends ComponentType>(
type: T, node: ParentNode = document type: T, node: ParentNode = document
): ComponentTypeMap[T] { ): ComponentTypeMap[T] {
return getElementOrThrow(`[data-md-component=${type}]`, node) return getElement(`[data-md-component=${type}]`, node)
} }
/** /**

View File

@ -53,7 +53,8 @@ export type Content =
interface MountOptions { interface MountOptions {
target$: Observable<HTMLElement> /* Location target observable */ target$: Observable<HTMLElement> /* Location target observable */
viewport$: Observable<Viewport> /* Viewport observable */ viewport$: Observable<Viewport> /* Viewport observable */
print$: Observable<boolean> /* Print observable */ hover$: Observable<boolean> /* Media hover observable */
print$: Observable<boolean> /* Media print observable */
} }
/* ---------------------------------------------------------------------------- /* ----------------------------------------------------------------------------
@ -72,13 +73,13 @@ interface MountOptions {
* @returns Content component observable * @returns Content component observable
*/ */
export function mountContent( export function mountContent(
el: HTMLElement, { target$, viewport$, print$ }: MountOptions el: HTMLElement, { target$, viewport$, hover$, print$ }: MountOptions
): Observable<Component<Content>> { ): Observable<Component<Content>> {
return merge( return merge(
/* Code blocks */ /* Code blocks */
...getElements("pre > code", el) ...getElements("pre > code", el)
.map(child => mountCodeBlock(child, { viewport$, print$ })), .map(child => mountCodeBlock(child, { viewport$, hover$, print$ })),
/* Data tables */ /* Data tables */
...getElements("table:not([class])", el) ...getElements("table:not([class])", el)

View File

@ -41,14 +41,17 @@ import {
} from "rxjs" } from "rxjs"
import { feature } from "~/_" import { feature } from "~/_"
import { resetFocusable, setFocusable } from "~/actions" import {
resetFocusable,
setFocusable
} from "~/actions"
import { import {
Viewport, Viewport,
getElement, getElement,
getElementContentSize, getElementContentSize,
getElementOrThrow,
getElementSize, getElementSize,
getElements, getElements,
getOptionalElement,
watchMedia watchMedia
} from "~/browser" } from "~/browser"
import { import {
@ -174,7 +177,7 @@ export function watchCodeBlock(
container.insertAdjacentElement("afterend", list) container.insertAdjacentElement("afterend", list)
for (const annotation of annotations) { for (const annotation of annotations) {
const id = parseInt(annotation.getAttribute("data-index")!, 10) const id = parseInt(annotation.getAttribute("data-index")!, 10)
const typeset = getElement(":scope .md-typeset", annotation)! const typeset = getOptionalElement(":scope .md-typeset", annotation)!
items[id - 1].append(...Array.from(typeset.childNodes)) items[id - 1].append(...Array.from(typeset.childNodes))
} }
} else { } else {
@ -182,7 +185,7 @@ export function watchCodeBlock(
for (const annotation of annotations) { for (const annotation of annotations) {
const id = parseInt(annotation.getAttribute("data-index")!, 10) const id = parseInt(annotation.getAttribute("data-index")!, 10)
const nodes = items[id - 1].childNodes const nodes = items[id - 1].childNodes
getElementOrThrow(":scope .md-typeset", annotation) getElement(":scope .md-typeset", annotation)
.append(...Array.from(nodes)) .append(...Array.from(nodes))
} }
} }
@ -239,7 +242,7 @@ export function mountCodeBlock(
take(1), take(1),
takeWhile(({ annotations }) => !!annotations?.length), takeWhile(({ annotations }) => !!annotations?.length),
map(({ annotations }) => annotations! map(({ annotations }) => annotations!
.map(annotation => getElementOrThrow(".md-tooltip", annotation)) .map(annotation => getElement(".md-tooltip", annotation))
), ),
combineLatestWith(viewport$ combineLatestWith(viewport$
.pipe( .pipe(

View File

@ -54,7 +54,7 @@ export interface Details {
*/ */
interface WatchOptions { interface WatchOptions {
target$: Observable<HTMLElement> /* Location target observable */ target$: Observable<HTMLElement> /* Location target observable */
print$: Observable<boolean> /* Print observable */ print$: Observable<boolean> /* Media print observable */
} }
/** /**
@ -62,7 +62,7 @@ interface WatchOptions {
*/ */
interface MountOptions { interface MountOptions {
target$: Observable<HTMLElement> /* Location target observable */ target$: Observable<HTMLElement> /* Location target observable */
print$: Observable<boolean> /* Print observable */ print$: Observable<boolean> /* Media print observable */
} }
/* ---------------------------------------------------------------------------- /* ----------------------------------------------------------------------------

View File

@ -22,7 +22,6 @@
import { Observable, of } from "rxjs" import { Observable, of } from "rxjs"
import { replaceElement } from "~/browser"
import { renderTable } from "~/templates" import { renderTable } from "~/templates"
import { h } from "~/utilities" import { h } from "~/utilities"
@ -63,8 +62,8 @@ const sentinel = h("table")
export function mountDataTable( export function mountDataTable(
el: HTMLElement el: HTMLElement
): Observable<Component<DataTable>> { ): Observable<Component<DataTable>> {
replaceElement(el, sentinel) el.replaceWith(sentinel)
replaceElement(sentinel, renderTable(el)) sentinel.replaceWith(renderTable(el))
/* Create and return component */ /* Create and return component */
return of({ ref: el }) return of({ ref: el })

View File

@ -21,7 +21,6 @@
*/ */
import { import {
NEVER,
Observable, Observable,
Subject, Subject,
finalize, finalize,
@ -33,7 +32,7 @@ import {
} from "rxjs" } from "rxjs"
import { import {
getElementOrThrow, getElement,
getElements getElements
} from "~/browser" } from "~/browser"
@ -64,17 +63,14 @@ export interface ContentTabs {
export function watchContentTabs( export function watchContentTabs(
el: HTMLElement el: HTMLElement
): Observable<ContentTabs> { ): Observable<ContentTabs> {
if (!el.classList.contains("tabbed-alternate")) return merge(...getElements(":scope > input", el)
return NEVER .map(input => fromEvent(input, "change").pipe(mapTo(input.id)))
else )
return merge(...getElements(":scope > input", el) .pipe(
.map(input => fromEvent(input, "change").pipe(mapTo(input.id))) map(id => ({
active: getElement<HTMLLabelElement>(`label[for=${id}]`)
}))
) )
.pipe(
map(id => ({
active: getElementOrThrow<HTMLLabelElement>(`label[for=${id}]`)
}))
)
} }
/** /**

View File

@ -40,7 +40,10 @@ import {
} from "rxjs" } from "rxjs"
import { feature } from "~/_" import { feature } from "~/_"
import { resetHeaderState, setHeaderState } from "~/actions" import {
resetHeaderState,
setHeaderState
} from "~/actions"
import { import {
Viewport, Viewport,
watchElementSize, watchElementSize,

View File

@ -38,8 +38,8 @@ import {
} from "~/actions" } from "~/actions"
import { import {
Viewport, Viewport,
getElement,
getElementSize, getElementSize,
getOptionalElement,
watchViewportAt watchViewportAt
} from "~/browser" } from "~/browser"
@ -131,7 +131,7 @@ export function mountHeaderTitle(
}) })
/* Obtain headline, if any */ /* Obtain headline, if any */
const headline = getElement<HTMLHeadingElement>("article h1") const headline = getOptionalElement<HTMLHeadingElement>("article h1")
if (typeof headline === "undefined") if (typeof headline === "undefined")
return NEVER return NEVER

View File

@ -29,7 +29,10 @@ import {
switchMap switchMap
} from "rxjs" } from "rxjs"
import { Viewport, watchElementSize } from "~/browser" import {
Viewport,
watchElementSize
} from "~/browser"
import { Header } from "../header" import { Header } from "../header"

View File

@ -53,10 +53,19 @@ import {
getComponentElement, getComponentElement,
getComponentElements getComponentElements
} from "../../_" } from "../../_"
import { SearchQuery, mountSearchQuery } from "../query" import {
SearchQuery,
mountSearchQuery
} from "../query"
import { mountSearchResult } from "../result" import { mountSearchResult } from "../result"
import { SearchShare, mountSearchShare } from "../share" import {
import { SearchSuggest, mountSearchSuggest } from "../suggest" SearchShare,
mountSearchShare
} from "../share"
import {
SearchSuggest,
mountSearchSuggest
} from "../suggest"
/* ---------------------------------------------------------------------------- /* ----------------------------------------------------------------------------
* Types * Types

View File

@ -46,8 +46,8 @@ import {
setSearchResultMeta setSearchResultMeta
} from "~/actions" } from "~/actions"
import { import {
getElementOrThrow, getElement,
watchElementThreshold watchElementBoundary
} from "~/browser" } from "~/browser"
import { import {
SearchResult, SearchResult,
@ -91,14 +91,14 @@ export function mountSearchResult(
el: HTMLElement, { rx$ }: SearchWorker, { query$ }: MountOptions el: HTMLElement, { rx$ }: SearchWorker, { query$ }: MountOptions
): Observable<Component<SearchResult>> { ): Observable<Component<SearchResult>> {
const internal$ = new Subject<SearchResult>() const internal$ = new Subject<SearchResult>()
const boundary$ = watchElementThreshold(el.parentElement!) const boundary$ = watchElementBoundary(el.parentElement!)
.pipe( .pipe(
filter(Boolean) filter(Boolean)
) )
/* Retrieve nested components */ /* Retrieve nested components */
const meta = getElementOrThrow(":scope > :first-child", el) const meta = getElement(":scope > :first-child", el)
const list = getElementOrThrow(":scope > :last-child", el) const list = getElement(":scope > :last-child", el)
/* Wait until search is ready */ /* Wait until search is ready */
const ready$ = rx$ const ready$ = rx$

View File

@ -98,9 +98,10 @@ interface MountOptions {
export function watchSidebar( export function watchSidebar(
el: HTMLElement, { viewport$, main$ }: WatchOptions el: HTMLElement, { viewport$, main$ }: WatchOptions
): Observable<Sidebar> { ): Observable<Sidebar> {
const parent = el.parentElement!
const adjust = const adjust =
el.parentElement!.offsetTop - parent.offsetTop -
el.parentElement!.parentElement!.offsetTop parent.parentElement!.offsetTop
/* Compute the sidebar's available height and if it should be locked */ /* Compute the sidebar's available height and if it should be locked */
return combineLatest([main$, viewport$]) return combineLatest([main$, viewport$])

View File

@ -34,11 +34,17 @@ import {
tap tap
} from "rxjs" } from "rxjs"
import { setSourceFacts, setSourceState } from "~/actions" import {
setSourceFacts,
setSourceState
} from "~/actions"
import { renderSourceFacts } from "~/templates" import { renderSourceFacts } from "~/templates"
import { Component } from "../../_" import { Component } from "../../_"
import { SourceFacts, fetchSourceFacts } from "../facts" import {
SourceFacts,
fetchSourceFacts
} from "../facts"
/* ---------------------------------------------------------------------------- /* ----------------------------------------------------------------------------
* Types * Types

View File

@ -34,7 +34,10 @@ import {
} from "rxjs" } from "rxjs"
import { feature } from "~/_" import { feature } from "~/_"
import { resetTabsState, setTabsState } from "~/actions" import {
resetTabsState,
setTabsState
} from "~/actions"
import { import {
Viewport, Viewport,
watchElementSize, watchElementSize,

View File

@ -48,9 +48,9 @@ import {
} from "~/actions" } from "~/actions"
import { import {
Viewport, Viewport,
getElement,
getElements, getElements,
getLocation, getLocation,
getOptionalElement,
watchElementSize watchElementSize
} from "~/browser" } from "~/browser"
@ -122,7 +122,7 @@ export function watchTableOfContents(
const anchors = getElements<HTMLAnchorElement>("[href^=\\#]", el) const anchors = getElements<HTMLAnchorElement>("[href^=\\#]", el)
for (const anchor of anchors) { for (const anchor of anchors) {
const id = decodeURIComponent(anchor.hash.substring(1)) const id = decodeURIComponent(anchor.hash.substring(1))
const target = getElement(`[id="${id}"]`) const target = getOptionalElement(`[id="${id}"]`)
if (typeof target !== "undefined") if (typeof target !== "undefined")
table.set(anchor, target) table.set(anchor, target)
} }

View File

@ -43,7 +43,10 @@ import {
setBackToTopState, setBackToTopState,
setFocusable setFocusable
} from "~/actions" } from "~/actions"
import { Viewport, setElementFocus } from "~/browser" import {
Viewport,
setElementFocus
} from "~/browser"
import { Component } from "../_" import { Component } from "../_"
import { Header } from "../header" import { Header } from "../header"

View File

@ -25,7 +25,7 @@ import { Observable, Subject } from "rxjs"
import { translation } from "~/_" import { translation } from "~/_"
import { import {
getElementOrThrow, getElement,
getElements getElements
} from "~/browser" } from "~/browser"
@ -85,7 +85,7 @@ export function setupClipboardJS(
new ClipboardJS("[data-clipboard-target], [data-clipboard-text]", { new ClipboardJS("[data-clipboard-target], [data-clipboard-text]", {
text: el => ( text: el => (
el.getAttribute("data-clipboard-text")! || el.getAttribute("data-clipboard-text")! ||
extract(getElementOrThrow( extract(getElement(
el.getAttribute("data-clipboard-target")! el.getAttribute("data-clipboard-target")!
)) ))
) )

View File

@ -47,9 +47,8 @@ import { configuration, feature } from "~/_"
import { import {
Viewport, Viewport,
ViewportOffset, ViewportOffset,
getElement,
getElements, getElements,
replaceElement, getOptionalElement,
request, request,
requestXML, requestXML,
setLocation, setLocation,
@ -166,7 +165,7 @@ export function setupInstantLoading(
} }
/* Hack: ensure absolute favicon link to omit 404s when switching */ /* Hack: ensure absolute favicon link to omit 404s when switching */
const favicon = getElement<HTMLLinkElement>("link[rel=icon]") const favicon = getOptionalElement<HTMLLinkElement>("link[rel=icon]")
if (typeof favicon !== "undefined") if (typeof favicon !== "undefined")
favicon.href = favicon.href favicon.href = favicon.href
@ -286,13 +285,13 @@ export function setupInstantLoading(
? ["[data-md-component=tabs]"] ? ["[data-md-component=tabs]"]
: [] : []
]) { ]) {
const source = getElement(selector) const source = getOptionalElement(selector)
const target = getElement(selector, replacement) const target = getOptionalElement(selector, replacement)
if ( if (
typeof source !== "undefined" && typeof source !== "undefined" &&
typeof target !== "undefined" typeof target !== "undefined"
) { ) {
replaceElement(source, target) source.replaceWith(target)
} }
} }
}) })
@ -308,7 +307,7 @@ export function setupInstantLoading(
if (el.src) { if (el.src) {
for (const name of el.getAttributeNames()) for (const name of el.getAttributeNames())
script.setAttribute(name, el.getAttribute(name)!) script.setAttribute(name, el.getAttribute(name)!)
replaceElement(el, script) el.replaceWith(script)
/* Complete when script is loaded */ /* Complete when script is loaded */
return new Observable(observer => { return new Observable(observer => {
@ -318,7 +317,7 @@ export function setupInstantLoading(
/* Complete immediately */ /* Complete immediately */
} else { } else {
script.textContent = el.textContent script.textContent = el.textContent
replaceElement(el, script) el.replaceWith(script)
return EMPTY return EMPTY
} }
}) })

View File

@ -24,7 +24,7 @@ import { combineLatest, map } from "rxjs"
import { configuration } from "~/_" import { configuration } from "~/_"
import { import {
getElementOrThrow, getElement,
requestJSON requestJSON
} from "~/browser" } from "~/browser"
import { getComponentElements } from "~/components" import { getComponentElements } from "~/components"
@ -60,7 +60,7 @@ export function setupVersionSelector(): void {
/* Render version selector and warning */ /* Render version selector and warning */
combineLatest([versions$, current$]) combineLatest([versions$, current$])
.subscribe(([versions, current]) => { .subscribe(([versions, current]) => {
const topic = getElementOrThrow(".md-header__topic") const topic = getElement(".md-header__topic")
topic.appendChild(renderVersionSelector(versions, current)) topic.appendChild(renderVersionSelector(versions, current))
/* Check if version state was already determined */ /* Check if version state was already determined */

View File

@ -43,7 +43,7 @@ import { getElements } from "~/browser"
*/ */
interface PatchOptions { interface PatchOptions {
document$: Observable<Document> /* Document observable */ document$: Observable<Document> /* Document observable */
tablet$: Observable<boolean> /* Tablet breakpoint observable */ tablet$: Observable<boolean> /* Media tablet observable */
} }
/* ---------------------------------------------------------------------------- /* ----------------------------------------------------------------------------

View File

@ -32,8 +32,14 @@ import {
withLatestFrom withLatestFrom
} from "rxjs" } from "rxjs"
import { resetScrollLock, setScrollLock } from "~/actions" import {
import { Viewport, watchToggle } from "~/browser" resetScrollLock,
setScrollLock
} from "~/actions"
import {
Viewport,
watchToggle
} from "~/browser"
/* ---------------------------------------------------------------------------- /* ----------------------------------------------------------------------------
* Helper types * Helper types
@ -44,7 +50,7 @@ import { Viewport, watchToggle } from "~/browser"
*/ */
interface PatchOptions { interface PatchOptions {
viewport$: Observable<Viewport> /* Viewport observable */ viewport$: Observable<Viewport> /* Viewport observable */
tablet$: Observable<boolean> /* Tablet breakpoint observable */ tablet$: Observable<boolean> /* Media tablet observable */
} }
/* ---------------------------------------------------------------------------- /* ----------------------------------------------------------------------------

View File

@ -27,7 +27,7 @@ import { h } from "~/utilities"
* ------------------------------------------------------------------------- */ * ------------------------------------------------------------------------- */
/** /**
* Render a 'copy-to-clipboard' button * Render a a code annotation
* *
* @param id - Unique identifier * @param id - Unique identifier
* @param content - Annotation content * @param content - Annotation content

View File

@ -20,7 +20,7 @@
* IN THE SOFTWARE. * IN THE SOFTWARE.
*/ */
import { getElementOrThrow, getElements } from "~/browser" import { getElement, getElements } from "~/browser"
/* ---------------------------------------------------------------------------- /* ----------------------------------------------------------------------------
* Types * Types
@ -84,7 +84,7 @@ interface ComponentTypeMap {
export function getComponentElement<T extends ComponentType>( export function getComponentElement<T extends ComponentType>(
type: T, node: ParentNode = document type: T, node: ParentNode = document
): ComponentTypeMap[T] { ): ComponentTypeMap[T] {
return getElementOrThrow(`[data-mdx-component=${type}]`, node) return getElement(`[data-mdx-component=${type}]`, node)
} }
/** /**

View File

@ -47,8 +47,8 @@ import {
setSearchResultMeta setSearchResultMeta
} from "~/actions" } from "~/actions"
import { import {
getElementOrThrow, getElement,
watchElementThreshold watchElementBoundary
} from "~/browser" } from "~/browser"
import { Icon, renderIconSearchResult } from "_/templates" import { Icon, renderIconSearchResult } from "_/templates"
@ -147,13 +147,13 @@ export function mountIconSearchResult(
el: HTMLElement, { index$, query$ }: MountOptions el: HTMLElement, { index$, query$ }: MountOptions
): Observable<Component<IconSearchResult, HTMLElement>> { ): Observable<Component<IconSearchResult, HTMLElement>> {
const internal$ = new Subject<IconSearchResult>() const internal$ = new Subject<IconSearchResult>()
const boundary$ = watchElementThreshold(el) const boundary$ = watchElementBoundary(el)
.pipe( .pipe(
filter(Boolean) filter(Boolean)
) )
/* Update search result metadata */ /* Update search result metadata */
const meta = getElementOrThrow(":scope > :first-child", el) const meta = getElement(":scope > :first-child", el)
internal$ internal$
.pipe( .pipe(
observeOn(animationFrameScheduler), observeOn(animationFrameScheduler),
@ -167,7 +167,7 @@ export function mountIconSearchResult(
}) })
/* Update icon search result list */ /* Update icon search result list */
const list = getElementOrThrow(":scope > :last-child", el) const list = getElement(":scope > :last-child", el)
internal$ internal$
.pipe( .pipe(
observeOn(animationFrameScheduler), observeOn(animationFrameScheduler),

View File

@ -22,7 +22,7 @@
import { Observable, map } from "rxjs" import { Observable, map } from "rxjs"
import { getElementOrThrow, requestJSON } from "~/browser" import { getElement, requestJSON } from "~/browser"
import { renderPrivateSponsor, renderPublicSponsor } from "_/templates" import { renderPrivateSponsor, renderPublicSponsor } from "_/templates"
@ -121,7 +121,7 @@ export function mountSponsorship(
el.removeAttribute("hidden") el.removeAttribute("hidden")
/* Render public sponsors with avatar and links */ /* Render public sponsors with avatar and links */
const list = getElementOrThrow(":scope > :first-child", el) const list = getElement(":scope > :first-child", el)
for (const sponsor of sponsorship.sponsors) for (const sponsor of sponsorship.sponsors)
if (sponsor.type === "public") if (sponsor.type === "public")
list.appendChild(renderPublicSponsor(sponsor.user)) list.appendChild(renderPublicSponsor(sponsor.user))

View File

@ -182,6 +182,7 @@ export function transformScript(
map: Buffer.from(data, "base64") map: Buffer.from(data, "base64")
}) })
}), }),
catchError(() => NEVER),
switchMap(({ js, map }) => { switchMap(({ js, map }) => {
const file = digest(options.to, js) const file = digest(options.to, js)
return concat( return concat(

View File

@ -106,9 +106,10 @@ declare global {
var target$: Observable<HTMLElement> /* Location target observable */ var target$: Observable<HTMLElement> /* Location target observable */
var keyboard$: Observable<Keyboard> /* Keyboard observable */ var keyboard$: Observable<Keyboard> /* Keyboard observable */
var viewport$: Observable<Viewport> /* Viewport obsevable */ var viewport$: Observable<Viewport> /* Viewport obsevable */
var tablet$: Observable<boolean> /* Tablet breakpoint observable */ var tablet$: Observable<boolean> /* Media tablet observable */
var screen$: Observable<boolean> /* Screen breakpoint observable */ var screen$: Observable<boolean> /* Media screen observable */
var print$: Observable<boolean> /* Print observable */ var hover$: Observable<boolean> /* Media hover observable */
var print$: Observable<boolean> /* Media print observable */
var alert$: Subject<string> /* Alert subject */ var alert$: Subject<string> /* Alert subject */
var component$: Observable<Component>/* Component observable */ var component$: Observable<Component>/* Component observable */
} }