aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/src/templates/assets/javascripts/integrations/search/internal/highlight
diff options
context:
space:
mode:
Diffstat (limited to 'src/templates/assets/javascripts/integrations/search/internal/highlight')
-rw-r--r--src/templates/assets/javascripts/integrations/search/internal/highlight/index.ts162
1 files changed, 162 insertions, 0 deletions
diff --git a/src/templates/assets/javascripts/integrations/search/internal/highlight/index.ts b/src/templates/assets/javascripts/integrations/search/internal/highlight/index.ts
new file mode 100644
index 00000000..7cc3bf1a
--- /dev/null
+++ b/src/templates/assets/javascripts/integrations/search/internal/highlight/index.ts
@@ -0,0 +1,162 @@
+/*
+ * Copyright (c) 2016-2023 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
+ * @param full - Full results
+ *
+ * @returns Highlighted string value
+ */
+export function highlight(
+ input: string, table: PositionTable, positions: Position[], full = false
+): string {
+ return highlightAll([input], table, positions, full).pop()!
+}
+
+/**
+ * Highlight all occurrences in a set of strings
+ *
+ * @param inputs - Input values
+ * @param table - Table for indexing
+ * @param positions - Occurrences
+ * @param full - Full results
+ *
+ * @returns Highlighted string values
+ */
+export function highlightAll(
+ inputs: string[], table: PositionTable, positions: Position[], full = false
+): 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) => {
+ let cursor = 0
+
+ /* 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
+
+ /* Add prefix, if full results are desired */
+ if (full && start > cursor)
+ slices.push(input.slice(cursor, start))
+
+ /* 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("")
+ }
+
+ /* Update cursor */
+ cursor = end + length
+
+ /* Append slice and abort if we have two */
+ if (slices.push(slice) === 2)
+ break
+ }
+
+ /* Add suffix, if full results are desired */
+ if (full && cursor < input.length)
+ slices.push(input.slice(cursor))
+
+ /* Return highlighted slices */
+ return slices.join("")
+ })
+}