diff --git a/package-lock.json b/package-lock.json index 0ebcf8c0e..57a9e27e2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -374,6 +374,12 @@ "integrity": "sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA==", "dev": true }, + "@types/escape-html": { + "version": "0.0.20", + "resolved": "https://registry.npmjs.org/@types/escape-html/-/escape-html-0.0.20.tgz", + "integrity": "sha512-6dhZJLbA7aOwkYB2GDGdIqJ20wmHnkDzaxV9PJXe7O02I2dSFTERzRB6JrX6cWKaS+VqhhY7cQUMCbO5kloFUw==", + "dev": true + }, "@types/events": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", @@ -391,6 +397,18 @@ "@types/node": "*" } }, + "@types/lunr": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@types/lunr/-/lunr-2.3.2.tgz", + "integrity": "sha512-zcUZYquYDUEegRRPQtkZ068U9CoIjW6pJMYCVDRK25r76FEWvMm1oHqZQUfQh4ayIZ42lipXOpXEiAtGXc1XUg==", + "dev": true + }, + "@types/lz-string": { + "version": "1.3.33", + "resolved": "https://registry.npmjs.org/@types/lz-string/-/lz-string-1.3.33.tgz", + "integrity": "sha512-yWj3OnlKlwNpq9+Jh/nJkVAD3ta8Abk2kIRpjWpVkDlAD43tn6Q6xk5hurp84ndcq54jBDBGCD/WcIR0pspG0A==", + "dev": true + }, "@types/minimatch": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", @@ -409,6 +427,15 @@ "integrity": "sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw==", "dev": true }, + "@types/ramda": { + "version": "0.26.26", + "resolved": "https://registry.npmjs.org/@types/ramda/-/ramda-0.26.26.tgz", + "integrity": "sha512-jDx2Lp2WvziMTcDxOU21yzRr6jP/VTWvpg6SQxeM63VldSjugUY1N9p1Q8YY/wpGvqzpkstUFmg2oGJdBSp5gg==", + "dev": true, + "requires": { + "ts-toolbelt": "^4.7.7" + } + }, "@types/source-list-map": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", @@ -2516,6 +2543,11 @@ "is-symbol": "^1.0.2" } }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -4886,6 +4918,11 @@ "resolved": "https://registry.npmjs.org/lunr-languages/-/lunr-languages-1.1.0.tgz", "integrity": "sha512-53pTQX8jkjZBWSPJa/+3UJyIPcGmeWWwqS4RRr5GxhRilqL9tv/Vuj7Vb1Nz3Dtz2HTK2Pdmrf3zevHS/ZycjQ==" }, + "lz-string": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.4.4.tgz", + "integrity": "sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY=" + }, "make-dir": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", @@ -6530,10 +6567,10 @@ "integrity": "sha1-Q2CxfGETatOAeDl/8RQW4Ybc+7g=", "dev": true }, - "rambda": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/rambda/-/rambda-3.1.0.tgz", - "integrity": "sha512-J9mLLbZabxjr5h1UrT0e6AIen7Ri5XqyUAEMs+8ceVbgp2WLaCqizmiwHjZdpkKTFjctjh/QHB9bxwB6F1LTwA==" + "ramda": { + "version": "0.26.1", + "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.26.1.tgz", + "integrity": "sha512-hLWjpy7EnsDBb0p+Z3B7rPi3GDeRG5ZtiI33kJhTt+ORCd38AbAIjB/9zRIUoeTbE/AVX5ZkU7m6bznsvrf8eQ==" }, "randomatic": { "version": "3.1.1", @@ -8675,6 +8712,12 @@ } } }, + "ts-toolbelt": { + "version": "4.8.3", + "resolved": "https://registry.npmjs.org/ts-toolbelt/-/ts-toolbelt-4.8.3.tgz", + "integrity": "sha512-qSC/t6vfUPqVMkH7wQmRwYbShubAJufUoUynJj8e+AlSXK3+M6rC/OnQ+Mdw6Qd/WYTf46QZDM8nUhxYVJTIPw==", + "dev": true + }, "tslib": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", @@ -9443,6 +9486,28 @@ "errno": "~0.1.7" } }, + "worker-loader": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/worker-loader/-/worker-loader-2.0.0.tgz", + "integrity": "sha512-tnvNp4K3KQOpfRnD20m8xltE3eWh89Ye+5oj7wXEEHKac1P4oZ6p9oTj8/8ExqoSBnk9nu5Pr4nKfQ1hn2APJw==", + "dev": true, + "requires": { + "loader-utils": "^1.0.0", + "schema-utils": "^0.4.0" + }, + "dependencies": { + "schema-utils": { + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.4.7.tgz", + "integrity": "sha512-v/iwU6wvwGK8HbU9yi3/nhGzP0yGSuhQMzL6ySiec1FSrZZDkhm4noOSWzrNFo/jEc+SJY6jRTwuwbSXJPDUnQ==", + "dev": true, + "requires": { + "ajv": "^6.1.0", + "ajv-keywords": "^3.1.0" + } + } + } + }, "wrap-ansi": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", diff --git a/package.json b/package.json index 6b658497a..cd8b763fb 100644 --- a/package.json +++ b/package.json @@ -30,14 +30,20 @@ }, "dependencies": { "clipboard": "^2.0.0", + "escape-html": "^1.0.3", "js-cookie": "^2.2.1", "lunr": "^2.3.6", "lunr-languages": "^1.1.0", - "rambda": "^3.1.0", + "lz-string": "^1.4.4", + "ramda": "^0.26.1", "rxjs": "^6.5.3" }, "devDependencies": { + "@types/escape-html": "0.0.20", + "@types/lunr": "^2.3.2", + "@types/lz-string": "^1.3.33", "@types/node": "^12.7.8", + "@types/ramda": "^0.26.26", "@types/webpack": "^4.39.2", "autoprefixer": "^9.6.1", "css-mqpacker": "^7.0.0", @@ -60,10 +66,11 @@ "tslint-sonarts": "^1.9.0", "typescript": "^3.6.3", "webpack": "^4.41.0", - "webpack-cli": "^3.3.9" + "webpack-cli": "^3.3.9", + "worker-loader": "^2.0.0" }, "engines": { - "node": ">= 8" + "node": ">= 10" }, "private": true } diff --git a/src/assets/javascripts/ui/element/index.ts b/src/assets/javascripts/ui/element/index.ts new file mode 100644 index 000000000..62377fb44 --- /dev/null +++ b/src/assets/javascripts/ui/element/index.ts @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2016-2019 Martin Donath + * + * 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 { OperatorFunction, pipe } from "rxjs" +import { filter, map } from "rxjs/operators" + +/* ---------------------------------------------------------------------------- + * Functions + * ------------------------------------------------------------------------- */ + +/** + * Retrieve an element matching the query selector + * + * @template T - Element type + * + * @param selector - Query selector + * + * @return HTML element + */ +export function getElement( + selector: string +): T | undefined { + return document.querySelector(selector) || undefined +} + +/* ------------------------------------------------------------------------- */ + +/** + * Retrieve an element matching the query selector + * + * @template T - Element type + * + * @return HTML element observable + */ +export function withElement< + T extends HTMLElement +>(): OperatorFunction { + return pipe( + map(selector => getElement(selector)!), + filter(Boolean) + ) +} diff --git a/src/assets/javascripts/state/index.ts b/src/assets/javascripts/ui/index.ts similarity index 87% rename from src/assets/javascripts/state/index.ts rename to src/assets/javascripts/ui/index.ts index 8a76f7bec..72381fb55 100644 --- a/src/assets/javascripts/state/index.ts +++ b/src/assets/javascripts/ui/index.ts @@ -20,6 +20,6 @@ * IN THE SOFTWARE. */ -/* ---------------------------------------------------------------------------- - * Types - * ------------------------------------------------------------------------- */ +export * from "./element" +export * from "./location" +export * from "./viewport" diff --git a/src/assets/javascripts/ui/location/index.ts b/src/assets/javascripts/ui/location/index.ts new file mode 100644 index 000000000..d1c19c86d --- /dev/null +++ b/src/assets/javascripts/ui/location/index.ts @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2016-2019 Martin Donath + * + * 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, fromEvent } from "rxjs" +import { filter, map, startWith } from "rxjs/operators" + +/* ---------------------------------------------------------------------------- + * Data + * ------------------------------------------------------------------------- */ + +/** + * Observable for window hash changes + */ +const hash$ = fromEvent(window, "hashchange") + +/* ---------------------------------------------------------------------------- + * Functions + * ------------------------------------------------------------------------- */ + +/** + * Create an observable emitting changes in location hashes + * + * @return Hash change observable + */ +export function fromLocationHash(): Observable { + return hash$.pipe( + startWith(document.location.hash), + map(() => document.location.hash), + filter(hash => hash.length > 0) + ) +} diff --git a/src/assets/javascripts/viewport/_/index.ts b/src/assets/javascripts/ui/viewport/_/index.ts similarity index 95% rename from src/assets/javascripts/viewport/_/index.ts rename to src/assets/javascripts/ui/viewport/_/index.ts index 9bf4af7ff..6160b41a2 100644 --- a/src/assets/javascripts/viewport/_/index.ts +++ b/src/assets/javascripts/ui/viewport/_/index.ts @@ -20,7 +20,7 @@ * IN THE SOFTWARE. */ -import { equals } from "rambda" +import { equals } from "ramda" import { Observable, fromEvent, merge } from "rxjs" import { distinctUntilChanged, map, startWith } from "rxjs/operators" @@ -93,7 +93,7 @@ export function getViewportSize(): ViewportSize { * * @return Viewport offset observable */ -export function watchViewportOffset(): Observable { +export function fromViewportOffset(): Observable { return merge(scroll$, resize$).pipe( map(getViewportOffset), startWith(getViewportOffset()), @@ -106,7 +106,7 @@ export function watchViewportOffset(): Observable { * * @return Viewport size observable */ -export function watchViewportSize(): Observable { +export function fromViewportSize(): Observable { return resize$.pipe( map(getViewportSize), startWith(getViewportSize()), diff --git a/src/assets/javascripts/viewport/breakpoint/index.ts b/src/assets/javascripts/ui/viewport/breakpoint/index.ts similarity index 97% rename from src/assets/javascripts/viewport/breakpoint/index.ts rename to src/assets/javascripts/ui/viewport/breakpoint/index.ts index 44b21047d..2cce69430 100644 --- a/src/assets/javascripts/viewport/breakpoint/index.ts +++ b/src/assets/javascripts/ui/viewport/breakpoint/index.ts @@ -35,7 +35,7 @@ import { startWith } from "rxjs/operators" * @return Media query observable */ export function fromMediaQuery(query: string): Observable { - const media = window.matchMedia(query) + const media = window.matchMedia(query) return fromEventPattern(next => media.addListener(() => next(media.matches)) ).pipe( diff --git a/src/assets/javascripts/viewport/index.ts b/src/assets/javascripts/ui/viewport/index.ts similarity index 100% rename from src/assets/javascripts/viewport/index.ts rename to src/assets/javascripts/ui/viewport/index.ts diff --git a/src/assets/javascripts/utilities/index.ts b/src/assets/javascripts/utilities/index.ts new file mode 100644 index 000000000..cb5cfb189 --- /dev/null +++ b/src/assets/javascripts/utilities/index.ts @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2016-2019 Martin Donath + * + * 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. + */ + +/* ---------------------------------------------------------------------------- + * Functions + * ------------------------------------------------------------------------- */ + +/** + * Convert a HTML collection to an array + * + * @template T - HTML element type + * + * @param collection - HTML collection + * + * @return Array of HTML elements + */ +export function toArray< + T extends HTMLElement +>(collection: HTMLCollection): T[] { + return Array.from(collection) as T[] +}