1
0
mirror of https://github.com/squidfunk/mkdocs-material.git synced 2024-09-24 19:38:27 +02:00

Refactored instant loading setup

This commit is contained in:
squidfunk 2020-02-21 10:18:49 +01:00
parent 9edf4e8068
commit c7e4063d86
13 changed files with 104 additions and 79 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,6 @@
{
"assets/javascripts/bundle.js": "assets/javascripts/bundle.1defb77e.min.js",
"assets/javascripts/bundle.js.map": "assets/javascripts/bundle.1defb77e.min.js.map",
"assets/javascripts/bundle.js": "assets/javascripts/bundle.d3f83a35.min.js",
"assets/javascripts/bundle.js.map": "assets/javascripts/bundle.d3f83a35.min.js.map",
"assets/javascripts/worker/search.js": "assets/javascripts/worker/search.926ffd9e.min.js",
"assets/javascripts/worker/search.js.map": "assets/javascripts/worker/search.926ffd9e.min.js.map",
"assets/stylesheets/app-palette.scss": "assets/stylesheets/app-palette.3f90c815.min.css",

View File

@ -190,7 +190,7 @@
{% endblock %}
</div>
{% block scripts %}
<script src="{{ 'assets/javascripts/bundle.1defb77e.min.js' | url }}"></script>
<script src="{{ 'assets/javascripts/bundle.d3f83a35.min.js' | url }}"></script>
{%- set translations = {} -%}
{%- for key in [
"clipboard.copy",

View File

@ -27,8 +27,7 @@ import {
map,
scan,
shareReplay,
switchMap,
tap
switchMap
} from "rxjs/operators"
import { getElement } from "observables"
@ -86,20 +85,18 @@ let components$: Observable<ComponentMap>
* ------------------------------------------------------------------------- */
/**
* Watch components with given names
* Setup bindings to elements with given names
*
* This function returns an observable that will maintain bindings to the given
* components in-between document switches and update the components in-place.
* This function will maintain bindings to the elements identified by the given
* names in-between document switches and update the elements in-place.
*
* @param names - Component names
* @param options - Options
*
* @return Component map observable
*/
export function watchComponentMap(
export function setupComponents(
names: Component[], { document$ }: WatchOptions
): Observable<ComponentMap> {
return components$ = document$
): void {
components$ = document$
.pipe(
/* Build component map */
@ -141,6 +138,9 @@ export function watchComponentMap(
/**
* Retrieve a component
*
* The returned observable will only re-emit if the element changed, i.e. if
* it was replaced from a document which was switched to.
*
* @template T - Element type
*
* @param name - Component name

View File

@ -45,7 +45,8 @@ import {
take,
mapTo,
shareReplay,
switchMapTo
switchMapTo,
skip
} from "rxjs/operators"
import {
@ -57,10 +58,10 @@ import {
watchLocation,
watchLocationHash,
watchViewport,
watchToggleMap,
setupToggles,
useToggle,
getElement,
watchDocumentSwitch
setViewportOffset
} from "./observables"
import { setupSearchWorker } from "./workers"
@ -74,7 +75,7 @@ import {
mountTableOfContents,
mountTabs,
useComponent,
watchComponentMap,
setupComponents,
mountHeaderTitle,
mountSearchQuery,
mountSearchReset,
@ -114,14 +115,9 @@ export function initialize(config: unknown) {
throw new SyntaxError(`Invalid configuration: ${JSON.stringify(config)}`)
const location$ = watchLocation()
// instant loading
const switch$ = config.feature.instant
? watchDocumentSwitch({ location$ })
: NEVER
const load$ = watchDocument()
const document$ = merge(load$, switch$)
const document$ = watchDocument(
config.feature.instant ? { location$ } : {}
)
const hash$ = watchLocationHash()
const viewport$ = watchViewport()
const tablet$ = watchMedia("(min-width: 960px)")
@ -135,12 +131,14 @@ export function initialize(config: unknown) {
/* ----------------------------------------------------------------------- */
watchToggleMap([
/* Setup toggle bindings */
setupToggles([
"drawer", /* Toggle for drawer */
"search" /* Toggle for search */
], { document$ })
watchComponentMap([
/* Setup components bindings */
setupComponents([
"container", /* Container */
"header", /* Header */
"header-title", /* Header title */
@ -155,6 +153,8 @@ export function initialize(config: unknown) {
"toc" /* Table of contents */
], { document$ })
/* ----------------------------------------------------------------------- */
/* Create header observable */
const header$ = useComponent("header")
.pipe(
@ -300,13 +300,11 @@ export function initialize(config: unknown) {
/* ----------------------------------------------------------------------- */
// instant loading
const instant$ = config.feature.instant ? load$ // TODO: just use document$ and take(1)
const instant$ = config.feature.instant ? document$ // TODO: just use document$ and take(1)
.pipe(
take(1), // only initial load
switchMap(({ body }) => fromEvent(body, "click")),
switchMap(ev => {
// two cases: search results which should always load from same domain
// and/or
if (ev.target && ev.target instanceof HTMLElement) {
const anchor = ev.target.closest("a")
if (anchor) {
@ -326,19 +324,20 @@ export function initialize(config: unknown) {
)
: NEVER
// deploy new location
// deploy new location - can be written as instant$.subscribe(location$)
instant$.subscribe(url => {
console.log(`Load ${url}`)
location$.next(url)
})
// scroll to top when document is loaded
// if a new url is deployed via instant loading, switch to document observable
// to exactly know when the content was loaded. then go to top.
instant$
.pipe(
switchMapTo(switch$), // TODO: just use document$ and skip(1)
switchMapTo(document$.pipe(skip(1))), // TODO: just use document$ and skip(1)
)
.subscribe(() => {
window.scrollTo(0, 0) // TODO: or scroll element into view
setViewportOffset({ y: 0 })
})
/* ----------------------------------------------------------------------- */

View File

@ -75,8 +75,11 @@ export function setupClipboard(
const clipboard$ = fromEventPattern<ClipboardJS.Event>(next => {
new ClipboardJS(".md-clipboard").on("success", next)
})
.pipe(
shareReplay(1)
)
/* Display notification upon clipboard copy */
/* Display notification for clipboard event */
clipboard$
.pipe(
tap(ev => ev.clearSelection()),
@ -84,9 +87,6 @@ export function setupClipboard(
)
.subscribe(dialog$)
/* Return clipboard as hot observable */
/* Return clipboard */
return clipboard$
.pipe(
shareReplay(1)
)
}

View File

@ -68,6 +68,7 @@ export function setupDialog(
switchMap(text => useComponent("container")
.pipe(
map(container => container.appendChild(dialog)),
observeOn(animationFrameScheduler),
delay(1), // Strangley it doesnt work when we push things to the new animation frame...
tap(el => {
el.innerHTML = text

View File

@ -20,9 +20,22 @@
* IN THE SOFTWARE.
*/
import { Observable, fromEvent } from "rxjs"
import { NEVER, Observable, fromEvent, merge } from "rxjs"
import { mapTo, shareReplay } from "rxjs/operators"
import { watchDocumentSwitch } from "../switch"
/* ----------------------------------------------------------------------------
* Helper types
* ------------------------------------------------------------------------- */
/**
* Watch options
*/
interface WatchOptions {
location$?: Observable<string> /* Location observable */
}
/* ----------------------------------------------------------------------------
* Functions
* ------------------------------------------------------------------------- */
@ -30,12 +43,24 @@ import { mapTo, shareReplay } from "rxjs/operators"
/**
* Watch document
*
* If the location observable is passed, instant loading will be enabled which
* means that new values will be emitted every time the location changes.
*
* @return Document observable
*/
export function watchDocument(): Observable<Document> {
return fromEvent(document, "DOMContentLoaded")
export function watchDocument(
{ location$ }: WatchOptions = {}
): Observable<Document> {
return merge(
fromEvent(document, "DOMContentLoaded")
.pipe(
mapTo(document)
),
typeof location$ !== "undefined"
? watchDocumentSwitch({ location$ })
: NEVER
)
.pipe(
mapTo(document),
shareReplay(1)
)
}

View File

@ -75,17 +75,15 @@ let toggles$: Observable<ToggleMap>
* ------------------------------------------------------------------------- */
/**
* Watch toggles with given names
* Setup bindings to toggles with given names
*
* @param names - Toggle names
* @param options - Options
*
* @return Toggle map observable
*/
export function watchToggleMap(
export function setupToggles(
names: Toggle[], { document$ }: WatchOptions
): Observable<ToggleMap> {
return toggles$ = document$
): void {
toggles$ = document$
.pipe(
/* Ignore document switches */
@ -108,7 +106,8 @@ export function watchToggleMap(
/**
* Retrieve a toggle
*
* @template T - Element type
* The returned observable will only re-emit if the element changed, i.e. if
* it was replaced from a document which was switched to.
*
* @param name - Toggle name
*
@ -136,7 +135,7 @@ export function useToggle(
* Simulating a click event seems to be the most cross-browser compatible way
* of changing the value while also emitting a `change` event. Before, Material
* used `CustomEvent` to programmatically change the value of a toggle, but this
* is a much simpler and cleaner solution.
* is a much simpler and cleaner solution which doesn't require a polyfill.
*
* @param el - Toggle element
* @param value - Toggle value

View File

@ -22,7 +22,7 @@
import { Subject, from } from "rxjs"
import { ajax } from "rxjs/ajax"
import { map, pluck } from "rxjs/operators"
import { map, pluck, shareReplay } from "rxjs/operators"
import { SearchIndexOptions } from "integrations/search"
import {
@ -106,7 +106,8 @@ export function setupSearchWorker(
}
}
return message
})
}),
shareReplay(1)
)
/* Fetch index if it wasn't passed explicitly */