Implemented instant loading

This commit is contained in:
squidfunk 2020-02-20 17:42:46 +01:00
parent 3aa251fb03
commit ae1ed3d924
31 changed files with 197 additions and 138 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.4cda9c77.min.js", "assets/javascripts/bundle.js": "assets/javascripts/bundle.1defb77e.min.js",
"assets/javascripts/bundle.js.map": "assets/javascripts/bundle.4cda9c77.min.js.map", "assets/javascripts/bundle.js.map": "assets/javascripts/bundle.1defb77e.min.js.map",
"assets/javascripts/worker/search.js": "assets/javascripts/worker/search.926ffd9e.min.js", "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/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", "assets/stylesheets/app-palette.scss": "assets/stylesheets/app-palette.3f90c815.min.css",

View File

@ -190,7 +190,7 @@
{% endblock %} {% endblock %}
</div> </div>
{% block scripts %} {% block scripts %}
<script src="{{ 'assets/javascripts/bundle.4cda9c77.min.js' | url }}"></script> <script src="{{ 'assets/javascripts/bundle.1defb77e.min.js' | url }}"></script>
<script id="__lang" type="application/json"> <script id="__lang" type="application/json">
{%- set translations = {} -%} {%- set translations = {} -%}
{%- for key in [ {%- for key in [
@ -209,7 +209,17 @@
{%- endfor -%} {%- endfor -%}
{{ translations | tojson }} {{ translations | tojson }}
</script> </script>
<script>app=initialize({base:"{{ base_url }}",worker:{search:"{{ 'assets/javascripts/worker/search.926ffd9e.min.js' | url }}"}})</script> <script>
app = initialize({
base: "{{ base_url }}",
worker: {
search: "{{ 'assets/javascripts/worker/search.926ffd9e.min.js' | url }}"
},
feature: {
instant: {{ feature.instant | tojson }}
}
});
</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 %}

View File

@ -32,6 +32,10 @@ feature:
# of tabs, especially useful for larger documentation projects # of tabs, especially useful for larger documentation projects
tabs: false tabs: false
# Instant loading will instruct the application to intercept all internal
# links, load and inject the HTML into the page and rebind all handlers
instant: false
# Sets the primary and accent color palettes as defined in the Material Design # Sets the primary and accent color palettes as defined in the Material Design
# documentation - possible values can be looked up in the getting started guide # documentation - possible values can be looked up in the getting started guide
palette: palette:

View File

@ -22,4 +22,4 @@
mkdocs>=1.0 mkdocs>=1.0
Pygments>=2.4 Pygments>=2.4
markdown>=3.2 markdown>=3.2
pymdown-extensions>=7.0b1 pymdown-extensions>=7.0b2

View File

@ -22,7 +22,14 @@
import { keys } from "ramda" import { keys } from "ramda"
import { NEVER, Observable, of } from "rxjs" import { NEVER, Observable, of } from "rxjs"
import { map, scan, shareReplay, switchMap } from "rxjs/operators" import {
distinctUntilChanged,
map,
scan,
shareReplay,
switchMap,
tap
} from "rxjs/operators"
import { getElement } from "observables" import { getElement } from "observables"
@ -92,7 +99,7 @@ let components$: Observable<ComponentMap>
export function watchComponentMap( export function watchComponentMap(
names: Component[], { document$ }: WatchOptions names: Component[], { document$ }: WatchOptions
): Observable<ComponentMap> { ): Observable<ComponentMap> {
components$ = document$ return components$ = document$
.pipe( .pipe(
/* Build component map */ /* Build component map */
@ -124,12 +131,9 @@ export function watchComponentMap(
} }
} }
return prev return prev
}) }),
)
/* Return component map as hot observable */ /* Convert to hot observable */
return components$
.pipe(
shareReplay(1) shareReplay(1)
) )
} }
@ -152,6 +156,7 @@ export function useComponent<T extends HTMLElement>(
typeof components[name] !== "undefined" typeof components[name] !== "undefined"
? of(components[name] as T) ? of(components[name] as T)
: NEVER : NEVER
)) )),
distinctUntilChanged()
) )
} }

View File

