Added back keyboard handlers

This commit is contained in:
squidfunk 2021-02-12 16:41:33 +01:00
parent 44751785a0
commit e7850ca184
13 changed files with 177 additions and 37 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.4fa4ebaf.min.js",
"assets/javascripts/bundle.js.map": "assets/javascripts/bundle.4fa4ebaf.min.js.map",
"assets/javascripts/bundle.js": "assets/javascripts/bundle.d8c6976f.min.js",
"assets/javascripts/bundle.js.map": "assets/javascripts/bundle.d8c6976f.min.js.map",
"assets/javascripts/vendor.js": "assets/javascripts/vendor.e32ed4d0.min.js",
"assets/javascripts/vendor.js.map": "assets/javascripts/vendor.e32ed4d0.min.js.map",
"assets/javascripts/worker/search.js": "assets/javascripts/worker/search.b9424174.min.js",
@ -9,8 +9,8 @@
"assets/stylesheets/main.css.map": "assets/stylesheets/main.a86e6725.min.css.map",
"assets/stylesheets/palette.css": "assets/stylesheets/palette.e70b70b6.min.css",
"assets/stylesheets/palette.css.map": "assets/stylesheets/palette.e70b70b6.min.css.map",
"overrides/assets/javascripts/bundle.js": "overrides/assets/javascripts/bundle.9f9f98ea.min.js",
"overrides/assets/javascripts/bundle.js.map": "overrides/assets/javascripts/bundle.9f9f98ea.min.js.map",
"overrides/assets/javascripts/bundle.js": "overrides/assets/javascripts/bundle.63116035.min.js",
"overrides/assets/javascripts/bundle.js.map": "overrides/assets/javascripts/bundle.63116035.min.js.map",
"overrides/assets/javascripts/vendor.js": "overrides/assets/javascripts/vendor.1aa446d9.min.js",
"overrides/assets/javascripts/vendor.js.map": "overrides/assets/javascripts/vendor.1aa446d9.min.js.map",
"overrides/assets/stylesheets/main.css": "overrides/assets/stylesheets/main.8e98f424.min.css",

View File

@ -217,7 +217,7 @@
{% endblock %}
{% block scripts %}
<script src="{{ 'assets/javascripts/vendor.e32ed4d0.min.js' | url }}"></script>
<script src="{{ 'assets/javascripts/bundle.4fa4ebaf.min.js' | url }}"></script>
<script src="{{ 'assets/javascripts/bundle.d8c6976f.min.js' | url }}"></script>
{% for path in config["extra_javascript"] %}
<script src="{{ path | url }}"></script>
{% endfor %}

View File

