mirror of
https://github.com/squidfunk/mkdocs-material.git
synced 2024-06-14 11:52:32 +03:00
Refactored instant loading and some other components
This commit is contained in:
parent
884330da3b
commit
9c9ea8a64d
54
.eslintignore
Normal file
54
.eslintignore
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Node, TypeScript, Python
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Dependencies
|
||||||
|
node_modules
|
||||||
|
__pycache__
|
||||||
|
venv
|
||||||
|
|
||||||
|
# Build files
|
||||||
|
build
|
||||||
|
MANIFEST
|
||||||
|
manifest.json
|
||||||
|
site
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
webpack.config.ts
|
||||||
|
|
||||||
|
# Distribution files
|
||||||
|
dist
|
||||||
|
mkdocs_material.egg-info
|
||||||
|
|
||||||
|
# Caches and logs
|
||||||
|
*.cpuprofile
|
||||||
|
*.log
|
||||||
|
*.tsbuildinfo
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# General
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Temporary files
|
||||||
|
tmp
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -27,10 +27,6 @@ node_modules
|
|||||||
__pycache__
|
__pycache__
|
||||||
venv
|
venv
|
||||||
|
|
||||||
# Coverage reports
|
|
||||||
.nyc_output
|
|
||||||
coverage
|
|
||||||
|
|
||||||
# Build files
|
# Build files
|
||||||
build
|
build
|
||||||
MANIFEST
|
MANIFEST
|
||||||
|
2
material/assets/javascripts/bundle.15008d17.min.js
vendored
Normal file
2
material/assets/javascripts/bundle.15008d17.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
material/assets/javascripts/bundle.15008d17.min.js.map
Normal file
1
material/assets/javascripts/bundle.15008d17.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
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.e4dae721.min.js.map
Normal file
1
material/assets/javascripts/vendor.e4dae721.min.js.map
Normal file
File diff suppressed because one or more lines are too long
@ -1,14 +1,14 @@
|
|||||||
{
|
{
|
||||||
"assets/javascripts/bundle.js": "assets/javascripts/bundle.1ed0f3ef.min.js",
|
"assets/javascripts/bundle.js": "assets/javascripts/bundle.15008d17.min.js",
|
||||||
"assets/javascripts/bundle.js.map": "assets/javascripts/bundle.1ed0f3ef.min.js.map",
|
"assets/javascripts/bundle.js.map": "assets/javascripts/bundle.15008d17.min.js.map",
|
||||||
"assets/javascripts/vendor.js": "assets/javascripts/vendor.250c9a34.min.js",
|
"assets/javascripts/vendor.js": "assets/javascripts/vendor.e4dae721.min.js",
|
||||||
"assets/javascripts/vendor.js.map": "assets/javascripts/vendor.250c9a34.min.js.map",
|
"assets/javascripts/vendor.js.map": "assets/javascripts/vendor.e4dae721.min.js.map",
|
||||||
"assets/javascripts/worker/search.js": "assets/javascripts/worker/search.b9424174.min.js",
|
"assets/javascripts/worker/search.js": "assets/javascripts/worker/search.b9424174.min.js",
|
||||||
"assets/javascripts/worker/search.js.map": "assets/javascripts/worker/search.b9424174.min.js.map",
|
"assets/javascripts/worker/search.js.map": "assets/javascripts/worker/search.b9424174.min.js.map",
|
||||||
"assets/stylesheets/main.css": "assets/stylesheets/main.1e9203c3.min.css",
|
"assets/stylesheets/main.css": "assets/stylesheets/main.77762a86.min.css",
|
||||||
"assets/stylesheets/main.css.map": "assets/stylesheets/main.1e9203c3.min.css.map",
|
"assets/stylesheets/main.css.map": "assets/stylesheets/main.77762a86.min.css.map",
|
||||||
"assets/stylesheets/overrides.css": "assets/stylesheets/overrides.470c2618.min.css",
|
"assets/stylesheets/overrides.css": "assets/stylesheets/overrides.177d4d59.min.css",
|
||||||
"assets/stylesheets/overrides.css.map": "assets/stylesheets/overrides.470c2618.min.css.map",
|
"assets/stylesheets/overrides.css.map": "assets/stylesheets/overrides.177d4d59.min.css.map",
|
||||||
"assets/stylesheets/palette.css": "assets/stylesheets/palette.a4a42c2a.min.css",
|
"assets/stylesheets/palette.css": "assets/stylesheets/palette.07fb9723.min.css",
|
||||||
"assets/stylesheets/palette.css.map": "assets/stylesheets/palette.a4a42c2a.min.css.map"
|
"assets/stylesheets/palette.css.map": "assets/stylesheets/palette.07fb9723.min.css.map"
|
||||||
}
|
}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,3 +1,3 @@
|
|||||||
@-webkit-keyframes tx-heart{0%,40%,80%,100%{transform:scale(1)}20%,60%{transform:scale(1.15)}}@keyframes tx-heart{0%,40%,80%,100%{transform:scale(1)}20%,60%{transform:scale(1.15)}}.md-typeset figure>p+figcaption{margin-top:-1.2rem}.md-typeset .twitter{color:#00acee}.md-typeset .tx-video{width:auto}.md-typeset .tx-video__inner{position:relative;width:100%;height:0;padding-bottom:56.138%}.md-typeset .tx-video iframe{position:absolute;top:0;left:0;width:100%;height:100%;overflow:hidden;border:none}.md-typeset .tx-heart{-webkit-animation:tx-heart 1000ms infinite;animation:tx-heart 1000ms infinite}.md-typeset .tx-insiders{color:#e91e63}.md-typeset .tx-insiders-button{font-weight:400}.md-typeset .tx-insiders-count{font-weight:700}.md-typeset .tx-insiders-list{margin:2em 0;overflow:auto}.md-typeset .tx-insiders-list__item{display:block;float:left;width:3rem;height:3rem;margin:.2rem;overflow:hidden;border-radius:100%;transform:scale(1);transition:color 125ms,transform 125ms}.md-typeset .tx-insiders-list__item img{display:block;width:100%;height:auto;-webkit-filter:grayscale(100%);filter:grayscale(100%);transition:-webkit-filter 125ms;transition:filter 125ms;transition:filter 125ms, -webkit-filter 125ms}.md-typeset .tx-insiders-list__item:focus,.md-typeset .tx-insiders-list__item:hover{transform:scale(1.1)}.md-typeset .tx-insiders-list__item:focus img,.md-typeset .tx-insiders-list__item:hover img{-webkit-filter:grayscale(0%);filter:grayscale(0%)}.md-typeset .tx-insiders-list__item--private{color:var(--md-default-fg-color--lighter);font-weight:700;font-size:1.2rem;line-height:3rem;text-align:center;background:var(--md-default-fg-color--lightest)}.md-typeset .tx-switch button{cursor:pointer;transition:opacity 250ms}.md-typeset .tx-switch button:focus,.md-typeset .tx-switch button:hover{opacity:.75}.md-typeset .tx-switch button>code{display:block;color:var(--md-primary-bg-color);background-color:var(--md-primary-fg-color)}.md-typeset .tx-columns ol,.md-typeset .tx-columns ul{-moz-columns:2;columns:2}@media screen and (max-width: 29.9375em){.md-typeset .tx-columns ol,.md-typeset .tx-columns ul{-moz-columns:initial;columns:initial}}.md-typeset .tx-columns li{-moz-column-break-inside:avoid;break-inside:avoid}.md-announce a,.md-announce a:focus,.md-announce a:hover{color:currentColor}.md-announce strong{white-space:nowrap}.md-announce .twitter{margin-left:.2em}.tx-content__footer{margin-top:1rem;text-align:center}.tx-content__footer a{display:inline-block;color:#e91e63;transition:transform 250ms cubic-bezier(0.1, 0.7, 0.1, 1),color 125ms}.tx-content__footer a:focus,.tx-content__footer a:hover{transform:scale(1.2)}.tx-content__footer hr{display:inline-block;width:2rem;margin:1em;vertical-align:middle;background-color:currentColor;border:none}.tx-container{padding-top:1rem;background:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1123 258'><path d='M1124,2c0,0 0,256 0,256l-1125,0l0,-48c0,0 16,5 55,5c116,0 197,-92 325,-92c121,0 114,46 254,46c140,0 214,-167 572,-166Z' style='fill: hsla(0, 0%, 100%, 1)' /></svg>") no-repeat bottom,linear-gradient(to bottom, var(--md-primary-fg-color), #a63fd9 99%, var(--md-default-bg-color) 99%)}[data-md-color-scheme=slate] .tx-container{background:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1123 258'><path d='M1124,2c0,0 0,256 0,256l-1125,0l0,-48c0,0 16,5 55,5c116,0 197,-92 325,-92c121,0 114,46 254,46c140,0 214,-167 572,-166Z' style='fill: hsla(232, 15%, 21%, 1)' /></svg>") no-repeat bottom,linear-gradient(to bottom, var(--md-primary-fg-color), #a63fd9 99%, var(--md-default-bg-color) 99%)}.tx-hero{margin:0 .8rem;color:var(--md-primary-bg-color)}.tx-hero h1{margin-bottom:1rem;color:currentColor;font-weight:700}@media screen and (max-width: 29.9375em){.tx-hero h1{font-size:1.4rem}}.tx-hero__content{padding-bottom:6rem}@media screen and (min-width: 60em){.tx-hero{display:flex;align-items:stretch}.tx-hero__content{max-width:19rem;margin-top:3.5rem;padding-bottom:14vw}.tx-hero__image{order:1;width:38rem;transform:translateX(4rem)}}@media screen and (min-width: 76.25em){.tx-hero__image{transform:translateX(8rem)}}.tx-hero .md-button{margin-top:.5rem;margin-right:.5rem;color:var(--md-primary-bg-color)}.tx-hero .md-button:focus,.tx-hero .md-button:hover{color:var(--md-default-bg-color);background-color:var(--md-accent-fg-color);border-color:var(--md-accent-fg-color)}.tx-hero .md-button--primary{color:#894da8;background-color:var(--md-primary-bg-color);border-color:var(--md-primary-bg-color)}
|
@-webkit-keyframes tx-heart{0%,40%,80%,100%{transform:scale(1)}20%,60%{transform:scale(1.15)}}@keyframes tx-heart{0%,40%,80%,100%{transform:scale(1)}20%,60%{transform:scale(1.15)}}.md-typeset figure>p+figcaption{margin-top:-1.2rem}.md-typeset .twitter{color:#00acee}.md-typeset .tx-video{width:auto}.md-typeset .tx-video__inner{position:relative;width:100%;height:0;padding-bottom:56.138%}.md-typeset .tx-video iframe{position:absolute;top:0;left:0;width:100%;height:100%;overflow:hidden;border:none}.md-typeset .tx-heart{-webkit-animation:tx-heart 1000ms infinite;animation:tx-heart 1000ms infinite}.md-typeset .tx-insiders{color:#e91e63}.md-typeset .tx-insiders-button{font-weight:400}.md-typeset .tx-insiders-count{font-weight:700}.md-typeset .tx-insiders-list{margin:2em 0;overflow:auto}.md-typeset .tx-insiders-list__item{display:block;float:left;width:3rem;height:3rem;margin:.2rem;overflow:hidden;border-radius:100%;transform:scale(1);transition:color 125ms,transform 125ms}.md-typeset .tx-insiders-list__item img{display:block;width:100%;height:auto;-webkit-filter:grayscale(100%);filter:grayscale(100%);transition:-webkit-filter 125ms;transition:filter 125ms;transition:filter 125ms, -webkit-filter 125ms}.md-typeset .tx-insiders-list__item:focus,.md-typeset .tx-insiders-list__item:hover{transform:scale(1.1)}.md-typeset .tx-insiders-list__item:focus img,.md-typeset .tx-insiders-list__item:hover img{-webkit-filter:grayscale(0%);filter:grayscale(0%)}.md-typeset .tx-insiders-list__item--private{color:var(--md-default-fg-color--lighter);font-weight:700;font-size:1.2rem;line-height:3rem;text-align:center;background:var(--md-default-fg-color--lightest)}.md-typeset .tx-switch button{cursor:pointer;transition:opacity 250ms}.md-typeset .tx-switch button:focus,.md-typeset .tx-switch button:hover{opacity:.75}.md-typeset .tx-switch button>code{display:block;color:var(--md-primary-bg-color);background-color:var(--md-primary-fg-color)}.md-typeset .tx-columns ol,.md-typeset .tx-columns ul{-moz-columns:2;columns:2}@media screen and (max-width: 29.9375em){.md-typeset .tx-columns ol,.md-typeset .tx-columns ul{-moz-columns:initial;columns:initial}}.md-typeset .tx-columns li{-moz-column-break-inside:avoid;break-inside:avoid}.md-announce a,.md-announce a:focus,.md-announce a:hover{color:currentColor}.md-announce strong{white-space:nowrap}.md-announce .twitter{margin-left:.2em}.tx-content__footer{margin-top:1rem;text-align:center}.tx-content__footer a{display:inline-block;color:#e91e63;transition:transform 250ms cubic-bezier(0.1, 0.7, 0.1, 1),color 125ms}.tx-content__footer a:focus,.tx-content__footer a:hover{transform:scale(1.2)}.tx-content__footer hr{display:inline-block;width:2rem;margin:1em;vertical-align:middle;background-color:currentColor;border:none}.tx-container{padding-top:1rem;background:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1123 258'><path d='M1124,2c0,0 0,256 0,256l-1125,0l0,-48c0,0 16,5 55,5c116,0 197,-92 325,-92c121,0 114,46 254,46c140,0 214,-167 572,-166Z' style='fill: hsla(0, 0%, 100%, 1)' /></svg>") no-repeat bottom,linear-gradient(to bottom, var(--md-primary-fg-color), #a63fd9 99%, var(--md-default-bg-color) 99%)}[data-md-color-scheme=slate] .tx-container{background:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1123 258'><path d='M1124,2c0,0 0,256 0,256l-1125,0l0,-48c0,0 16,5 55,5c116,0 197,-92 325,-92c121,0 114,46 254,46c140,0 214,-167 572,-166Z' style='fill: hsla(232, 15%, 21%, 1)' /></svg>") no-repeat bottom,linear-gradient(to bottom, var(--md-primary-fg-color), #a63fd9 99%, var(--md-default-bg-color) 99%)}.tx-hero{margin:0 .8rem;color:var(--md-primary-bg-color)}.tx-hero h1{margin-bottom:1rem;color:currentColor;font-weight:700}@media screen and (max-width: 29.9375em){.tx-hero h1{font-size:1.4rem}}.tx-hero__content{padding-bottom:6rem}@media screen and (min-width: 60em){.tx-hero{display:flex;align-items:stretch}.tx-hero__content{max-width:19rem;margin-top:3.5rem;padding-bottom:14vw}.tx-hero__image{order:1;width:38rem;transform:translateX(4rem)}}@media screen and (min-width: 76.25em){.tx-hero__image{transform:translateX(8rem)}}.tx-hero .md-button{margin-top:.5rem;margin-right:.5rem;color:var(--md-primary-bg-color)}.tx-hero .md-button:focus,.tx-hero .md-button:hover{color:var(--md-default-bg-color);background-color:var(--md-accent-fg-color);border-color:var(--md-accent-fg-color)}.tx-hero .md-button--primary{color:#894da8;background-color:var(--md-primary-bg-color);border-color:var(--md-primary-bg-color)}
|
||||||
|
|
||||||
/*# sourceMappingURL=overrides.470c2618.min.css.map*/
|
/*# sourceMappingURL=overrides.177d4d59.min.css.map*/
|
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
@ -39,10 +39,10 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block styles %}
|
{% block styles %}
|
||||||
<link rel="stylesheet" href="{{ 'assets/stylesheets/main.1e9203c3.min.css' | url }}">
|
<link rel="stylesheet" href="{{ 'assets/stylesheets/main.77762a86.min.css' | url }}">
|
||||||
{% if config.theme.palette %}
|
{% if config.theme.palette %}
|
||||||
{% set palette = config.theme.palette %}
|
{% set palette = config.theme.palette %}
|
||||||
<link rel="stylesheet" href="{{ 'assets/stylesheets/palette.a4a42c2a.min.css' | url }}">
|
<link rel="stylesheet" href="{{ 'assets/stylesheets/palette.07fb9723.min.css' | url }}">
|
||||||
{% if palette.primary %}
|
{% if palette.primary %}
|
||||||
{% import "partials/palette.html" as map %}
|
{% import "partials/palette.html" as map %}
|
||||||
{% set primary = map.primary(
|
{% set primary = map.primary(
|
||||||
@ -182,10 +182,10 @@
|
|||||||
{% block footer %}
|
{% block footer %}
|
||||||
{% include "partials/footer.html" %}
|
{% include "partials/footer.html" %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
</div>
|
||||||
<div class="md-dialog" data-md-component="dialog">
|
<div class="md-dialog" data-md-component="dialog">
|
||||||
<div class="md-dialog__inner md-typeset"></div>
|
<div class="md-dialog__inner md-typeset"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
{% block config %}
|
{% block config %}
|
||||||
{%- set app = {
|
{%- set app = {
|
||||||
"base": base_url,
|
"base": base_url,
|
||||||
@ -216,8 +216,8 @@
|
|||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
<script src="{{ 'assets/javascripts/vendor.250c9a34.min.js' | url }}"></script>
|
<script src="{{ 'assets/javascripts/vendor.e4dae721.min.js' | url }}"></script>
|
||||||
<script src="{{ 'assets/javascripts/bundle.1ed0f3ef.min.js' | url }}"></script>
|
<script src="{{ 'assets/javascripts/bundle.15008d17.min.js' | url }}"></script>
|
||||||
{% for path in config["extra_javascript"] %}
|
{% for path in config["extra_javascript"] %}
|
||||||
<script src="{{ path | url }}"></script>
|
<script src="{{ path | url }}"></script>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
<meta name="twitter:title" content="{{ title }}">
|
<meta name="twitter:title" content="{{ title }}">
|
||||||
<meta name="twitter:description" content="{{ config.site_description }}">
|
<meta name="twitter:description" content="{{ config.site_description }}">
|
||||||
<meta name="twitter:image" content="{{ image }}">
|
<meta name="twitter:image" content="{{ image }}">
|
||||||
<link rel="stylesheet" href="{{ 'assets/stylesheets/overrides.470c2618.min.css' | url }}">
|
<link rel="stylesheet" href="{{ 'assets/stylesheets/overrides.177d4d59.min.css' | url }}">
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block announce %}
|
{% block announce %}
|
||||||
<a href="https://twitter.com/squidfunk">
|
<a href="https://twitter.com/squidfunk">
|
||||||
|
@ -51,7 +51,7 @@ theme:
|
|||||||
# Default values, taken from mkdocs_theme.yml
|
# Default values, taken from mkdocs_theme.yml
|
||||||
language: en
|
language: en
|
||||||
features:
|
features:
|
||||||
# - navigation.instant
|
- navigation.instant
|
||||||
- navigation.sections
|
- navigation.sections
|
||||||
- navigation.tabs
|
- navigation.tabs
|
||||||
palette:
|
palette:
|
||||||
|
@ -22,10 +22,10 @@
|
|||||||
|
|
||||||
export * from "./document"
|
export * from "./document"
|
||||||
export * from "./element"
|
export * from "./element"
|
||||||
export * from "./fetch"
|
|
||||||
export * from "./keyboard"
|
export * from "./keyboard"
|
||||||
export * from "./location"
|
export * from "./location"
|
||||||
export * from "./media"
|
export * from "./media"
|
||||||
|
export * from "./request"
|
||||||
export * from "./toggle"
|
export * from "./toggle"
|
||||||
export * from "./viewport"
|
export * from "./viewport"
|
||||||
export * from "./worker"
|
export * from "./worker"
|
||||||
|
@ -20,8 +20,14 @@
|
|||||||
* IN THE SOFTWARE.
|
* IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Observable, fromEvent, merge } from "rxjs"
|
import { NEVER, Observable, fromEvent, merge } from "rxjs"
|
||||||
import { filter, map, mapTo, startWith } from "rxjs/operators"
|
import {
|
||||||
|
filter,
|
||||||
|
map,
|
||||||
|
mapTo,
|
||||||
|
startWith,
|
||||||
|
switchMap
|
||||||
|
} from "rxjs/operators"
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------------
|
/* ----------------------------------------------------------------------------
|
||||||
* Functions
|
* Functions
|
||||||
@ -57,3 +63,24 @@ export function watchPrint(): Observable<void> {
|
|||||||
mapTo(undefined)
|
mapTo(undefined)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle an observable with another one
|
||||||
|
*
|
||||||
|
* @template T - Data type
|
||||||
|
*
|
||||||
|
* @param toggle$ - Toggle observable
|
||||||
|
* @param factory - Observable factory
|
||||||
|
*
|
||||||
|
* @returns Toggled observable
|
||||||
|
*/
|
||||||
|
export function at<T>(
|
||||||
|
toggle$: Observable<boolean>, factory: () => Observable<T>
|
||||||
|
): Observable<T> {
|
||||||
|
return toggle$
|
||||||
|
.pipe(
|
||||||
|
switchMap(active => active ? factory() : NEVER)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@ -28,21 +28,29 @@ import {
|
|||||||
switchMap
|
switchMap
|
||||||
} from "rxjs/operators"
|
} from "rxjs/operators"
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------------
|
|
||||||
* Data
|
|
||||||
* ------------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* XML parser
|
|
||||||
*/
|
|
||||||
const dom = new DOMParser()
|
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------------
|
/* ----------------------------------------------------------------------------
|
||||||
* Functions
|
* Functions
|
||||||
* ------------------------------------------------------------------------- */
|
* ------------------------------------------------------------------------- */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch given URL as JSON
|
* Fetch the given URL
|
||||||
|
*
|
||||||
|
* @param url - Request URL
|
||||||
|
* @param options - Request options
|
||||||
|
*
|
||||||
|
* @returns Response observable
|
||||||
|
*/
|
||||||
|
export function request(
|
||||||
|
url: string, options: RequestInit = { credentials: "same-origin" }
|
||||||
|
): Observable<Response> {
|
||||||
|
return from(fetch(url, options))
|
||||||
|
.pipe(
|
||||||
|
filter(res => res.status === 200),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch JSON from the given URL
|
||||||
*
|
*
|
||||||
* @template T - Data type
|
* @template T - Data type
|
||||||
*
|
*
|
||||||
@ -51,32 +59,31 @@ const dom = new DOMParser()
|
|||||||
*
|
*
|
||||||
* @returns Data observable
|
* @returns Data observable
|
||||||
*/
|
*/
|
||||||
export function fetchJSON<T>(
|
export function requestJSON<T>(
|
||||||
url: string, options: RequestInit = { credentials: "same-origin" }
|
url: string, options?: RequestInit
|
||||||
): Observable<T> {
|
): Observable<T> {
|
||||||
return from(fetch(url, options))
|
return request(url, options)
|
||||||
.pipe(
|
.pipe(
|
||||||
filter(res => res.status === 200),
|
|
||||||
switchMap(res => res.json()),
|
switchMap(res => res.json()),
|
||||||
shareReplay(1)
|
shareReplay(1)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch given URL as XML
|
* Fetch XML from the given URL
|
||||||
*
|
*
|
||||||
* @param url - Request URL
|
* @param url - Request URL
|
||||||
* @param options - Request options
|
* @param options - Request options
|
||||||
*
|
*
|
||||||
* @returns Data observable
|
* @returns Data observable
|
||||||
*/
|
*/
|
||||||
export function fetchXML(
|
export function requestXML(
|
||||||
url: string, options: RequestInit = { credentials: "same-origin" }
|
url: string, options?: RequestInit
|
||||||
): Observable<Document> {
|
): Observable<Document> {
|
||||||
return from(fetch(url, options))
|
const dom = new DOMParser()
|
||||||
|
return request(url, options)
|
||||||
.pipe(
|
.pipe(
|
||||||
filter(res => res.status === 200),
|
switchMap(res => res.text()),
|
||||||
switchMap(res => res.json()),
|
|
||||||
map(res => dom.parseFromString(res, "text/xml")),
|
map(res => dom.parseFromString(res, "text/xml")),
|
||||||
shareReplay(1)
|
shareReplay(1)
|
||||||
)
|
)
|
@ -64,14 +64,14 @@ export interface Dialog {
|
|||||||
* Watch options
|
* Watch options
|
||||||
*/
|
*/
|
||||||
interface WatchOptions {
|
interface WatchOptions {
|
||||||
message$: Subject<string> /* Message subject */
|
alert$: Subject<string> /* Alert subject */
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mount options
|
* Mount options
|
||||||
*/
|
*/
|
||||||
interface MountOptions {
|
interface MountOptions {
|
||||||
message$: Subject<string> /* Message subject */
|
alert$: Subject<string> /* Alert subject */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------------
|
/* ----------------------------------------------------------------------------
|
||||||
@ -87,9 +87,9 @@ interface MountOptions {
|
|||||||
* @returns Dialog observable
|
* @returns Dialog observable
|
||||||
*/
|
*/
|
||||||
export function watchDialog(
|
export function watchDialog(
|
||||||
_el: HTMLElement, { message$ }: WatchOptions
|
_el: HTMLElement, { alert$ }: WatchOptions
|
||||||
): Observable<Dialog> {
|
): Observable<Dialog> {
|
||||||
return message$
|
return alert$
|
||||||
.pipe(
|
.pipe(
|
||||||
switchMap(message => merge(
|
switchMap(message => merge(
|
||||||
of(true),
|
of(true),
|
||||||
@ -111,7 +111,7 @@ export function watchDialog(
|
|||||||
* @returns Dialog component observable
|
* @returns Dialog component observable
|
||||||
*/
|
*/
|
||||||
export function mountDialog(
|
export function mountDialog(
|
||||||
el: HTMLElement, { message$ }: MountOptions
|
el: HTMLElement, options: MountOptions
|
||||||
): Observable<Component<Dialog>> {
|
): Observable<Component<Dialog>> {
|
||||||
const internal$ = new Subject<Dialog>()
|
const internal$ = new Subject<Dialog>()
|
||||||
internal$
|
internal$
|
||||||
@ -127,7 +127,7 @@ export function mountDialog(
|
|||||||
})
|
})
|
||||||
|
|
||||||
/* Create and return component */
|
/* Create and return component */
|
||||||
return watchDialog(el, { message$ })
|
return watchDialog(el, options)
|
||||||
.pipe(
|
.pipe(
|
||||||
tap(internal$),
|
tap(internal$),
|
||||||
finalize(() => internal$.complete()),
|
finalize(() => internal$.complete()),
|
||||||
|
@ -97,7 +97,8 @@ export function watchHeader(
|
|||||||
distinctUntilChanged((a, b) => (
|
distinctUntilChanged((a, b) => (
|
||||||
a.sticky === b.sticky &&
|
a.sticky === b.sticky &&
|
||||||
a.height === b.height
|
a.height === b.height
|
||||||
))
|
)),
|
||||||
|
shareReplay(1)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,7 +135,6 @@ export function mountHeader(
|
|||||||
main$.subscribe(main => internal$.next(main))
|
main$.subscribe(main => internal$.next(main))
|
||||||
return header$
|
return header$
|
||||||
.pipe(
|
.pipe(
|
||||||
map(state => ({ ref: el, ...state })),
|
map(state => ({ ref: el, ...state }))
|
||||||
shareReplay(1)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,6 @@ import {
|
|||||||
distinctUntilChanged,
|
distinctUntilChanged,
|
||||||
distinctUntilKeyChanged,
|
distinctUntilKeyChanged,
|
||||||
map,
|
map,
|
||||||
shareReplay,
|
|
||||||
switchMap
|
switchMap
|
||||||
} from "rxjs/operators"
|
} from "rxjs/operators"
|
||||||
|
|
||||||
@ -120,7 +119,6 @@ export function watchMain(
|
|||||||
a.offset === b.offset &&
|
a.offset === b.offset &&
|
||||||
a.height === b.height &&
|
a.height === b.height &&
|
||||||
a.active === b.active
|
a.active === b.active
|
||||||
)),
|
))
|
||||||
shareReplay(1)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@ import { Observable, merge } from "rxjs"
|
|||||||
import { filter, sample, take } from "rxjs/operators"
|
import { filter, sample, take } from "rxjs/operators"
|
||||||
|
|
||||||
import { configuration } from "~/_"
|
import { configuration } from "~/_"
|
||||||
import { fetchJSON, getElementOrThrow } from "~/browser"
|
import { requestJSON, getElementOrThrow } from "~/browser"
|
||||||
import {
|
import {
|
||||||
SearchIndex,
|
SearchIndex,
|
||||||
isSearchQueryMessage,
|
isSearchQueryMessage,
|
||||||
@ -59,7 +59,7 @@ export type Search =
|
|||||||
* @returns Promise resolving with search index
|
* @returns Promise resolving with search index
|
||||||
*/
|
*/
|
||||||
function fetchSearchIndex(url: string) {
|
function fetchSearchIndex(url: string) {
|
||||||
return __search?.index || fetchJSON<SearchIndex>(url)
|
return __search?.index || requestJSON<SearchIndex>(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------------
|
/* ----------------------------------------------------------------------------
|
||||||
|
@ -24,7 +24,7 @@ import { Repo, User } from "github-types"
|
|||||||
import { Observable } from "rxjs"
|
import { Observable } from "rxjs"
|
||||||
import { defaultIfEmpty, map } from "rxjs/operators"
|
import { defaultIfEmpty, map } from "rxjs/operators"
|
||||||
|
|
||||||
import { fetchJSON } from "~/browser"
|
import { requestJSON } from "~/browser"
|
||||||
import { round } from "~/utilities"
|
import { round } from "~/utilities"
|
||||||
|
|
||||||
import { SourceFacts } from "../_"
|
import { SourceFacts } from "../_"
|
||||||
@ -47,7 +47,7 @@ export function fetchSourceFactsFromGitHub(
|
|||||||
const url = typeof repo !== "undefined"
|
const url = typeof repo !== "undefined"
|
||||||
? `https://api.github.com/repos/${user}/${repo}`
|
? `https://api.github.com/repos/${user}/${repo}`
|
||||||
: `https://api.github.com/users/${user}`
|
: `https://api.github.com/users/${user}`
|
||||||
return fetchJSON<Repo & User>(url)
|
return requestJSON<Repo & User>(url)
|
||||||
.pipe(
|
.pipe(
|
||||||
map(data => {
|
map(data => {
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ import { ProjectSchema } from "gitlab"
|
|||||||
import { Observable } from "rxjs"
|
import { Observable } from "rxjs"
|
||||||
import { defaultIfEmpty, map } from "rxjs/operators"
|
import { defaultIfEmpty, map } from "rxjs/operators"
|
||||||
|
|
||||||
import { fetchJSON } from "~/browser"
|
import { requestJSON } from "~/browser"
|
||||||
import { round } from "~/utilities"
|
import { round } from "~/utilities"
|
||||||
|
|
||||||
import { SourceFacts } from "../_"
|
import { SourceFacts } from "../_"
|
||||||
@ -45,7 +45,7 @@ export function fetchSourceFactsFromGitLab(
|
|||||||
base: string, project: string
|
base: string, project: string
|
||||||
): Observable<SourceFacts> {
|
): Observable<SourceFacts> {
|
||||||
const url = `https://${base}/api/v4/projects/${encodeURIComponent(project)}`
|
const url = `https://${base}/api/v4/projects/${encodeURIComponent(project)}`
|
||||||
return fetchJSON<ProjectSchema>(url)
|
return requestJSON<ProjectSchema>(url)
|
||||||
.pipe(
|
.pipe(
|
||||||
map(({ star_count, forks_count }) => ([
|
map(({ star_count, forks_count }) => ([
|
||||||
`${round(star_count)} Stars`,
|
`${round(star_count)} Stars`,
|
||||||
|
@ -21,12 +21,21 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import "focus-visible"
|
import "focus-visible"
|
||||||
import { NEVER, Observable, Subject, merge } from "rxjs"
|
import { Subject, defer, merge } from "rxjs"
|
||||||
import { switchMap } from "rxjs/operators"
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
map,
|
||||||
|
mergeWith,
|
||||||
|
shareReplay,
|
||||||
|
switchMap
|
||||||
|
} from "rxjs/operators"
|
||||||
|
|
||||||
|
import { feature } from "./_"
|
||||||
|
import {
|
||||||
|
at,
|
||||||
getElementOrThrow,
|
getElementOrThrow,
|
||||||
getElements,
|
getElements,
|
||||||
|
watchDocument,
|
||||||
|
watchLocation,
|
||||||
watchLocationTarget,
|
watchLocationTarget,
|
||||||
watchMedia,
|
watchMedia,
|
||||||
watchPrint,
|
watchPrint,
|
||||||
@ -46,7 +55,8 @@ import {
|
|||||||
watchMain
|
watchMain
|
||||||
} from "./components"
|
} from "./components"
|
||||||
import {
|
import {
|
||||||
setupClipboardJS
|
setupClipboardJS,
|
||||||
|
setupInstantLoading
|
||||||
} from "./integrations"
|
} from "./integrations"
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------------
|
/* ----------------------------------------------------------------------------
|
||||||
@ -57,61 +67,53 @@ import {
|
|||||||
document.documentElement.classList.remove("no-js")
|
document.documentElement.classList.remove("no-js")
|
||||||
document.documentElement.classList.add("js")
|
document.documentElement.classList.add("js")
|
||||||
|
|
||||||
/* Set up subjects */
|
/* Set up navigation observables */
|
||||||
|
const document$ = watchDocument()
|
||||||
|
const location$ = watchLocation()
|
||||||
const target$ = watchLocationTarget()
|
const target$ = watchLocationTarget()
|
||||||
|
|
||||||
/* Set up user interface observables */
|
/* Set up media observables */
|
||||||
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)")
|
||||||
const print$ = watchPrint()
|
const print$ = watchPrint()
|
||||||
|
|
||||||
// these elements MUST be available
|
|
||||||
const header = getElementOrThrow("[data-md-component=header]")
|
|
||||||
const main = getElementOrThrow("[data-md-component=main]")
|
|
||||||
|
|
||||||
const header$ = watchHeader(header)
|
|
||||||
const main$ = watchMain(main, { header$, viewport$ })
|
|
||||||
|
|
||||||
/* Set up Clipboard.js integration */
|
/* Set up Clipboard.js integration */
|
||||||
const message$ = new Subject<string>()
|
const alert$ = new Subject<string>()
|
||||||
setupClipboardJS({ message$ })
|
setupClipboardJS({ alert$ })
|
||||||
|
|
||||||
// TODO: watchElements + general mount function that takes a factory...
|
/* Set up instant loading, if enabled */
|
||||||
// + a toggle function (optionally)
|
if (feature("navigation.instant"))
|
||||||
|
setupInstantLoading({ document$, location$, viewport$ })
|
||||||
|
|
||||||
// TODO: catch errors on all components when mounting, so one error doesn't
|
/* Set up header observable */
|
||||||
// take down the whole site.
|
const header$ = watchHeader(
|
||||||
|
getElementOrThrow("[data-md-component=header]")
|
||||||
|
)
|
||||||
|
|
||||||
const app$ = merge(
|
/* Set up main area observable */
|
||||||
|
const main$ = document$
|
||||||
|
.pipe(
|
||||||
|
map(() => getElementOrThrow("[data-md-component=main]")),
|
||||||
|
switchMap(el => watchMain(el, { viewport$, header$ })),
|
||||||
|
shareReplay(1)
|
||||||
|
)
|
||||||
|
|
||||||
/* Content */
|
/* Set up control component observables */
|
||||||
...getElements("[data-md-component=content]")
|
const control$ = merge(
|
||||||
.map(child => mountContent(child, { target$, viewport$, print$ })),
|
|
||||||
|
|
||||||
/* Dialog */
|
/* Dialog */
|
||||||
...getElements("[data-md-component=dialog]")
|
...getElements("[data-md-component=dialog]")
|
||||||
.map(child => mountDialog(child, { message$ })),
|
.map(child => mountDialog(child, { alert$ })),
|
||||||
|
|
||||||
/* Header */
|
/* Header */
|
||||||
...getElements("[data-md-component=header]")
|
...getElements("[data-md-component=header]")
|
||||||
.map(child => mountHeader(child, { viewport$, header$, main$ })),
|
.map(child => mountHeader(child, { viewport$, header$, main$ })),
|
||||||
|
|
||||||
/* Header title */
|
|
||||||
...getElements("[data-md-component=header-title]")
|
|
||||||
.map(child => mountHeaderTitle(child, { viewport$, header$ })),
|
|
||||||
|
|
||||||
/* Search */
|
/* Search */
|
||||||
...getElements("[data-md-component=search]")
|
...getElements("[data-md-component=search]")
|
||||||
.map(child => mountSearch(child)),
|
.map(child => mountSearch(child)),
|
||||||
|
|
||||||
/* Sidebar */
|
|
||||||
...getElements("[data-md-component=sidebar]")
|
|
||||||
.map(child => child.getAttribute("data-md-type") === "navigation"
|
|
||||||
? at(screen$, () => mountSidebar(child, { viewport$, header$, main$ }))
|
|
||||||
: at(tablet$, () => mountSidebar(child, { viewport$, header$, main$ }))
|
|
||||||
),
|
|
||||||
|
|
||||||
/* Repository information */
|
/* Repository information */
|
||||||
...getElements("[data-md-component=source]")
|
...getElements("[data-md-component=source]")
|
||||||
.map(child => mountSource(child as HTMLAnchorElement)),
|
.map(child => mountSource(child as HTMLAnchorElement)),
|
||||||
@ -119,30 +121,46 @@ const app$ = merge(
|
|||||||
/* Navigation tabs */
|
/* Navigation tabs */
|
||||||
...getElements("[data-md-component=tabs]")
|
...getElements("[data-md-component=tabs]")
|
||||||
.map(child => mountTabs(child, { viewport$, header$ })),
|
.map(child => mountTabs(child, { viewport$, header$ })),
|
||||||
|
)
|
||||||
|
|
||||||
|
/* Set up content component observables */
|
||||||
|
const content$ = defer(() => merge(
|
||||||
|
|
||||||
|
/* Content */
|
||||||
|
...getElements("[data-md-component=content]")
|
||||||
|
.map(child => mountContent(child, { target$, viewport$, print$ })),
|
||||||
|
|
||||||
|
/* Header title */
|
||||||
|
...getElements("[data-md-component=header-title]")
|
||||||
|
.map(child => mountHeaderTitle(child, { viewport$, header$ })),
|
||||||
|
|
||||||
|
/* Sidebar */
|
||||||
|
...getElements("[data-md-component=sidebar]")
|
||||||
|
.map(child => child.getAttribute("data-md-type") === "navigation"
|
||||||
|
? at(screen$, () => mountSidebar(child, { viewport$, header$, main$ }))
|
||||||
|
: at(tablet$, () => mountSidebar(child, { viewport$, header$, main$ }))
|
||||||
|
),
|
||||||
|
|
||||||
/* Table of contents */
|
/* Table of contents */
|
||||||
...getElements("[data-md-component=toc]")
|
...getElements("[data-md-component=toc]")
|
||||||
.map(child => mountTableOfContents(child, { viewport$, header$ })),
|
.map(child => mountTableOfContents(child, { viewport$, header$ })),
|
||||||
)
|
))
|
||||||
|
|
||||||
// eslint-disable-next-line
|
/* Set up component observables */
|
||||||
app$.subscribe(console.log)
|
const component$ = document$
|
||||||
|
|
||||||
/* ------------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test
|
|
||||||
*
|
|
||||||
* @param toggle$ - Toggle observable
|
|
||||||
* @param factory - Observable factory
|
|
||||||
*
|
|
||||||
* @returns New observable
|
|
||||||
*/
|
|
||||||
function at<T>(
|
|
||||||
toggle$: Observable<boolean>, factory: () => Observable<T>
|
|
||||||
) {
|
|
||||||
return toggle$
|
|
||||||
.pipe(
|
.pipe(
|
||||||
switchMap(active => active ? factory() : NEVER),
|
switchMap(() => content$),
|
||||||
|
mergeWith(control$)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/* Export to window */
|
||||||
|
export {
|
||||||
|
document$,
|
||||||
|
component$,
|
||||||
|
viewport$,
|
||||||
|
location$,
|
||||||
|
target$,
|
||||||
|
screen$,
|
||||||
|
tablet$,
|
||||||
|
print$
|
||||||
}
|
}
|
||||||
|
@ -33,7 +33,7 @@ import { translation } from "~/_"
|
|||||||
* Setup options
|
* Setup options
|
||||||
*/
|
*/
|
||||||
interface SetupOptions {
|
interface SetupOptions {
|
||||||
message$: Subject<string> /* Message subject */
|
alert$: Subject<string> /* Alert subject */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------------
|
/* ----------------------------------------------------------------------------
|
||||||
@ -46,13 +46,13 @@ interface SetupOptions {
|
|||||||
* @param options - Options
|
* @param options - Options
|
||||||
*/
|
*/
|
||||||
export function setupClipboardJS(
|
export function setupClipboardJS(
|
||||||
{ message$ }: SetupOptions
|
{ alert$ }: SetupOptions
|
||||||
): void {
|
): void {
|
||||||
if (!ClipboardJS.isSupported()) {
|
if (ClipboardJS.isSupported()) {
|
||||||
new Observable<ClipboardJS.Event>(subscriber => {
|
new Observable<ClipboardJS.Event>(subscriber => {
|
||||||
new ClipboardJS("[data-clipboard-target], [data-clipboard-text]")
|
new ClipboardJS("[data-clipboard-target], [data-clipboard-text]")
|
||||||
.on("success", ev => subscriber.next(ev))
|
.on("success", ev => subscriber.next(ev))
|
||||||
})
|
})
|
||||||
.subscribe(() => message$.next(translation("clipboard.copied")))
|
.subscribe(() => alert$.next(translation("clipboard.copied")))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"rules": {
|
"rules": {
|
||||||
"no-self-assign": "off"
|
"no-self-assign": "off",
|
||||||
|
"no-null/no-null": "off"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,9 +20,42 @@
|
|||||||
* IN THE SOFTWARE.
|
* IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Observable, Subject, fromEvent } from "rxjs"
|
import {
|
||||||
|
NEVER,
|
||||||
|
Observable,
|
||||||
|
Subject,
|
||||||
|
fromEvent,
|
||||||
|
merge,
|
||||||
|
of
|
||||||
|
} from "rxjs"
|
||||||
|
import {
|
||||||
|
bufferCount,
|
||||||
|
catchError,
|
||||||
|
debounceTime,
|
||||||
|
distinctUntilChanged,
|
||||||
|
distinctUntilKeyChanged,
|
||||||
|
filter,
|
||||||
|
map,
|
||||||
|
sample,
|
||||||
|
share,
|
||||||
|
skip,
|
||||||
|
skipUntil,
|
||||||
|
switchMap
|
||||||
|
} from "rxjs/operators"
|
||||||
|
|
||||||
import { Viewport, ViewportOffset, getElement } from "~/browser"
|
import { configuration } from "~/_"
|
||||||
|
import {
|
||||||
|
Viewport,
|
||||||
|
ViewportOffset,
|
||||||
|
getElement,
|
||||||
|
getElements,
|
||||||
|
replaceElement,
|
||||||
|
request,
|
||||||
|
requestXML,
|
||||||
|
setLocation,
|
||||||
|
setLocationHash,
|
||||||
|
setViewportOffset
|
||||||
|
} from "~/browser"
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------------
|
/* ----------------------------------------------------------------------------
|
||||||
* Types
|
* Types
|
||||||
@ -49,6 +82,42 @@ interface SetupOptions {
|
|||||||
viewport$: Observable<Viewport> /* Viewport observable */
|
viewport$: Observable<Viewport> /* Viewport observable */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
* Helper functions
|
||||||
|
* ------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Preprocess a list of URLs
|
||||||
|
*
|
||||||
|
* This function replaces the `site_url` in the sitemap with the actual base
|
||||||
|
* URL, to allow instant loading to work in occasions like Netlify previews.
|
||||||
|
*
|
||||||
|
* @param urls - URLs
|
||||||
|
*
|
||||||
|
* @returns Processed URLs
|
||||||
|
*/
|
||||||
|
function preprocess(urls: string[]): string[] {
|
||||||
|
if (urls.length < 2)
|
||||||
|
return urls
|
||||||
|
|
||||||
|
/* Compute references URLs */
|
||||||
|
const [root, next] = urls.sort((a, b) => a.length - b.length)
|
||||||
|
|
||||||
|
/* Compute common prefix */
|
||||||
|
let index = 0
|
||||||
|
if (root === next)
|
||||||
|
index = root.length
|
||||||
|
else
|
||||||
|
while (root.charCodeAt(index) === root.charCodeAt(index))
|
||||||
|
index++
|
||||||
|
|
||||||
|
/* Replace common prefix (i.e. base) with effective base */
|
||||||
|
const config = configuration()
|
||||||
|
return urls.map(url => (
|
||||||
|
url.replace(root.slice(0, index), `${config.base}/`)
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------------
|
/* ----------------------------------------------------------------------------
|
||||||
* Functions
|
* Functions
|
||||||
* ------------------------------------------------------------------------- */
|
* ------------------------------------------------------------------------- */
|
||||||
@ -72,12 +141,12 @@ interface SetupOptions {
|
|||||||
* history will be irreversibly overwritten. In case the request fails, the
|
* history will be irreversibly overwritten. In case the request fails, the
|
||||||
* location change is dispatched regularly.
|
* location change is dispatched regularly.
|
||||||
*
|
*
|
||||||
* @param urls - URLs to load with XHR
|
|
||||||
* @param options - Options
|
* @param options - Options
|
||||||
*/
|
*/
|
||||||
export function setupInstantLoading(
|
export function setupInstantLoading(
|
||||||
urls: string[], { document$, location$, viewport$ }: SetupOptions
|
{ document$, location$, viewport$ }: SetupOptions
|
||||||
): void {
|
): void {
|
||||||
|
const config = configuration()
|
||||||
if (location.protocol === "file:")
|
if (location.protocol === "file:")
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -97,6 +166,149 @@ export function setupInstantLoading(
|
|||||||
if (typeof favicon !== "undefined")
|
if (typeof favicon !== "undefined")
|
||||||
favicon.href = favicon.href
|
favicon.href = favicon.href
|
||||||
|
|
||||||
// eslint-disable-next-line
|
/* Intercept internal navigation */
|
||||||
console.log(urls, document$, location$, viewport$)
|
const push$ = requestXML(`${config.base}/sitemap.xml`)
|
||||||
|
.pipe(
|
||||||
|
map(sitemap => preprocess(getElements("loc", sitemap)
|
||||||
|
.map(node => node.textContent!)
|
||||||
|
)),
|
||||||
|
switchMap(urls => fromEvent<MouseEvent>(document.body, "click")
|
||||||
|
.pipe(
|
||||||
|
filter(ev => !ev.metaKey && !ev.ctrlKey),
|
||||||
|
switchMap(ev => {
|
||||||
|
if (ev.target instanceof HTMLElement) {
|
||||||
|
const el = ev.target.closest("a")
|
||||||
|
if (el && !el.target && urls.includes(el.href)) {
|
||||||
|
ev.preventDefault()
|
||||||
|
return of<HistoryState>({
|
||||||
|
url: new URL(el.href)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NEVER
|
||||||
|
})
|
||||||
|
)
|
||||||
|
),
|
||||||
|
share()
|
||||||
|
)
|
||||||
|
|
||||||
|
/* Intercept history back and forward */
|
||||||
|
const pop$ = fromEvent<PopStateEvent>(window, "popstate")
|
||||||
|
.pipe(
|
||||||
|
filter(ev => ev.state !== null),
|
||||||
|
map(ev => ({
|
||||||
|
url: new URL(location.href),
|
||||||
|
offset: ev.state
|
||||||
|
} as HistoryState)),
|
||||||
|
share()
|
||||||
|
)
|
||||||
|
|
||||||
|
/* Emit location change */
|
||||||
|
merge(push$, pop$)
|
||||||
|
.pipe(
|
||||||
|
distinctUntilChanged((a, b) => a.url.href === b.url.href),
|
||||||
|
map(({ url }) => url)
|
||||||
|
)
|
||||||
|
.subscribe(location$)
|
||||||
|
|
||||||
|
/* Fetch document via `XMLHTTPRequest` */
|
||||||
|
const response$ = location$
|
||||||
|
.pipe(
|
||||||
|
distinctUntilKeyChanged("pathname"),
|
||||||
|
skip(1),
|
||||||
|
switchMap(url => request(url.href)
|
||||||
|
.pipe(
|
||||||
|
catchError(() => {
|
||||||
|
setLocation(url)
|
||||||
|
return NEVER
|
||||||
|
})
|
||||||
|
)
|
||||||
|
),
|
||||||
|
share()
|
||||||
|
)
|
||||||
|
|
||||||
|
/* Set new location via `history.pushState` */
|
||||||
|
push$
|
||||||
|
.pipe(
|
||||||
|
sample(response$)
|
||||||
|
)
|
||||||
|
.subscribe(({ url }) => {
|
||||||
|
history.pushState({}, "", url.toString())
|
||||||
|
})
|
||||||
|
|
||||||
|
/* Parse and emit fetched document */
|
||||||
|
const dom = new DOMParser()
|
||||||
|
response$
|
||||||
|
.pipe(
|
||||||
|
switchMap(res => res.text()),
|
||||||
|
map(res => dom.parseFromString(res, "text/html"))
|
||||||
|
)
|
||||||
|
.subscribe(document$)
|
||||||
|
|
||||||
|
/* Emit history state change */
|
||||||
|
merge(push$, pop$)
|
||||||
|
.pipe(
|
||||||
|
sample(document$)
|
||||||
|
)
|
||||||
|
.subscribe(({ url, offset }) => {
|
||||||
|
if (url.hash && !offset)
|
||||||
|
setLocationHash(url.hash)
|
||||||
|
else
|
||||||
|
setViewportOffset(offset || { y: 0 })
|
||||||
|
})
|
||||||
|
|
||||||
|
/* Replace components */
|
||||||
|
document$
|
||||||
|
.pipe(
|
||||||
|
skip(1)
|
||||||
|
)
|
||||||
|
.subscribe(replacement => {
|
||||||
|
|
||||||
|
/* Replace meta tags and components */
|
||||||
|
for (const selector of [
|
||||||
|
|
||||||
|
/* Meta tags */
|
||||||
|
"title",
|
||||||
|
"link[rel='canonical']",
|
||||||
|
"meta[name='author']",
|
||||||
|
"meta[name='description']",
|
||||||
|
|
||||||
|
/* Components */
|
||||||
|
"[data-md-component=announce]",
|
||||||
|
"[data-md-component=header-title]",
|
||||||
|
"[data-md-component=container]",
|
||||||
|
"[data-md-component=skip]"
|
||||||
|
]) {
|
||||||
|
const next = getElement(selector, replacement)
|
||||||
|
const prev = getElement(selector)
|
||||||
|
if (
|
||||||
|
typeof next !== "undefined" &&
|
||||||
|
typeof prev !== "undefined"
|
||||||
|
) {
|
||||||
|
replaceElement(prev, next)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/* Debounce update of viewport offset */
|
||||||
|
viewport$
|
||||||
|
.pipe(
|
||||||
|
skipUntil(push$),
|
||||||
|
debounceTime(250),
|
||||||
|
distinctUntilKeyChanged("offset")
|
||||||
|
)
|
||||||
|
.subscribe(({ offset }) => {
|
||||||
|
history.replaceState(offset, "")
|
||||||
|
})
|
||||||
|
|
||||||
|
/* Set viewport offset from history */
|
||||||
|
merge(push$, pop$)
|
||||||
|
.pipe(
|
||||||
|
bufferCount(2, 1),
|
||||||
|
filter(([a, b]) => a.url.pathname === b.url.pathname),
|
||||||
|
map(([, state]) => state)
|
||||||
|
)
|
||||||
|
.subscribe(({ offset }) => {
|
||||||
|
setViewportOffset(offset || { y: 0 })
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
@ -346,12 +346,12 @@
|
|||||||
{% block footer %}
|
{% block footer %}
|
||||||
{% include "partials/footer.html" %}
|
{% include "partials/footer.html" %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Dialog -->
|
<!-- Dialog -->
|
||||||
<div class="md-dialog" data-md-component="dialog">
|
<div class="md-dialog" data-md-component="dialog">
|
||||||
<div class="md-dialog__inner md-typeset"></div>
|
<div class="md-dialog__inner md-typeset"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Theme-related configuration -->
|
<!-- Theme-related configuration -->
|
||||||
{% block config %}
|
{% block config %}
|
||||||
|
Loading…
Reference in New Issue
Block a user