@ -21,7 +21,7 @@
*/ */
import { Observable, OperatorFunction, pipe } from "rxjs" import { Observable, OperatorFunction, pipe } from "rxjs"
import { shareReplay, switchMap } from "rxjs/operators" import { switchMap } from "rxjs/operators"
import { Header, Viewport, watchHeader } from "observables" import { Header, Viewport, watchHeader } from "observables"
@ -51,7 +51,6 @@ export function mountHeader(
{ viewport$ }: MountOptions { viewport$ }: MountOptions
): OperatorFunction<HTMLElement, Header> { ): OperatorFunction<HTMLElement, Header> {
return pipe( return pipe(
switchMap(el => watchHeader(el, { viewport$ })), switchMap(el => watchHeader(el, { viewport$ }))
shareReplay(1)
) )
} }

View File

@ -21,12 +21,7 @@
*/ */
import { Observable, OperatorFunction, pipe } from "rxjs" import { Observable, OperatorFunction, pipe } from "rxjs"
import { import { filter, map, switchMap } from "rxjs/operators"
filter,
map,
shareReplay,
switchMap
} from "rxjs/operators"
import { import {
Header, Header,
@ -74,8 +69,7 @@ export function mountHeaderTitle(
map(({ offset: { y } }) => y >= hx.offsetHeight), map(({ offset: { y } }) => y >= hx.offsetHeight),
paintHeaderTitle(el) paintHeaderTitle(el)
) )
), )
shareReplay(1)
) )
) )
) )

View File

@ -21,7 +21,7 @@
*/ */
import { Observable, OperatorFunction, pipe } from "rxjs" import { Observable, OperatorFunction, pipe } from "rxjs"
import { map, shareReplay, switchMap } from "rxjs/operators" import { map, switchMap } from "rxjs/operators"
import { import {
Header, Header,
@ -73,7 +73,6 @@ export function mountHero(
paintHideable(el, 20), paintHideable(el, 20),
map(hidden => ({ hidden })) map(hidden => ({ hidden }))
) )
), )
shareReplay(1)
) )
} }

View File

@ -21,11 +21,7 @@
*/ */
import { Observable, OperatorFunction, pipe } from "rxjs" import { Observable, OperatorFunction, pipe } from "rxjs"
import { import { distinctUntilKeyChanged, switchMap } from "rxjs/operators"
distinctUntilKeyChanged,
shareReplay,
switchMap
} from "rxjs/operators"
import { import {
Header, Header,
@ -81,7 +77,6 @@ export function mountMain(
return main$ return main$
}) })
) )
), )
shareReplay(1)
) )
} }

View File

@ -21,7 +21,7 @@
*/ */
import { Observable, OperatorFunction, pipe } from "rxjs" import { Observable, OperatorFunction, pipe } from "rxjs"
import { map, shareReplay, switchMap } from "rxjs/operators" import { map, switchMap } from "rxjs/operators"
import { import {
Header, Header,
@ -115,7 +115,6 @@ export function mountNavigation(
} }
}) })
) )
), )
shareReplay(1)
) )
} }

View File

@ -21,7 +21,7 @@
*/ */
import { Observable, OperatorFunction, combineLatest, pipe } from "rxjs" import { Observable, OperatorFunction, combineLatest, pipe } from "rxjs"
import { map, shareReplay, switchMap } from "rxjs/operators" import { map, switchMap } from "rxjs/operators"
import { SearchResult } from "integrations/search" import { SearchResult } from "integrations/search"
import { SearchQuery } from "observables" import { SearchQuery } from "observables"
@ -68,8 +68,7 @@ export function mountSearch(
return pipe( return pipe(
switchMap(() => combineLatest([query$, result$, reset$]) switchMap(() => combineLatest([query$, result$, reset$])
.pipe( .pipe(
map(([query, result]) => ({ query, result })), map(([query, result]) => ({ query, result }))
shareReplay(1)
)) ))
) )
} }

View File

@ -27,7 +27,6 @@ import {
filter, filter,
map, map,
pluck, pluck,
shareReplay,
switchMap switchMap
} from "rxjs/operators" } from "rxjs/operators"
@ -90,7 +89,6 @@ export function mountSearchResult(
pluck("data"), pluck("data"),
paintSearchResult(el, { query$, fetch$ }) paintSearchResult(el, { query$, fetch$ })
) )
}), })
shareReplay(1)
) )
} }