@ -1,2 +1,2 @@
!function(e,t){for(var n in t)e[n]=t[n]}(window,function(e){function t(t){for(var r,i,a=t[0],s=t[1],u=t[2],f=0,p=[];f<a.length;f++)i=a[f],Object.prototype.hasOwnProperty.call(o,i)&&o[i]&&p.push(o[i][0]),o[i]=0;for(r in s)Object.prototype.hasOwnProperty.call(s,r)&&(e[r]=s[r]);for(l&&l(t);p.length;)p.shift()();return c.push.apply(c,u||[]),n()}function n(){for(var e,t=0;t<c.length;t++){for(var n=c[t],r=!0,a=1;a<n.length;a++){var s=n[a];0!==o[s]&&(r=!1)}r&&(c.splice(t--,1),e=i(i.s=n[0]))}return e}var r={},o={0:0},c=[];function i(t){if(r[t])return r[t].exports;var n=r[t]={i:t,l:!1,exports:{}};return e[t].call(n.exports,n,n.exports,i),n.l=!0,n.exports}i.m=e,i.c=r,i.d=function(e,t,n){i.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},i.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.t=function(e,t){if(1&t&&(e=i(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(i.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var r in e)i.d(n,r,function(t){return e[t]}.bind(null,r));return n},i.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return i.d(t,"a",t),t},i.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},i.p="";var a=window.webpackJsonp=window.webpackJsonp||[],s=a.push.bind(a);a.push=t,a=a.slice();for(var u=0;u<a.length;u++)t(a[u]);var l=s;return c.push([37,1]),n()}({37:function(e,t,n){"use strict";n.r(t);var r=n(20),o=n(3),c=n(38),i=n(29),a=n(25);n(32),n(39);function s(e,t=document){return t.querySelector(e)||void 0}function u(e,t=document){const n=s(e,t);if(void 0===n)throw new ReferenceError(`Missing element: expected "${e}" to be present`);return n}n(50);var l=n(51);var f=n(17),p=n(40),d=n(41),b=n(42),g=n(43),h=n(44);n(45),n(46);const v=new f.a;Object(p.a)(()=>Object(d.a)(new ResizeObserver(e=>{for(const t of e)v.next(t)}))).pipe(Object(i.a)(e=>b.a.pipe(Object(l.a)(e)).pipe(Object(g.a)(()=>e.disconnect()))),Object(h.a)(1));n(31);n(47);u("[data-md-toggle=drawer]"),u("[data-md-toggle=search]");n(52),n(53);n(48),n(49);function O(e,t){if("string"==typeof t||"number"==typeof t)e.innerHTML+=t.toString();else if(t instanceof Node)e.appendChild(t);else if(Array.isArray(t))for(const n of t)O(e,n)}function j(e,t,...n){const r=document.createElement(e);if(t)for(const e of Object.keys(t))"boolean"!=typeof t[e]?r.setAttribute(e,t[e]):t[e]&&r.setAttribute(e,"");for(const e of n)O(r,e);return r}function m(e,t){return t.length?j("div",{class:""},j("span",null,function(e){if(e>999){return((e+1e-6)/1e3).toFixed(+((e-950)%1e3>99))+"k"}return e.toString()}(e.length)," results"),j("ul",{class:"tx-icon-search__list"},e.slice(0,10).map(e=>j("li",{class:"tx-icon-search__item"},j("span",{class:"twemoji"},j("img",{src:"https://raw.githubusercontent.com/squidfunk/mkdocs-material/master/material/.icons/"+e,style:"width: 18px; height: 18px"}))," ",j("button",{class:"md-clipboard--inline","data-clipboard-text":":"+e.replace(/\.svg$/,"").replace(/\//g,"-")+":"},j("code",null,function(e,t){return`:${Object(r.wrap)(e.replace(/\.svg$/,"").replace(/\//g,"-"),t,{wrap:{tagOpen:"<b>",tagClose:"</b>"}})}:`}(e,t))))))):j("div",{class:""})}const y=u("#__config"),w=JSON.parse(y.textContent),x=Object(o.a)(fetch(w.base+"/overrides/assets/javascripts/icons.json").then(e=>e.json())),_=s("#icon-search");_&&x.pipe(Object(i.a)(e=>Object(c.a)(_,"keyup").pipe(Object(a.a)(()=>Object(r.filter)(e,_.value))))).subscribe(e=>{const t=u(".tx-icon-result");t.innerHTML="",t.appendChild(m(e,_.value))}),Object(c.a)(document.body,"click").subscribe(e=>{if(e.target instanceof HTMLElement){e.target.closest("a[href^=http]")instanceof HTMLLinkElement&&ga("send","event","outbound","click",y.href)}})}}));
//# sourceMappingURL=bundle.9f9f98ea.min.js.map
!function(e,t){for(var n in t)e[n]=t[n]}(window,function(e){function t(t){for(var r,i,a=t[0],s=t[1],u=t[2],f=0,p=[];f<a.length;f++)i=a[f],Object.prototype.hasOwnProperty.call(o,i)&&o[i]&&p.push(o[i][0]),o[i]=0;for(r in s)Object.prototype.hasOwnProperty.call(s,r)&&(e[r]=s[r]);for(l&&l(t);p.length;)p.shift()();return c.push.apply(c,u||[]),n()}function n(){for(var e,t=0;t<c.length;t++){for(var n=c[t],r=!0,a=1;a<n.length;a++){var s=n[a];0!==o[s]&&(r=!1)}r&&(c.splice(t--,1),e=i(i.s=n[0]))}return e}var r={},o={0:0},c=[];function i(t){if(r[t])return r[t].exports;var n=r[t]={i:t,l:!1,exports:{}};return e[t].call(n.exports,n,n.exports,i),n.l=!0,n.exports}i.m=e,i.c=r,i.d=function(e,t,n){i.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},i.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.t=function(e,t){if(1&t&&(e=i(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(i.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var r in e)i.d(n,r,function(t){return e[t]}.bind(null,r));return n},i.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return i.d(t,"a",t),t},i.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},i.p="";var a=window.webpackJsonp=window.webpackJsonp||[],s=a.push.bind(a);a.push=t,a=a.slice();for(var u=0;u<a.length;u++)t(a[u]);var l=s;return c.push([37,1]),n()}({37:function(e,t,n){"use strict";n.r(t);var r=n(20),o=n(3),c=n(38),i=n(29),a=n(25);n(32),n(39);function s(e,t=document){return t.querySelector(e)||void 0}function u(e,t=document){const n=s(e,t);if(void 0===n)throw new ReferenceError(`Missing element: expected "${e}" to be present`);return n}n(50);var l=n(51);var f=n(17),p=n(40),d=n(41),b=n(42),g=n(43),h=n(44);n(45),n(46);const v=new f.a;Object(p.a)(()=>Object(d.a)(new ResizeObserver(e=>{for(const t of e)v.next(t)}))).pipe(Object(i.a)(e=>b.a.pipe(Object(l.a)(e)).pipe(Object(g.a)(()=>e.disconnect()))),Object(h.a)(1));n(31);u("[data-md-toggle=drawer]"),u("[data-md-toggle=search]");n(47);n(52),n(53);n(48),n(49);function O(e,t){if("string"==typeof t||"number"==typeof t)e.innerHTML+=t.toString();else if(t instanceof Node)e.appendChild(t);else if(Array.isArray(t))for(const n of t)O(e,n)}function j(e,t,...n){const r=document.createElement(e);if(t)for(const e of Object.keys(t))"boolean"!=typeof t[e]?r.setAttribute(e,t[e]):t[e]&&r.setAttribute(e,"");for(const e of n)O(r,e);return r}function m(e,t){return t.length?j("div",{class:""},j("span",null,function(e){if(e>999){return((e+1e-6)/1e3).toFixed(+((e-950)%1e3>99))+"k"}return e.toString()}(e.length)," results"),j("ul",{class:"tx-icon-search__list"},e.slice(0,10).map(e=>j("li",{class:"tx-icon-search__item"},j("span",{class:"twemoji"},j("img",{src:"https://raw.githubusercontent.com/squidfunk/mkdocs-material/master/material/.icons/"+e,style:"width: 18px; height: 18px"}))," ",j("button",{class:"md-clipboard--inline","data-clipboard-text":":"+e.replace(/\.svg$/,"").replace(/\//g,"-")+":"},j("code",null,function(e,t){return`:${Object(r.wrap)(e.replace(/\.svg$/,"").replace(/\//g,"-"),t,{wrap:{tagOpen:"<b>",tagClose:"</b>"}})}:`}(e,t))))))):j("div",{class:""})}const y=u("#__config"),w=JSON.parse(y.textContent),x=Object(o.a)(fetch(w.base+"/overrides/assets/javascripts/icons.json").then(e=>e.json())),_=s("#icon-search");_&&x.pipe(Object(i.a)(e=>Object(c.a)(_,"keyup").pipe(Object(a.a)(()=>Object(r.filter)(e,_.value))))).subscribe(e=>{const t=u(".tx-icon-result");t.innerHTML="",t.appendChild(m(e,_.value))}),Object(c.a)(document.body,"click").subscribe(e=>{if(e.target instanceof HTMLElement){e.target.closest("a[href^=http]")instanceof HTMLLinkElement&&ga("send","event","outbound","click",y.href)}})}}));
//# sourceMappingURL=bundle.63116035.min.js.map

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -54,5 +54,5 @@
{% block scripts %}
{{ super() }}
<script src="{{ 'overrides/assets/javascripts/vendor.1aa446d9.min.js' | url }}"></script>
<script src="{{ 'overrides/assets/javascripts/bundle.9f9f98ea.min.js' | url }}"></script>
<script src="{{ 'overrides/assets/javascripts/bundle.63116035.min.js' | url }}"></script>
{% endblock %}

View File

@ -23,20 +23,33 @@
import { Observable, fromEvent } from "rxjs"
import { filter, map, share } from "rxjs/operators"
import { getActiveElement } from "../element"
import { getToggle } from "../toggle"
/* ----------------------------------------------------------------------------
* Types
* ------------------------------------------------------------------------- */
/**
* Key
* Keyboard mode
*/
export interface Key {
export type KeyboardMode =
| "global" /* Global */
| "search" /* Search is open */
/* ------------------------------------------------------------------------- */
/**
* Keyboard
*/
export interface Keyboard {
mode: KeyboardMode /* Keyboard mode */
type: string /* Key type */
claim(): void /* Key claim */
}
/* ----------------------------------------------------------------------------
* Functions
* Helper functions
* ------------------------------------------------------------------------- */
/**
@ -46,7 +59,7 @@ export interface Key {
*
* @returns Test result
*/
export function isSusceptibleToKeyboard(el: HTMLElement): boolean {
function isSusceptibleToKeyboard(el: HTMLElement): boolean {
switch (el.tagName) {
/* Form elements */
@ -61,24 +74,35 @@ export function isSusceptibleToKeyboard(el: HTMLElement): boolean {
}
}
/* ------------------------------------------------------------------------- */
/* ----------------------------------------------------------------------------
* Functions
* ------------------------------------------------------------------------- */
/**
* Watch keyboard
*
* @returns Keyboard observable
*/
export function watchKeyboard(): Observable<Key> {
export function watchKeyboard(): Observable<Keyboard> {
return fromEvent<KeyboardEvent>(window, "keydown")
.pipe(
filter(ev => !(ev.metaKey || ev.ctrlKey)),
map(ev => ({
mode: getToggle("search") ? "search" : "global",
type: ev.key,
claim() {
ev.preventDefault()
ev.stopPropagation()
}
})),
} as Keyboard)),
filter(({ mode }) => {
if (mode === "global") {
const active = getActiveElement()
if (typeof active !== "undefined")
return !isSusceptibleToKeyboard(active)
}
return true
}),
share()
)
}

View File

@ -25,8 +25,14 @@ import { filter, sample, take } from "rxjs/operators"
import { configuration } from "~/_"
import {
Keyboard,
getActiveElement,
getElementOrThrow,
requestJSON
getElements,
requestJSON,
setElementFocus,
setElementSelection,
setToggle
} from "~/browser"
import {
SearchIndex,
@ -50,6 +56,17 @@ export type Search =
| SearchQuery
| SearchResult
/* ----------------------------------------------------------------------------
* Helper types
* ------------------------------------------------------------------------- */
/**
* Mount options
*/
interface MountOptions {
keyboard$: Observable<Keyboard> /* Keyboard observable */
}
/* ----------------------------------------------------------------------------
* Helper functions
* ------------------------------------------------------------------------- */
@ -73,17 +90,22 @@ function fetchSearchIndex(url: string) {
* Mount search
*
* @param el - Search element
* @param options - Options
*
* @returns Search component observable
*/
export function mountSearch(
el: HTMLElement
el: HTMLElement, { keyboard$ }: MountOptions
): Observable<Component<Search>> {
const config = configuration()
const worker = setupSearchWorker(config.search, fetchSearchIndex(
`${config.base}/search/search_index.json`
))
/* Retrieve elements */
const query = getElementOrThrow("[data-md-component=search-query]", el)
const result = getElementOrThrow("[data-md-component=search-result]", el)
/* Re-emit query when search is ready */
const { tx$, rx$ } = worker
tx$
@ -94,18 +116,80 @@ export function mountSearch(
)
.subscribe(tx$.next.bind(tx$))
/* Mount search query component */
const query$ = mountSearchQuery(
getElementOrThrow("[data-md-component=search-query]", el),
worker
/* Set up search keyboard handlers */
keyboard$
.pipe(
filter(({ mode }) => mode === "search")
)
.subscribe(key => {
const active = getActiveElement()
switch (key.type) {
/* Mount search result and return component */
/* Enter: prevent form submission */
case "Enter":
if (active === query)
key.claim()
break
/* Escape or Tab: close search */
case "Escape":
case "Tab":
setToggle("search", false)
setElementFocus(query, false)
break
/* Vertical arrows: select previous or next search result */
case "ArrowUp":
case "ArrowDown":
if (typeof active === "undefined") {
setElementFocus(query)
} else {
const els = [query, ...getElements(
":not(details) > [href], summary, details[open] [href]",
result
)]
const i = Math.max(0, (
Math.max(0, els.indexOf(active)) + els.length + (
key.type === "ArrowUp" ? -1 : +1
)
) % els.length)
setElementFocus(els[i])
}
/* Prevent scrolling of page */
key.claim()
break
/* All other keys: hand to search query */
default:
if (query !== getActiveElement())
setElementFocus(query)
}
})
/* Set up global keyboard handlers */
keyboard$
.pipe(
filter(({ mode }) => mode === "global"),
)
.subscribe(key => {
switch (key.type) {
/* Open search and select query */
case "f":
case "s":
case "/":
setElementFocus(query)
setElementSelection(query)
key.claim()
break
}
})
/* Create and return component */
const query$ = mountSearchQuery(query as HTMLInputElement, worker)
return merge(
query$,
mountSearchResult(
getElementOrThrow("[data-md-component=search-result]", el),
worker, { query$ }
)
mountSearchResult(result, worker, { query$ })
)
}

View File

@ -23,6 +23,7 @@
import "focus-visible"
import { Subject, defer, merge } from "rxjs"
import {
filter,
map,
mergeWith,
shareReplay,
@ -32,9 +33,11 @@ import {
import { feature } from "./_"
import {
at,
getElement,
getElementOrThrow,
getElements,
watchDocument,
watchKeyboard,
watchLocation,
watchLocationTarget,
watchMedia,
@ -64,7 +67,7 @@ import {
} from "./patches"
/* ----------------------------------------------------------------------------
* Program
* Application
* ------------------------------------------------------------------------- */
/* Yay, JavaScript is available */
@ -75,6 +78,7 @@ document.documentElement.classList.add("js")
const document$ = watchDocument()
const location$ = watchLocation()
const target$ = watchLocationTarget()
const keyboard$ = watchKeyboard()
/* Set up media observables */
const viewport$ = watchViewport()
@ -90,6 +94,32 @@ setupClipboardJS({ alert$ })
if (feature("navigation.instant"))
setupInstantLoading({ document$, location$, viewport$ })
/* Set up global keyboard handlers */
keyboard$
.pipe(
filter(({ mode }) => mode === "global")
)
.subscribe(key => {
switch (key.type) {
/* 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
}
})
/* Set up patches */
patchIndeterminate({ document$ })
patchScrollfix({ document$ })
@ -121,7 +151,7 @@ const control$ = merge(
/* Search */
...getElements("[data-md-component=search]")
.map(child => mountSearch(child)),
.map(child => mountSearch(child, { keyboard$ })),
/* Repository information */
...getElements("[data-md-component=source]")
@ -162,16 +192,18 @@ const component$ = document$
mergeWith(control$)
)
/* Subscribe to all components */
component$.subscribe()
/* Export to window */
export {
document$,
component$,
viewport$,
location$,
target$,
screen$,
keyboard$,
viewport$,
tablet$,
print$
screen$,
print$,
component$
}