1
0
mirror of https://github.com/squidfunk/mkdocs-material.git synced 2024-11-12 10:00:52 +01:00

Fixed Travis and added more components

This commit is contained in:
squidfunk 2019-11-27 19:12:49 +01:00
parent 9cae185391
commit c409fe3953
38 changed files with 937 additions and 244 deletions

View File

@ -44,7 +44,6 @@ install:
# Perform build and release
script:
- make -v
# - npm run lint
- npm run clean
- npm run build

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

@ -606,6 +606,7 @@ hr {
.md-header-nav__topic {
display: block;
position: absolute;
width: calc(100% - 1rem);
-webkit-transition: opacity 0.15s, -webkit-transform 0.4s cubic-bezier(0.1, 0.7, 0.1, 1);
transition: opacity 0.15s, -webkit-transform 0.4s cubic-bezier(0.1, 0.7, 0.1, 1);
transition: transform 0.4s cubic-bezier(0.1, 0.7, 0.1, 1), opacity 0.15s;
@ -614,8 +615,8 @@ hr {
white-space: nowrap;
overflow: hidden; }
.md-header-nav__topic + .md-header-nav__topic {
-webkit-transform: translateY(1.25rem);
transform: translateY(1.25rem);
-webkit-transform: translateX(1.25rem);
transform: translateX(1.25rem);
-webkit-transition: opacity 0.15s, -webkit-transform 0.4s cubic-bezier(1, 0.7, 0.1, 0.1);
transition: opacity 0.15s, -webkit-transform 0.4s cubic-bezier(1, 0.7, 0.1, 0.1);
transition: transform 0.4s cubic-bezier(1, 0.7, 0.1, 0.1), opacity 0.15s;
@ -624,8 +625,8 @@ hr {
z-index: -1;
pointer-events: none; }
[dir="rtl"] .md-header-nav__topic + .md-header-nav__topic {
-webkit-transform: translateY(-1.25rem);
transform: translateY(-1.25rem); }
-webkit-transform: translateX(-1.25rem);
transform: translateX(-1.25rem); }
.no-js .md-header-nav__topic {
position: initial; }
.no-js .md-header-nav__topic + .md-header-nav__topic {
@ -635,8 +636,8 @@ hr {
font-size: 0.9rem;
line-height: 2.4rem; }
.md-header-nav__title[data-md-state="active"] .md-header-nav__topic {
-webkit-transform: translateY(-1.25rem);
transform: translateY(-1.25rem);
-webkit-transform: translateX(-1.25rem);
transform: translateX(-1.25rem);
-webkit-transition: opacity 0.15s, -webkit-transform 0.4s cubic-bezier(1, 0.7, 0.1, 0.1);
transition: opacity 0.15s, -webkit-transform 0.4s cubic-bezier(1, 0.7, 0.1, 0.1);
transition: transform 0.4s cubic-bezier(1, 0.7, 0.1, 0.1), opacity 0.15s;
@ -645,11 +646,11 @@ hr {
z-index: -1;
pointer-events: none; }
[dir="rtl"] .md-header-nav__title[data-md-state="active"] .md-header-nav__topic {
-webkit-transform: translateY(1.25rem);
transform: translateY(1.25rem); }
-webkit-transform: translateX(1.25rem);
transform: translateX(1.25rem); }
.md-header-nav__title[data-md-state="active"] .md-header-nav__topic + .md-header-nav__topic {
-webkit-transform: translateY(0);
transform: translateY(0);
-webkit-transform: translateX(0);
transform: translateX(0);
-webkit-transition: opacity 0.15s, -webkit-transform 0.4s cubic-bezier(0.1, 0.7, 0.1, 1);
transition: opacity 0.15s, -webkit-transform 0.4s cubic-bezier(0.1, 0.7, 0.1, 1);
transition: transform 0.4s cubic-bezier(0.1, 0.7, 0.1, 1), opacity 0.15s;

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

@ -31,7 +31,7 @@
<meta name="author" content="{{ config.site_author }}">
{% endif %}
<link rel="shortcut icon" href="{{ config.theme.favicon | url }}">
<meta name="generator" content="mkdocs-{{ mkdocs_version }}, mkdocs-material-4.4.2">
<meta name="generator" content="mkdocs-{{ mkdocs_version }}, $md-name$-$md-version$">
{% endblock %}
{% block htmltitle %}
{% if page and page.meta and page.meta.title %}
@ -192,7 +192,7 @@
{% if language == "ja" %}
<script src="{{ (path ~ 'tinyseg.js') | url }}"></script>
{% endif %}
{% if language in ("da","de","du","es","fi","fr","hu","it","ja","jp","nl","no","pt","ro","ru","sv","tr") %}
{% if language in ($md-lunr-languages$) %}
<script src="{{ (path ~ 'lunr.' ~ language ~ '.js') | url }}"></script>
{% endif %}
{% endif %}
@ -219,7 +219,7 @@
{%- endfor -%}
{{ translations | tojson }}
</script>
<script>app({version:"{{ mkdocs_version }}",url:{base:"{{ base_url }}"}})</script>
<script>app=initialize({base:"{{ base_url }}"})</script>
{% for path in config["extra_javascript"] %}
<script src="{{ path | url }}"></script>
{% endfor %}

View File

@ -20,17 +20,44 @@
* IN THE SOFTWARE.
*/
import { shareReplay, switchMap } from "rxjs/operators"
import { identity } from "ramda"
import {
EMPTY,
MonoTypeOperatorFunction,
NEVER,
Observable,
fromEvent,
merge,
of,
pipe
} from "rxjs"
import {
delay,
filter,
map,
shareReplay,
switchMap,
switchMapTo,
tap,
withLatestFrom
} from "rxjs/operators"
import { isConfig } from "./config"
import {
setupSidebar,
Component,
paintHidden,
paintSidebar,
switchComponent,
switchMapIfActive,
watchBottomOffset,
watchComponentMap,
watchHeader,
watchMain
watchMain,
watchSearchReset,
watchSidebar,
watchToggle,
watchTopOffset
} from "./theme"
import { paintHeaderShadow } from "./theme/component/header/shadow"
import {
watchDocument,
watchDocumentSwitch,
@ -40,9 +67,15 @@ import {
watchViewportOffset,
watchViewportSize
} from "./ui"
import {
getElement,
not,
switchMapIf
} from "./utilities"
// TBD
// TODO: put this somewhere else... (merge with config!) JSON schema!?
const names = [
"header", /* Header */
"title", /* Header title */
@ -56,7 +89,11 @@ const names = [
"tabs", /* Tabs */
"navigation", /* Navigation */
"toc" /* Table of contents */
] as const // TODO: put this somewhere else... (merge with config!) JSON schema!?
] as const
// modernizr for the poor
document.documentElement.classList.remove("no-js")
document.documentElement.classList.add("js")
/* ----------------------------------------------------------------------------
* Functions
@ -82,66 +119,212 @@ export function initialize(config: unknown) {
const tablet$ = watchMedia("(min-width: 960px)")
/* Create location observables */
const url$ = watchLocation()
const location$ = watchLocation()
const fragment$ = watchLocationFragment()
/* Create document observables */
const load$ = watchDocument()
const switch$ = watchDocumentSwitch({ url$ })
const switch$ = watchDocumentSwitch({ location$ })
/* ----------------------------------------------------------------------- */
/* Create component map observable */
const components$ = watchComponentMap(names, { load$, switch$ })
const component = (name: Component): Observable<HTMLElement> => {
return components$
.pipe(
switchComponent(name)
)
}
/* Create header observable */
const header$ = components$
const header$ = component("header")
.pipe(
switchComponent("header"),
switchMap(watchHeader)
)
/* Create main area observable */
const main$ = components$
const main$ = component("main")
.pipe(
switchComponent("main"),
switchMap(el => watchMain(el, { size$, offset$, header$ })),
shareReplay(1)
)
// ----------------------------------------------------------------------------
// WIP
load$
.pipe(
switchMap(({ body }) => fromEvent(body, "click")),
switchMap(ev => {
if (ev.target instanceof HTMLElement) {
const el = ev.target.closest("a") || undefined
if (el) {
if (!/^(https?:|#)/.test(el.getAttribute("href")!)) {
ev.preventDefault()
}
const href = el.href
history.pushState({}, "", href) // TODO: reference necessary!?
return of(href)
}
}
return EMPTY
}),
// try to reduce the jiggle upon instant page load. ideally, the location
// should directly be resolved and the respective document loaded, but
// we must scroll to the top at first and wait at least 250ms.
//
// Furthermore, this doesn't include the back/next buttons of the browser
// which must be delayed
tap(url => {
if (!/#/.test(url))
scrollTo({ top: 0 })
}), // only when loading something we havent loaded!
delay(250)
)
.subscribe(location$)
location$.subscribe(x => {
console.log("L", x)
})
switch$.subscribe(x => {
console.log("S", x)
})
/* ----------------------------------------------------------------------- */
/* Create sidebar with navigation */
screen$
/* Create header shadow toggle */
component("header")
.pipe(
switchMapIfActive(() => components$ // TODO: write an observable creation function...
switchMap(el => main$
.pipe(
switchComponent("navigation"),
switchMap(el => setupSidebar(el, { offset$, main$ }))
paintHeaderShadow(el)
)
)
)
.subscribe()
/* Create sidebar with table of contents (missing on 404 page) */
tablet$
/* Create sidebar with navigation */
component("navigation")
.pipe(
switchMapIfActive(() => components$
switchMapIf(screen$, el => watchSidebar(el, { offset$, main$ })
.pipe(
switchComponent("toc"),
switchMap(el => setupSidebar(el, { offset$, main$ }))
paintSidebar(el)
)
),
shareReplay(1)
)
.subscribe()
/* Create sidebar with table of contents */
component("toc")
.pipe(
switchMapIf(tablet$, el => watchSidebar(el, { offset$, main$ })
.pipe(
paintSidebar(el)
)
),
shareReplay(1)
)
.subscribe()
/* Create tabs visibility toggle */
component("tabs")
.pipe(
switchMapIf(screen$, el => watchTopOffset(el, { size$, offset$, header$ })
.pipe(
paintHidden(el, 8)
)
),
shareReplay(1)
)
.subscribe()
/* Create hero visibility toggle */
component("hero")
.pipe(
switchMap(el => watchTopOffset(el, { size$, offset$, header$ })
.pipe(
paintHidden(el, 20)
)
),
shareReplay(1)
)
.subscribe()
/* Create header title toggle */
component("main")
.pipe(
delay(1000), // initial delay
switchMap(el => typeof getElement("h1", el) !== "undefined"
? watchBottomOffset(getElement("h1", el)!, { size$, offset$, header$ })
.pipe(
map(({ y }) => y >= 0),
withLatestFrom(component("title")),
tap(([active, title]) => {
title.dataset.mdState = active ? "active" : ""
})
)
: NEVER
)
)
.subscribe(console.log)
.subscribe()
/* Return all observables */
// TODO: replace title as inner text
/* ----------------------------------------------------------------------- */
const drawer = getElement<HTMLInputElement>("[data-md-toggle=drawer]")!
const search = getElement<HTMLInputElement>("[data-md-toggle=search]")!
// watchToggle
// --> watchSearchQuery?
// watchSearch
// watchSearchReset
// toggles stay the same...
const a$ = watchToggle(search)
.pipe(
filter(identity),
delay(400)
)
// watchSearchReset()
const b$ = component("reset")
.pipe(
switchMap(watchSearchReset)
)
function focusQuery(): MonoTypeOperatorFunction<HTMLElement> {
return pipe(
tap(el => el.focus())
)
}
merge(a$, b$)
.pipe(
switchMapTo(component("query")),
focusQuery()
)
.subscribe()
/* Return observable factories */
return {
ui: {
document: { load$, switch$ },
location: { url$, fragment$ },
media: { screen$, tablet$ },
viewport: { offset$, size$ }
}
/* User interface */
watchDocument: () => load$,
watchDocumentSwitch: () => switch$,
watchLocation: () => location$,
watchLocationFragment: () => fragment$,
watchMediaScreen: () => screen$,
watchMediaTablet: () => tablet$,
watchViewportOffset: () => offset$,
watchViewportSize: () => size$
}
}

View File

@ -0,0 +1,48 @@
/*
* Copyright (c) 2016-2019 Martin Donath <martin.donath@squidfunk.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER 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 shadow
*
* @param el - Header element
* @param value - Whether the shadow is shown
*/
export function setHeaderShadow(
el: HTMLElement, value: boolean
): void {
el.setAttribute("data-md-state", value ? "shadow" : "")
}
/**
* Reset header shadow
*
* @param el - Header element
*/
export function resetHeaderShadow(
el: HTMLElement
): void {
el.removeAttribute("data-md-state")
}

View File

@ -0,0 +1,48 @@
/*
* Copyright (c) 2016-2019 Martin Donath <martin.donath@squidfunk.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER 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 hidden
*
* @param el - Hideable element
* @param value - Whether the element is hidden
*/
export function setHidden(
el: HTMLElement, value: boolean
): void {
el.setAttribute("data-md-state", value ? "hidden" : "")
}
/**
* Reset hidden
*
* @param el - Hideable element
*/
export function resetHidden(
el: HTMLElement
): void {
el.removeAttribute("data-md-state")
}

View File

@ -0,0 +1,25 @@
/*
* Copyright (c) 2016-2019 Martin Donath <martin.donath@squidfunk.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER 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 "./header"
export * from "./hidden"
export * from "./sidebar"

View File

@ -24,14 +24,14 @@ import { keys } from "ramda"
import { NEVER, Observable, OperatorFunction, merge, of, pipe } from "rxjs"
import { map, scan, shareReplay, switchMap } from "rxjs/operators"
import { getElement } from "../../utilities"
import { getElement } from "../../../utilities"
/* ----------------------------------------------------------------------------
* Types
* ------------------------------------------------------------------------- */
/**
* Components
* Component names
*/
export type Component =
| "header" /* Header */
@ -76,7 +76,7 @@ interface Options {
* This function returns an observable that will maintain bindings to the given
* components in-between document switches and update the document in-place.
*
* @param names - Components
* @param names - Component names
* @param options - Options
*
* @return Component map observable
@ -133,7 +133,7 @@ export function watchComponentMap(
*
* @template T - Element type
*
* @param name - Component
* @param name - Component name
*
* @return Operator function
*/

View File

@ -21,3 +21,4 @@
*/
export * from "./_"
export * from "./offset"

View File

@ -0,0 +1,111 @@
/*
* Copyright (c) 2016-2019 Martin Donath <martin.donath@squidfunk.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
import { Observable, combineLatest } from "rxjs"
import {
distinctUntilChanged,
map,
shareReplay,
switchMapTo
} from "rxjs/operators"
import { ViewportOffset, ViewportSize } from "../../../../ui"
import { Header } from "../_"
/* ----------------------------------------------------------------------------
* Function types
* ------------------------------------------------------------------------- */
/**
* Options
*/
interface Options {
size$: Observable<ViewportSize> /* Viewport size observable */
offset$: Observable<ViewportOffset> /* Viewport offset observable */
header$: Observable<Header> /* Header observable */
}
/* ----------------------------------------------------------------------------
* Functions
* ------------------------------------------------------------------------- */
/**
* Watch viewport offset relative to an element's top
*
* This function returns an observable that computes the relative offset to the
* top of the given element based on the current viewport offset.
*
* @param el - Element
* @param options - Options
*
* @return Viewport offset observable
*/
export function watchTopOffset(
el: HTMLElement, { size$, offset$, header$ }: Options
): Observable<ViewportOffset> {
/* Compute necessary adjustment for offset */
const adjust$ = size$
.pipe(
switchMapTo(header$),
map(({ height }) => el.offsetTop - height),
distinctUntilChanged()
)
/* Compute relative offset and return as hot observable */
return combineLatest([offset$, adjust$])
.pipe(
map(([{ x, y }, adjust]) => ({ x, y: y - adjust })),
shareReplay(1)
)
}
/**
* Watch viewport offset relative to an element's bottom
*
* This function returns an observable that computes the relative offset to the
* bottom of the given element based on the current viewport offset.
*
* @param el - Element
* @param options - Options
*
* @return Viewport offset observable
*/
export function watchBottomOffset(
el: HTMLElement, { size$, offset$, header$ }: Options
): Observable<ViewportOffset> {
/* Compute necessary adjustment for offset */
const adjust$ = size$
.pipe(
switchMapTo(header$),
map(({ height }) => el.offsetTop + el.offsetHeight - height),
distinctUntilChanged()
)
/* Compute relative offset and return as hot observable */
return combineLatest([offset$, adjust$])
.pipe(
map(([{ x, y }, adjust]) => ({ x, y: y - adjust })),
shareReplay(1)
)
}

View File

@ -20,43 +20,50 @@
* IN THE SOFTWARE.
*/
import { EMPTY, Observable, OperatorFunction, pipe } from "rxjs"
import { switchMap } from "rxjs/operators"
import {
MonoTypeOperatorFunction,
animationFrameScheduler,
pipe
} from "rxjs"
import {
distinctUntilKeyChanged,
finalize,
observeOn,
tap
} from "rxjs/operators"
import {
resetHeaderShadow,
setHeaderShadow
} from "../../../action"
import { Main } from "../../main"
/* ----------------------------------------------------------------------------
* Functions
* ------------------------------------------------------------------------- */
/**
* Switch to another observable if source observable emits `true`
* Paint header shadow from source observable
*
* @template T - Observable value type
*
* @param project - Project function
* @param el - Header element
*
* @return Operator function
*/
export function switchMapIfActive<T>(
project: (value: boolean) => Observable<T>
): OperatorFunction<boolean, T> {
export function paintHeaderShadow(
el: HTMLElement
): MonoTypeOperatorFunction<Main> {
return pipe(
switchMap(value => value ? project(value) : EMPTY)
)
}
distinctUntilKeyChanged("active"),
/**
* Switch to another observable if source observable emits `false`
*
* @template T - Observable value type
*
* @param project - Project function
*
* @return Operator function
*/
export function switchMapIfNotActive<T>(
project: (value: boolean) => Observable<T>
): OperatorFunction<boolean, T> {
return pipe(
switchMap(value => value ? EMPTY : project(value))
/* Defer repaint to next animation frame */
observeOn(animationFrameScheduler),
tap(({ active }) => {
setHeaderShadow(el, active)
}),
/* Reset on complete or error */
finalize(() => {
resetHeaderShadow(el)
})
)
}

View File

@ -0,0 +1,65 @@
/*
* Copyright (c) 2016-2019 Martin Donath <martin.donath@squidfunk.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER 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 { OperatorFunction, animationFrameScheduler, pipe } from "rxjs"
import {
distinctUntilChanged,
finalize,
map,
observeOn,
tap
} from "rxjs/operators"
import { ViewportOffset } from "../../../ui"
import { resetHidden, setHidden } from "../../action"
/* ----------------------------------------------------------------------------
* Functions
* ------------------------------------------------------------------------- */
/**
* Paint hideable from source observable
*
* @param el - Hideable element
* @param offset - Additional offset
*
* @return Operator function
*/
export function paintHidden(
el: HTMLElement, offset: number = 0
): OperatorFunction<ViewportOffset, boolean> {
return pipe(
map(({ y }) => y >= offset),
distinctUntilChanged(),
/* Defer repaint to next animation frame */
observeOn(animationFrameScheduler),
tap(value => {
setHidden(el, value)
}),
/* Reset on complete or error */
finalize(() => {
resetHidden(el)
})
)
}

View File

@ -22,5 +22,7 @@
export * from "./_"
export * from "./header"
export * from "./hidden"
export * from "./main"
export * from "./search"
export * from "./sidebar"

View File

@ -0,0 +1,24 @@
/*
* Copyright (c) 2016-2019 Martin Donath <martin.donath@squidfunk.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER 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 "./reset"

View File

@ -0,0 +1,22 @@
/*
* Copyright (c) 2016-2019 Martin Donath <martin.donath@squidfunk.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER 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.
*/

View File

@ -0,0 +1,44 @@
/*
* Copyright (c) 2016-2019 Martin Donath <martin.donath@squidfunk.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER 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 } from "rxjs"
import { mapTo } from "rxjs/operators"
/* ----------------------------------------------------------------------------
* Functions
* ------------------------------------------------------------------------- */
/**
* Watch search reset
*
* @param el - Search reset element
*
* @return Search reset observable
*/
export function watchSearchReset(
el: HTMLElement
): Observable<boolean> {
return fromEvent(el, "click")
.pipe(
mapTo(true)
)
}

View File

@ -1,144 +0,0 @@
/*
* Copyright (c) 2016-2019 Martin Donath <martin.donath@squidfunk.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER 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 { equals } from "ramda"
import { Observable, animationFrameScheduler, combineLatest } from "rxjs"
import {
distinctUntilChanged,
finalize,
map,
observeOn,
shareReplay,
tap
} from "rxjs/operators"
import { ViewportOffset } from "../../../../ui"
import { Main } from "../../main"
import {
resetSidebarHeight,
resetSidebarLock,
setSidebarHeight,
setSidebarLock
} from "../element"
/* ----------------------------------------------------------------------------
* Types
* ------------------------------------------------------------------------- */
/**
* Sidebar
*/
export interface Sidebar {
height: number /* Sidebar height */
lock: boolean /* Sidebar lock */
}
/* ----------------------------------------------------------------------------
* Function types
* ------------------------------------------------------------------------- */
/**
* Options
*/
interface Options {
offset$: Observable<ViewportOffset> /* Viewport offset observable */
main$: Observable<Main> /* Main area observable */
}
/* ----------------------------------------------------------------------------
* Functions
* ------------------------------------------------------------------------- */
/**
* Watch sidebar
*
* This function returns an observable that computes the visual parameters of
* the given element (a sidebar) from the vertical viewport offset, as well as
* the height of the main area. When the page is scrolled beyond the header,
* the sidebar is locked and fills the remaining space.
*
* @param el - Sidebar element
* @param options - Options
*
* @return Sidebar observable
*/
export function watchSidebar(
el: HTMLElement, { offset$, main$ }: Options
): Observable<Sidebar> {
/* Adjust for internal main area offset */
const adjust = parseFloat(
getComputedStyle(el.parentElement!)
.getPropertyValue("padding-top")
)
/* Compute the sidebar's available height */
const height$ = combineLatest([offset$, main$])
.pipe(
map(([{ y }, { offset, height }]) => {
return height - adjust + Math.min(adjust, Math.max(0, y - offset))
})
)
/* Compute whether the sidebar should be locked */
const lock$ = combineLatest([offset$, main$])
.pipe(
map(([{ y }, { offset }]) => y >= offset + adjust)
)
/* Combine into single hot observable */
return combineLatest([height$, lock$])
.pipe(
map(([height, lock]) => ({ height, lock })),
distinctUntilChanged<Sidebar>(equals),
shareReplay(1)
)
}
/**
* Setup sidebar
*
* @param el - Sidebar element
* @param options - Options
*
* @return Sidebar observable
*/
export function setupSidebar(
el: HTMLElement, options: Options
): Observable<Sidebar> {
return watchSidebar(el, options)
.pipe(
observeOn(animationFrameScheduler),
/* Apply mutations (side effects) */
tap(({ height, lock }) => {
setSidebarHeight(el, height)
setSidebarLock(el, lock)
}),
/* Reset on complete or error */
finalize(() => {
resetSidebarHeight(el)
resetSidebarLock(el)
})
)
}

View File

@ -20,5 +20,131 @@
* IN THE SOFTWARE.
*/
export * from "./_"
export * from "./element"
import { equals } from "ramda"
import {
MonoTypeOperatorFunction,
Observable,
animationFrameScheduler,
combineLatest,
pipe
} from "rxjs"
import {
distinctUntilChanged,
finalize,
map,
observeOn,
shareReplay,
tap
} from "rxjs/operators"
import { ViewportOffset } from "../../../ui"
import {
resetSidebarHeight,
resetSidebarLock,
setSidebarHeight,
setSidebarLock
} from "../../action"
import { Main } from "../main"
/* ----------------------------------------------------------------------------
* Types
* ------------------------------------------------------------------------- */
/**
* Sidebar
*/
export interface Sidebar {
height: number /* Sidebar height */
lock: boolean /* Sidebar lock */
}
/* ----------------------------------------------------------------------------
* Function types
* ------------------------------------------------------------------------- */
/**
* Options
*/
interface Options {
offset$: Observable<ViewportOffset> /* Viewport offset observable */
main$: Observable<Main> /* Main area observable */
}
/* ----------------------------------------------------------------------------
* Functions
* ------------------------------------------------------------------------- */
/**
* Watch sidebar
*
* This function returns an observable that computes the visual parameters of
* the given element (a sidebar) from the vertical viewport offset, as well as
* the height of the main area. When the page is scrolled beyond the header,
* the sidebar is locked and fills the remaining space.
*
* @param el - Sidebar element
* @param options - Options
*
* @return Sidebar observable
*/
export function watchSidebar(
el: HTMLElement, { offset$, main$ }: Options
): Observable<Sidebar> {
/* Adjust for internal main area offset */
const adjust = parseFloat(
getComputedStyle(el.parentElement!)
.getPropertyValue("padding-top")
)
/* Compute the sidebar's available height */
const height$ = combineLatest([offset$, main$])
.pipe(
map(([{ y }, { offset, height }]) => {
return height - adjust + Math.min(adjust, Math.max(0, y - offset))
})
)
/* Compute whether the sidebar should be locked */
const lock$ = combineLatest([offset$, main$])
.pipe(
map(([{ y }, { offset }]) => y >= offset + adjust)
)
/* Combine into single hot observable */
return combineLatest([height$, lock$])
.pipe(
map(([height, lock]) => ({ height, lock })),
distinctUntilChanged<Sidebar>(equals),
shareReplay(1)
)
}
/* ------------------------------------------------------------------------- */
/**
* Paint sidebar from source observable
*
* @param el - Sidebar element
*
* @return Operator function
*/
export function paintSidebar(
el: HTMLElement
): MonoTypeOperatorFunction<Sidebar> {
return pipe(
/* Defer repaint to next animation frame */
observeOn(animationFrameScheduler),
tap(({ height, lock }) => {
setSidebarHeight(el, height)
setSidebarLock(el, lock)
}),
/* Reset on complete or error */
finalize(() => {
resetSidebarHeight(el)
resetSidebarLock(el)
})
)
}

View File

@ -20,6 +20,7 @@
* IN THE SOFTWARE.
*/
export * from "./action"
export * from "./component"
export * from "./utilities"
export * from "./toggle"
// export * from "./worker"

View File

@ -0,0 +1,44 @@
/*
* Copyright (c) 2016-2019 Martin Donath <martin.donath@squidfunk.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER 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 } from "rxjs"
import { pluck } from "rxjs/operators"
/* ----------------------------------------------------------------------------
* Functions
* ------------------------------------------------------------------------- */
/**
* Watch toggle
*
* @param el - Toggle element
*
* @return Toggle observable
*/
export function watchToggle(
el: HTMLInputElement
): Observable<boolean> {
return fromEvent(el, "change")
.pipe(
pluck("checked")
)
}

View File

@ -41,7 +41,7 @@ import {
* Switch options
*/
interface SwitchOptions {
url$: Observable<string> /* Location observable */
location$: Observable<string> /* Location observable */
}
/* ----------------------------------------------------------------------------
@ -83,14 +83,16 @@ export function watchDocument(): Observable<Document> {
* @return Document switch observable
*/
export function watchDocumentSwitch(
{ url$ }: SwitchOptions
{ location$ }: SwitchOptions
): Observable<Document> {
return url$
return location$
.pipe(
startWith(location.href),
map(url => url.replace(/#[^#]+$/, "")),
distinctUntilChanged(),
skip(1),
/* Fetch document */
switchMap(url => ajax({
url,
responseType: "document",

View File

@ -0,0 +1,86 @@
/*
* Copyright (c) 2016-2019 Martin Donath <martin.donath@squidfunk.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
import {
EMPTY,
Observable,
OperatorFunction,
combineLatest,
of,
pipe
} from "rxjs"
import {
filter,
map,
switchMap,
takeUntil
} from "rxjs/operators"
/* ----------------------------------------------------------------------------
* Functions
* ------------------------------------------------------------------------- */
/**
* Invert boolean value of source observable
*
* @param toggle$ - Toggle observable
*
* @return Inverted toggle observable
*/
export function not(
toggle$: Observable<boolean>
): Observable<boolean> {
return toggle$
.pipe(
map(active => !active)
)
}
/* ------------------------------------------------------------------------- */
/**
* Toggle switch map with another observable
*
* @template T - Source value type
* @template U - Target value type
*
* @param toggle$ - Toggle observable
* @param project - Projection
*
* @return Operator function
*/
export function switchMapIf<T, U>(
toggle$: Observable<boolean>, project: (value: T) => Observable<U>
): OperatorFunction<T, U> {
const begin$ = toggle$.pipe(filter(value => value))
const end$ = toggle$.pipe(filter(value => !value))
return pipe(
switchMap(value => combineLatest([of(value), begin$])),
switchMap(([value, active]) => active
? project(value)
.pipe(
takeUntil(end$)
)
: EMPTY
)
)
}

View File

@ -32,8 +32,8 @@
// Local imports
// ----------------------------------------------------------------------------
@import "helpers/break";
@import "helpers/px2em";
@import "utilities/break";
@import "utilities/px2em";
@import "config";

View File

@ -32,8 +32,8 @@
// Local imports
// ----------------------------------------------------------------------------
@import "helpers/break";
@import "helpers/px2em";
@import "utilities/break";
@import "utilities/px2em";
@import "config";

View File

@ -124,6 +124,7 @@
&__topic {
display: block;
position: absolute;
width: calc(100% - #{px2rem(20px)});
transition:
transform 0.4s cubic-bezier(0.1, 0.7, 0.1, 1),
opacity 0.15s;
@ -133,7 +134,7 @@
// Page title
& + & {
transform: translateY(px2rem(25px));
transform: translateX(px2rem(25px));
transition:
transform 0.4s cubic-bezier(1, 0.7, 0.1, 0.1),
opacity 0.15s;
@ -143,7 +144,7 @@
// Adjust for RTL languages
[dir="rtl"] & {
transform: translateY(px2rem(-25px));
transform: translateX(px2rem(-25px));
}
}
@ -166,7 +167,7 @@
// Show page title
&[data-md-state="active"] .md-header-nav__topic {
transform: translateY(px2rem(-25px));
transform: translateX(px2rem(-25px));
transition:
transform 0.4s cubic-bezier(1, 0.7, 0.1, 0.1),
opacity 0.15s;
@ -176,12 +177,12 @@
// Adjust for RTL languages
[dir="rtl"] & {
transform: translateY(px2rem(25px));
transform: translateX(px2rem(25px));
}
// Page title
& + .md-header-nav__topic {
transform: translateY(0);
transform: translateX(0);
transition:
transform 0.4s cubic-bezier(0.1, 0.7, 0.1, 1),
opacity 0.15s;

View File

@ -419,11 +419,8 @@
<!-- Application initialization -->
<script>
app({
version: "{{ mkdocs_version }}",
url: {
base: "{{ base_url }}"
}
app = initialize({
base: "{{ base_url }}"
});
</script>