This commit is contained in:
squidfunk 2019-12-18 20:58:31 +01:00
parent 367211f477
commit 60330fd18d
8 changed files with 95 additions and 21 deletions

View File

@ -61,15 +61,14 @@ theme:
# Plugins # Plugins
plugins: plugins:
- search - search:
prebuild_index: false
# - minify: # - minify:
# minify_html: true # minify_html: true
# Customization # Customization
extra: extra:
social: social:
- type: globe
link: http://struct.cc
- type: github-alt - type: github-alt
link: https://github.com/squidfunk link: https://github.com/squidfunk
- type: twitter - type: twitter

23
package-lock.json generated
View File

@ -1683,6 +1683,14 @@
"has-ansi": "^2.0.0", "has-ansi": "^2.0.0",
"strip-ansi": "^3.0.0", "strip-ansi": "^3.0.0",
"supports-color": "^2.0.0" "supports-color": "^2.0.0"
},
"dependencies": {
"escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
"dev": true
}
} }
}, },
"supports-color": { "supports-color": {
@ -2549,10 +2557,9 @@
"integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
}, },
"escape-string-regexp": { "escape-string-regexp": {
"version": "1.0.5", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz",
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w=="
"dev": true
}, },
"eslint-scope": { "eslint-scope": {
"version": "4.0.3", "version": "4.0.3",
@ -5388,6 +5395,14 @@
"has-ansi": "^2.0.0", "has-ansi": "^2.0.0",
"strip-ansi": "^3.0.0", "strip-ansi": "^3.0.0",
"supports-color": "^2.0.0" "supports-color": "^2.0.0"
},
"dependencies": {
"escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
"dev": true
}
} }
}, },
"cross-spawn": { "cross-spawn": {

View File

@ -32,6 +32,7 @@
"dependencies": { "dependencies": {
"clipboard": "^2.0.0", "clipboard": "^2.0.0",
"escape-html": "^1.0.3", "escape-html": "^1.0.3",
"escape-string-regexp": "^2.0.0",
"lunr": "^2.3.6", "lunr": "^2.3.6",
"lunr-languages": "^1.1.0", "lunr-languages": "^1.1.0",
"lz-string": "^1.4.4", "lz-string": "^1.4.4",

View File

@ -52,9 +52,9 @@ type Child = Child[] | Element | Text | string | number
*/ */
function appendChild(el: Element, child: Child): void { function appendChild(el: Element, child: Child): void {
/* Handle primitive types */ /* Handle primitive types (including raw HTML) */
if (typeof child === "string" || typeof child === "number") { if (typeof child === "string" || typeof child === "number") {
el.appendChild(new Text(child.toString())) el.innerHTML += child.toString()
/* Handle nodes */ /* Handle nodes */
} else if (child instanceof Node) { } else if (child instanceof Node) {

View File

@ -28,6 +28,10 @@ import {
SectionDocument, SectionDocument,
setupSearchDocumentMap setupSearchDocumentMap
} from "../document" } from "../document"
import {
SearchHighlightFactoryFn,
setupSearchHighlighter
} from "../highlight"
/* ---------------------------------------------------------------------------- /* ----------------------------------------------------------------------------
* Types * Types
@ -113,6 +117,11 @@ export class Search {
*/ */
protected documents: SearchDocumentMap protected documents: SearchDocumentMap
/**
* Search highlight factory function
*/
protected highlight: SearchHighlightFactoryFn
/** /**
* The lunr search index * The lunr search index
*/ */
@ -124,8 +133,9 @@ export class Search {
* @param index - Search index * @param index - Search index
* @param options - Options * @param options - Options
*/ */
public constructor({ docs, options, index }: SearchIndex) { public constructor({ config, docs, options, index }: SearchIndex) {
this.documents = setupSearchDocumentMap(docs) this.documents = setupSearchDocumentMap(docs)
this.highlight = setupSearchHighlighter(config)
/* If no index was given, create it */ /* If no index was given, create it */
if (typeof index === "undefined") { if (typeof index === "undefined") {
@ -171,16 +181,24 @@ export class Search {
* page. For this reason, section results are grouped within their respective * page. For this reason, section results are grouped within their respective
* articles which are the top-level results that are returned. * articles which are the top-level results that are returned.
* *
* Rogue control characters must be filtered before handing the query to the
* search index, as lunr will throw otherwise.
*
* @param query - Query string * @param query - Query string
* *
* @return Search results * @return Search results
*/ */
public search(query: string): SearchResult[] { public search(query: string): SearchResult[] {
const groups = this.index.search(query query = query
.replace(/\s+[+-](?:\s+|$)/, "") /* Filter rogue quantifiers */ .replace(/(?:^|\s+)[+-:~^]+(?=\s+|$)/g, "")
) .trim()
/* Group sections by containing article */ /* Abort early, if query is empty */
if (!query)
return []
/* Group sections by containing article */
const groups = this.index.search(query)
.reduce((results, result) => { .reduce((results, result) => {
const document = this.documents.get(result.ref) const document = this.documents.get(result.ref)
if (typeof document !== "undefined") { if (typeof document !== "undefined") {
@ -195,11 +213,14 @@ export class Search {
return results return results
}, new Map<string, lunr.Index.Result[]>()) }, new Map<string, lunr.Index.Result[]>())
/* Create highlighter for query */
const fn = this.highlight(query)
/* Map groups to search documents */ /* Map groups to search documents */
return [...groups].map(([ref, sections]) => ({ return [...groups].map(([ref, sections]) => ({
article: this.documents.get(ref) as ArticleDocument, article: fn(this.documents.get(ref) as ArticleDocument),
sections: sections.map(section => { sections: sections.map(section => {
return this.documents.get(section.ref) as SectionDocument return fn(this.documents.get(section.ref) as SectionDocument)
}) })
})) }))
} }

View File

@ -61,7 +61,7 @@ export type SearchDocumentMap = Map<string, SearchDocument>
* ------------------------------------------------------------------------- */ * ------------------------------------------------------------------------- */
/** /**
* Build a search document mapping * Create a search document mapping
* *
* @param docs - Search index documents * @param docs - Search index documents
* *

View File

@ -21,10 +21,45 @@
* IN THE SOFTWARE. * IN THE SOFTWARE.
*/ */
import { compress, decompress } from "lz-string" import {
compress,
compressToUTF16,
decompress,
decompressFromUTF16
} from "lz-string"
import { PackerMessage, PackerMessageType } from "../_" import { PackerMessage, PackerMessageType } from "../_"
/* ----------------------------------------------------------------------------
* Data
* ------------------------------------------------------------------------- */
/**
* Determine methods for packing and unpacking
*
* While all Webkit-based browsers can store invalid UTF-16 strings in local
* storage, other browsers may only store valid UTF-16 strings.
*
* @see https://bit.ly/2Q1ArhU - LZ-String documentation
*/
const isWebkit = navigator.userAgent.includes("AppleWebKit")
/* ------------------------------------------------------------------------- */
/**
* Method for packing
*/
const pack = isWebkit
? compress
: compressToUTF16
/**
* Method for unpacking
*/
const unpack = isWebkit
? decompress
: decompressFromUTF16
/* ---------------------------------------------------------------------------- /* ----------------------------------------------------------------------------
* Functions * Functions
* ------------------------------------------------------------------------- */ * ------------------------------------------------------------------------- */
@ -43,14 +78,14 @@ export function handler(message: PackerMessage): PackerMessage {
case PackerMessageType.STRING: case PackerMessageType.STRING:
return { return {
type: PackerMessageType.PACKED, type: PackerMessageType.PACKED,
data: compress(message.data) data: pack(message.data)
} }
/* Unpack a packed string */ /* Unpack a packed string */
case PackerMessageType.PACKED: case PackerMessageType.PACKED:
return { return {
type: PackerMessageType.STRING, type: PackerMessageType.STRING,
data: decompress(message.data) data: unpack(message.data)
} }
} }
} }

View File

@ -20,7 +20,10 @@
* IN THE SOFTWARE. * IN THE SOFTWARE.
*/ */
import { SearchIndex, SearchResult } from "../../../modules" import { Subject } from "rxjs"
import { SearchIndex, SearchResult } from "modules"
import { watchWorker } from "utilities"
/* ---------------------------------------------------------------------------- /* ----------------------------------------------------------------------------
* Types * Types