mirror of
https://github.com/squidfunk/mkdocs-material.git
synced 2026-07-22 05:53:03 -04:00
Migrated first features to RxJS
This commit is contained in:
@@ -1 +1,266 @@
|
||||
console.log("LOL")
|
||||
/*
|
||||
* 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, reduce } from "rambda"
|
||||
import { BehaviorSubject, combineLatest, fromEvent, merge, of } from "rxjs"
|
||||
import {
|
||||
distinctUntilChanged,
|
||||
filter,
|
||||
map,
|
||||
mapTo,
|
||||
pluck,
|
||||
startWith,
|
||||
switchMapTo
|
||||
} from "rxjs/operators"
|
||||
|
||||
import {
|
||||
fromMediaQuery,
|
||||
watchViewportOffset,
|
||||
watchViewportSize
|
||||
} from "./viewport"
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
const screen$ = fromMediaQuery("(min-width: 1220px)")
|
||||
const offset$ = watchViewportOffset()
|
||||
const size$ = watchViewportSize()
|
||||
|
||||
const hash$ = fromEvent<HashChangeEvent>(window, "hashchange").pipe(
|
||||
startWith(document.location.hash),
|
||||
map(() => document.location.hash),
|
||||
filter(hash => hash.length > 0)
|
||||
)
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
const component$ = of({
|
||||
container: document.querySelector("[data-md-component=container]") as HTMLElement,
|
||||
navigation: document.querySelector("[data-md-component=navigation]") as HTMLElement,
|
||||
header: document.querySelector("[data-md-component=header]") as HTMLElement,
|
||||
title: document.querySelector("[data-md-component=title]") as HTMLElement,
|
||||
toc: document.querySelector("[data-md-component=toc]") as HTMLElement, // TODO: !?
|
||||
headline: document.querySelector(".md-typeset h1") as HTMLElement
|
||||
})
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// helper function
|
||||
function toArray(collection: HTMLCollection): HTMLElement[] {
|
||||
return Array.from(collection) as HTMLElement[]
|
||||
}
|
||||
|
||||
document.documentElement.classList.remove("no-js")
|
||||
document.documentElement.classList.add("js")
|
||||
|
||||
// we need TWO observables. first, if it's a resize, we need to set up stuff anew
|
||||
// bind size and offset to animationFrame !!!
|
||||
// TODO: combine latest with element!? navigation -> el
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// sidebar lock + height
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// Sidebar { offset, height, locked }
|
||||
|
||||
const sidebarOffset$ = size$.pipe(
|
||||
switchMapTo(component$),
|
||||
map(({ header, navigation }) => {
|
||||
return -1 * (header.offsetParent === null ? header.offsetHeight : 0) +
|
||||
reduce((offset, child) => {
|
||||
return Math.max(offset, child.offsetTop)
|
||||
}, 0, toArray(navigation.parentElement!.children))
|
||||
}),
|
||||
distinctUntilChanged<number>(equals)
|
||||
)
|
||||
|
||||
const sidebarHeight$ = combineLatest(offset$, size$, component$, sidebarOffset$).pipe(
|
||||
map(([{ y }, { height }, { header, navigation }, offset]) => {
|
||||
const parent = navigation.parentElement as HTMLElement
|
||||
return height - (
|
||||
header.offsetParent === null ? header.offsetHeight : 0
|
||||
) - Math.max(0, offset - y)
|
||||
- Math.max(0, y + height - parent.offsetTop - parent.offsetHeight)
|
||||
})
|
||||
)
|
||||
|
||||
const sidebarActive$ = combineLatest(offset$, sidebarOffset$).pipe(
|
||||
map(([{ y }, threshold]) => y >= threshold),
|
||||
distinctUntilChanged<boolean>(equals)
|
||||
)
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
combineLatest(component$, sidebarActive$)
|
||||
.subscribe(([{ navigation }, active]) => {
|
||||
navigation.dataset.mdState = active ? "lock" : ""
|
||||
})
|
||||
|
||||
combineLatest(component$, sidebarHeight$)
|
||||
.subscribe(([{ navigation }, height]) => {
|
||||
navigation.style.height = `${height}px`
|
||||
})
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// re-use calculation for toc, if present!
|
||||
combineLatest(component$, sidebarActive$).pipe(
|
||||
filter(([{ toc }]) => Boolean(toc))
|
||||
)
|
||||
.subscribe(([{ toc }, active]) => {
|
||||
toc.dataset.mdState = active ? "lock" : ""
|
||||
})
|
||||
|
||||
combineLatest(component$, sidebarHeight$).pipe(
|
||||
filter(([{ toc }]) => Boolean(toc))
|
||||
)
|
||||
.subscribe(([{ toc }, height]) => {
|
||||
toc.style.height = `${height}px`
|
||||
})
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// header shadow
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// Shadow { offset, locked }
|
||||
|
||||
const shadowOffset$ = size$.pipe(
|
||||
switchMapTo(component$),
|
||||
map(({ container }) => {
|
||||
let parent = container.parentElement as HTMLElement
|
||||
let height = 0
|
||||
do {
|
||||
parent = parent.previousElementSibling as HTMLElement // toElement -> throw if not!
|
||||
height += parent.offsetHeight
|
||||
} while (parent.previousElementSibling)
|
||||
return height
|
||||
})
|
||||
)
|
||||
|
||||
const shadowActive$ = combineLatest(offset$, shadowOffset$).pipe(
|
||||
map(([{ y }, threshold]) => y >= threshold),
|
||||
distinctUntilChanged<boolean>(equals)
|
||||
)
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
combineLatest(component$, shadowActive$)
|
||||
.subscribe(([{ header }, active]) => {
|
||||
header.dataset.mdState = active ? "shadow" : ""
|
||||
})
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// header title swap
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
const headlineOffset$ = size$.pipe(
|
||||
switchMapTo(component$),
|
||||
map(({ headline }) => headline.offsetTop),
|
||||
distinctUntilChanged<number>(equals)
|
||||
)
|
||||
|
||||
const headlineWidth$ = size$.pipe(
|
||||
switchMapTo(component$),
|
||||
map(({ title }) => title.offsetWidth - 20),
|
||||
distinctUntilChanged<number>(equals)
|
||||
)
|
||||
|
||||
const headlineActive$ = combineLatest(offset$, headlineOffset$).pipe(
|
||||
map(([{ y }, threshold]) => y >= threshold),
|
||||
distinctUntilChanged<boolean>(equals)
|
||||
)
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
combineLatest(component$, headlineActive$)
|
||||
.subscribe(([{ title }, active]) => {
|
||||
title.dataset.mdState = active ? "active" : ""
|
||||
})
|
||||
|
||||
combineLatest(component$, headlineWidth$)
|
||||
.subscribe(([{ title }, width]) => {
|
||||
for (const child of toArray(title.children))
|
||||
child.style.width = `${width}px`
|
||||
})
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// open details on hashchange
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
hash$.pipe(
|
||||
map(hash => document.querySelector(hash) as HTMLElement), // TODO: write a getElement function...
|
||||
filter<HTMLElement>(Boolean)
|
||||
)
|
||||
.subscribe(el => {
|
||||
let parent = el.parentNode
|
||||
while (parent && !(parent instanceof HTMLDetailsElement))
|
||||
parent = parent.parentNode
|
||||
|
||||
/* If there's a details tag, open it */
|
||||
if (parent && !parent.open) {
|
||||
parent.open = true
|
||||
|
||||
/* Force reload, so the viewport repositions */
|
||||
const hash = location.hash
|
||||
location.hash = " "
|
||||
location.hash = hash // tslint:disable-line
|
||||
}
|
||||
})
|
||||
|
||||
// size$.pipe(
|
||||
// switchMapTo(component$),
|
||||
// map(({ title }) => {
|
||||
// title.children
|
||||
// }
|
||||
|
||||
// just combine all header observables into one!
|
||||
|
||||
// headerLock$.subscribe(console.log)
|
||||
// headerHeight$.subscribe(console.log)
|
||||
// TODO just tap both? toc and nav?
|
||||
|
||||
// // first, calculate top offset
|
||||
// size$.subscribe(size => {
|
||||
// const children = Array.from(navigation.parentElement!.children) as HTMLElement[] // TODO
|
||||
|
||||
// const top = children.reduce((offset, child) => Math.max(offset, child.offsetTop), 0)
|
||||
|
||||
// const offset = top - (adjust ? header.offsetHeight : 0)
|
||||
|
||||
// })
|
||||
|
||||
// const top = Array.from(navigation.parentElement!.children)
|
||||
// .reduce()
|
||||
|
||||
// this.parent_.children, (offset, child) => {
|
||||
// return Math.max(offset, child.offsetTop)
|
||||
// }, 0)
|
||||
|
||||
/* Set lock offset for element with largest top offset */
|
||||
// this.offset_ = top - (this.pad_ ? this.header_.offsetHeight : 0)
|
||||
|
||||
// offset$.subscribe(offset => {
|
||||
// console.log(offset)
|
||||
// })
|
||||
|
||||
export function app(_x: any) {
|
||||
|
||||
}
|
||||
|
||||
25
src/assets/javascripts/state/index.ts
Normal file
25
src/assets/javascripts/state/index.ts
Normal 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.
|
||||
*/
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Types
|
||||
* ------------------------------------------------------------------------- */
|
||||
115
src/assets/javascripts/viewport/_/index.ts
Normal file
115
src/assets/javascripts/viewport/_/index.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* 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 "rambda"
|
||||
import { Observable, fromEvent, merge } from "rxjs"
|
||||
import { distinctUntilChanged, map, startWith } from "rxjs/operators"
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Data
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Observable for window scroll events
|
||||
*/
|
||||
const scroll$ = fromEvent<UIEvent>(window, "scroll")
|
||||
|
||||
/**
|
||||
* Observable for window resize events
|
||||
*/
|
||||
const resize$ = fromEvent<UIEvent>(window, "resize")
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Types
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Viewport offset
|
||||
*/
|
||||
export interface ViewportOffset {
|
||||
x: number /* Horizontal offset */
|
||||
y: number /* Vertical offset */
|
||||
}
|
||||
|
||||
/**
|
||||
* Viewport size
|
||||
*/
|
||||
export interface ViewportSize {
|
||||
width: number /* Viewport width */
|
||||
height: number /* Viewport height */
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Functions
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Retrieve the viewport offset
|
||||
*
|
||||
* @return Viewport offset
|
||||
*/
|
||||
export function getViewportOffset(): ViewportOffset {
|
||||
return {
|
||||
x: window.pageXOffset,
|
||||
y: window.pageYOffset
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the viewport size
|
||||
*
|
||||
* @return Viewport size
|
||||
*/
|
||||
export function getViewportSize(): ViewportSize {
|
||||
return {
|
||||
width: window.innerWidth,
|
||||
height: window.innerHeight
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Create an observable emitting changes in viewport offset
|
||||
*
|
||||
* @return Viewport offset observable
|
||||
*/
|
||||
export function watchViewportOffset(): Observable<ViewportOffset> {
|
||||
return merge(scroll$, resize$).pipe(
|
||||
map(getViewportOffset),
|
||||
startWith(getViewportOffset()),
|
||||
distinctUntilChanged<ViewportOffset>(equals)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an observable emitting changes in viewport size
|
||||
*
|
||||
* @return Viewport size observable
|
||||
*/
|
||||
export function watchViewportSize(): Observable<ViewportSize> {
|
||||
return resize$.pipe(
|
||||
map(getViewportSize),
|
||||
startWith(getViewportSize()),
|
||||
distinctUntilChanged<ViewportSize>(equals)
|
||||
)
|
||||
}
|
||||
44
src/assets/javascripts/viewport/breakpoint/index.ts
Normal file
44
src/assets/javascripts/viewport/breakpoint/index.ts
Normal 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, fromEventPattern } from "rxjs"
|
||||
import { startWith } from "rxjs/operators"
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Functions
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Create an observable for a media query
|
||||
*
|
||||
* @param query - Media query
|
||||
*
|
||||
* @return Media query observable
|
||||
*/
|
||||
export function fromMediaQuery(query: string): Observable<boolean> {
|
||||
const media = window.matchMedia(query)
|
||||
return fromEventPattern<boolean>(next =>
|
||||
media.addListener(() => next(media.matches))
|
||||
).pipe(
|
||||
startWith(media.matches)
|
||||
)
|
||||
}
|
||||
24
src/assets/javascripts/viewport/index.ts
Normal file
24
src/assets/javascripts/viewport/index.ts
Normal 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 "./_"
|
||||
export * from "./breakpoint"
|
||||
@@ -372,7 +372,7 @@
|
||||
|
||||
<!-- Initialize application -->
|
||||
<script>
|
||||
app.initialize({
|
||||
app({
|
||||
version: "{{ mkdocs_version }}",
|
||||
url: {
|
||||
base: "{{ base_url }}"
|
||||
|
||||
Reference in New Issue
Block a user