-
+
+
{% block site_nav %} {% if nav %}
@@ -235,7 +235,7 @@ {%- endfor -%} {{ translations | tojson }} - + {% for path in config["extra_javascript"] %} {% endfor %} diff --git a/src/assets/javascripts/index.ts b/src/assets/javascripts/index.ts index 3ab0dbda8..185f84b0b 100644 --- a/src/assets/javascripts/index.ts +++ b/src/assets/javascripts/index.ts @@ -22,18 +22,16 @@ import { identity } from "ramda" import { - EMPTY, MonoTypeOperatorFunction, - NEVER, Observable, + Subject, fromEvent, merge, - of, pipe, - zip } from "rxjs" import { delay, + distinctUntilKeyChanged, filter, map, pluck, @@ -41,12 +39,9 @@ import { switchMap, switchMapTo, tap, - withLatestFrom } from "rxjs/operators" import { ajax } from "rxjs/ajax" -import { Config, isConfig } from "./config" -import { setupSearch } from "./search" import { Component, paintHeaderShadow, @@ -59,23 +54,57 @@ import { watchMain, watchSearchReset, watchSidebar, - watchToggle, watchTopOffset -} from "./theme" +} from "./components" import { + not, + switchMapIf +} from "./extensions" +import { SearchIndex } from "./modules/search" +import { + getElement, watchDocument, watchDocumentSwitch, watchLocation, watchLocationFragment, watchMedia, + watchToggle, watchViewportOffset, - watchViewportSize -} from "./ui" -import { - getElement, - not, - switchMapIf + watchViewportSize, + watchWorker } from "./utilities" +import { SearchMessage, SearchMessageType } from "./workers" + +/** + * Configuration + */ +export interface Config { + base: string /* Base URL */ + worker: { + search: string /* Web worker URL */ + packer: string /* Web worker URL */ + } +} + +import { PackerMessage, PackerMessageType } from "./workers/packer" + +import { renderSearchResult } from "./templates" + +/* ---------------------------------------------------------------------------- + * Functions + * ------------------------------------------------------------------------- */ + +/** + * Ensure that the given value is a valid configuration + * + * @param config - Configuration + * + * @return Test result + */ +export function isConfig(config: any): config is Config { + return typeof config === "object" + && typeof config.base === "string" +} // TBD @@ -112,6 +141,72 @@ export function initialize(config: unknown) { if (!isConfig(config)) throw new SyntaxError(`Invalid configuration: ${JSON.stringify(config)}`) + const worker = new Worker(config.worker.search) + const packer = new Worker(config.worker.packer) + + // const query = message.data.trim().replace(/\s+|$/g, "* ") // TODO: do this outside of the worker + + const packerMessage$ = new Subject() + const packer$ = watchWorker(packer, { message$: packerMessage$ }) + + // send a message, then switchMapTo worker! + + packer$.subscribe(message => { + console.log("PACKER.MSG", message) + // is always packed! + console.log(message.data.length) + localStorage.setItem("index", message.data) + }) + + const searchMessage$ = new Subject() + + const search$ = watchWorker(worker, { message$: searchMessage$ }) + search$.subscribe(message => { + if (message.type === SearchMessageType.DUMP) { + console.log(message.data.length) + packerMessage$.next({ + type: PackerMessageType.STRING, + data: message.data + }) + } else if (message.type === SearchMessageType.RESULT) { + console.log("RESULT", message) + + const list = document.querySelector(".md-search-result__list")! + list.innerHTML = "" + for (const el of message.data.map(renderSearchResult)) // TODO: perform entire lazy render!!!! + list.appendChild(el as any) // only render visibile stuff...! + + // paint on next animation frame!? + + // build a rendering pipeline for search results + scroll bottom! + + } + // if (message.type === 0) { + // console.log("Packing...") + // packerMessage$.next(message.toString()) + // } else { + // console.log((message as any).term, ":", (message as any).res) + // } + }) + + // filter singular "+" or "-",as it will result in a lunr.js error + + ajax({ + url: `${config.base}/search/search_index.json`, + responseType: "json", + withCredentials: true + }) + .pipe( + pluck("response"), + map(data => ({ + type: SearchMessageType.SETUP, + data + })) + ) + .subscribe(message => { + searchMessage$.next(message) // TODO: this shall not complete + }) + /* ----------------------------------------------------------------------- */ /* Create viewport observables */ @@ -133,7 +228,7 @@ export function initialize(config: unknown) { /* ----------------------------------------------------------------------- */ /* Create component map observable */ - const components$ = watchComponentMap(names, { load$ }) + const components$ = watchComponentMap(names, { document$: load$ }) const component = (name: Component): Observable => { return components$ @@ -157,6 +252,22 @@ export function initialize(config: unknown) { // ---------------------------------------------------------------------------- + component("query") + .pipe( + switchMap(el => fromEvent(el, "keyup") // not super nice... + .pipe( + map(() => ({ + type: SearchMessageType.QUERY, + data: (el as HTMLInputElement).value + })), // TODO. ugly... + distinctUntilKeyChanged("data") + ) + ) + ) + .subscribe(x => { + searchMessage$.next(x) + }) + // // WIP: instant loading // load$ // .pipe( diff --git a/src/assets/javascripts/templates/search/_/index.tsx b/src/assets/javascripts/templates/search/_/index.tsx index adfb0f97b..2796b96b2 100644 --- a/src/assets/javascripts/templates/search/_/index.tsx +++ b/src/assets/javascripts/templates/search/_/index.tsx @@ -31,7 +31,7 @@ import { renderSectionDocument } from "../section" * ------------------------------------------------------------------------- */ /** - * CSS class references + * CSS classes */ const css = { item: "md-search-result__item" diff --git a/src/assets/javascripts/templates/search/article/index.tsx b/src/assets/javascripts/templates/search/article/index.tsx index 732cddbeb..5d4257193 100644 --- a/src/assets/javascripts/templates/search/article/index.tsx +++ b/src/assets/javascripts/templates/search/article/index.tsx @@ -22,13 +22,14 @@ import { h } from "extensions" import { ArticleDocument } from "modules" +import { truncate } from "utilities" /* ---------------------------------------------------------------------------- * Data * ------------------------------------------------------------------------- */ /** - * CSS class references + * CSS classes */ const css = { link: "md-search-result__link", @@ -55,7 +56,10 @@ export function renderArticleDocument(

{title}

- {text.length ?

{text}

: undefined} + {text.length + ?

{truncate(text, 320)}

+ : undefined + }
) diff --git a/src/assets/javascripts/templates/search/section/index.tsx b/src/assets/javascripts/templates/search/section/index.tsx index 0ac058e45..1f776fd0e 100644 --- a/src/assets/javascripts/templates/search/section/index.tsx +++ b/src/assets/javascripts/templates/search/section/index.tsx @@ -22,13 +22,14 @@ import { h } from "extensions" import { SectionDocument } from "modules" +import { truncate } from "utilities" /* ---------------------------------------------------------------------------- * Data * ------------------------------------------------------------------------- */ /** - * CSS class references + * CSS classes */ const css = { link: "md-search-result__link", @@ -55,7 +56,10 @@ export function renderSectionDocument(

{title}

- {text.length ?

{text}

: undefined} + {text.length + ?

{truncate(text, 320)}

+ : undefined + }
) diff --git a/src/assets/javascripts/utilities/index.ts b/src/assets/javascripts/utilities/index.ts index 7086e784e..862f8ca46 100644 --- a/src/assets/javascripts/utilities/index.ts +++ b/src/assets/javascripts/utilities/index.ts @@ -21,4 +21,5 @@ */ export * from "./agent" +export * from "./string" export * from "./toggle" diff --git a/src/assets/javascripts/utilities/string/index.ts b/src/assets/javascripts/utilities/string/index.ts new file mode 100644 index 000000000..030b97da1 --- /dev/null +++ b/src/assets/javascripts/utilities/string/index.ts @@ -0,0 +1,42 @@ +/* + * 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 + * ------------------------------------------------------------------------- */ + +/** + * Truncate a string after the given number of characters + * + * @param string - String to be truncated + * @param n - Number of characters + * + * @return Truncated string + */ +export function truncate(string: string, n: number): string { + let i = n + if (string.length > i) { + while (string[i] !== " " && --i > 0); + return `${string.substring(0, i)}...` + } + return string +} diff --git a/tsconfig.json b/tsconfig.json index 87717a4a3..0d5226b22 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "alwaysStrict": true, - "baseUrl": ".", + "baseUrl": "src/assets/javascripts", "declaration": false, "declarationMap": false, "downlevelIteration": true, @@ -21,7 +21,15 @@ "noImplicitThis": true, "noUnusedLocals": true, "noUnusedParameters": true, - "paths": {}, + "paths": { + "actions": ["actions"], + "components": ["components"], + "extensions": ["extensions"], + "modules": ["modules"], + "templates": ["templates"], + "utilities": ["utilities"], + "workers": ["workers"] + }, "removeComments": false, "resolveJsonModule": true, "sourceMap": true,