mirror of
https://github.com/squidfunk/mkdocs-material.git
synced 2024-06-14 11:52:32 +03:00
Improved keyboard handlers and added prev/next hotkeys
This commit is contained in:
parent
297a63313d
commit
9b0410962d
24
material/assets/javascripts/bundle.02fd1bf7.min.js
vendored
Normal file
24
material/assets/javascripts/bundle.02fd1bf7.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
material/assets/javascripts/bundle.02fd1bf7.min.js.map
Normal file
1
material/assets/javascripts/bundle.02fd1bf7.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,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"assets/javascripts/bundle.js": "assets/javascripts/bundle.24f9b6fb.min.js",
|
"assets/javascripts/bundle.js": "assets/javascripts/bundle.02fd1bf7.min.js",
|
||||||
"assets/javascripts/bundle.js.map": "assets/javascripts/bundle.24f9b6fb.min.js.map",
|
"assets/javascripts/bundle.js.map": "assets/javascripts/bundle.02fd1bf7.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",
|
||||||
|
@ -190,7 +190,7 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
</div>
|
</div>
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
<script src="{{ 'assets/javascripts/bundle.24f9b6fb.min.js' | url }}"></script>
|
<script src="{{ 'assets/javascripts/bundle.02fd1bf7.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 [
|
||||||
|
@ -79,7 +79,7 @@ import {
|
|||||||
patchScrollfix,
|
patchScrollfix,
|
||||||
patchSource
|
patchSource
|
||||||
} from "patches"
|
} from "patches"
|
||||||
import { takeIf, not, isConfig } from "utilities"
|
import { isConfig } from "utilities"
|
||||||
import { renderDialog } from "templates/dialog"
|
import { renderDialog } from "templates/dialog"
|
||||||
|
|
||||||
/* ------------------------------------------------------------------------- */
|
/* ------------------------------------------------------------------------- */
|
||||||
@ -108,7 +108,6 @@ export function initialize(config: unknown) {
|
|||||||
const document$ = watchDocument()
|
const document$ = watchDocument()
|
||||||
const location$ = watchLocation()
|
const location$ = watchLocation()
|
||||||
const hash$ = watchLocationHash()
|
const hash$ = watchLocationHash()
|
||||||
const keyboard$ = watchKeyboard()
|
|
||||||
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)")
|
||||||
@ -187,7 +186,7 @@ export function initialize(config: unknown) {
|
|||||||
|
|
||||||
/* ----------------------------------------------------------------------- */
|
/* ----------------------------------------------------------------------- */
|
||||||
|
|
||||||
setupKeyboard({ keyboard$ })
|
const keyboard$ = setupKeyboard()
|
||||||
|
|
||||||
patchTables({ document$ })
|
patchTables({ document$ })
|
||||||
patchDetails({ document$, hash$ })
|
patchDetails({ document$, hash$ })
|
||||||
@ -217,9 +216,6 @@ export function initialize(config: unknown) {
|
|||||||
)
|
)
|
||||||
.subscribe()
|
.subscribe()
|
||||||
|
|
||||||
// TODO: general keyboard handler...
|
|
||||||
// put into main!?
|
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------- */
|
/* ----------------------------------------------------------------------- */
|
||||||
|
|
||||||
// Close drawer and search on hash change
|
// Close drawer and search on hash change
|
||||||
@ -279,8 +275,7 @@ export function initialize(config: unknown) {
|
|||||||
// TODO: experimental. necessary!?
|
// TODO: experimental. necessary!?
|
||||||
keyboard$
|
keyboard$
|
||||||
.pipe(
|
.pipe(
|
||||||
takeIf(not(toggle$.pipe(switchMap(watchToggle)))),
|
filter(key => key.mode === "global" && ["Tab"].includes(key.type)),
|
||||||
filter(key => ["Tab"].includes(key.type)),
|
|
||||||
take(1)
|
take(1)
|
||||||
)
|
)
|
||||||
.subscribe(() => {
|
.subscribe(() => {
|
||||||
|
@ -21,30 +21,46 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Observable } from "rxjs"
|
import { Observable } from "rxjs"
|
||||||
import { switchMap, withLatestFrom } from "rxjs/operators"
|
import {
|
||||||
|
filter,
|
||||||
|
map,
|
||||||
|
share,
|
||||||
|
switchMap,
|
||||||
|
withLatestFrom
|
||||||
|
} from "rxjs/operators"
|
||||||
|
|
||||||
import { useComponent } from "components"
|
import { useComponent } from "components"
|
||||||
import {
|
import {
|
||||||
Key,
|
Key,
|
||||||
getActiveElement,
|
getActiveElement,
|
||||||
|
getElement,
|
||||||
getElements,
|
getElements,
|
||||||
isSusceptibleToKeyboard,
|
isSusceptibleToKeyboard,
|
||||||
setElementFocus,
|
setElementFocus,
|
||||||
setToggle,
|
setToggle,
|
||||||
useToggle,
|
useToggle,
|
||||||
|
watchKeyboard,
|
||||||
watchToggle
|
watchToggle
|
||||||
} from "observables"
|
} from "observables"
|
||||||
import { not, takeIf } from "utilities"
|
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------------
|
/* ----------------------------------------------------------------------------
|
||||||
* Helper types
|
* Types
|
||||||
* ------------------------------------------------------------------------- */
|
* ------------------------------------------------------------------------- */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setup options
|
* Keyboard mode
|
||||||
*/
|
*/
|
||||||
interface SetupOptions {
|
export type KeyboardMode =
|
||||||
keyboard$: Observable<Key> /* Keyboard observable */
|
| "global" /* Global */
|
||||||
|
| "search" /* Search is open */
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keyboard
|
||||||
|
*/
|
||||||
|
export interface Keyboard extends Key {
|
||||||
|
mode: KeyboardMode /* Keyboard mode */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------------
|
/* ----------------------------------------------------------------------------
|
||||||
@ -54,17 +70,44 @@ interface SetupOptions {
|
|||||||
/**
|
/**
|
||||||
* Setup keyboard
|
* Setup keyboard
|
||||||
*
|
*
|
||||||
|
* This function will setup the keyboard handlers and ensure that keys are
|
||||||
|
* correctly propagated. Currently there are two modes:
|
||||||
|
*
|
||||||
|
* - `global`: This mode is active when the search is closed. It is intended
|
||||||
|
* to assign hotkeys to specific functions of the site. Currently the search,
|
||||||
|
* previous and next page can be triggered.
|
||||||
|
*
|
||||||
|
* - `search`: This mode is active when the search is open. It maps certain
|
||||||
|
* navigational keys to offer search results that can be entirely navigated
|
||||||
|
* through keyboard input.
|
||||||
|
*
|
||||||
|
* The keyboard observable is returned and can be used to monitor the keyboard
|
||||||
|
* in order toassign further hotkeys to custom functions.
|
||||||
|
*
|
||||||
* @return Keyboard observable
|
* @return Keyboard observable
|
||||||
*/
|
*/
|
||||||
export function setupKeyboard(
|
export function setupKeyboard(): Observable<Keyboard> {
|
||||||
{ keyboard$ }: SetupOptions
|
|
||||||
): Observable<Key> {
|
|
||||||
|
|
||||||
/* Setup keyboard handlers in search mode */
|
|
||||||
const toggle$ = useToggle("search")
|
const toggle$ = useToggle("search")
|
||||||
|
const search$ = toggle$
|
||||||
|
.pipe(
|
||||||
|
switchMap(watchToggle)
|
||||||
|
)
|
||||||
|
|
||||||
|
/* Setup keyboard and determine mode */
|
||||||
|
const keyboard$ = watchKeyboard()
|
||||||
|
.pipe(
|
||||||
|
withLatestFrom(search$),
|
||||||
|
map(([key, toggle]): Keyboard => ({
|
||||||
|
mode: toggle ? "search" : "global",
|
||||||
|
...key
|
||||||
|
})),
|
||||||
|
share()
|
||||||
|
)
|
||||||
|
|
||||||
|
/* Setup search keyboard handlers */
|
||||||
keyboard$
|
keyboard$
|
||||||
.pipe(
|
.pipe(
|
||||||
takeIf(toggle$.pipe(switchMap(watchToggle))),
|
filter(({ mode }) => mode === "search"),
|
||||||
withLatestFrom(
|
withLatestFrom(
|
||||||
toggle$,
|
toggle$,
|
||||||
useComponent("search-query"),
|
useComponent("search-query"),
|
||||||
@ -114,23 +157,43 @@ export function setupKeyboard(
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
/* Setup general keyboard handlers */
|
/* Setup global keyboard handlers */
|
||||||
keyboard$
|
keyboard$
|
||||||
.pipe(
|
.pipe(
|
||||||
takeIf(not(toggle$.pipe(switchMap(watchToggle)))),
|
filter(({ mode }) => {
|
||||||
|
if (mode === "global") {
|
||||||
|
const active = getActiveElement()
|
||||||
|
if (typeof active !== "undefined")
|
||||||
|
return !isSusceptibleToKeyboard(active)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}),
|
||||||
withLatestFrom(useComponent("search-query"))
|
withLatestFrom(useComponent("search-query"))
|
||||||
)
|
)
|
||||||
.subscribe(([key, query]) => {
|
.subscribe(([key, query]) => {
|
||||||
const active = getActiveElement()
|
|
||||||
switch (key.type) {
|
switch (key.type) {
|
||||||
|
|
||||||
/* [s]earch / [f]ind: open search */
|
/* Open search */
|
||||||
case "s":
|
|
||||||
case "f":
|
case "f":
|
||||||
if (!(active && isSusceptibleToKeyboard(active))) {
|
case "s":
|
||||||
setElementFocus(query)
|
setElementFocus(query)
|
||||||
key.claim()
|
key.claim()
|
||||||
}
|
break
|
||||||
|
|
||||||
|
/* Go to previous page */
|
||||||
|
case "p":
|
||||||
|
case ",":
|
||||||
|
const prev = getElement("[href][rel=prev]")
|
||||||
|
if (typeof prev !== "undefined")
|
||||||
|
prev.click()
|
||||||
|
break
|
||||||
|
|
||||||
|
/* Go to next page */
|
||||||
|
case "n":
|
||||||
|
case ".":
|
||||||
|
const next = getElement("[href][rel=next]")
|
||||||
|
if (typeof next !== "undefined")
|
||||||
|
next.click()
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -32,6 +32,8 @@ import {
|
|||||||
switchMap
|
switchMap
|
||||||
} from "rxjs/operators"
|
} from "rxjs/operators"
|
||||||
|
|
||||||
|
import { getLocation } from "../../location"
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------------
|
/* ----------------------------------------------------------------------------
|
||||||
* Helper types
|
* Helper types
|
||||||
* ------------------------------------------------------------------------- */
|
* ------------------------------------------------------------------------- */
|
||||||
@ -64,7 +66,7 @@ export function watchDocumentSwitch(
|
|||||||
): Observable<Document> {
|
): Observable<Document> {
|
||||||
return location$
|
return location$
|
||||||
.pipe(
|
.pipe(
|
||||||
startWith(location.href),
|
startWith(getLocation()),
|
||||||
map(url => url.replace(/#[^#]+$/, "")),
|
map(url => url.replace(/#[^#]+$/, "")),
|
||||||
distinctUntilChanged(),
|
distinctUntilChanged(),
|
||||||
skip(1),
|
skip(1),
|
||||||
|
@ -1,85 +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 { Observable, defer, of } from "rxjs"
|
|
||||||
import { map } from "rxjs/operators"
|
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------------
|
|
||||||
* Functions
|
|
||||||
* ------------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invert the value of a toggle observable
|
|
||||||
*
|
|
||||||
* @param toggle$ - Toggle observable
|
|
||||||
*
|
|
||||||
* @return Inverted toggle observable
|
|
||||||
*/
|
|
||||||
export function not(
|
|
||||||
toggle$: Observable<boolean>
|
|
||||||
): Observable<boolean> {
|
|
||||||
return toggle$
|
|
||||||
.pipe(
|
|
||||||
map(active => !active)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cache the last value emitted by an observable in session storage
|
|
||||||
*
|
|
||||||
* If the key is not found in session storage, the factory is executed and the
|
|
||||||
* latest value emitted will automatically be persisted to sessions storage.
|
|
||||||
* Note that the values emitted by the returned observable must be serializable
|
|
||||||
* as `JSON`, or data will be lost.
|
|
||||||
*
|
|
||||||
* @template T - Value type
|
|
||||||
*
|
|
||||||
* @param key - Cache key
|
|
||||||
* @param factory - Observable factory
|
|
||||||
*
|
|
||||||
* @return Value observable
|
|
||||||
*/
|
|
||||||
export function cache<T>(
|
|
||||||
key: string, factory: () => Observable<T>
|
|
||||||
): Observable<T> {
|
|
||||||
return defer(() => {
|
|
||||||
const data = sessionStorage.getItem(key)
|
|
||||||
if (data) {
|
|
||||||
return of(JSON.parse(data) as T)
|
|
||||||
|
|
||||||
/* Retrieve value from observable factory and write to storage */
|
|
||||||
} else {
|
|
||||||
const value$ = factory()
|
|
||||||
value$
|
|
||||||
.subscribe(value => {
|
|
||||||
try {
|
|
||||||
sessionStorage.setItem(key, JSON.stringify(value))
|
|
||||||
} catch (err) {
|
|
||||||
/* Uncritical, just swallow */
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
/* Return value observable */
|
|
||||||
return value$
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
@ -20,5 +20,49 @@
|
|||||||
* IN THE SOFTWARE.
|
* IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export * from "./_"
|
import { Observable, defer, of } from "rxjs"
|
||||||
export * from "./operators"
|
|
||||||
|
/* ----------------------------------------------------------------------------
|
||||||
|
* Functions
|
||||||
|
* ------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cache the last value emitted by an observable in session storage
|
||||||
|
*
|
||||||
|
* If the key is not found in session storage, the factory is executed and the
|
||||||
|
* latest value emitted will automatically be persisted to sessions storage.
|
||||||
|
* Note that the values emitted by the returned observable must be serializable
|
||||||
|
* as `JSON`, or data will be lost.
|
||||||
|
*
|
||||||
|
* @template T - Value type
|
||||||
|
*
|
||||||
|
* @param key - Cache key
|
||||||
|
* @param factory - Observable factory
|
||||||
|
*
|
||||||
|
* @return Value observable
|
||||||
|
*/
|
||||||
|
export function cache<T>(
|
||||||
|
key: string, factory: () => Observable<T>
|
||||||
|
): Observable<T> {
|
||||||
|
return defer(() => {
|
||||||
|
const data = sessionStorage.getItem(key)
|
||||||
|
if (data) {
|
||||||
|
return of(JSON.parse(data) as T)
|
||||||
|
|
||||||
|
/* Retrieve value from observable factory and write to storage */
|
||||||
|
} else {
|
||||||
|
const value$ = factory()
|
||||||
|
value$
|
||||||
|
.subscribe(value => {
|
||||||
|
try {
|
||||||
|
sessionStorage.setItem(key, JSON.stringify(value))
|
||||||
|
} catch (err) {
|
||||||
|
/* Uncritical, just swallow */
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/* Return value observable */
|
||||||
|
return value$
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -1,50 +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 { MonoTypeOperatorFunction, Observable, pipe } from "rxjs"
|
|
||||||
import { filter, map, withLatestFrom } from "rxjs/operators"
|
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------------
|
|
||||||
* Functions
|
|
||||||
* ------------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Toggle emission with another observable
|
|
||||||
*
|
|
||||||
* While this could also be implemented using window operators, it may lead to
|
|
||||||
* an unnecessary increase in bundle size, so we use operators we use anyway.
|
|
||||||
*
|
|
||||||
* @template T - Value type
|
|
||||||
*
|
|
||||||
* @param toggle$ - Toggle observable
|
|
||||||
*
|
|
||||||
* @return Operator function
|
|
||||||
*/
|
|
||||||
export function takeIf<T>(
|
|
||||||
toggle$: Observable<boolean>
|
|
||||||
): MonoTypeOperatorFunction<T> {
|
|
||||||
return pipe(
|
|
||||||
withLatestFrom(toggle$),
|
|
||||||
filter(([, active]) => active),
|
|
||||||
map(([value]) => value)
|
|
||||||
)
|
|
||||||
}
|
|
@ -32,8 +32,6 @@ $ms-ratio: $major-third;
|
|||||||
// Variables: breakpoints
|
// Variables: breakpoints
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
// stylelint-disable unit-whitelist
|
|
||||||
|
|
||||||
// Device-specific breakpoints
|
// Device-specific breakpoints
|
||||||
$break-devices: (
|
$break-devices: (
|
||||||
mobile: (
|
mobile: (
|
||||||
@ -51,8 +49,6 @@ $break-devices: (
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
// stylelint-enable unit-whitelist
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
// Variables: base colors
|
// Variables: base colors
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
Loading…
Reference in New Issue
Block a user