Files
mkdocs-material/src/assets/javascripts/integrations/search/internal/highlight/index.ts
2022-12-11 15:55:12 +01:00

149 lines
4.5 KiB
TypeScript

/*
* Copyright (c) 2016-2022 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 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.
*/
/* ----------------------------------------------------------------------------
* Types
* ------------------------------------------------------------------------- */
/**
* Position table
*/
export type PositionTable = number[][]
/**
* Position
*/
export type Position = number
/* ----------------------------------------------------------------------------
* Functions
* ------------------------------------------------------------------------- */
/**
* Highlight all occurrences in a string
*
* This function receives a field's value (e.g. like `title` or `text`), it's
* position table that was generated during indexing, and the positions found
* when executing the query. It then highlights all occurrences, and returns
* their concatenation. In case of multiple blocks, two are returned.
*
* @param input - Input value
* @param table - Table for indexing
* @param positions - Occurrences
*
* @returns Highlighted string value
*/
export function highlight(
input: string, table: PositionTable, positions: Position[]
): string {
return highlightAll([input], table, positions).pop()!
}
/**
* Highlight all occurrences in a set of strings
*
* @param inputs - Input values
* @param table - Table for indexing
* @param positions - Occurrences
*
* @returns Highlighted string values
*/
export function highlightAll(
inputs: string[], table: PositionTable, positions: Position[]
): string[] {
/* Map blocks to input values */
const mapping = [0]
for (let t = 1; t < table.length; t++) {
const prev = table[t - 1]
const next = table[t]
/* Check if table points to new block */
const p = prev[prev.length - 1] >>> 2 & 0x3FF
const q = next[0] >>> 12
/* Add block to mapping */
mapping.push(+(p > q) + mapping[mapping.length - 1])
}
/* Highlight strings one after another */
return inputs.map((input, i) => {
/* Map occurrences to blocks */
const blocks = new Map<number, number[]>()
for (const p of positions.sort((a, b) => a - b)) {
const index = p & 0xFFFFF
const block = p >>> 20
if (mapping[block] !== i)
continue
/* Ensure presence of block group */
let group = blocks.get(block)
if (typeof group === "undefined")
blocks.set(block, group = [])
/* Add index to group */
group.push(index)
}
/* Just return string, if no occurrences */
if (blocks.size === 0)
return input
/* Compute slices */
const slices: string[] = []
for (const [block, indexes] of blocks) {
const t = table[block]
/* Extract positions and length */
const start = t[0] >>> 12
const end = t[t.length - 1] >>> 12
const length = t[t.length - 1] >>> 2 & 0x3FF
/* Extract and highlight slice */
let slice = input.slice(start, end + length)
for (const j of indexes.sort((a, b) => b - a)) {
/* Retrieve offset and length of match */
const p = (t[j] >>> 12) - start
const q = (t[j] >>> 2 & 0x3FF) + p
/* Wrap occurrence */
slice = [
slice.slice(0, p),
"<mark>",
slice.slice(p, q),
"</mark>",
slice.slice(q)
].join("")
}
/* Append slice and abort if we have two */
if (slices.push(slice) === 2)
break
}
/* Return highlighted slices */
return slices.join("")
})
}