Skip to content

Commit

Permalink
Improved keyboard handlers and added prev/next hotkeys
Browse files Browse the repository at this point in the history
  • Loading branch information
squidfunk committed Feb 20, 2020
1 parent 297a633 commit 9b04109
Show file tree
Hide file tree
Showing 13 changed files with 165 additions and 200 deletions.
24 changes: 24 additions & 0 deletions material/assets/javascripts/bundle.02fd1bf7.min.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions material/assets/javascripts/bundle.02fd1bf7.min.js.map

Large diffs are not rendered by default.

24 changes: 0 additions & 24 deletions material/assets/javascripts/bundle.24f9b6fb.min.js

This file was deleted.

1 change: 0 additions & 1 deletion material/assets/javascripts/bundle.24f9b6fb.min.js.map

This file was deleted.

4 changes: 2 additions & 2 deletions material/assets/manifest.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"assets/javascripts/bundle.js": "assets/javascripts/bundle.24f9b6fb.min.js",
"assets/javascripts/bundle.js.map": "assets/javascripts/bundle.24f9b6fb.min.js.map",
"assets/javascripts/bundle.js": "assets/javascripts/bundle.02fd1bf7.min.js",
"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.map": "assets/javascripts/worker/search.926ffd9e.min.js.map",
"assets/stylesheets/app-palette.scss": "assets/stylesheets/app-palette.3f90c815.min.css",
Expand Down
2 changes: 1 addition & 1 deletion material/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ <h2 id="__source">{{ lang.t("meta.source") }}</h2>
{% endblock %}
</div>
{% 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">
{%- set translations = {} -%}
{%- for key in [
Expand Down
13 changes: 4 additions & 9 deletions src/assets/javascripts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ import {
patchScrollfix,
patchSource
} from "patches"
import { takeIf, not, isConfig } from "utilities"
import { isConfig } from "utilities"
import { renderDialog } from "templates/dialog"

/* ------------------------------------------------------------------------- */
Expand Down Expand Up @@ -108,7 +108,6 @@ export function initialize(config: unknown) {
const document$ = watchDocument()
const location$ = watchLocation()
const hash$ = watchLocationHash()
const keyboard$ = watchKeyboard()
const viewport$ = watchViewport()
const tablet$ = watchMedia("(min-width: 960px)")
const screen$ = watchMedia("(min-width: 1220px)")
Expand Down Expand Up @@ -187,7 +186,7 @@ export function initialize(config: unknown) {

/* ----------------------------------------------------------------------- */

setupKeyboard({ keyboard$ })
const keyboard$ = setupKeyboard()

patchTables({ document$ })
patchDetails({ document$, hash$ })
Expand Down Expand Up @@ -215,10 +214,7 @@ export function initialize(config: unknown) {
)
})
)
.subscribe()

// TODO: general keyboard handler...
// put into main!?
.subscribe()

/* ----------------------------------------------------------------------- */

Expand Down Expand Up @@ -279,8 +275,7 @@ export function initialize(config: unknown) {
// TODO: experimental. necessary!?
keyboard$
.pipe(
takeIf(not(toggle$.pipe(switchMap(watchToggle)))),
filter(key => ["Tab"].includes(key.type)),
filter(key => key.mode === "global" && ["Tab"].includes(key.type)),
take(1)
)
.subscribe(() => {
Expand Down
105 changes: 84 additions & 21 deletions src/assets/javascripts/integrations/keyboard/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,30 +21,46 @@
*/

import { Observable } from "rxjs"
import { switchMap, withLatestFrom } from "rxjs/operators"
import {
filter,
map,
share,
switchMap,
withLatestFrom
} from "rxjs/operators"

import { useComponent } from "components"
import {
Key,
getActiveElement,
getElement,
getElements,
isSusceptibleToKeyboard,
setElementFocus,
setToggle,
useToggle,
watchKeyboard,
watchToggle
} from "observables"
import { not, takeIf } from "utilities"

/* ----------------------------------------------------------------------------
* Helper types
* Types
* ------------------------------------------------------------------------- */

/**
* Setup options
* Keyboard mode
*/
interface SetupOptions {
keyboard$: Observable<Key> /* Keyboard observable */
export type KeyboardMode =
| "global" /* Global */
| "search" /* Search is open */

/* ------------------------------------------------------------------------- */

/**
* Keyboard
*/
export interface Keyboard extends Key {
mode: KeyboardMode /* Keyboard mode */
}

/* ----------------------------------------------------------------------------
Expand All @@ -54,17 +70,44 @@ interface SetupOptions {
/**
* 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
*/
export function setupKeyboard(
{ keyboard$ }: SetupOptions
): Observable<Key> {

/* Setup keyboard handlers in search mode */
export function setupKeyboard(): Observable<Keyboard> {
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$
.pipe(
takeIf(toggle$.pipe(switchMap(watchToggle))),
filter(({ mode }) => mode === "search"),
withLatestFrom(
toggle$,
useComponent("search-query"),
Expand Down Expand Up @@ -114,23 +157,43 @@ export function setupKeyboard(
}
})

/* Setup general keyboard handlers */
/* Setup global keyboard handlers */
keyboard$
.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"))
)
.subscribe(([key, query]) => {
const active = getActiveElement()
switch (key.type) {

/* [s]earch / [f]ind: open search */
case "s":
/* Open search */
case "f":
if (!(active && isSusceptibleToKeyboard(active))) {
setElementFocus(query)
key.claim()
}
case "s":
setElementFocus(query)
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
}
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ import {
switchMap
} from "rxjs/operators"

import { getLocation } from "../../location"

/* ----------------------------------------------------------------------------
* Helper types
* ------------------------------------------------------------------------- */
Expand Down Expand Up @@ -64,7 +66,7 @@ export function watchDocumentSwitch(
): Observable<Document> {
return location$
.pipe(
startWith(location.href),
startWith(getLocation()),
map(url => url.replace(/#[^#]+$/, "")),
distinctUntilChanged(),
skip(1),
Expand Down
85 changes: 0 additions & 85 deletions src/assets/javascripts/utilities/rxjs/_/index.ts

This file was deleted.

48 changes: 46 additions & 2 deletions src/assets/javascripts/utilities/rxjs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,49 @@
* IN THE SOFTWARE.
*/

export * from "./_"
export * from "./operators"
import { Observable, defer, of } from "rxjs"

/* ----------------------------------------------------------------------------
* 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$
}
})
}
Loading

0 comments on commit 9b04109

Please sign in to comment.