View File

@ -21,7 +21,7 @@
*/ */
import { Observable, OperatorFunction, of, pipe } from "rxjs" import { Observable, OperatorFunction, of, pipe } from "rxjs"
import { map, shareReplay, switchMap } from "rxjs/operators" import { map, switchMap } from "rxjs/operators"
import { import {
Header, Header,
@ -87,7 +87,6 @@ export function mountTabs(
} }
}) })
) )
), )
shareReplay(1)
) )
} }

View File

@ -27,7 +27,7 @@ import {
of, of,
pipe pipe
} from "rxjs" } from "rxjs"
import { map, shareReplay, switchMap } from "rxjs/operators" import { map, switchMap } from "rxjs/operators"
import { import {
AnchorList, AnchorList,
@ -129,7 +129,6 @@ export function mountTableOfContents(
} }
}) })
) )
), )
shareReplay(1)
) )
} }

View File

@ -30,7 +30,10 @@ import { values } from "ramda"
import { import {
merge, merge,
combineLatest, combineLatest,
animationFrameScheduler animationFrameScheduler,
fromEvent,
of,
NEVER
} from "rxjs" } from "rxjs"
import { import {
delay, delay,
@ -41,7 +44,8 @@ import {
observeOn, observeOn,
take, take,
mapTo, mapTo,
shareReplay shareReplay,
switchMapTo
} from "rxjs/operators" } from "rxjs/operators"
import { import {
@ -55,7 +59,8 @@ import {
watchViewport, watchViewport,
watchToggleMap, watchToggleMap,
useToggle, useToggle,
getElement getElement,
watchDocumentSwitch
} from "./observables" } from "./observables"
import { setupSearchWorker } from "./workers" import { setupSearchWorker } from "./workers"
@ -108,9 +113,15 @@ 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)}`)
// pass config here!?
const document$ = watchDocument()
const location$ = watchLocation() const location$ = watchLocation()
// instant loading
const switch$ = config.feature.instant
? watchDocumentSwitch({ location$ })
: NEVER
const load$ = watchDocument()
const document$ = merge(load$, switch$)
const hash$ = watchLocationHash() const hash$ = watchLocationHash()
const viewport$ = watchViewport() const viewport$ = watchViewport()
const tablet$ = watchMedia("(min-width: 960px)") const tablet$ = watchMedia("(min-width: 960px)")
@ -147,12 +158,14 @@ export function initialize(config: unknown) {
/* Create header observable */ /* Create header observable */
const header$ = useComponent("header") const header$ = useComponent("header")
.pipe( .pipe(
mountHeader({ viewport$ }) mountHeader({ viewport$ }),
shareReplay(1)
) )
const main$ = useComponent("main") const main$ = useComponent("main")
.pipe( .pipe(
mountMain({ header$, viewport$ }) mountMain({ header$, viewport$ }),
shareReplay(1)
) )
/* ----------------------------------------------------------------------- */ /* ----------------------------------------------------------------------- */
@ -167,47 +180,55 @@ export function initialize(config: unknown) {
/* Mount search reset */ /* Mount search reset */
const reset$ = useComponent("search-reset") const reset$ = useComponent("search-reset")
.pipe( .pipe(
mountSearchReset() mountSearchReset(),
shareReplay(1)
) )
/* Mount search result */ /* Mount search result */
const result$ = useComponent("search-result") const result$ = useComponent("search-result")
.pipe( .pipe(
mountSearchResult(worker, { query$ }) mountSearchResult(worker, { query$ }),
shareReplay(1)
) )
/* ----------------------------------------------------------------------- */ /* ----------------------------------------------------------------------- */
const search$ = useComponent("search") const search$ = useComponent("search")
.pipe( .pipe(
mountSearch({ query$, reset$, result$ }) mountSearch({ query$, reset$, result$ }),
shareReplay(1)
) )
/* ----------------------------------------------------------------------- */ /* ----------------------------------------------------------------------- */
const navigation$ = useComponent("navigation") const navigation$ = useComponent("navigation")
.pipe( .pipe(
mountNavigation({ header$, main$, viewport$, screen$ }) mountNavigation({ header$, main$, viewport$, screen$ }),
shareReplay(1)
) )
const toc$ = useComponent("toc") const toc$ = useComponent("toc")
.pipe( .pipe(
mountTableOfContents({ header$, main$, viewport$, tablet$ }) mountTableOfContents({ header$, main$, viewport$, tablet$ }),
shareReplay(1)
) )
const tabs$ = useComponent("tabs") const tabs$ = useComponent("tabs")
.pipe( .pipe(
mountTabs({ header$, viewport$, screen$ }) mountTabs({ header$, viewport$, screen$ }),
shareReplay(1)
) )
const hero$ = useComponent("hero") const hero$ = useComponent("hero")
.pipe( .pipe(
mountHero({ header$, viewport$ }) mountHero({ header$, viewport$ }),
shareReplay(1)
) )
const title$ = useComponent("header-title") const title$ = useComponent("header-title")
.pipe( .pipe(
mountHeaderTitle({ header$, viewport$ }) mountHeaderTitle({ header$, viewport$ }),
shareReplay(1)
) )
/* ----------------------------------------------------------------------- */ /* ----------------------------------------------------------------------- */
@ -278,6 +299,50 @@ export function initialize(config: unknown) {
/* ----------------------------------------------------------------------- */ /* ----------------------------------------------------------------------- */
// instant loading
const instant$ = config.feature.instant ? load$ // TODO: just use document$ and take(1)
.pipe(
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) {
if (/(:\/\/|^#)/.test(anchor.getAttribute("href")!) === false) {
ev.preventDefault()
// we must copy the value, or weird stuff will happen
const href = anchor.href
history.pushState(true, "", href)
return of(href)
}
}
}
return NEVER
}),
shareReplay(1)
)
: NEVER
// deploy new location
instant$.subscribe(url => {
console.log(`Load ${url}`)
location$.next(url)
})
// scroll to top when document is loaded
instant$
.pipe(
switchMapTo(switch$), // TODO: just use document$ and skip(1)
)
.subscribe(() => {
window.scrollTo(0, 0) // TODO: or scroll element into view
})
/* ----------------------------------------------------------------------- */
// if we use a single tab outside of search, unhide all permalinks. // if we use a single tab outside of search, unhide all permalinks.
// TODO: experimental. necessary!? // TODO: experimental. necessary!?
keyboard$ keyboard$

View File

@ -26,7 +26,7 @@ import {
distinctUntilChanged, distinctUntilChanged,
map, map,
pluck, pluck,
shareReplay, share,
skip, skip,
startWith, startWith,
switchMap switchMap
@ -67,7 +67,7 @@ export function watchDocumentSwitch(
return location$ return location$
.pipe( .pipe(
startWith(getLocation()), startWith(getLocation()),
map(url => url.replace(/#[^#]+$/, "")), map(url => url.replace(/#[^#]*$/, "")),
distinctUntilChanged(), distinctUntilChanged(),
skip(1), skip(1),
@ -81,6 +81,6 @@ export function watchDocumentSwitch(
pluck("response") pluck("response")
) )
), ),
shareReplay(1) share()
) )
} }

View File

@ -35,7 +35,6 @@ import {
map, map,
observeOn, observeOn,
scan, scan,
shareReplay,
switchMap, switchMap,
tap tap
} from "rxjs/operators" } from "rxjs/operators"
@ -184,8 +183,8 @@ export function watchAnchorList(
) )
) )
/* Compute anchor list migrations */ /* Compute and return anchor list migrations */
const migration$ = partition$ return partition$
.pipe( .pipe(
map(([prev, next]) => ({ map(([prev, next]) => ({
prev: prev.map(([path]) => path), prev: prev.map(([path]) => path),
@ -202,12 +201,6 @@ export function watchAnchorList(
} }
}, { prev: [], next: [] }) }, { prev: [], next: [] })
) )
/* Return anchor list migrations as hot observable */
return migration$
.pipe(
shareReplay(1)
)
} }
/* ------------------------------------------------------------------------- */ /* ------------------------------------------------------------------------- */

View File

@ -21,12 +21,7 @@
*/ */
import { Observable, combineLatest } from "rxjs" import { Observable, combineLatest } from "rxjs"
import { import { distinctUntilChanged, map, pluck } from "rxjs/operators"
distinctUntilChanged,
map,
pluck,
shareReplay
} from "rxjs/operators"
import { Viewport } from "../../agent" import { Viewport } from "../../agent"
import { Header } from "../../header" import { Header } from "../../header"
@ -103,14 +98,13 @@ export function watchMain(
distinctUntilChanged() distinctUntilChanged()
) )
/* Combine into a single hot observable */ /* Combine into a single observable */
return combineLatest([adjust$, height$, active$]) return combineLatest([adjust$, height$, active$])
.pipe( .pipe(
map(([adjust, height, active]) => ({ map(([adjust, height, active]) => ({
offset: el.offsetTop - adjust, offset: el.offsetTop - adjust,
height, height,
active active
})), }))
shareReplay(1)
) )
} }

View File

@ -33,7 +33,6 @@ import {
finalize, finalize,
map, map,
observeOn, observeOn,
shareReplay,
tap, tap,
withLatestFrom withLatestFrom
} from "rxjs/operators" } from "rxjs/operators"
@ -136,11 +135,10 @@ export function watchSidebar(
distinctUntilChanged() distinctUntilChanged()
) )
/* Combine into single hot observable */ /* Combine into single observable */
return combineLatest([height$, lock$]) return combineLatest([height$, lock$])
.pipe( .pipe(
map(([height, lock]) => ({ height, lock })), map(([height, lock]) => ({ height, lock }))
shareReplay(1)
) )
} }

View File

@ -35,7 +35,6 @@ import {
map, map,
observeOn, observeOn,
scan, scan,
shareReplay,
tap tap
} from "rxjs/operators" } from "rxjs/operators"
@ -99,12 +98,11 @@ export function watchNavigationLayer(
))) )))
) )
/* Return previous and next layer as hot observable */ /* Return previous and next layer */
return layer$ return layer$
.pipe( .pipe(
map(next => ({ next })), map(next => ({ next })),
scan(({ next: prev }, { next }) => ({ prev, next })), scan(({ next: prev }, { next }) => ({ prev, next }))
shareReplay(1)
) )
} }

View File

@ -25,7 +25,6 @@ import {
delay, delay,
distinctUntilChanged, distinctUntilChanged,
map, map,
shareReplay,
startWith startWith
} from "rxjs/operators" } from "rxjs/operators"
@ -109,10 +108,9 @@ export function watchSearchQuery(
/* Intercept focus events */ /* Intercept focus events */
const focus$ = watchElementFocus(el) const focus$ = watchElementFocus(el)
/* Combine into a single hot observable */ /* Combine into a single observable */
return combineLatest([value$, focus$]) return combineLatest([value$, focus$])
.pipe( .pipe(
map(([value, focus]) => ({ value, focus })), map(([value, focus]) => ({ value, focus }))
shareReplay(1)
) )
} }

View File

@ -22,6 +22,7 @@
import { NEVER, Observable, fromEvent, of } from "rxjs" import { NEVER, Observable, fromEvent, of } from "rxjs"
import { import {
distinctUntilChanged,
map, map,
shareReplay, shareReplay,
startWith, startWith,
@ -84,7 +85,7 @@ let toggles$: Observable<ToggleMap>
export function watchToggleMap( export function watchToggleMap(
names: Toggle[], { document$ }: WatchOptions names: Toggle[], { document$ }: WatchOptions
): Observable<ToggleMap> { ): Observable<ToggleMap> {
toggles$ = document$ return toggles$ = document$
.pipe( .pipe(
/* Ignore document switches */ /* Ignore document switches */
@ -97,12 +98,9 @@ export function watchToggleMap(
...toggles, ...toggles,
...typeof el !== "undefined" ? { [name]: el } : {} ...typeof el !== "undefined" ? { [name]: el } : {}
} }
}, {})) }, {})),
)
/* Return toggle map as hot observable */ /* Convert to hot observable */
return toggles$
.pipe(
shareReplay(1) shareReplay(1)
) )
} }
@ -125,7 +123,8 @@ export function useToggle(
typeof toggles[name] !== "undefined" typeof toggles[name] !== "undefined"
? of(toggles[name]!) ? of(toggles[name]!)
: NEVER : NEVER
)) )),
distinctUntilChanged()
) )
} }

View File

@ -83,7 +83,7 @@ export function patchDetails(
/* Open parent details and fix anchor jump */ /* Open parent details and fix anchor jump */
hash$ hash$
.pipe( .pipe(
map(hash => getElement(hash)!), map(id => getElement(`[id="${id}"]`)!),
filter(el => typeof el !== "undefined"), filter(el => typeof el !== "undefined"),
tap(el => { tap(el => {
const details = el.closest("details") const details = el.closest("details")

View File

@ -32,6 +32,9 @@ export interface Config {
worker: { worker: {
search: string /* Search worker URL */ search: string /* Search worker URL */
} }
feature: {
instant: true /* Instant loading */
}
} }
/* ---------------------------------------------------------------------------- /* ----------------------------------------------------------------------------

View File

@ -56,14 +56,18 @@ interface SetupOptions {
/** /**
* Resolve URL * Resolve URL
* * TODO: document what's going on here + cache results
* *
* @param base - Base URL * @param origin - Base URL
* @param paths - Further URL paths * @param paths - Further URL paths
* *
* @return Absolute URL * @return Relative URL
*/ */
function resolve(base: URL | string, ...paths: string[]) { function resolve(origin: URL, ...paths: string[]) {
return [base, ...paths].join("") const path = location.pathname
.replace(origin.pathname, "")
.replace(/[^\/]+/g, "..")
return [path, ...paths].join("")
} }
/* ---------------------------------------------------------------------------- /* ----------------------------------------------------------------------------
@ -87,7 +91,7 @@ export function setupSearchWorker(
url: string, { base, index }: SetupOptions url: string, { base, index }: SetupOptions
): WorkerHandler<SearchMessage> { ): WorkerHandler<SearchMessage> {
const worker = new Worker(url) const worker = new Worker(url)
const prefix = new URL(base, getLocation()) const origin = new URL(base, getLocation())
/* Create communication channels and resolve relative links */ /* Create communication channels and resolve relative links */
const tx$ = new Subject<SearchMessage>() const tx$ = new Subject<SearchMessage>()
@ -96,9 +100,9 @@ export function setupSearchWorker(
map(message => { map(message => {
if (isSearchResultMessage(message)) { if (isSearchResultMessage(message)) {
for (const { article, sections } of message.data) { for (const { article, sections } of message.data) {
article.location = resolve(prefix, article.location) article.location = resolve(origin, article.location)
for (const section of sections) for (const section of sections)
section.location = resolve(prefix, section.location) section.location = resolve(origin, section.location)
} }
} }
return message return message
@ -109,7 +113,7 @@ export function setupSearchWorker(
const index$ = typeof index !== "undefined" const index$ = typeof index !== "undefined"
? from(index) ? from(index)
: ajax({ : ajax({
url: resolve(prefix, "search/search_index.json"), url: resolve(origin, "search/search_index.json"),
responseType: "json", responseType: "json",
withCredentials: true withCredentials: true
}) })

View File

@ -400,6 +400,9 @@
base: "{{ base_url }}", base: "{{ base_url }}",
worker: { worker: {
search: "{{ 'assets/javascripts/worker/search.js' | url }}" search: "{{ 'assets/javascripts/worker/search.js' | url }}"
},
feature: {
instant: {{ feature.instant | tojson }}
} }
}); });
</script> </script>

View File

@ -32,6 +32,10 @@ feature:
# of tabs, especially useful for larger documentation projects # of tabs, especially useful for larger documentation projects
tabs: false tabs: false
# Instant loading will instruct the application to intercept all internal
# links, load and inject the HTML into the page and rebind all handlers
instant: false
# Sets the primary and accent color palettes as defined in the Material Design # Sets the primary and accent color palettes as defined in the Material Design
# documentation - possible values can be looked up in the getting started guide # documentation - possible values can be looked up in the getting started guide
palette: palette: