Files
mkdocs-material/src/assets/javascripts/integrations/search/worker/main/index.ts
2020-09-27 22:19:38 +02:00

154 lines
4.9 KiB
TypeScript

/*
* 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 RTICULAR 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 "lunr"
import { Search, SearchIndexConfig } from "../../_"
import {
SearchMessage,
SearchMessageType
} from "../message"
/* ----------------------------------------------------------------------------
* Types
* ------------------------------------------------------------------------- */
/**
* Add support for usage with `iframe-worker` polyfill
*
* While `importScripts` is synchronous when executed inside of a web worker,
* it's not possible to provide a synchronous polyfilled implementation. The
* cool thing is that awaiting a non-Promise is a noop, so extending the type
* definition to return a `Promise` shouldn't break anything.
*
* @see https://bit.ly/2PjDnXi - GitHub comment
*/
declare global {
function importScripts(...urls: string[]): Promise<void> | void
}
/* ----------------------------------------------------------------------------
* Data
* ------------------------------------------------------------------------- */
/**
* Search index
*/
let index: Search
/* ----------------------------------------------------------------------------
* Helper functions
* ------------------------------------------------------------------------- */
/**
* Fetch (= import) multi-language support through `lunr-languages`
*
* This function will automatically import the stemmers necessary to process
* the languages which were given through the search index configuration.
*
* If the worker runs inside of an `iframe` (when using `iframe-worker` as
* a shim), the base URL for the stemmers to be loaded must be determined by
* searching for the first `script` element with a `src` attribute, which will
* contain the contents of this script.
*
* @param config - Search index configuration
*
* @return Promise resolving with no result
*/
async function setupSearchLanguages(
config: SearchIndexConfig
): Promise<void> {
let base = "../lunr"
/* Detect `iframe-worker` and fix base URL */
if (typeof parent !== "undefined" && "IFrameWorker" in parent) {
const worker = document.querySelector<HTMLScriptElement>("script[src]")!
const [path] = worker.src.split("/worker")
/* Prefix base with path */
base = base.replace("..", path)
}
/* Add scripts for languages */
const scripts = []
for (const lang of config.lang) {
if (lang === "ja") scripts.push(`${base}/tinyseg.min.js`)
if (lang !== "en") scripts.push(`${base}/min/lunr.${lang}.min.js`)
}
/* Add multi-language support */
if (config.lang.length > 1)
scripts.push(`${base}/min/lunr.multi.min.js`)
/* Load scripts synchronously */
if (scripts.length)
await importScripts(
`${base}/min/lunr.stemmer.support.min.js`,
...scripts
)
}
/* ----------------------------------------------------------------------------
* Functions
* ------------------------------------------------------------------------- */
/**
* Message handler
*
* @param message - Source message
*
* @return Target message
*/
export async function handler(
message: SearchMessage
): Promise<SearchMessage> {
switch (message.type) {
/* Search setup message */
case SearchMessageType.SETUP:
await setupSearchLanguages(message.data.config)
index = new Search(message.data)
return {
type: SearchMessageType.READY
}
/* Search query message */
case SearchMessageType.QUERY:
return {
type: SearchMessageType.RESULT,
data: index ? index.search(message.data) : []
}
/* All other messages */
default:
throw new TypeError("Invalid message type")
}
}
/* ----------------------------------------------------------------------------
* Worker
* ------------------------------------------------------------------------- */
addEventListener("message", async ev => {
postMessage(await handler(ev.data))
})