mirror of
https://github.com/squidfunk/mkdocs-material.git
synced 2024-06-14 11:52:32 +03:00
Merge branch 'refactor/rxjs-typescript' into feature/landing-page
This commit is contained in:
commit
9c48baa791
@ -6,7 +6,7 @@ template: overrides/main.html
|
|||||||
|
|
||||||
## Highlights
|
## Highlights
|
||||||
|
|
||||||
* Reactive architecture – try `__material.dialog$.next("Hi!")` in the console
|
* Reactive architecture – try `app.dialog$.next("Hi!")` in the console
|
||||||
* [Instant loading][5] – make Material behave like a Single Page Application
|
* [Instant loading][5] – make Material behave like a Single Page Application
|
||||||
* Improved CSS customization with [CSS variables][1] – set your brand's colors
|
* Improved CSS customization with [CSS variables][1] – set your brand's colors
|
||||||
* Improved CSS resilience, e.g. proper sidebar locking for customized headers
|
* Improved CSS resilience, e.g. proper sidebar locking for customized headers
|
||||||
@ -191,7 +191,7 @@ matches the new structure:
|
|||||||
- <meta name="lang:{{ key }}" content="{{ lang.t(key) }}">
|
- <meta name="lang:{{ key }}" content="{{ lang.t(key) }}">
|
||||||
- {% endfor %}
|
- {% endfor %}
|
||||||
<link rel="shortcut icon" href="{{ config.theme.favicon | url }}">
|
<link rel="shortcut icon" href="{{ config.theme.favicon | url }}">
|
||||||
<meta name="generator" content="mkdocs-{{ mkdocs_version }}, mkdocs-material-5.0.0b2-1">
|
<meta name="generator" content="mkdocs-{{ mkdocs_version }}, mkdocs-material-5.0.0">
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -56,9 +42,9 @@
|
@@ -56,9 +42,9 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -225,7 +225,7 @@ matches the new structure:
|
|||||||
{% if config.extra.manifest %}
|
{% if config.extra.manifest %}
|
||||||
<link rel="manifest" href="{{ config.extra.manifest | url }}" crossorigin="use-credentials">
|
<link rel="manifest" href="{{ config.extra.manifest | url }}" crossorigin="use-credentials">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -95,47 +78,46 @@
|
@@ -95,47 +77,46 @@
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block extrahead %}{% endblock %}
|
{% block extrahead %}{% endblock %}
|
||||||
</head>
|
</head>
|
||||||
@ -294,7 +294,7 @@ matches the new structure:
|
|||||||
{% block site_nav %}
|
{% block site_nav %}
|
||||||
{% if nav %}
|
{% if nav %}
|
||||||
<div class="md-sidebar md-sidebar--primary" data-md-component="navigation">
|
<div class="md-sidebar md-sidebar--primary" data-md-component="navigation">
|
||||||
@@ -160,41 +142,25 @@
|
@@ -160,41 +141,25 @@
|
||||||
<article class="md-content__inner md-typeset">
|
<article class="md-content__inner md-typeset">
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% if page.edit_url %}
|
{% if page.edit_url %}
|
||||||
@ -349,15 +349,12 @@ matches the new structure:
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block disqus %}
|
{% block disqus %}
|
||||||
@@ -208,29 +174,40 @@
|
@@ -208,29 +174,35 @@
|
||||||
{% include "partials/footer.html" %}
|
{% include "partials/footer.html" %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</div>
|
</div>
|
||||||
+ {% block config %}
|
|
||||||
+ <script>var __config={}</script>
|
|
||||||
+ {% endblock %}
|
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
- <script src="{{ 'assets/javascripts/application.df00da5d.js' | url }}"></script>
|
- <script src="{{ 'assets/javascripts/application.********.js' | url }}"></script>
|
||||||
- {% if lang.t("search.language") != "en" %}
|
- {% if lang.t("search.language") != "en" %}
|
||||||
- {% set languages = lang.t("search.language").split(",") %}
|
- {% set languages = lang.t("search.language").split(",") %}
|
||||||
- {% if languages | length and languages[0] != "" %}
|
- {% if languages | length and languages[0] != "" %}
|
||||||
@ -379,8 +376,8 @@ matches the new structure:
|
|||||||
- {% endif %}
|
- {% endif %}
|
||||||
- {% endif %}
|
- {% endif %}
|
||||||
- <script>app.initialize({version:"{{ mkdocs_version }}",url:{base:"{{ base_url }}"}})</script>
|
- <script>app.initialize({version:"{{ mkdocs_version }}",url:{base:"{{ base_url }}"}})</script>
|
||||||
+ <script src="{{ 'assets/javascripts/vendor.31a2e7b9.min.js' | url }}"></script>
|
+ <script src="{{ 'assets/javascripts/vendor.********.min.js' | url }}"></script>
|
||||||
+ <script src="{{ 'assets/javascripts/bundle.5b33ad8d.min.js' | url }}"></script>
|
+ <script src="{{ 'assets/javascripts/bundle.********.min.js' | url }}"></script>
|
||||||
+ {%- set translations = {} -%}
|
+ {%- set translations = {} -%}
|
||||||
+ {%- for key in [
|
+ {%- for key in [
|
||||||
+ "clipboard.copy",
|
+ "clipboard.copy",
|
||||||
@ -396,18 +393,17 @@ matches the new structure:
|
|||||||
+ {%- set _ = translations.update({ key: lang.t(key) }) -%}
|
+ {%- set _ = translations.update({ key: lang.t(key) }) -%}
|
||||||
+ {%- endfor -%}
|
+ {%- endfor -%}
|
||||||
+ <script id="__lang" type="application/json">
|
+ <script id="__lang" type="application/json">
|
||||||
+ {{ translations | tojson }}
|
+ {{- translations | tojson -}}
|
||||||
+ </script>
|
+ </script>
|
||||||
|
+ {% block config %}{% endblock %}
|
||||||
+ <script>
|
+ <script>
|
||||||
+ __material = initialize(Object.assign({
|
+ app = initialize({
|
||||||
+ url: {
|
+ base: "{{ base_url }}",
|
||||||
+ base: "{{ base_url }}",
|
+ features: {{ config.theme.features | tojson }},
|
||||||
+ worker: {
|
+ search: Object.assign({
|
||||||
+ search: "{{ 'assets/javascripts/worker/search.edc88caf.min.js' | url }}"
|
+ worker: "{{ 'assets/javascripts/worker/search.********.min.js' | url }}"
|
||||||
+ }
|
+ }, typeof search !== "undefined" && search)
|
||||||
+ },
|
+ })
|
||||||
+ features: {{ config.theme.features | tojson }}
|
|
||||||
+ }, __config))
|
|
||||||
+ </script>
|
+ </script>
|
||||||
{% for path in config["extra_javascript"] %}
|
{% for path in config["extra_javascript"] %}
|
||||||
<script src="{{ path | url }}"></script>
|
<script src="{{ path | url }}"></script>
|
||||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
material/assets/javascripts/bundle.edc2ff56.min.js
vendored
Normal file
2
material/assets/javascripts/bundle.edc2ff56.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
material/assets/javascripts/bundle.edc2ff56.min.js.map
Normal file
1
material/assets/javascripts/bundle.edc2ff56.min.js.map
Normal file
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
1
material/assets/javascripts/vendor.c1fcc1cc.min.js.map
Normal file
1
material/assets/javascripts/vendor.c1fcc1cc.min.js.map
Normal file
File diff suppressed because one or more lines are too long
@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"assets/javascripts/bundle.js": "assets/javascripts/bundle.eb3d5d63.min.js",
|
"assets/javascripts/bundle.js": "assets/javascripts/bundle.edc2ff56.min.js",
|
||||||
"assets/javascripts/bundle.js.map": "assets/javascripts/bundle.eb3d5d63.min.js.map",
|
"assets/javascripts/bundle.js.map": "assets/javascripts/bundle.edc2ff56.min.js.map",
|
||||||
"assets/javascripts/vendor.js": "assets/javascripts/vendor.3340e0de.min.js",
|
"assets/javascripts/vendor.js": "assets/javascripts/vendor.c1fcc1cc.min.js",
|
||||||
"assets/javascripts/vendor.js.map": "assets/javascripts/vendor.3340e0de.min.js.map",
|
"assets/javascripts/vendor.js.map": "assets/javascripts/vendor.c1fcc1cc.min.js.map",
|
||||||
"assets/javascripts/worker/search.js": "assets/javascripts/worker/search.3bc815f0.min.js",
|
"assets/javascripts/worker/search.js": "assets/javascripts/worker/search.3bc815f0.min.js",
|
||||||
"assets/javascripts/worker/search.js.map": "assets/javascripts/worker/search.3bc815f0.min.js.map",
|
"assets/javascripts/worker/search.js.map": "assets/javascripts/worker/search.3bc815f0.min.js.map",
|
||||||
"assets/stylesheets/main.css": "assets/stylesheets/main.b32d3181.min.css",
|
"assets/stylesheets/main.css": "assets/stylesheets/main.b32d3181.min.css",
|
||||||
|
@ -174,8 +174,8 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
</div>
|
</div>
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
<script src="{{ 'assets/javascripts/vendor.3340e0de.min.js' | url }}"></script>
|
<script src="{{ 'assets/javascripts/vendor.c1fcc1cc.min.js' | url }}"></script>
|
||||||
<script src="{{ 'assets/javascripts/bundle.eb3d5d63.min.js' | url }}"></script>
|
<script src="{{ 'assets/javascripts/bundle.edc2ff56.min.js' | url }}"></script>
|
||||||
{%- set translations = {} -%}
|
{%- set translations = {} -%}
|
||||||
{%- for key in [
|
{%- for key in [
|
||||||
"clipboard.copy",
|
"clipboard.copy",
|
||||||
|
@ -52,7 +52,7 @@ theme:
|
|||||||
language: en
|
language: en
|
||||||
features:
|
features:
|
||||||
- tabs
|
- tabs
|
||||||
# - instant
|
- instant
|
||||||
palette:
|
palette:
|
||||||
primary: indigo
|
primary: indigo
|
||||||
accent: indigo
|
accent: indigo
|
||||||
|
@ -1,66 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2016-2020 Martin Donath <martin.donath@squidfunk.com>
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to
|
|
||||||
* deal in the Software without restriction, including without limitation the
|
|
||||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
||||||
* sell copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in
|
|
||||||
* all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
||||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
||||||
* IN THE SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { NEVER, Observable, fromEvent, merge } from "rxjs"
|
|
||||||
import { mapTo, shareReplay } from "rxjs/operators"
|
|
||||||
|
|
||||||
import { watchDocumentSwitch } from "../switch"
|
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------------
|
|
||||||
* Helper types
|
|
||||||
* ------------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Watch options
|
|
||||||
*/
|
|
||||||
interface WatchOptions {
|
|
||||||
location$?: Observable<URL> /* Location observable */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------------
|
|
||||||
* Functions
|
|
||||||
* ------------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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(
|
|
||||||
{ location$ }: WatchOptions = {}
|
|
||||||
): Observable<Document> {
|
|
||||||
return merge(
|
|
||||||
fromEvent(document, "DOMContentLoaded")
|
|
||||||
.pipe(
|
|
||||||
mapTo(document)
|
|
||||||
),
|
|
||||||
typeof location$ !== "undefined"
|
|
||||||
? watchDocumentSwitch({ location$ })
|
|
||||||
: NEVER
|
|
||||||
)
|
|
||||||
.pipe(
|
|
||||||
shareReplay(1)
|
|
||||||
)
|
|
||||||
}
|
|
@ -20,5 +20,30 @@
|
|||||||
* IN THE SOFTWARE.
|
* IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export * from "./_"
|
import { ReplaySubject, Subject, fromEvent } from "rxjs"
|
||||||
export * from "./switch"
|
import { mapTo } from "rxjs/operators"
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
* Functions
|
||||||
|
* ------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Watch document
|
||||||
|
*
|
||||||
|
* Documents must be implemented as subjects, so all downstream observables are
|
||||||
|
* automatically updated when a new document is emitted. This enabled features
|
||||||
|
* like instant loading.
|
||||||
|
*
|
||||||
|
* @return Document subject
|
||||||
|
*/
|
||||||
|
export function watchDocument(): Subject<Document> {
|
||||||
|
const document$ = new ReplaySubject<Document>()
|
||||||
|
fromEvent(document, "DOMContentLoaded")
|
||||||
|
.pipe(
|
||||||
|
mapTo(document)
|
||||||
|
)
|
||||||
|
.subscribe(document$)
|
||||||
|
|
||||||
|
/* Return document */
|
||||||
|
return document$
|
||||||
|
}
|
||||||
|
@ -1,96 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2016-2020 Martin Donath <martin.donath@squidfunk.com>
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to
|
|
||||||
* deal in the Software without restriction, including without limitation the
|
|
||||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
||||||
* sell copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in
|
|
||||||
* all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
||||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
||||||
* IN THE SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { NEVER, Observable } from "rxjs"
|
|
||||||
import { ajax } from "rxjs/ajax"
|
|
||||||
import {
|
|
||||||
catchError,
|
|
||||||
distinctUntilKeyChanged,
|
|
||||||
map,
|
|
||||||
share,
|
|
||||||
skip,
|
|
||||||
switchMap
|
|
||||||
} from "rxjs/operators"
|
|
||||||
|
|
||||||
import { setLocation } from "../../location"
|
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------------
|
|
||||||
* Helper types
|
|
||||||
* ------------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Watch options
|
|
||||||
*/
|
|
||||||
interface WatchOptions {
|
|
||||||
location$: Observable<URL> /* Location observable */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------------
|
|
||||||
* Functions
|
|
||||||
* ------------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Watch document switch
|
|
||||||
*
|
|
||||||
* This function returns an observable that fetches a document if the provided
|
|
||||||
* location observable emits a new value (i.e. URL). If the emitted URL points
|
|
||||||
* to the same page, the request is effectively ignored (i.e. when only the
|
|
||||||
* fragment identifier changes).
|
|
||||||
*
|
|
||||||
* Theoretically, we could use `responseType: "document"`, but since all MkDocs
|
|
||||||
* links are relative, we need to make sure that the current location matches
|
|
||||||
* the document we just loaded. Otherwise any relative links in the document
|
|
||||||
* may use the old location.
|
|
||||||
*
|
|
||||||
* @param options - Options
|
|
||||||
*
|
|
||||||
* @return Document observable
|
|
||||||
*/
|
|
||||||
export function watchDocumentSwitch(
|
|
||||||
{ location$ }: WatchOptions
|
|
||||||
): Observable<Document> {
|
|
||||||
const dom = new DOMParser()
|
|
||||||
return location$
|
|
||||||
.pipe(
|
|
||||||
distinctUntilKeyChanged("pathname"),
|
|
||||||
skip(1),
|
|
||||||
|
|
||||||
/* Fetch document */
|
|
||||||
switchMap(url => ajax({
|
|
||||||
url: url.href,
|
|
||||||
responseType: "text",
|
|
||||||
withCredentials: true
|
|
||||||
})
|
|
||||||
.pipe(
|
|
||||||
map(({ response }): Document => {
|
|
||||||
history.pushState({}, "", url.toString()) // TODO: abstract into function
|
|
||||||
return dom.parseFromString(response, "text/html")
|
|
||||||
}),
|
|
||||||
catchError(() => {
|
|
||||||
setLocation(url)
|
|
||||||
return NEVER
|
|
||||||
})
|
|
||||||
)
|
|
||||||
),
|
|
||||||
share()
|
|
||||||
)
|
|
||||||
}
|
|
@ -49,6 +49,7 @@ export function getLocationHash(): string {
|
|||||||
export function setLocationHash(hash: string): void {
|
export function setLocationHash(hash: string): void {
|
||||||
const el = document.createElement("a")
|
const el = document.createElement("a")
|
||||||
el.href = hash
|
el.href = hash
|
||||||
|
el.addEventListener("click", ev => ev.stopPropagation())
|
||||||
el.click()
|
el.click()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,7 +59,7 @@ export function getViewportSize(): ViewportSize {
|
|||||||
* @return Viewport size observable
|
* @return Viewport size observable
|
||||||
*/
|
*/
|
||||||
export function watchViewportSize(): Observable<ViewportSize> {
|
export function watchViewportSize(): Observable<ViewportSize> {
|
||||||
return fromEvent(window, "resize")
|
return fromEvent(window, "resize", { passive: true })
|
||||||
.pipe(
|
.pipe(
|
||||||
map(getViewportSize),
|
map(getViewportSize),
|
||||||
startWith(getViewportSize())
|
startWith(getViewportSize())
|
||||||
|
@ -49,13 +49,6 @@ export interface SearchQuery {
|
|||||||
focus: boolean /* Query focus */
|
focus: boolean /* Query focus */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Search query transform
|
|
||||||
*/
|
|
||||||
export type SearchQueryTransform = (value: string) => string
|
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------------
|
/* ----------------------------------------------------------------------------
|
||||||
* Helper types
|
* Helper types
|
||||||
* ------------------------------------------------------------------------- */
|
* ------------------------------------------------------------------------- */
|
||||||
|
@ -20,7 +20,8 @@
|
|||||||
* IN THE SOFTWARE.
|
* IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// TODO: remove this after we finished refactoring
|
// DISCLAIMER: this file is still WIP. There're some refactoring opportunities
|
||||||
|
// which must be tackled after we gathered some feedback on v5.
|
||||||
// tslint:disable
|
// tslint:disable
|
||||||
|
|
||||||
import "../stylesheets/main.scss"
|
import "../stylesheets/main.scss"
|
||||||
@ -32,8 +33,6 @@ import {
|
|||||||
combineLatest,
|
combineLatest,
|
||||||
animationFrameScheduler,
|
animationFrameScheduler,
|
||||||
fromEvent,
|
fromEvent,
|
||||||
of,
|
|
||||||
NEVER,
|
|
||||||
from
|
from
|
||||||
} from "rxjs"
|
} from "rxjs"
|
||||||
import { ajax } from "rxjs/ajax"
|
import { ajax } from "rxjs/ajax"
|
||||||
@ -46,7 +45,6 @@ import {
|
|||||||
observeOn,
|
observeOn,
|
||||||
take,
|
take,
|
||||||
shareReplay,
|
shareReplay,
|
||||||
share,
|
|
||||||
pluck
|
pluck
|
||||||
} from "rxjs/operators"
|
} from "rxjs/operators"
|
||||||
|
|
||||||
@ -60,7 +58,6 @@ import {
|
|||||||
watchLocationHash,
|
watchLocationHash,
|
||||||
watchViewport,
|
watchViewport,
|
||||||
isLocalLocation,
|
isLocalLocation,
|
||||||
isAnchorLocation,
|
|
||||||
setLocationHash,
|
setLocationHash,
|
||||||
watchLocationBase
|
watchLocationBase
|
||||||
} from "browser"
|
} from "browser"
|
||||||
@ -146,19 +143,17 @@ export function initialize(config: unknown) {
|
|||||||
if (!isConfig(config))
|
if (!isConfig(config))
|
||||||
throw new SyntaxError(`Invalid configuration: ${JSON.stringify(config)}`)
|
throw new SyntaxError(`Invalid configuration: ${JSON.stringify(config)}`)
|
||||||
|
|
||||||
/* Set up user interface observables */
|
/* Set up subjects */
|
||||||
|
const document$ = watchDocument()
|
||||||
const location$ = watchLocation()
|
const location$ = watchLocation()
|
||||||
|
|
||||||
|
/* Set up user interface observables */
|
||||||
const base$ = watchLocationBase(config.base, { location$ })
|
const base$ = watchLocationBase(config.base, { location$ })
|
||||||
const hash$ = watchLocationHash()
|
const hash$ = watchLocationHash()
|
||||||
const viewport$ = watchViewport()
|
const viewport$ = watchViewport()
|
||||||
const tablet$ = watchMedia("(min-width: 960px)")
|
const tablet$ = watchMedia("(min-width: 960px)")
|
||||||
const screen$ = watchMedia("(min-width: 1220px)")
|
const screen$ = watchMedia("(min-width: 1220px)")
|
||||||
|
|
||||||
/* Set up document observable */
|
|
||||||
const document$ = config.features.includes("instant")
|
|
||||||
? watchDocument({ location$ })
|
|
||||||
: watchDocument()
|
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------- */
|
/* ----------------------------------------------------------------------- */
|
||||||
|
|
||||||
/* Set up component bindings */
|
/* Set up component bindings */
|
||||||
@ -256,7 +251,6 @@ export function initialize(config: unknown) {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
const worker = setupSearchWorker(config.search.worker, {
|
const worker = setupSearchWorker(config.search.worker, {
|
||||||
base$, index$
|
base$, index$
|
||||||
})
|
})
|
||||||
@ -300,10 +294,9 @@ export function initialize(config: unknown) {
|
|||||||
tap(() => setToggle("search", false)),
|
tap(() => setToggle("search", false)),
|
||||||
delay(125), // ensure that it runs after the body scroll reset...
|
delay(125), // ensure that it runs after the body scroll reset...
|
||||||
)
|
)
|
||||||
.subscribe(hash => setLocationHash(`#${hash}`)) // TODO: must be unified
|
.subscribe(hash => setLocationHash(`#${hash}`))
|
||||||
|
|
||||||
// Scroll lock // document -> document$ => { body } !?
|
// TODO: scroll restoration must be centralized
|
||||||
// put into search...
|
|
||||||
combineLatest([
|
combineLatest([
|
||||||
watchToggle("search"),
|
watchToggle("search"),
|
||||||
tablet$,
|
tablet$,
|
||||||
@ -314,7 +307,7 @@ export function initialize(config: unknown) {
|
|||||||
const active = toggle && !tablet
|
const active = toggle && !tablet
|
||||||
return document$
|
return document$
|
||||||
.pipe(
|
.pipe(
|
||||||
delay(active ? 400 : 100), // TOOD: directly combine this with the hash!
|
delay(active ? 400 : 100),
|
||||||
observeOn(animationFrameScheduler),
|
observeOn(animationFrameScheduler),
|
||||||
tap(({ body }) => active
|
tap(({ body }) => active
|
||||||
? setScrollLock(body, y)
|
? setScrollLock(body, y)
|
||||||
@ -327,63 +320,40 @@ export function initialize(config: unknown) {
|
|||||||
|
|
||||||
/* ----------------------------------------------------------------------- */
|
/* ----------------------------------------------------------------------- */
|
||||||
|
|
||||||
/* Intercept internal link clicks */
|
/* Always close drawer on click */
|
||||||
const link$ = fromEvent<MouseEvent>(document.body, "click")
|
fromEvent<MouseEvent>(document.body, "click")
|
||||||
.pipe(
|
.pipe(
|
||||||
filter(ev => !(ev.metaKey || ev.ctrlKey)),
|
filter(ev => !(ev.metaKey || ev.ctrlKey)),
|
||||||
switchMap(ev => {
|
filter(ev => {
|
||||||
if (ev.target instanceof HTMLElement) {
|
if (ev.target instanceof HTMLElement) {
|
||||||
const el = ev.target.closest("a") // TODO: abstract as link click?
|
const el = ev.target.closest("a") // TODO: abstract as link click?
|
||||||
if (el && isLocalLocation(el)) {
|
if (el && isLocalLocation(el)) {
|
||||||
if (!isAnchorLocation(el) && config.features.includes("instant"))
|
return true
|
||||||
ev.preventDefault()
|
|
||||||
return of(el)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return NEVER
|
return false
|
||||||
}),
|
})
|
||||||
share()
|
|
||||||
)
|
)
|
||||||
|
.subscribe(() => {
|
||||||
|
setToggle("drawer", false)
|
||||||
|
})
|
||||||
|
|
||||||
/* Always close drawer on click */
|
/* Enable instant loading, if not on file:// protocol */
|
||||||
link$.subscribe(() => {
|
if (config.features.includes("instant") && location.protocol !== "file:")
|
||||||
setToggle("drawer", false)
|
setupInstantLoading({ document$, location$, viewport$ })
|
||||||
})
|
|
||||||
|
|
||||||
// instant loading
|
|
||||||
if (config.features.includes("instant")) {
|
|
||||||
|
|
||||||
/* Disable automatic scroll restoration, as it doesn't work nicely */
|
|
||||||
if ("scrollRestoration" in history)
|
|
||||||
history.scrollRestoration = "manual"
|
|
||||||
|
|
||||||
/* Resolve relative links for stability */
|
|
||||||
for (const selector of [
|
|
||||||
`link[rel="shortcut icon"]`,
|
|
||||||
// `link[rel="stylesheet"]` // reduce style computations
|
|
||||||
])
|
|
||||||
for (const el of getElements<HTMLLinkElement>(selector))
|
|
||||||
el.href = el.href
|
|
||||||
|
|
||||||
setupInstantLoading({
|
|
||||||
document$, link$, location$, viewport$
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------- */
|
/* ----------------------------------------------------------------------- */
|
||||||
|
|
||||||
// if we use a single tab outside of search, unhide all permalinks.
|
/* Unhide permalinks on first tab */
|
||||||
// TODO: experimental. necessary!?
|
|
||||||
keyboard$
|
keyboard$
|
||||||
.pipe(
|
.pipe(
|
||||||
filter(key => key.mode === "global" && ["Tab"].includes(key.type)),
|
filter(key => key.mode === "global" && key.type === "Tab"),
|
||||||
take(1)
|
take(1)
|
||||||
)
|
)
|
||||||
.subscribe(() => {
|
.subscribe(() => {
|
||||||
for (const link of getElements(".headerlink"))
|
for (const link of getElements(".headerlink"))
|
||||||
link.style.visibility = "visible"
|
link.style.visibility = "visible"
|
||||||
})
|
})
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------- */
|
/* ----------------------------------------------------------------------- */
|
||||||
|
|
||||||
@ -391,6 +361,7 @@ export function initialize(config: unknown) {
|
|||||||
|
|
||||||
/* Browser observables */
|
/* Browser observables */
|
||||||
document$,
|
document$,
|
||||||
|
location$,
|
||||||
viewport$,
|
viewport$,
|
||||||
|
|
||||||
/* Component observables */
|
/* Component observables */
|
||||||
@ -402,7 +373,7 @@ export function initialize(config: unknown) {
|
|||||||
tabs$,
|
tabs$,
|
||||||
toc$,
|
toc$,
|
||||||
|
|
||||||
/* Integation observables */
|
/* Integration observables */
|
||||||
clipboard$,
|
clipboard$,
|
||||||
keyboard$,
|
keyboard$,
|
||||||
dialog$
|
dialog$
|
||||||
|
@ -25,7 +25,7 @@ import { NEVER, Observable, Subject, fromEventPattern } from "rxjs"
|
|||||||
import { mapTo, share, tap } from "rxjs/operators"
|
import { mapTo, share, tap } from "rxjs/operators"
|
||||||
|
|
||||||
import { getElements } from "browser"
|
import { getElements } from "browser"
|
||||||
import { renderClipboard } from "templates"
|
import { renderClipboardButton } from "templates"
|
||||||
import { translate } from "utilities"
|
import { translate } from "utilities"
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------------
|
/* ----------------------------------------------------------------------------
|
||||||
@ -66,7 +66,7 @@ export function setupClipboard(
|
|||||||
blocks.forEach((block, index) => {
|
blocks.forEach((block, index) => {
|
||||||
const parent = block.parentElement!
|
const parent = block.parentElement!
|
||||||
parent.id = `__code_${index}`
|
parent.id = `__code_${index}`
|
||||||
parent.insertBefore(renderClipboard(parent.id), block)
|
parent.insertBefore(renderClipboardButton(parent.id), block)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -84,6 +84,6 @@ export function setupDialog(
|
|||||||
)
|
)
|
||||||
.subscribe()
|
.subscribe()
|
||||||
|
|
||||||
/* Return dialog subject */
|
/* Return dialog */
|
||||||
return dialog$
|
return dialog$
|
||||||
}
|
}
|
||||||
|
@ -20,9 +20,11 @@
|
|||||||
* IN THE SOFTWARE.
|
* IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Observable, Subject, fromEvent, merge } from "rxjs"
|
import { NEVER, Observable, Subject, fromEvent, merge, of } from "rxjs"
|
||||||
|
import { ajax } from "rxjs//ajax"
|
||||||
import {
|
import {
|
||||||
bufferCount,
|
bufferCount,
|
||||||
|
catchError,
|
||||||
debounceTime,
|
debounceTime,
|
||||||
distinctUntilChanged,
|
distinctUntilChanged,
|
||||||
distinctUntilKeyChanged,
|
distinctUntilKeyChanged,
|
||||||
@ -31,6 +33,8 @@ import {
|
|||||||
pluck,
|
pluck,
|
||||||
sample,
|
sample,
|
||||||
share,
|
share,
|
||||||
|
skip,
|
||||||
|
switchMap,
|
||||||
withLatestFrom
|
withLatestFrom
|
||||||
} from "rxjs/operators"
|
} from "rxjs/operators"
|
||||||
|
|
||||||
@ -39,7 +43,11 @@ import {
|
|||||||
ViewportOffset,
|
ViewportOffset,
|
||||||
getElement,
|
getElement,
|
||||||
isAnchorLocation,
|
isAnchorLocation,
|
||||||
|
isLocalLocation,
|
||||||
|
replaceElement,
|
||||||
|
setLocation,
|
||||||
setLocationHash,
|
setLocationHash,
|
||||||
|
setToggle,
|
||||||
setViewportOffset
|
setViewportOffset
|
||||||
} from "browser"
|
} from "browser"
|
||||||
|
|
||||||
@ -61,10 +69,9 @@ interface State {
|
|||||||
* Setup options
|
* Setup options
|
||||||
*/
|
*/
|
||||||
interface SetupOptions {
|
interface SetupOptions {
|
||||||
document$: Observable<Document> /* Document observable */
|
document$: Subject<Document> /* Document subject */
|
||||||
viewport$: Observable<Viewport> /* Viewport observable */
|
|
||||||
link$: Observable<HTMLAnchorElement> /* Internal link observable */
|
|
||||||
location$: Subject<URL> /* Location subject */
|
location$: Subject<URL> /* Location subject */
|
||||||
|
viewport$: Observable<Viewport> /* Viewport observable */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------------
|
/* ----------------------------------------------------------------------------
|
||||||
@ -74,20 +81,68 @@ interface SetupOptions {
|
|||||||
/**
|
/**
|
||||||
* Set up instant loading
|
* Set up instant loading
|
||||||
*
|
*
|
||||||
* @param options - Options
|
* When fetching, theoretically, we could use `responseType: "document"`, but
|
||||||
|
* since all MkDocs links are relative, we need to make sure that the current
|
||||||
|
* location matches the document we just loaded. Otherwise any relative links
|
||||||
|
* in the document could use the old location.
|
||||||
*
|
*
|
||||||
* @return TODO ?
|
* This is the reason why we need to synchronize history events and the process
|
||||||
|
* of fetching the document for navigation changes (except `popstate` events):
|
||||||
|
*
|
||||||
|
* 1. Fetch document via `XMLHTTPRequest`
|
||||||
|
* 2. Set new location via `history.pushState`
|
||||||
|
* 3. Parse and emit fetched document
|
||||||
|
*
|
||||||
|
* For `popstate` events, we must not use `history.pushState`, or the forward
|
||||||
|
* history will be irreversibly overwritten. In case the request fails, the
|
||||||
|
* location change is dispatched regularly.
|
||||||
|
*
|
||||||
|
* @param options - Options
|
||||||
*/
|
*/
|
||||||
export function setupInstantLoading(
|
export function setupInstantLoading(
|
||||||
{ document$, viewport$, link$, location$ }: SetupOptions
|
{ document$, viewport$, location$ }: SetupOptions
|
||||||
) { // TODO: add return type
|
): void {
|
||||||
const state$ = link$
|
|
||||||
|
/* Disable automatic scroll restoration */
|
||||||
|
if ("scrollRestoration" in history)
|
||||||
|
history.scrollRestoration = "manual"
|
||||||
|
|
||||||
|
/* Hack: ensure that reloads restore viewport offset */
|
||||||
|
fromEvent(window, "beforeunload")
|
||||||
|
.subscribe(() => {
|
||||||
|
history.scrollRestoration = "auto"
|
||||||
|
})
|
||||||
|
|
||||||
|
/* Hack: ensure absolute favicon link to omit 404s on document switch */
|
||||||
|
const favicon = getElement<HTMLLinkElement>(`link[rel="shortcut icon"]`)
|
||||||
|
if (typeof favicon !== "undefined")
|
||||||
|
favicon.href = favicon.href // tslint:disable-line no-self-assignment
|
||||||
|
|
||||||
|
/* Intercept link clicks and convert to state change */
|
||||||
|
const state$ = fromEvent<MouseEvent>(document.body, "click")
|
||||||
.pipe(
|
.pipe(
|
||||||
|
filter(ev => !(ev.metaKey || ev.ctrlKey)),
|
||||||
|
switchMap(ev => {
|
||||||
|
if (ev.target instanceof HTMLElement) {
|
||||||
|
const el = ev.target.closest("a")
|
||||||
|
if (el && isLocalLocation(el)) {
|
||||||
|
if (!isAnchorLocation(el))
|
||||||
|
ev.preventDefault()
|
||||||
|
return of(el)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NEVER
|
||||||
|
}),
|
||||||
map(el => ({ url: new URL(el.href) })),
|
map(el => ({ url: new URL(el.href) })),
|
||||||
share<State>()
|
share<State>()
|
||||||
)
|
)
|
||||||
|
|
||||||
/* Intercept internal links to dispatch */
|
/* Always close search on link click */
|
||||||
|
state$.subscribe(() => {
|
||||||
|
setToggle("search", false)
|
||||||
|
})
|
||||||
|
|
||||||
|
/* Filter state changes to dispatch */
|
||||||
const push$ = state$
|
const push$ = state$
|
||||||
.pipe(
|
.pipe(
|
||||||
distinctUntilChanged((prev, next) => prev.url.href === next.url.href),
|
distinctUntilChanged((prev, next) => prev.url.href === next.url.href),
|
||||||
@ -99,11 +154,11 @@ export function setupInstantLoading(
|
|||||||
const pop$ = fromEvent<PopStateEvent>(window, "popstate")
|
const pop$ = fromEvent<PopStateEvent>(window, "popstate")
|
||||||
.pipe(
|
.pipe(
|
||||||
filter(ev => ev.state !== null),
|
filter(ev => ev.state !== null),
|
||||||
map<PopStateEvent, State>(ev => ({
|
map(ev => ({
|
||||||
url: new URL(location.href),
|
url: new URL(location.href),
|
||||||
offset: ev.state
|
offset: ev.state
|
||||||
})),
|
})),
|
||||||
share()
|
share<State>()
|
||||||
)
|
)
|
||||||
|
|
||||||
/* Emit location change */
|
/* Emit location change */
|
||||||
@ -113,47 +168,58 @@ export function setupInstantLoading(
|
|||||||
)
|
)
|
||||||
.subscribe(location$)
|
.subscribe(location$)
|
||||||
|
|
||||||
/* History: debounce update of viewport offset */
|
/* Fetch document on location change */
|
||||||
viewport$
|
const ajax$ = location$
|
||||||
.pipe(
|
.pipe(
|
||||||
debounceTime(250),
|
distinctUntilKeyChanged("pathname"),
|
||||||
distinctUntilKeyChanged("offset")
|
skip(1),
|
||||||
|
switchMap(url => ajax({
|
||||||
|
url: url.href,
|
||||||
|
responseType: "text",
|
||||||
|
withCredentials: true
|
||||||
|
})
|
||||||
|
.pipe(
|
||||||
|
catchError(() => {
|
||||||
|
setLocation(url)
|
||||||
|
return NEVER
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
.subscribe(({ offset }) => {
|
|
||||||
history.replaceState(offset, "")
|
/* Set new location as soon as the document was fetched */
|
||||||
|
push$
|
||||||
|
.pipe(
|
||||||
|
sample(ajax$)
|
||||||
|
)
|
||||||
|
.subscribe(({ url }) => {
|
||||||
|
history.pushState({}, "", url.toString())
|
||||||
})
|
})
|
||||||
|
|
||||||
/* Apply viewport offset from history */
|
/* Parse and emit document */
|
||||||
merge(state$, pop$)
|
const dom = new DOMParser()
|
||||||
|
ajax$
|
||||||
.pipe(
|
.pipe(
|
||||||
bufferCount(2, 1),
|
map(({ response }) => dom.parseFromString(response, "text/html"))
|
||||||
filter(([prev, next]) => {
|
|
||||||
return prev.url.pathname === next.url.pathname
|
|
||||||
&& !isAnchorLocation(next.url)
|
|
||||||
}),
|
|
||||||
map(([, state]) => state)
|
|
||||||
)
|
)
|
||||||
.subscribe(({ offset }) => {
|
.subscribe(document$)
|
||||||
setViewportOffset(offset || { y: 0 })
|
|
||||||
})
|
|
||||||
|
|
||||||
/* Intercept actual instant loading */
|
/* Intercept instant loading */
|
||||||
const instant$ = merge(push$, pop$)
|
const instant$ = merge(push$, pop$)
|
||||||
.pipe(
|
.pipe(
|
||||||
sample(document$)
|
sample(document$)
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO: from here on, everything is beta.... ###############################
|
// TODO: this must be combined with search scroll restoration on mobile
|
||||||
|
|
||||||
instant$.subscribe(({ url, offset }) => {
|
instant$.subscribe(({ url, offset }) => {
|
||||||
if (url.hash && !offset) {
|
if (url.hash && !offset) {
|
||||||
// console.log("set hash!")
|
setLocationHash(url.hash)
|
||||||
setLocationHash(url.hash) // must delay, if search is open!
|
|
||||||
} else {
|
} else {
|
||||||
setViewportOffset(offset || { y: 0 })
|
setViewportOffset(offset || { y: 0 })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/* Replace document metadata */
|
||||||
instant$
|
instant$
|
||||||
.pipe(
|
.pipe(
|
||||||
withLatestFrom(document$)
|
withLatestFrom(document$)
|
||||||
@ -174,8 +240,32 @@ export function setupInstantLoading(
|
|||||||
typeof next !== "undefined" &&
|
typeof next !== "undefined" &&
|
||||||
typeof prev !== "undefined"
|
typeof prev !== "undefined"
|
||||||
) {
|
) {
|
||||||
prev.replaceWith(next)
|
replaceElement(prev, next)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/* Debounce update of viewport offset */
|
||||||
|
viewport$
|
||||||
|
.pipe(
|
||||||
|
debounceTime(250),
|
||||||
|
distinctUntilKeyChanged("offset")
|
||||||
|
)
|
||||||
|
.subscribe(({ offset }) => {
|
||||||
|
history.replaceState(offset, "")
|
||||||
|
})
|
||||||
|
|
||||||
|
/* Set viewport offset from history */
|
||||||
|
merge(state$, pop$)
|
||||||
|
.pipe(
|
||||||
|
bufferCount(2, 1),
|
||||||
|
filter(([prev, next]) => {
|
||||||
|
return prev.url.pathname === next.url.pathname
|
||||||
|
&& !isAnchorLocation(next.url)
|
||||||
|
}),
|
||||||
|
map(([, state]) => state)
|
||||||
|
)
|
||||||
|
.subscribe(({ offset }) => {
|
||||||
|
setViewportOffset(offset || { y: 0 })
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
@ -53,14 +53,14 @@ const path =
|
|||||||
*
|
*
|
||||||
* @return Element
|
* @return Element
|
||||||
*/
|
*/
|
||||||
export function renderClipboard(
|
export function renderClipboardButton(
|
||||||
id: string
|
id: string
|
||||||
) {
|
) {
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
class={css.container}
|
class={css.container}
|
||||||
title={translate("clipboard.copy")}
|
title={translate("clipboard.copy")}
|
||||||
data-clipboard-target={`#${id} code`}
|
data-clipboard-target={`#${id} > code`}
|
||||||
>
|
>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
<path d={path}></path>
|
<path d={path}></path>
|
||||||
|
Loading…
Reference in New Issue
Block a user