mirror of
https://github.com/squidfunk/mkdocs-material.git
synced 2026-08-01 02:08:46 -04:00
Fixed observable completion semantics
This commit is contained in:
@@ -1,78 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Functions
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Set focusable property
|
||||
*
|
||||
* @param el - Element
|
||||
* @param value - Tabindex value
|
||||
*/
|
||||
export function setFocusable(
|
||||
el: HTMLElement, value = 0
|
||||
): void {
|
||||
el.setAttribute("tabindex", value.toString())
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset focusable property
|
||||
*
|
||||
* @param el - Element
|
||||
*/
|
||||
export function resetFocusable(
|
||||
el: HTMLElement
|
||||
): void {
|
||||
el.removeAttribute("tabindex")
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Set scroll lock
|
||||
*
|
||||
* @param el - Scrollable element
|
||||
* @param value - Vertical offset
|
||||
*/
|
||||
export function setScrollLock(
|
||||
el: HTMLElement, value: number
|
||||
): void {
|
||||
el.setAttribute("data-md-state", "lock")
|
||||
el.style.top = `-${value}px`
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset scroll lock
|
||||
*
|
||||
* @param el - Scrollable element
|
||||
*/
|
||||
export function resetScrollLock(
|
||||
el: HTMLElement
|
||||
): void {
|
||||
const value = -1 * parseInt(el.style.top, 10)
|
||||
el.removeAttribute("data-md-state")
|
||||
el.style.top = ""
|
||||
if (value)
|
||||
window.scrollTo(0, value)
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Functions
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Set anchor state
|
||||
*
|
||||
* @param el - Anchor element
|
||||
* @param state - Anchor state
|
||||
*/
|
||||
export function setAnchorState(
|
||||
el: HTMLElement, state: "blur"
|
||||
): void {
|
||||
el.setAttribute("data-md-state", state)
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset anchor state
|
||||
*
|
||||
* @param el - Anchor element
|
||||
*/
|
||||
export function resetAnchorState(
|
||||
el: HTMLElement
|
||||
): void {
|
||||
el.removeAttribute("data-md-state")
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Set anchor active
|
||||
*
|
||||
* @param el - Anchor element
|
||||
* @param value - Whether the anchor is active
|
||||
*/
|
||||
export function setAnchorActive(
|
||||
el: HTMLElement, value: boolean
|
||||
): void {
|
||||
el.classList.toggle("md-nav__link--active", value)
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset anchor active
|
||||
*
|
||||
* @param el - Anchor element
|
||||
*/
|
||||
export function resetAnchorActive(
|
||||
el: HTMLElement
|
||||
): void {
|
||||
el.classList.remove("md-nav__link--active")
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Functions
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Set dialog message
|
||||
*
|
||||
* @param el - Dialog element
|
||||
* @param value - Dialog message
|
||||
*/
|
||||
export function setDialogMessage(
|
||||
el: HTMLElement, value: string
|
||||
): void {
|
||||
el.firstElementChild!.innerHTML = value
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Set dialog state
|
||||
*
|
||||
* @param el - Dialog element
|
||||
* @param state - Dialog state
|
||||
*/
|
||||
export function setDialogState(
|
||||
el: HTMLElement, state: "open"
|
||||
): void {
|
||||
el.setAttribute("data-md-state", state)
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset dialog state
|
||||
*
|
||||
* @param el - Dialog element
|
||||
*/
|
||||
export function resetDialogState(
|
||||
el: HTMLElement
|
||||
): void {
|
||||
el.removeAttribute("data-md-state")
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Functions
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Set header state
|
||||
*
|
||||
* @param el - Header element
|
||||
* @param state - Header state
|
||||
*/
|
||||
export function setHeaderState(
|
||||
el: HTMLElement, state: "shadow" | "hidden"
|
||||
): void {
|
||||
el.setAttribute("data-md-state", state)
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset header state
|
||||
*
|
||||
* @param el - Header element
|
||||
*/
|
||||
export function resetHeaderState(
|
||||
el: HTMLElement
|
||||
): void {
|
||||
el.removeAttribute("data-md-state")
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export * from "./_"
|
||||
export * from "./title"
|
||||
@@ -1,48 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Functions
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Set header title state
|
||||
*
|
||||
* @param el - Header title element
|
||||
* @param state - Header title state
|
||||
*/
|
||||
export function setHeaderTitleState(
|
||||
el: HTMLElement, state: "active"
|
||||
): void {
|
||||
el.setAttribute("data-md-state", state)
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset header title state
|
||||
*
|
||||
* @param el - Header title element
|
||||
*/
|
||||
export function resetHeaderTitleState(
|
||||
el: HTMLElement
|
||||
): void {
|
||||
el.removeAttribute("data-md-state")
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export * from "./_"
|
||||
export * from "./anchor"
|
||||
export * from "./dialog"
|
||||
export * from "./header"
|
||||
export * from "./search"
|
||||
export * from "./sidebar"
|
||||
export * from "./source"
|
||||
export * from "./tabs"
|
||||
export * from "./top"
|
||||
@@ -1,24 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export * from "./query"
|
||||
export * from "./result"
|
||||
@@ -1,50 +0,0 @@
|
||||
/*
|
||||
* 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 { translation } from "~/_"
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Functions
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Set search query placeholder
|
||||
*
|
||||
* @param el - Search query element
|
||||
* @param value - Placeholder
|
||||
*/
|
||||
export function setSearchQueryPlaceholder(
|
||||
el: HTMLInputElement, value: string
|
||||
): void {
|
||||
el.placeholder = value
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset search query placeholder
|
||||
*
|
||||
* @param el - Search query element
|
||||
*/
|
||||
export function resetSearchQueryPlaceholder(
|
||||
el: HTMLInputElement
|
||||
): void {
|
||||
el.placeholder = translation("search.placeholder")
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
/*
|
||||
* 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 { translation } from "~/_"
|
||||
import { round } from "~/utilities"
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Functions
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Set number of search results
|
||||
*
|
||||
* @param el - Search result metadata element
|
||||
* @param value - Number of results
|
||||
*/
|
||||
export function setSearchResultMeta(
|
||||
el: HTMLElement, value: number
|
||||
): void {
|
||||
switch (value) {
|
||||
|
||||
/* No results */
|
||||
case 0:
|
||||
el.textContent = translation("search.result.none")
|
||||
break
|
||||
|
||||
/* One result */
|
||||
case 1:
|
||||
el.textContent = translation("search.result.one")
|
||||
break
|
||||
|
||||
/* Multiple result */
|
||||
default:
|
||||
el.textContent = translation("search.result.other", round(value))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset number of search results
|
||||
*
|
||||
* @param el - Search result metadata element
|
||||
*/
|
||||
export function resetSearchResultMeta(
|
||||
el: HTMLElement
|
||||
): void {
|
||||
el.textContent = translation("search.result.placeholder")
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Add an element to the search result list
|
||||
*
|
||||
* @param el - Search result list element
|
||||
* @param child - Search result element
|
||||
*/
|
||||
export function addToSearchResultList(
|
||||
el: HTMLElement, child: Element
|
||||
): void {
|
||||
el.appendChild(child)
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset search result list
|
||||
*
|
||||
* @param el - Search result list element
|
||||
*/
|
||||
export function resetSearchResultList(
|
||||
el: HTMLElement
|
||||
): void {
|
||||
el.innerHTML = ""
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Functions
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Set sidebar offset
|
||||
*
|
||||
* @param el - Sidebar element
|
||||
* @param value - Sidebar offset
|
||||
*/
|
||||
export function setSidebarOffset(
|
||||
el: HTMLElement, value: number
|
||||
): void {
|
||||
el.style.top = `${value}px`
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset sidebar offset
|
||||
*
|
||||
* @param el - Sidebar element
|
||||
*/
|
||||
export function resetSidebarOffset(
|
||||
el: HTMLElement
|
||||
): void {
|
||||
el.style.top = ""
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Set sidebar height
|
||||
*
|
||||
* This function doesn't set the height of the actual sidebar, but of its first
|
||||
* child – the `.md-sidebar__scrollwrap` element in order to mitigiate jittery
|
||||
* sidebars when the footer is scrolled into view. At some point we switched
|
||||
* from `absolute` / `fixed` positioning to `sticky` positioning, significantly
|
||||
* reducing jitter in some browsers (respectively Firefox and Safari) when
|
||||
* scrolling from the top. However, top-aligned sticky positioning means that
|
||||
* the sidebar snaps to the bottom when the end of the container is reached.
|
||||
* This is what leads to the mentioned jitter, as the sidebar's height may be
|
||||
* updated too slowly.
|
||||
*
|
||||
* This behaviour can be mitigiated by setting the height of the sidebar to `0`
|
||||
* while preserving the padding, and the height on its first element.
|
||||
*
|
||||
* @param el - Sidebar element
|
||||
* @param value - Sidebar height
|
||||
*/
|
||||
export function setSidebarHeight(
|
||||
el: HTMLElement, value: number
|
||||
): void {
|
||||
const scrollwrap = el.firstElementChild as HTMLElement
|
||||
scrollwrap.style.height = `${value - 2 * scrollwrap.offsetTop}px`
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset sidebar height
|
||||
*
|
||||
* @param el - Sidebar element
|
||||
*/
|
||||
export function resetSidebarHeight(
|
||||
el: HTMLElement
|
||||
): void {
|
||||
const scrollwrap = el.firstElementChild as HTMLElement
|
||||
scrollwrap.style.height = ""
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Functions
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Set repository facts
|
||||
*
|
||||
* @param el - Repository element
|
||||
* @param child - Repository facts element
|
||||
*/
|
||||
export function setSourceFacts(
|
||||
el: HTMLElement, child: Element
|
||||
): void {
|
||||
el.lastElementChild!.appendChild(child)
|
||||
}
|
||||
|
||||
/**
|
||||
* Set repository state
|
||||
*
|
||||
* @param el - Repository element
|
||||
* @param state - Repository state
|
||||
*/
|
||||
export function setSourceState(
|
||||
el: HTMLElement, state: "done"
|
||||
): void {
|
||||
el.lastElementChild!.setAttribute("data-md-state", state)
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Functions
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Set tabs state
|
||||
*
|
||||
* @param el - Tabs element
|
||||
* @param state - Tabs state
|
||||
*/
|
||||
export function setTabsState(
|
||||
el: HTMLElement, state: "hidden"
|
||||
): void {
|
||||
el.setAttribute("data-md-state", state)
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset tabs state
|
||||
*
|
||||
* @param el - Tabs element
|
||||
*/
|
||||
export function resetTabsState(
|
||||
el: HTMLElement
|
||||
): void {
|
||||
el.removeAttribute("data-md-state")
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Functions
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Set back-to-top state
|
||||
*
|
||||
* @param el - Back-to-top element
|
||||
* @param state - Back-to-top state
|
||||
*/
|
||||
export function setBackToTopState(
|
||||
el: HTMLElement, state: "hidden"
|
||||
): void {
|
||||
el.setAttribute("data-md-state", state)
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset back-to-top state
|
||||
*
|
||||
* @param el - Back-to-top element
|
||||
*/
|
||||
export function resetBackToTopState(
|
||||
el: HTMLElement
|
||||
): void {
|
||||
el.removeAttribute("data-md-state")
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Set back-to-top offset
|
||||
*
|
||||
* @param el - Back-to-top element
|
||||
* @param value - Back-to-top offset
|
||||
*/
|
||||
export function setBackToTopOffset(
|
||||
el: HTMLElement, value: number
|
||||
): void {
|
||||
el.style.top = `${value}px`
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset back-to-top offset
|
||||
*
|
||||
* @param el - Back-to-top element
|
||||
*/
|
||||
export function resetBackToTopOffset(
|
||||
el: HTMLElement
|
||||
): void {
|
||||
el.style.top = ""
|
||||
}
|
||||
@@ -34,23 +34,6 @@ import { getActiveElement } from "../_"
|
||||
* Functions
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Set element focus
|
||||
*
|
||||
* @param el - Element
|
||||
* @param value - Whether the element should be focused
|
||||
*/
|
||||
export function setElementFocus(
|
||||
el: HTMLElement, value = true
|
||||
): void {
|
||||
if (value)
|
||||
el.focus()
|
||||
else
|
||||
el.blur()
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Watch element focus
|
||||
*
|
||||
|
||||
@@ -23,6 +23,5 @@
|
||||
export * from "./_"
|
||||
export * from "./focus"
|
||||
export * from "./offset"
|
||||
export * from "./selection"
|
||||
export * from "./size"
|
||||
export * from "./visibility"
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Functions
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Set element text selection
|
||||
*
|
||||
* @param el - Element
|
||||
*/
|
||||
export function setElementSelection(
|
||||
el: HTMLElement
|
||||
): void {
|
||||
if (el instanceof HTMLInputElement)
|
||||
el.select()
|
||||
else
|
||||
throw new Error("Not implemented")
|
||||
}
|
||||
@@ -32,7 +32,6 @@ import {
|
||||
merge,
|
||||
of,
|
||||
shareReplay,
|
||||
startWith,
|
||||
switchMap,
|
||||
tap
|
||||
} from "rxjs"
|
||||
@@ -113,7 +112,7 @@ export function watchElementVisibility(
|
||||
* @param el - Element
|
||||
* @param threshold - Threshold
|
||||
*
|
||||
* @returns Element threshold observable
|
||||
* @returns Element boundary observable
|
||||
*/
|
||||
export function watchElementBoundary(
|
||||
el: HTMLElement, threshold = 16
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
NEVER,
|
||||
EMPTY,
|
||||
Observable,
|
||||
fromEvent,
|
||||
fromEventPattern,
|
||||
@@ -86,6 +86,6 @@ export function at<T>(
|
||||
): Observable<T> {
|
||||
return query$
|
||||
.pipe(
|
||||
switchMap(active => active ? factory() : NEVER)
|
||||
switchMap(active => active ? factory() : EMPTY)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -59,17 +59,6 @@ export function getViewportOffset(): ViewportOffset {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set viewport offset
|
||||
*
|
||||
* @param offset - Viewport offset
|
||||
*/
|
||||
export function setViewportOffset(
|
||||
{ x, y }: Partial<ViewportOffset>
|
||||
): void {
|
||||
window.scrollTo(x || 0, y || 0)
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
import "focus-visible"
|
||||
import {
|
||||
EMPTY,
|
||||
NEVER,
|
||||
Subject,
|
||||
defer,
|
||||
@@ -198,13 +199,13 @@ const content$ = defer(() => merge(
|
||||
|
||||
/* Content */
|
||||
...getComponentElements("content")
|
||||
.map(el => mountContent(el, { target$, viewport$, hover$, print$ })),
|
||||
.map(el => mountContent(el, { target$, hover$, print$ })),
|
||||
|
||||
/* Search highlighting */
|
||||
...getComponentElements("content")
|
||||
.map(el => feature("search.highlight")
|
||||
? mountSearchHiglight(el, { index$, location$ })
|
||||
: NEVER
|
||||
: EMPTY
|
||||
),
|
||||
|
||||
/* Header title */
|
||||
|
||||
@@ -36,6 +36,7 @@ import {
|
||||
|
||||
import {
|
||||
ElementOffset,
|
||||
getElement,
|
||||
watchElementContentOffset,
|
||||
watchElementFocus,
|
||||
watchElementOffset
|
||||
@@ -122,7 +123,7 @@ export function mountAnnotation(
|
||||
})
|
||||
|
||||
/* Blur open annotation on click (= close) */
|
||||
const index = el.lastElementChild!
|
||||
const index = getElement(":scope > :last-child")
|
||||
const blur$ = fromEvent(index, "mousedown", { once: true })
|
||||
push$
|
||||
.pipe(
|
||||
|
||||
@@ -120,36 +120,36 @@ export function mountAnnotationList(
|
||||
|
||||
/* Create and return component */
|
||||
return defer(() => {
|
||||
const push$ = new Subject<Annotation>()
|
||||
const done$ = new Subject<void>()
|
||||
|
||||
/* Handle print mode - see https://bit.ly/3rgPdpt */
|
||||
print$
|
||||
.pipe(
|
||||
startWith(false),
|
||||
takeUntil(push$.pipe(takeLast(1)))
|
||||
takeUntil(done$.pipe(takeLast(1)))
|
||||
)
|
||||
.subscribe(active => {
|
||||
el.hidden = !active
|
||||
|
||||
/* Move annotation contents back into list */
|
||||
for (const [id, annotation] of annotations) {
|
||||
const tooltip = getElement(".md-typeset", annotation)
|
||||
const inner = getElement(".md-typeset", annotation)
|
||||
const child = getElement(`li:nth-child(${id})`, el)
|
||||
if (!active)
|
||||
swap(child, tooltip)
|
||||
swap(child, inner)
|
||||
else
|
||||
swap(tooltip, child)
|
||||
swap(inner, child)
|
||||
}
|
||||
})
|
||||
|
||||
/* Create and return component */
|
||||
return merge(
|
||||
...[...annotations].map(([, annotation]) => (
|
||||
return merge(...[...annotations]
|
||||
.map(([, annotation]) => (
|
||||
mountAnnotation(annotation, container)
|
||||
))
|
||||
)
|
||||
.pipe(
|
||||
finalize(() => push$.complete()),
|
||||
finalize(() => done$.complete()),
|
||||
share()
|
||||
)
|
||||
})
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
import {
|
||||
Observable,
|
||||
Subject,
|
||||
defer,
|
||||
filter,
|
||||
finalize,
|
||||
map,
|
||||
@@ -41,7 +42,7 @@ import { Component } from "../../_"
|
||||
* Details
|
||||
*/
|
||||
export interface Details {
|
||||
action: "open" | "close" /* Action */
|
||||
action: "open" | "close" /* Details state */
|
||||
scroll?: boolean /* Scroll into view */
|
||||
}
|
||||
|
||||
@@ -117,21 +118,23 @@ export function watchDetails(
|
||||
export function mountDetails(
|
||||
el: HTMLDetailsElement, options: MountOptions
|
||||
): Observable<Component<Details>> {
|
||||
const internal$ = new Subject<Details>()
|
||||
internal$.subscribe(({ action, scroll }) => {
|
||||
if (action === "open")
|
||||
el.setAttribute("open", "")
|
||||
else
|
||||
el.removeAttribute("open")
|
||||
if (scroll)
|
||||
el.scrollIntoView()
|
||||
})
|
||||
return defer(() => {
|
||||
const push$ = new Subject<Details>()
|
||||
push$.subscribe(({ action, scroll }) => {
|
||||
if (action === "open")
|
||||
el.setAttribute("open", "")
|
||||
else
|
||||
el.removeAttribute("open")
|
||||
if (scroll)
|
||||
el.scrollIntoView()
|
||||
})
|
||||
|
||||
/* Create and return component */
|
||||
return watchDetails(el, options)
|
||||
.pipe(
|
||||
tap(state => internal$.next(state)),
|
||||
finalize(() => internal$.complete()),
|
||||
map(state => ({ ref: el, ...state }))
|
||||
)
|
||||
/* Create and return component */
|
||||
return watchDetails(el, options)
|
||||
.pipe(
|
||||
tap(state => push$.next(state)),
|
||||
finalize(() => push$.complete()),
|
||||
map(state => ({ ref: el, ...state }))
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
import {
|
||||
Observable,
|
||||
Subject,
|
||||
defer,
|
||||
finalize,
|
||||
fromEvent,
|
||||
map,
|
||||
@@ -33,6 +34,7 @@ import {
|
||||
|
||||
import {
|
||||
getElement,
|
||||
getElementOffset,
|
||||
getElements
|
||||
} from "~/browser"
|
||||
|
||||
@@ -64,7 +66,11 @@ export function watchContentTabs(
|
||||
el: HTMLElement
|
||||
): Observable<ContentTabs> {
|
||||
return merge(...getElements(":scope > input", el)
|
||||
.map(input => fromEvent(input, "change").pipe(mapTo(input.id)))
|
||||
.map(input => fromEvent(input, "change")
|
||||
.pipe(
|
||||
mapTo(input.id)
|
||||
)
|
||||
)
|
||||
)
|
||||
.pipe(
|
||||
map(id => ({
|
||||
@@ -76,6 +82,11 @@ export function watchContentTabs(
|
||||
/**
|
||||
* Mount content tabs
|
||||
*
|
||||
* This function scrolls the active tab into view. While this functionality is
|
||||
* provided by browsers as part of `scrollInfoView`, browsers will always also
|
||||
* scroll the vertical axis, which we do not want. Thus, we decided to provide
|
||||
* this functionality ourselves.
|
||||
*
|
||||
* @param el - Content tabs element
|
||||
*
|
||||
* @returns Content tabs component observable
|
||||
@@ -83,25 +94,23 @@ export function watchContentTabs(
|
||||
export function mountContentTabs(
|
||||
el: HTMLElement
|
||||
): Observable<Component<ContentTabs>> {
|
||||
const internal$ = new Subject<ContentTabs>()
|
||||
internal$.subscribe(({ active }) => {
|
||||
// TODO: Hack, scrollIntoView is too buggy
|
||||
const container = active.parentElement!
|
||||
if (
|
||||
active.offsetLeft + active.offsetWidth > container.scrollLeft + container.offsetWidth ||
|
||||
active.offsetLeft < container.scrollLeft
|
||||
)
|
||||
const container = getElement(".tabbed-labels", el)
|
||||
return defer(() => {
|
||||
const push$ = new Subject<ContentTabs>()
|
||||
push$.subscribe(({ active }) => {
|
||||
const { x } = getElementOffset(active)
|
||||
container.scrollTo({
|
||||
behavior: "smooth",
|
||||
left: active.offsetLeft
|
||||
left: x
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
/* Create and return component */
|
||||
return watchContentTabs(el)
|
||||
.pipe(
|
||||
tap(state => internal$.next(state)),
|
||||
finalize(() => internal$.complete()),
|
||||
map(state => ({ ref: el, ...state }))
|
||||
)
|
||||
/* Create and return component */
|
||||
return watchContentTabs(el)
|
||||
.pipe(
|
||||
tap(state => push$.next(state)),
|
||||
finalize(() => push$.complete()),
|
||||
map(state => ({ ref: el, ...state }))
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -23,22 +23,17 @@
|
||||
import {
|
||||
Observable,
|
||||
Subject,
|
||||
animationFrameScheduler,
|
||||
defer,
|
||||
delay,
|
||||
finalize,
|
||||
map,
|
||||
merge,
|
||||
observeOn,
|
||||
of,
|
||||
switchMap,
|
||||
tap
|
||||
} from "rxjs"
|
||||
|
||||
import {
|
||||
resetDialogState,
|
||||
setDialogMessage,
|
||||
setDialogState
|
||||
} from "~/actions"
|
||||
import { getElement } from "~/browser"
|
||||
|
||||
import { Component } from "../_"
|
||||
|
||||
@@ -103,7 +98,7 @@ export function watchDialog(
|
||||
/**
|
||||
* Mount dialog
|
||||
*
|
||||
* This function reveals the dialog in the right cornerwhen a new alert is
|
||||
* This function reveals the dialog in the right corner when a new alert is
|
||||
* emitted through the subject that is passed as part of the options.
|
||||
*
|
||||
* @param el - Dialog element
|
||||
@@ -114,24 +109,23 @@ export function watchDialog(
|
||||
export function mountDialog(
|
||||
el: HTMLElement, options: MountOptions
|
||||
): Observable<Component<Dialog>> {
|
||||
const internal$ = new Subject<Dialog>()
|
||||
internal$
|
||||
.pipe(
|
||||
observeOn(animationFrameScheduler)
|
||||
)
|
||||
.subscribe(({ message, open }) => {
|
||||
setDialogMessage(el, message)
|
||||
if (open)
|
||||
setDialogState(el, "open")
|
||||
else
|
||||
resetDialogState(el)
|
||||
})
|
||||
const inner = getElement(".md-typeset", el)
|
||||
return defer(() => {
|
||||
const push$ = new Subject<Dialog>()
|
||||
push$.subscribe(({ message, open }) => {
|
||||
inner.textContent = message
|
||||
if (open)
|
||||
el.setAttribute("data-md-state", "open")
|
||||
else
|
||||
el.removeAttribute("data-md-state")
|
||||
})
|
||||
|
||||
/* Create and return component */
|
||||
return watchDialog(el, options)
|
||||
.pipe(
|
||||
tap(state => internal$.next(state)),
|
||||
finalize(() => internal$.complete()),
|
||||
map(state => ({ ref: el, ...state }))
|
||||
)
|
||||
/* Create and return component */
|
||||
return watchDialog(el, options)
|
||||
.pipe(
|
||||
tap(state => push$.next(state)),
|
||||
finalize(() => push$.complete()),
|
||||
map(state => ({ ref: el, ...state }))
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
import {
|
||||
Observable,
|
||||
Subject,
|
||||
animationFrameScheduler,
|
||||
bufferCount,
|
||||
combineLatest,
|
||||
combineLatestWith,
|
||||
@@ -32,18 +31,15 @@ import {
|
||||
distinctUntilKeyChanged,
|
||||
filter,
|
||||
map,
|
||||
observeOn,
|
||||
of,
|
||||
shareReplay,
|
||||
startWith,
|
||||
switchMap
|
||||
switchMap,
|
||||
takeLast,
|
||||
takeUntil
|
||||
} from "rxjs"
|
||||
|
||||
import { feature } from "~/_"
|
||||
import {
|
||||
resetHeaderState,
|
||||
setHeaderState
|
||||
} from "~/actions"
|
||||
import {
|
||||
Viewport,
|
||||
watchElementSize,
|
||||
@@ -184,24 +180,28 @@ export function watchHeader(
|
||||
export function mountHeader(
|
||||
el: HTMLElement, { header$, main$ }: MountOptions
|
||||
): Observable<Component<Header>> {
|
||||
const internal$ = new Subject<Main>()
|
||||
internal$
|
||||
.pipe(
|
||||
distinctUntilKeyChanged("active"),
|
||||
combineLatestWith(header$),
|
||||
observeOn(animationFrameScheduler)
|
||||
)
|
||||
.subscribe(([{ active }, { hidden }]) => {
|
||||
if (active)
|
||||
setHeaderState(el, hidden ? "hidden" : "shadow")
|
||||
else
|
||||
resetHeaderState(el)
|
||||
})
|
||||
return defer(() => {
|
||||
const push$ = new Subject<Main>()
|
||||
push$
|
||||
.pipe(
|
||||
distinctUntilKeyChanged("active"),
|
||||
combineLatestWith(header$)
|
||||
)
|
||||
.subscribe(([{ active }, { hidden }]) => {
|
||||
if (active)
|
||||
el.setAttribute("data-md-state", hidden ? "hidden" : "shadow")
|
||||
else
|
||||
el.removeAttribute("data-md-state")
|
||||
})
|
||||
|
||||
/* Connect to long-living subject and return component */
|
||||
main$.subscribe(main => internal$.next(main))
|
||||
return header$
|
||||
.pipe(
|
||||
map(state => ({ ref: el, ...state }))
|
||||
)
|
||||
/* Link to main area */
|
||||
main$.subscribe(push$)
|
||||
|
||||
/* Create and return component */
|
||||
return header$
|
||||
.pipe(
|
||||
takeUntil(push$.pipe(takeLast(1))),
|
||||
map(state => ({ ref: el, ...state }))
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -21,21 +21,16 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
NEVER,
|
||||
EMPTY,
|
||||
Observable,
|
||||
Subject,
|
||||
animationFrameScheduler,
|
||||
defer,
|
||||
distinctUntilKeyChanged,
|
||||
finalize,
|
||||
map,
|
||||
observeOn,
|
||||
tap
|
||||
} from "rxjs"
|
||||
|
||||
import {
|
||||
resetHeaderTitleState,
|
||||
setHeaderTitleState
|
||||
} from "~/actions"
|
||||
import {
|
||||
Viewport,
|
||||
getElementSize,
|
||||
@@ -118,28 +113,26 @@ export function watchHeaderTitle(
|
||||
export function mountHeaderTitle(
|
||||
el: HTMLElement, options: MountOptions
|
||||
): Observable<Component<HeaderTitle>> {
|
||||
const internal$ = new Subject<HeaderTitle>()
|
||||
internal$
|
||||
.pipe(
|
||||
observeOn(animationFrameScheduler)
|
||||
)
|
||||
.subscribe(({ active }) => {
|
||||
if (active)
|
||||
setHeaderTitleState(el, "active")
|
||||
else
|
||||
resetHeaderTitleState(el)
|
||||
})
|
||||
return defer(() => {
|
||||
const push$ = new Subject<HeaderTitle>()
|
||||
push$.subscribe(({ active }) => {
|
||||
if (active)
|
||||
el.setAttribute("data-md-state", "active")
|
||||
else
|
||||
el.removeAttribute("data-md-state")
|
||||
})
|
||||
|
||||
/* Obtain headline, if any */
|
||||
const headline = getOptionalElement<HTMLHeadingElement>("article h1")
|
||||
if (typeof headline === "undefined")
|
||||
return NEVER
|
||||
/* Obtain headline, if any */
|
||||
const heading = getOptionalElement<HTMLHeadingElement>("article h1")
|
||||
if (typeof heading === "undefined")
|
||||
return EMPTY
|
||||
|
||||
/* Create and return component */
|
||||
return watchHeaderTitle(headline, options)
|
||||
.pipe(
|
||||
tap(state => internal$.next(state)),
|
||||
finalize(() => internal$.complete()),
|
||||
map(state => ({ ref: el, ...state }))
|
||||
)
|
||||
/* Create and return component */
|
||||
return watchHeaderTitle(heading, options)
|
||||
.pipe(
|
||||
tap(state => push$.next(state)),
|
||||
finalize(() => push$.complete()),
|
||||
map(state => ({ ref: el, ...state }))
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
import {
|
||||
Observable,
|
||||
Subject,
|
||||
defer,
|
||||
finalize,
|
||||
fromEvent,
|
||||
map,
|
||||
@@ -74,9 +75,9 @@ export function watchPalette(
|
||||
inputs: HTMLInputElement[]
|
||||
): Observable<Palette> {
|
||||
const current = __md_get<Palette>("__palette") || {
|
||||
index: inputs.findIndex(input => (
|
||||
matchMedia(input.getAttribute("data-md-color-media")!).matches
|
||||
))
|
||||
index: inputs.findIndex(input => matchMedia(
|
||||
input.getAttribute("data-md-color-media")!
|
||||
).matches)
|
||||
}
|
||||
|
||||
/* Emit changes in color palette */
|
||||
@@ -99,11 +100,6 @@ export function watchPalette(
|
||||
shareReplay(1)
|
||||
)
|
||||
|
||||
/* Persist preference in local storage */
|
||||
palette$.subscribe(palette => {
|
||||
__md_set("__palette", palette)
|
||||
})
|
||||
|
||||
/* Return palette */
|
||||
return palette$
|
||||
}
|
||||
@@ -118,28 +114,33 @@ export function watchPalette(
|
||||
export function mountPalette(
|
||||
el: HTMLElement
|
||||
): Observable<Component<Palette>> {
|
||||
const internal$ = new Subject<Palette>()
|
||||
return defer(() => {
|
||||
const push$ = new Subject<Palette>()
|
||||
push$.subscribe(palette => {
|
||||
|
||||
/* Set color palette */
|
||||
internal$.subscribe(palette => {
|
||||
for (const [key, value] of Object.entries(palette.color))
|
||||
if (typeof value === "string")
|
||||
document.body.setAttribute(`data-md-color-${key}`, value)
|
||||
/* Set color palette */
|
||||
for (const [key, value] of Object.entries(palette.color))
|
||||
if (typeof value === "string")
|
||||
document.body.setAttribute(`data-md-color-${key}`, value)
|
||||
|
||||
/* Toggle visibility */
|
||||
for (let index = 0; index < inputs.length; index++) {
|
||||
const label = inputs[index].nextElementSibling
|
||||
if (label instanceof HTMLElement)
|
||||
label.hidden = palette.index !== index
|
||||
}
|
||||
/* Toggle visibility */
|
||||
for (let index = 0; index < inputs.length; index++) {
|
||||
const label = inputs[index].nextElementSibling
|
||||
if (label instanceof HTMLElement)
|
||||
label.hidden = palette.index !== index
|
||||
}
|
||||
|
||||
/* Persist preference in local storage */
|
||||
__md_set("__palette", palette)
|
||||
})
|
||||
|
||||
/* Create and return component */
|
||||
const inputs = getElements<HTMLInputElement>("input", el)
|
||||
return watchPalette(inputs)
|
||||
.pipe(
|
||||
tap(state => push$.next(state)),
|
||||
finalize(() => push$.complete()),
|
||||
map(state => ({ ref: el, ...state }))
|
||||
)
|
||||
})
|
||||
|
||||
/* Create and return component */
|
||||
const inputs = getElements<HTMLInputElement>("input", el)
|
||||
return watchPalette(inputs)
|
||||
.pipe(
|
||||
tap(state => internal$.next(state)),
|
||||
finalize(() => internal$.complete()),
|
||||
map(state => ({ ref: el, ...state }))
|
||||
)
|
||||
}
|
||||
|
||||
@@ -36,8 +36,6 @@ import {
|
||||
Keyboard,
|
||||
getActiveElement,
|
||||
getElements,
|
||||
setElementFocus,
|
||||
setElementSelection,
|
||||
setToggle
|
||||
} from "~/browser"
|
||||
import {
|
||||
@@ -166,14 +164,14 @@ export function mountSearch(
|
||||
case "Escape":
|
||||
case "Tab":
|
||||
setToggle("search", false)
|
||||
setElementFocus(query, false)
|
||||
query.blur()
|
||||
break
|
||||
|
||||
/* Vertical arrows: select previous or next search result */
|
||||
case "ArrowUp":
|
||||
case "ArrowDown":
|
||||
if (typeof active === "undefined") {
|
||||
setElementFocus(query)
|
||||
query.focus()
|
||||
} else {
|
||||
const els = [query, ...getElements(
|
||||
":not(details) > [href], summary, details[open] [href]",
|
||||
@@ -184,7 +182,7 @@ export function mountSearch(
|
||||
key.type === "ArrowUp" ? -1 : +1
|
||||
)
|
||||
) % els.length)
|
||||
setElementFocus(els[i])
|
||||
els[i].focus()
|
||||
}
|
||||
|
||||
/* Prevent scrolling of page */
|
||||
@@ -194,7 +192,7 @@ export function mountSearch(
|
||||
/* All other keys: hand to search query */
|
||||
default:
|
||||
if (query !== getActiveElement())
|
||||
setElementFocus(query)
|
||||
query.focus()
|
||||
}
|
||||
})
|
||||
|
||||
@@ -210,8 +208,10 @@ export function mountSearch(
|
||||
case "f":
|
||||
case "s":
|
||||
case "/":
|
||||
setElementFocus(query)
|
||||
setElementSelection(query)
|
||||
query.focus()
|
||||
query.select()
|
||||
|
||||
/* Prevent scrolling of page */
|
||||
key.claim()
|
||||
break
|
||||
}
|
||||
|
||||
@@ -40,13 +40,9 @@ import {
|
||||
tap
|
||||
} from "rxjs"
|
||||
|
||||
import {
|
||||
resetSearchQueryPlaceholder,
|
||||
setSearchQueryPlaceholder
|
||||
} from "~/actions"
|
||||
import { translation } from "~/_"
|
||||
import {
|
||||
getLocation,
|
||||
setElementFocus,
|
||||
setToggle,
|
||||
watchElementFocus
|
||||
} from "~/browser"
|
||||
@@ -143,10 +139,10 @@ export function watchSearchQuery(
|
||||
export function mountSearchQuery(
|
||||
el: HTMLInputElement, { tx$, rx$ }: SearchWorker
|
||||
): Observable<Component<SearchQuery, HTMLInputElement>> {
|
||||
const internal$ = new Subject<SearchQuery>()
|
||||
const push$ = new Subject<SearchQuery>()
|
||||
|
||||
/* Handle value changes */
|
||||
internal$
|
||||
push$
|
||||
.pipe(
|
||||
distinctUntilKeyChanged("value"),
|
||||
map(({ value }): SearchQueryMessage => ({
|
||||
@@ -157,31 +153,31 @@ export function mountSearchQuery(
|
||||
.subscribe(tx$.next.bind(tx$))
|
||||
|
||||
/* Handle focus changes */
|
||||
internal$
|
||||
push$
|
||||
.pipe(
|
||||
distinctUntilKeyChanged("focus")
|
||||
)
|
||||
.subscribe(({ focus }) => {
|
||||
if (focus) {
|
||||
setToggle("search", focus)
|
||||
setSearchQueryPlaceholder(el, "")
|
||||
el.placeholder = ""
|
||||
} else {
|
||||
resetSearchQueryPlaceholder(el)
|
||||
el.placeholder = translation("search.placeholder")
|
||||
}
|
||||
})
|
||||
|
||||
/* Handle reset */
|
||||
fromEvent(el.form!, "reset")
|
||||
.pipe(
|
||||
takeUntil(internal$.pipe(takeLast(1)))
|
||||
takeUntil(push$.pipe(takeLast(1)))
|
||||
)
|
||||
.subscribe(() => setElementFocus(el))
|
||||
.subscribe(() => el.focus())
|
||||
|
||||
/* Create and return component */
|
||||
return watchSearchQuery(el, { tx$, rx$ })
|
||||
.pipe(
|
||||
tap(state => internal$.next(state)),
|
||||
finalize(() => internal$.complete()),
|
||||
tap(state => push$.next(state)),
|
||||
finalize(() => push$.complete()),
|
||||
map(state => ({ ref: el, ...state }))
|
||||
)
|
||||
}
|
||||
|
||||
@@ -39,12 +39,7 @@ import {
|
||||
zipWith
|
||||
} from "rxjs"
|
||||
|
||||
import {
|
||||
addToSearchResultList,
|
||||
resetSearchResultList,
|
||||
resetSearchResultMeta,
|
||||
setSearchResultMeta
|
||||
} from "~/actions"
|
||||
import { translation } from "~/_"
|
||||
import {
|
||||
getElement,
|
||||
watchElementBoundary
|
||||
@@ -56,6 +51,7 @@ import {
|
||||
isSearchResultMessage
|
||||
} from "~/integrations"
|
||||
import { renderSearchResultItem } from "~/templates"
|
||||
import { round } from "~/utilities"
|
||||
|
||||
import { Component } from "../../_"
|
||||
import { SearchQuery } from "../query"
|
||||
@@ -90,7 +86,7 @@ interface MountOptions {
|
||||
export function mountSearchResult(
|
||||
el: HTMLElement, { rx$ }: SearchWorker, { query$ }: MountOptions
|
||||
): Observable<Component<SearchResult>> {
|
||||
const internal$ = new Subject<SearchResult>()
|
||||
const push$ = new Subject<SearchResult>()
|
||||
const boundary$ = watchElementBoundary(el.parentElement!)
|
||||
.pipe(
|
||||
filter(Boolean)
|
||||
@@ -108,24 +104,43 @@ export function mountSearchResult(
|
||||
)
|
||||
|
||||
/* Update search result metadata */
|
||||
internal$
|
||||
push$
|
||||
.pipe(
|
||||
observeOn(animationFrameScheduler),
|
||||
withLatestFrom(query$),
|
||||
skipUntil(ready$)
|
||||
)
|
||||
.subscribe(([{ items }, { value }]) => {
|
||||
if (value)
|
||||
setSearchResultMeta(meta, items.length)
|
||||
else
|
||||
resetSearchResultMeta(meta)
|
||||
if (value) {
|
||||
switch (items.length) {
|
||||
|
||||
/* No results */
|
||||
case 0:
|
||||
meta.textContent = translation("search.result.none")
|
||||
break
|
||||
|
||||
/* One result */
|
||||
case 1:
|
||||
meta.textContent = translation("search.result.one")
|
||||
break
|
||||
|
||||
/* Multiple result */
|
||||
default:
|
||||
meta.textContent = translation(
|
||||
"search.result.other",
|
||||
round(items.length)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
meta.textContent = translation("search.result.placeholder")
|
||||
}
|
||||
})
|
||||
|
||||
/* Update search result list */
|
||||
internal$
|
||||
push$
|
||||
.pipe(
|
||||
observeOn(animationFrameScheduler),
|
||||
tap(() => resetSearchResultList(list)),
|
||||
tap(() => list.innerHTML = ""),
|
||||
switchMap(({ items }) => merge(
|
||||
of(...items.slice(0, 10)),
|
||||
of(...items.slice(10))
|
||||
@@ -136,9 +151,9 @@ export function mountSearchResult(
|
||||
)
|
||||
))
|
||||
)
|
||||
.subscribe(result => {
|
||||
addToSearchResultList(list, renderSearchResultItem(result))
|
||||
})
|
||||
.subscribe(result => list.appendChild(
|
||||
renderSearchResultItem(result)
|
||||
))
|
||||
|
||||
/* Filter search result message */
|
||||
const result$ = rx$
|
||||
@@ -150,8 +165,8 @@ export function mountSearchResult(
|
||||
/* Create and return component */
|
||||
return result$
|
||||
.pipe(
|
||||
tap(state => internal$.next(state)),
|
||||
finalize(() => internal$.complete()),
|
||||
tap(state => push$.next(state)),
|
||||
finalize(() => push$.complete()),
|
||||
map(state => ({ ref: el, ...state }))
|
||||
)
|
||||
}
|
||||
|
||||
@@ -101,8 +101,8 @@ export function watchSearchShare(
|
||||
export function mountSearchShare(
|
||||
el: HTMLAnchorElement, options: MountOptions
|
||||
): Observable<Component<SearchShare>> {
|
||||
const internal$ = new Subject<SearchShare>()
|
||||
internal$.subscribe(({ url }) => {
|
||||
const push$ = new Subject<SearchShare>()
|
||||
push$.subscribe(({ url }) => {
|
||||
el.setAttribute("data-clipboard-text", el.href)
|
||||
el.href = `${url}`
|
||||
})
|
||||
@@ -114,8 +114,8 @@ export function mountSearchShare(
|
||||
/* Create and return component */
|
||||
return watchSearchShare(el, options)
|
||||
.pipe(
|
||||
tap(state => internal$.next(state)),
|
||||
finalize(() => internal$.complete()),
|
||||
tap(state => push$.next(state)),
|
||||
finalize(() => push$.complete()),
|
||||
map(state => ({ ref: el, ...state }))
|
||||
)
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ interface MountOptions {
|
||||
export function mountSearchSuggest(
|
||||
el: HTMLElement, { rx$ }: SearchWorker, { keyboard$ }: MountOptions
|
||||
): Observable<Component<SearchSuggest>> {
|
||||
const internal$ = new Subject<SearchResult>()
|
||||
const push$ = new Subject<SearchResult>()
|
||||
|
||||
/* Retrieve query component and track all changes */
|
||||
const query = getComponentElement("search-query")
|
||||
@@ -98,7 +98,7 @@ export function mountSearchSuggest(
|
||||
)
|
||||
|
||||
/* Update search suggestions */
|
||||
internal$
|
||||
push$
|
||||
.pipe(
|
||||
combineLatestWith(query$),
|
||||
map(([{ suggestions }, value]) => {
|
||||
@@ -147,8 +147,8 @@ export function mountSearchSuggest(
|
||||
/* Create and return component */
|
||||
return result$
|
||||
.pipe(
|
||||
tap(state => internal$.next(state)),
|
||||
finalize(() => internal$.complete()),
|
||||
tap(state => push$.next(state)),
|
||||
finalize(() => push$.complete()),
|
||||
map(() => ({ ref: el }))
|
||||
)
|
||||
}
|
||||
|
||||
@@ -24,22 +24,21 @@ import {
|
||||
Observable,
|
||||
Subject,
|
||||
animationFrameScheduler,
|
||||
auditTime,
|
||||
combineLatest,
|
||||
defer,
|
||||
distinctUntilChanged,
|
||||
finalize,
|
||||
map,
|
||||
observeOn,
|
||||
tap,
|
||||
withLatestFrom
|
||||
} from "rxjs"
|
||||
|
||||
import {
|
||||
resetSidebarHeight,
|
||||
resetSidebarOffset,
|
||||
setSidebarHeight,
|
||||
setSidebarOffset
|
||||
} from "~/actions"
|
||||
import { Viewport } from "~/browser"
|
||||
Viewport,
|
||||
getElement,
|
||||
getElementOffset
|
||||
} from "~/browser"
|
||||
|
||||
import { Component } from "../_"
|
||||
import { Header } from "../header"
|
||||
@@ -125,6 +124,19 @@ export function watchSidebar(
|
||||
/**
|
||||
* Mount sidebar
|
||||
*
|
||||
* This function doesn't set the height of the actual sidebar, but of its first
|
||||
* child – the `.md-sidebar__scrollwrap` element in order to mitigiate jittery
|
||||
* sidebars when the footer is scrolled into view. At some point we switched
|
||||
* from `absolute` / `fixed` positioning to `sticky` positioning, significantly
|
||||
* reducing jitter in some browsers (respectively Firefox and Safari) when
|
||||
* scrolling from the top. However, top-aligned sticky positioning means that
|
||||
* the sidebar snaps to the bottom when the end of the container is reached.
|
||||
* This is what leads to the mentioned jitter, as the sidebar's height may be
|
||||
* updated too slowly.
|
||||
*
|
||||
* This behaviour can be mitigiated by setting the height of the sidebar to `0`
|
||||
* while preserving the padding, and the height on its first element.
|
||||
*
|
||||
* @param el - Sidebar element
|
||||
* @param options - Options
|
||||
*
|
||||
@@ -133,32 +145,36 @@ export function watchSidebar(
|
||||
export function mountSidebar(
|
||||
el: HTMLElement, { header$, ...options }: MountOptions
|
||||
): Observable<Component<Sidebar>> {
|
||||
const internal$ = new Subject<Sidebar>()
|
||||
internal$
|
||||
.pipe(
|
||||
observeOn(animationFrameScheduler),
|
||||
withLatestFrom(header$)
|
||||
)
|
||||
.subscribe({
|
||||
const inner = getElement(".md-sidebar__scrollwrap", el)
|
||||
const { y } = getElementOffset(inner)
|
||||
return defer(() => {
|
||||
const push$ = new Subject<Sidebar>()
|
||||
push$
|
||||
.pipe(
|
||||
auditTime(0, animationFrameScheduler),
|
||||
withLatestFrom(header$)
|
||||
)
|
||||
.subscribe({
|
||||
|
||||
/* Update height and offset */
|
||||
next([{ height }, { height: offset }]) {
|
||||
setSidebarHeight(el, height)
|
||||
setSidebarOffset(el, offset)
|
||||
},
|
||||
/* Handle emission */
|
||||
next([{ height }, { height: offset }]) {
|
||||
inner.style.height = `${height - 2 * y}px`
|
||||
el.style.top = `${offset}px`
|
||||
},
|
||||
|
||||
/* Reset on complete */
|
||||
complete() {
|
||||
resetSidebarOffset(el)
|
||||
resetSidebarHeight(el)
|
||||
}
|
||||
})
|
||||
/* Handle complete */
|
||||
complete() {
|
||||
inner.style.height = ""
|
||||
el.style.top = ""
|
||||
}
|
||||
})
|
||||
|
||||
/* Create and return component */
|
||||
return watchSidebar(el, options)
|
||||
.pipe(
|
||||
tap(state => internal$.next(state)),
|
||||
finalize(() => internal$.complete()),
|
||||
map(state => ({ ref: el, ...state }))
|
||||
)
|
||||
/* Create and return component */
|
||||
return watchSidebar(el, options)
|
||||
.pipe(
|
||||
tap(state => push$.next(state)),
|
||||
finalize(() => push$.complete()),
|
||||
map(state => ({ ref: el, ...state }))
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
NEVER,
|
||||
EMPTY,
|
||||
Observable,
|
||||
Subject,
|
||||
catchError,
|
||||
@@ -34,10 +34,7 @@ import {
|
||||
tap
|
||||
} from "rxjs"
|
||||
|
||||
import {
|
||||
setSourceFacts,
|
||||
setSourceState
|
||||
} from "~/actions"
|
||||
import { getElement } from "~/browser"
|
||||
import { renderSourceFacts } from "~/templates"
|
||||
|
||||
import { Component } from "../../_"
|
||||
@@ -94,7 +91,7 @@ export function watchSource(
|
||||
)
|
||||
})
|
||||
.pipe(
|
||||
catchError(() => NEVER),
|
||||
catchError(() => EMPTY),
|
||||
filter(facts => Object.keys(facts).length > 0),
|
||||
map(facts => ({ facts })),
|
||||
shareReplay(1)
|
||||
@@ -111,17 +108,20 @@ export function watchSource(
|
||||
export function mountSource(
|
||||
el: HTMLAnchorElement
|
||||
): Observable<Component<Source>> {
|
||||
const internal$ = new Subject<Source>()
|
||||
internal$.subscribe(({ facts }) => {
|
||||
setSourceFacts(el, renderSourceFacts(facts))
|
||||
setSourceState(el, "done")
|
||||
})
|
||||
const inner = getElement(":scope > :last-child", el)
|
||||
return defer(() => {
|
||||
const push$ = new Subject<Source>()
|
||||
push$.subscribe(({ facts }) => {
|
||||
inner.appendChild(renderSourceFacts(facts))
|
||||
inner.setAttribute("data-md-state", "done")
|
||||
})
|
||||
|
||||
/* Create and return component */
|
||||
return watchSource(el)
|
||||
.pipe(
|
||||
tap(state => internal$.next(state)),
|
||||
finalize(() => internal$.complete()),
|
||||
map(state => ({ ref: el, ...state }))
|
||||
)
|
||||
/* Create and return component */
|
||||
return watchSource(el)
|
||||
.pipe(
|
||||
tap(state => push$.next(state)),
|
||||
finalize(() => push$.complete()),
|
||||
map(state => ({ ref: el, ...state }))
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import { NEVER, Observable } from "rxjs"
|
||||
import { EMPTY, Observable } from "rxjs"
|
||||
|
||||
import { fetchSourceFactsFromGitHub } from "../github"
|
||||
import { fetchSourceFactsFromGitLab } from "../gitlab"
|
||||
@@ -83,6 +83,6 @@ export function fetchSourceFacts(
|
||||
|
||||
/* Everything else */
|
||||
default:
|
||||
return NEVER
|
||||
return EMPTY
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ interface Release {
|
||||
/**
|
||||
* Fetch GitHub repository facts
|
||||
*
|
||||
* @param user - GitHub user
|
||||
* @param user - GitHub user or organization
|
||||
* @param repo - GitHub repository
|
||||
*
|
||||
* @returns Repository facts observable
|
||||
|
||||
@@ -23,21 +23,16 @@
|
||||
import {
|
||||
Observable,
|
||||
Subject,
|
||||
animationFrameScheduler,
|
||||
defer,
|
||||
distinctUntilKeyChanged,
|
||||
finalize,
|
||||
map,
|
||||
observeOn,
|
||||
of,
|
||||
switchMap,
|
||||
tap
|
||||
} from "rxjs"
|
||||
|
||||
import { feature } from "~/_"
|
||||
import {
|
||||
resetTabsState,
|
||||
setTabsState
|
||||
} from "~/actions"
|
||||
import {
|
||||
Viewport,
|
||||
watchElementSize,
|
||||
@@ -119,36 +114,34 @@ export function watchTabs(
|
||||
export function mountTabs(
|
||||
el: HTMLElement, options: MountOptions
|
||||
): Observable<Component<Tabs>> {
|
||||
const internal$ = new Subject<Tabs>()
|
||||
internal$
|
||||
.pipe(
|
||||
observeOn(animationFrameScheduler)
|
||||
)
|
||||
.subscribe({
|
||||
|
||||
/* Update state */
|
||||
next({ hidden }) {
|
||||
if (hidden)
|
||||
setTabsState(el, "hidden")
|
||||
else
|
||||
resetTabsState(el)
|
||||
},
|
||||
|
||||
/* Reset on complete */
|
||||
complete() {
|
||||
resetTabsState(el)
|
||||
}
|
||||
})
|
||||
|
||||
/* Create and return component */
|
||||
return (
|
||||
feature("navigation.tabs.sticky")
|
||||
? of({ hidden: false })
|
||||
: watchTabs(el, options)
|
||||
)
|
||||
.pipe(
|
||||
tap(state => internal$.next(state)),
|
||||
finalize(() => internal$.complete()),
|
||||
map(state => ({ ref: el, ...state }))
|
||||
return defer(() => {
|
||||
const push$ = new Subject<Tabs>()
|
||||
push$.subscribe({
|
||||
|
||||
/* Handle emission */
|
||||
next({ hidden }) {
|
||||
if (hidden)
|
||||
el.setAttribute("data-md-state", "hidden")
|
||||
else
|
||||
el.removeAttribute("data-md-state")
|
||||
},
|
||||
|
||||
/* Handle complete */
|
||||
complete() {
|
||||
el.removeAttribute("data-md-state")
|
||||
}
|
||||
})
|
||||
|
||||
/* Create and return component */
|
||||
return (
|
||||
feature("navigation.tabs.sticky")
|
||||
? of({ hidden: false })
|
||||
: watchTabs(el, options)
|
||||
)
|
||||
.pipe(
|
||||
tap(state => push$.next(state)),
|
||||
finalize(() => push$.complete()),
|
||||
map(state => ({ ref: el, ...state }))
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
import {
|
||||
Observable,
|
||||
Subject,
|
||||
animationFrameScheduler,
|
||||
bufferCount,
|
||||
combineLatest,
|
||||
defer,
|
||||
@@ -31,7 +30,6 @@ import {
|
||||
distinctUntilKeyChanged,
|
||||
finalize,
|
||||
map,
|
||||
observeOn,
|
||||
of,
|
||||
scan,
|
||||
startWith,
|
||||
@@ -40,12 +38,6 @@ import {
|
||||
} from "rxjs"
|
||||
|
||||
import { feature } from "~/_"
|
||||
import {
|
||||
resetAnchorActive,
|
||||
resetAnchorState,
|
||||
setAnchorActive,
|
||||
setAnchorState
|
||||
} from "~/actions"
|
||||
import {
|
||||
Viewport,
|
||||
getElements,
|
||||
@@ -253,52 +245,55 @@ export function watchTableOfContents(
|
||||
export function mountTableOfContents(
|
||||
el: HTMLElement, options: MountOptions
|
||||
): Observable<Component<TableOfContents>> {
|
||||
const internal$ = new Subject<TableOfContents>()
|
||||
internal$
|
||||
.pipe(
|
||||
observeOn(animationFrameScheduler),
|
||||
)
|
||||
.subscribe(({ prev, next }) => {
|
||||
return defer(() => {
|
||||
const push$ = new Subject<TableOfContents>()
|
||||
push$.subscribe(({ prev, next }) => {
|
||||
|
||||
/* Look forward */
|
||||
for (const [anchor] of next) {
|
||||
resetAnchorActive(anchor)
|
||||
resetAnchorState(anchor)
|
||||
}
|
||||
/* Look forward */
|
||||
for (const [anchor] of next) {
|
||||
anchor.removeAttribute("data-md-state")
|
||||
anchor.classList.remove(
|
||||
"md-nav__link--active"
|
||||
)
|
||||
}
|
||||
|
||||
/* Look backward */
|
||||
for (const [index, [anchor]] of prev.entries()) {
|
||||
setAnchorActive(anchor, index === prev.length - 1)
|
||||
setAnchorState(anchor, "blur")
|
||||
}
|
||||
/* Look backward */
|
||||
for (const [index, [anchor]] of prev.entries()) {
|
||||
anchor.setAttribute("data-md-state", "blur")
|
||||
anchor.classList.toggle(
|
||||
"md-nav__link--active",
|
||||
index === prev.length - 1
|
||||
)
|
||||
}
|
||||
|
||||
/* Set up anchor tracking, if enabled */
|
||||
if (feature("navigation.tracking")) {
|
||||
const url = getLocation()
|
||||
/* Set up anchor tracking, if enabled */
|
||||
if (feature("navigation.tracking")) {
|
||||
const url = getLocation()
|
||||
|
||||
/* Set hash fragment to active anchor */
|
||||
const anchor = prev[prev.length - 1]
|
||||
if (anchor && anchor.length) {
|
||||
const [active] = anchor
|
||||
const { hash } = new URL(active.href)
|
||||
if (url.hash !== hash) {
|
||||
url.hash = hash
|
||||
history.replaceState({}, "", `${url}`)
|
||||
}
|
||||
|
||||
/* Reset anchor when at the top */
|
||||
} else {
|
||||
url.hash = ""
|
||||
/* Set hash fragment to active anchor */
|
||||
const anchor = prev[prev.length - 1]
|
||||
if (anchor && anchor.length) {
|
||||
const [active] = anchor
|
||||
const { hash } = new URL(active.href)
|
||||
if (url.hash !== hash) {
|
||||
url.hash = hash
|
||||
history.replaceState({}, "", `${url}`)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
/* Create and return component */
|
||||
return watchTableOfContents(el, options)
|
||||
.pipe(
|
||||
tap(state => internal$.next(state)),
|
||||
finalize(() => internal$.complete()),
|
||||
map(state => ({ ref: el, ...state }))
|
||||
)
|
||||
/* Reset anchor when at the top */
|
||||
} else {
|
||||
url.hash = ""
|
||||
history.replaceState({}, "", `${url}`)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
/* Create and return component */
|
||||
return watchTableOfContents(el, options)
|
||||
.pipe(
|
||||
tap(state => push$.next(state)),
|
||||
finalize(() => push$.complete()),
|
||||
map(state => ({ ref: el, ...state }))
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -23,30 +23,17 @@
|
||||
import {
|
||||
Observable,
|
||||
Subject,
|
||||
animationFrameScheduler,
|
||||
bufferCount,
|
||||
combineLatest,
|
||||
distinctUntilChanged,
|
||||
distinctUntilKeyChanged,
|
||||
finalize,
|
||||
map,
|
||||
observeOn,
|
||||
tap,
|
||||
withLatestFrom
|
||||
} from "rxjs"
|
||||
|
||||
import {
|
||||
resetBackToTopOffset,
|
||||
resetBackToTopState,
|
||||
resetFocusable,
|
||||
setBackToTopOffset,
|
||||
setBackToTopState,
|
||||
setFocusable
|
||||
} from "~/actions"
|
||||
import {
|
||||
Viewport,
|
||||
setElementFocus
|
||||
} from "~/browser"
|
||||
import { Viewport } from "~/browser"
|
||||
|
||||
import { Component } from "../_"
|
||||
import { Header } from "../header"
|
||||
@@ -141,10 +128,9 @@ export function watchBackToTop(
|
||||
export function mountBackToTop(
|
||||
el: HTMLElement, { viewport$, header$, main$ }: MountOptions
|
||||
): Observable<Component<BackToTop>> {
|
||||
const internal$ = new Subject<BackToTop>()
|
||||
internal$
|
||||
const push$ = new Subject<BackToTop>()
|
||||
push$
|
||||
.pipe(
|
||||
observeOn(animationFrameScheduler),
|
||||
withLatestFrom(header$
|
||||
.pipe(
|
||||
distinctUntilKeyChanged("height")
|
||||
@@ -153,32 +139,33 @@ export function mountBackToTop(
|
||||
)
|
||||
.subscribe({
|
||||
|
||||
/* Update state */
|
||||
/* Handle emission */
|
||||
next([{ hidden }, { height }]) {
|
||||
setBackToTopOffset(el, height + 16)
|
||||
el.style.top = `${height + 16}px`
|
||||
if (hidden) {
|
||||
setBackToTopState(el, "hidden")
|
||||
setElementFocus(el, false)
|
||||
setFocusable(el, -1)
|
||||
el.setAttribute("data-md-state", "hidden")
|
||||
el.setAttribute("tabindex", "-1")
|
||||
el.blur()
|
||||
} else {
|
||||
resetBackToTopState(el)
|
||||
resetFocusable(el)
|
||||
el.style.top = ""
|
||||
el.removeAttribute("data-md-state")
|
||||
el.removeAttribute("tabindex")
|
||||
}
|
||||
},
|
||||
|
||||
/* Reset on complete */
|
||||
/* Handle complete */
|
||||
complete() {
|
||||
resetBackToTopOffset(el)
|
||||
resetBackToTopState(el)
|
||||
resetFocusable(el)
|
||||
el.style.top = ""
|
||||
el.removeAttribute("data-md-state")
|
||||
el.removeAttribute("tabindex")
|
||||
}
|
||||
})
|
||||
|
||||
/* Create and return component */
|
||||
return watchBackToTop(el, { viewport$, header$, main$ })
|
||||
.pipe(
|
||||
tap(state => internal$.next(state)),
|
||||
finalize(() => internal$.complete()),
|
||||
tap(state => push$.next(state)),
|
||||
finalize(() => push$.complete()),
|
||||
map(state => ({ ref: el, ...state }))
|
||||
)
|
||||
}
|
||||
|
||||
@@ -52,8 +52,7 @@ import {
|
||||
request,
|
||||
requestXML,
|
||||
setLocation,
|
||||
setLocationHash,
|
||||
setViewportOffset
|
||||
setLocationHash
|
||||
} from "~/browser"
|
||||
import { getComponentElement } from "~/components"
|
||||
import { h } from "~/utilities"
|
||||
@@ -329,11 +328,11 @@ export function setupInstantLoading(
|
||||
.pipe(
|
||||
sample(document$),
|
||||
)
|
||||
.subscribe(({ url, offset }) => {
|
||||
.subscribe(({ url, offset = { y: 0 } }) => {
|
||||
if (url.hash && !offset) {
|
||||
setLocationHash(url.hash)
|
||||
} else {
|
||||
setViewportOffset(offset || { y: 0 })
|
||||
window.scrollTo(0, offset.y)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -355,7 +354,7 @@ export function setupInstantLoading(
|
||||
filter(([a, b]) => a.url.pathname === b.url.pathname),
|
||||
map(([, state]) => state)
|
||||
)
|
||||
.subscribe(({ offset }) => {
|
||||
setViewportOffset(offset || { y: 0 })
|
||||
.subscribe(({ offset = { y: 0 } }) => {
|
||||
window.scrollTo(0, offset.y)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -32,10 +32,6 @@ import {
|
||||
withLatestFrom
|
||||
} from "rxjs"
|
||||
|
||||
import {
|
||||
resetScrollLock,
|
||||
setScrollLock
|
||||
} from "~/actions"
|
||||
import {
|
||||
Viewport,
|
||||
watchToggle
|
||||
@@ -82,9 +78,15 @@ export function patchScrolllock(
|
||||
withLatestFrom(viewport$)
|
||||
)
|
||||
.subscribe(([active, { offset: { y }}]) => {
|
||||
if (active)
|
||||
setScrollLock(document.body, y)
|
||||
else
|
||||
resetScrollLock(document.body)
|
||||
if (active) {
|
||||
document.body.setAttribute("data-md-state", "lock")
|
||||
document.body.style.top = `-${y}px`
|
||||
} else {
|
||||
const value = -1 * parseInt(document.body.style.top, 10)
|
||||
document.body.removeAttribute("data-md-state")
|
||||
document.body.style.top = ""
|
||||
if (value)
|
||||
window.scrollTo(0, value)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -70,21 +70,3 @@ export function round(value: number): string {
|
||||
return value.toString()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple hash function
|
||||
*
|
||||
* @see https://bit.ly/2wsVjJ4 - Original source
|
||||
*
|
||||
* @param value - Value to be hashed
|
||||
*
|
||||
* @returns Hash as 32bit integer
|
||||
*/
|
||||
export function hash(value: string): number {
|
||||
let h = 0
|
||||
for (let i = 0, len = value.length; i < len; i++) {
|
||||
h = ((h << 5) - h) + value.charCodeAt(i)
|
||||
h |= 0 // Convert to 32bit integer
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user