- {@html subres.title}
diff --git a/pagefind_web_js/lib/highlight.ts b/pagefind_web_js/lib/highlight.ts
index f2130c08..ad30ce6f 100644
--- a/pagefind_web_js/lib/highlight.ts
+++ b/pagefind_web_js/lib/highlight.ts
@@ -1,114 +1,100 @@
-// document.querySelector(".test")!.innerHTML = "Hello from the highlight script";
-
// this script should be imported on the result pages to enable highlighting
-// tbh not sure how to read this option
-const pagefindQueryParamName = "pagefind-highlight";
-const highlightNodeElementName = "mark";
-const highlightNodeClassName = "pagefind__highlight";
-
-// wait for the DOM to be ready
-// read the query param
-// find all occurrences of the query param in the DOM, respecting the data-pagefind attributes
-// wrap the text in a mark with a class of pagefind__highlight
+import Mark from "mark.js";
-// code from https://stackoverflow.com/a/31369978
+// tbh not sure how to read this option
-function getElementsToHighlight() {
- // could have more than one element with [data-pagefind-body]
- // make sure it falls back correctly if no [data-pagefind-body]
- // should fall back to the root selector ig
- let pagefindBody =
- document.querySelectorAll("[data-pagefind-body]") || document.body;
-}
+// I think it's ok to let the user decide when to run this script
+// waiting for DOMContentLoaded doesn't work if it already is loaded
+// Ik I could work around, but this is simpler
+
+export default class PagefindHighlight {
+ pagefindQueryParamName: string;
+ // ? should this be an option?
+ highlightNodeElementName: string;
+ highlightNodeClassName: string;
+ markContext: string | HTMLElement | HTMLElement[] | NodeList | null;
+ markOptions: Mark.MarkOptions;
+ addStyles: boolean;
+
+ constructor(
+ options = {
+ markContext: null,
+ pagefindQueryParamName: "pagefind-highlight",
+ // ? should this be an option?
+ highlightNodeElementName: "mark",
+ highlightNodeClassName: "pagefind__highlight",
+ markOptions: undefined,
+ addStyles: true,
+ }
+ ) {
+ const {
+ pagefindQueryParamName,
+ highlightNodeElementName,
+ highlightNodeClassName,
+ markContext,
+ markOptions,
+ addStyles,
+ } = options;
+
+ this.pagefindQueryParamName = pagefindQueryParamName;
+ this.highlightNodeElementName = highlightNodeElementName || "mark";
+ this.highlightNodeClassName = highlightNodeClassName;
+ this.addStyles = addStyles;
+ this.markContext = markContext;
+
+ if (markOptions) {
+ this.markOptions = markOptions;
+ } else {
+ this.markOptions = {
+ className: this.highlightNodeClassName,
+ exclude: ["[data-pagefind-ignore]"],
+ };
+ }
-function highlight(element, regex: RegExp) {
- let document = element.ownerDocument;
+ this.highlight();
+ }
- let nodes = [],
- text = "",
- node,
- nodeIterator = document.createNodeIterator(
- element,
- NodeFilter.SHOW_TEXT,
- null,
- false
- );
+ // wait for the DOM to be ready
+ // read the query param
+ // find all occurrences of the query param in the DOM, respecting the data-pagefind attributes
+ // wrap the text in a mark with a class of pagefind__highlight
- while ((node = nodeIterator.nextNode())) {
- nodes.push({
- textNode: node,
- start: text.length,
- });
- text += node.nodeValue;
+ getHighlightParam(paramName: string): string {
+ const urlParams = new URLSearchParams(window.location.search);
+ return urlParams.get(paramName) || "";
}
- if (!nodes.length) return;
-
- let match;
- while ((match = regex.exec(text))) {
- let matchLength = match[0].length;
+ // Inline styles might be too hard to override
+ addHighlightStyles(className: string) {
+ const styleElement = document.createElement("style");
+ styleElement.innerText = `:where(.${className}) { background-color: yellow; color: black; }`;
+ document.head.appendChild(styleElement);
+ }
- // Prevent empty matches causing infinite loops
- if (!matchLength) {
- regex.lastIndex++;
- continue;
+ createMarkInstance() {
+ if (this.markContext) {
+ return new Mark(this.markContext);
}
-
- for (let i = 0; i < nodes.length; ++i) {
- node = nodes[i];
- let nodeLength = node.textNode.nodeValue.length;
-
- // Skip nodes before the match
- if (node.start + nodeLength <= match.index) continue;
-
- // Break after the match
- if (node.start >= match.index + matchLength) break;
-
- // Split the start node if required
- if (node.start < match.index) {
- nodes.splice(i + 1, 0, {
- textNode: node.textNode.splitText(match.index - node.start),
- start: match.index,
- });
- continue;
- }
-
- // Split the end node if required
- if (node.start + nodeLength > match.index + matchLength) {
- nodes.splice(i + 1, 0, {
- textNode: node.textNode.splitText(
- match.index + matchLength - node.start
- ),
- start: match.index + matchLength,
- });
- }
-
- // Highlight the current node
- let highlightNode = document.createElement(highlightNodeElementName);
- highlightNode.className = highlightNodeClassName;
-
- node.textNode.parentNode.replaceChild(highlightNode, node.textNode);
- highlightNode.appendChild(node.textNode);
+ const pagefindBody = document.querySelectorAll("[data-pagefind-body]");
+ if (pagefindBody.length !== 0) {
+ return new Mark(pagefindBody);
+ } else {
+ return new Mark(document.body);
}
}
-}
-if (window) {
- window.addEventListener("DOMContentLoaded", () => {
- const query = new URLSearchParams(window.location.search).get(
- pagefindQueryParamName
- );
- if (!query) return;
-
- // regex to match the query param
- const queryRegex = new RegExp(query, "gi");
-
- highlight(getElementsToHighlight(), queryRegex);
+ markText(instance: Mark, text: string) {
+ instance.mark(text, this.markOptions);
+ }
- // add styles
- document.head.appendChild(
- document.createElement("style")
- ).textContent = `:where(.${highlightNodeClassName}) { background-color: yellow; text-color: #ccc;}`;
- });
+ highlight() {
+ const param = this.getHighlightParam(this.pagefindQueryParamName);
+ if (!param) return;
+ this.addStyles && this.addHighlightStyles(this.highlightNodeClassName);
+ const markInstance = this.createMarkInstance();
+ this.markText(markInstance, param);
+ }
}
+
+window.PagefindHighlight = PagefindHighlight;
From 705c1328aeb66d66ece43faf14908b6c7c9414e2 Mon Sep 17 00:00:00 2001
From: Jothsa <58094796+Jothsa@users.noreply.github.com>
Date: Tue, 12 Sep 2023 10:56:42 -0500
Subject: [PATCH 07/18] cleanup
---
pagefind_ui/default/svelte/result.svelte | 11 +----------
pagefind_ui/default/svelte/result_with_subs.svelte | 8 +-------
2 files changed, 2 insertions(+), 17 deletions(-)
diff --git a/pagefind_ui/default/svelte/result.svelte b/pagefind_ui/default/svelte/result.svelte
index a61d4e05..1c895aed 100644
--- a/pagefind_ui/default/svelte/result.svelte
+++ b/pagefind_ui/default/svelte/result.svelte
@@ -8,15 +8,6 @@
let data;
let meta = [];
-
- // make sure reactive
- // let href = data?.meta?.url || data.url;
-
- // make sure reactive
- // if (highlight_query_param) {
- // href = `${href}?${highlight_query_param}`;
- // }
-
const load = async (r) => {
data = await r.data();
data = process_result?.(data) ?? data;
@@ -46,7 +37,7 @@
{@html data.meta?.title}
diff --git a/pagefind_ui/default/svelte/result_with_subs.svelte b/pagefind_ui/default/svelte/result_with_subs.svelte
index 6fb7e1ab..dbb75480 100644
--- a/pagefind_ui/default/svelte/result_with_subs.svelte
+++ b/pagefind_ui/default/svelte/result_with_subs.svelte
@@ -12,12 +12,6 @@
let non_root_sub_results = [];
let has_root_sub_result = false;
- // make sure reactive
- // let href = data?.meta?.url || data.url;
-
- // if (highlight_query_param) {
- // href = `${href}?${highlight_query_param}`;
- // }
const thin_sub_results = (results, limit) => {
if (results.length <= limit) {
return results;
@@ -71,7 +65,7 @@
{@html data.meta?.title}
From b9af496d440a72755cc2a61273e6009cc8f4d6d5 Mon Sep 17 00:00:00 2001
From: Jothsa <58094796+Jothsa@users.noreply.github.com>
Date: Thu, 14 Sep 2023 14:35:08 -0500
Subject: [PATCH 08/18] tests!
---
pagefind/features/highlighting/base.feature | 18 ----
.../highlighting/highlighting_base.feature | 85 +++++++++++++++++++
pagefind/features/ui/ui_base.feature | 44 ++++++++++
3 files changed, 129 insertions(+), 18 deletions(-)
delete mode 100644 pagefind/features/highlighting/base.feature
create mode 100644 pagefind/features/highlighting/highlighting_base.feature
diff --git a/pagefind/features/highlighting/base.feature b/pagefind/features/highlighting/base.feature
deleted file mode 100644
index 8919d10f..00000000
--- a/pagefind/features/highlighting/base.feature
+++ /dev/null
@@ -1,18 +0,0 @@
-Feature: Highlighting Tests
- Background:
- Given I have the environment variables:
- | PAGEFIND_SITE | public |
-
- Scenario: Highlight script is loaded
- Given I have a "public/page/index.html" file with the body:
- """
-
Nothing
-
- """
- When I run my program
- Then I should see "Running Pagefind" in stdout
- Then I should see the file "public/pagefind/pagefind-highlight.js"
- When I serve the "public" directory
- When I load "/page/"
- Then There should be no logs
- Then The selector ".test" should contain "Hello from the highlight script"
diff --git a/pagefind/features/highlighting/highlighting_base.feature b/pagefind/features/highlighting/highlighting_base.feature
new file mode 100644
index 00000000..8365645d
--- /dev/null
+++ b/pagefind/features/highlighting/highlighting_base.feature
@@ -0,0 +1,85 @@
+Feature: Highlighting Tests
+
+ Background:
+ Given I have the environment variables:
+ | PAGEFIND_SITE | public |
+ Given I have a "public/words/index.html" file with the body:
+ """
+
Is this highlighted? It should be!
+
This should not be highlighted
+
This should not be highlighted
+
+ """
+ Given I have a "public/single-body/index.html" file with the body:
+ """
+
+ This should be highlighted
+ This should not be highlighted
+
+
This should not be highlighted
+
+ """
+ Given I have a "public/multiple-bodies/index.html" file with the body:
+ """
+
+ This should be highlighted
+ This should not be highlighted
+
+
This should not be highlighted
+
+
This should be highlighted
+
This should not be highlighted
+
+
+ """
+ When I run my program
+ Then I should see "Running Pagefind" in stdout
+ When I serve the "public" directory
+
+ Scenario: Highlight script is loaded
+ When I load "/words/"
+ Then I should see the file "public/pagefind/pagefind-highlight.js"
+ Then There should be no logs
+
+ Scenario: Highlight script marks correctly
+ When I load "/words/?pagefind-highlight=this"
+ Then There should be no logs
+ Then The selector "#has-highlight mark" should contain "this"
+ Then The selector "p[data-pagefind-ignore]:not(:has(span))" should contain "This should not be highlighted"
+ Then The selector "p[data-pagefind-ignore]:has(span)" should contain "
This should not be highlighted"
+ When I load "/words/?pagefind-highlight=this&pagefind-highlight=should"
+ Then There should be no logs
+ Then The selector "#has-highlight mark:first-of-type" should contain "this"
+ Then The selector "#has-highlight mark:nth-of-type(2)" should contain "should"
+ When I load "/words/?pagefind-highlight=is+this"
+ Then There should be no logs
+ Then The selector "#has-highlight mark" should contain "Is this"
+ Then The selector "p[data-pagefind-ignore]" should contain "This should not be highlighted"
+ When I load "/words/?pagefind-highlight=highlighted%3F"
+ Then There should be no logs
+ Then The selector "#has-highlight mark" should contain "highlighted?"
+ When I load "/words/?pagefind-highlight=this+highlighted%3F"
+ Then There should be no logs
+ Then The selector "#has-highlight mark:first-of-type" should contain "this highlighted?"
+
+ Scenario: Highlight script stays within pagefind-body
+ When I load "/single-body/?pagefind-highlight=this"
+ Then There should be no logs
+ Then The selector "#has-highlight mark" should contain "This"
+ Then The selector "p[data-pagefind-ignore]" should contain "This should not be highlighted"
+ Then The selector "#no-highlight" should contain "This should not be highlighted"
+ When I load "/multiple-bodies/?pagefind-highlight=this"
+ Then There should be no logs
+ Then The selector "#has-highlight mark" should contain "This"
+ Then The selector "p[data-pagefind-ignore]" should contain "This should not be highlighted"
+ Then The selector "#no-highlight" should contain "This should not be highlighted"
+
diff --git a/pagefind/features/ui/ui_base.feature b/pagefind/features/ui/ui_base.feature
index 7359452f..c45bfe37 100644
--- a/pagefind/features/ui/ui_base.feature
+++ b/pagefind/features/ui/ui_base.feature
@@ -44,3 +44,47 @@ Feature: Base UI Tests
"""
Then There should be no logs
Then The selector ".pagefind-ui__result-link" should contain "world"
+
+ # in this senario I use the css attribute selector to make sure the link has the query param as the end
+ # if the link doesn't exist, the check will fail
+ # see https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors#syntax:~:text=%5Battr%24%3Dvalue%5D,by%20value.
+
+ Scenario: Pagefind UI adds highlight query param
+ Given I have a "public/cat/index.html" file with the body:
+ """
+
hello world
+
Hello world! How are you
+ """
+ When I run my program
+ Then I should see "Running Pagefind" in stdout
+ Then I should see the file "public/pagefind/pagefind.js"
+ When I serve the "public" directory
+ When I load "/"
+ When I evaluate:
+ """
+ async function() {
+ window.pui.triggerSearch("world");
+ await new Promise(r => setTimeout(r, 1500)); // TODO: await el in humane
+ }
+ """
+ Then There should be no logs
+ Then The selector ".pagefind-ui__result-link[href$='?pagefind-highlight=world']" should contain "hello world"
+ When I evaluate:
+ """
+ async function() {
+ window.pui.triggerSearch("hello world");
+ await new Promise(r => setTimeout(r, 1500)); // TODO: await el in humane
+ }
+ """
+ Then There should be no logs
+ Then The selector ".pagefind-ui__result-link[href$='?pagefind-highlight=hello+world']" should contain "hello world"
+ When I evaluate:
+ """
+ async function() {
+ window.pui.triggerSearch("hello world!");
+ await new Promise(r => setTimeout(r, 1500)); // TODO: await el in humane
+ }
+ """
+ Then There should be no logs
+ Then The selector ".pagefind-ui__result-link[href$='?pagefind-highlight=hello+world%21']" should contain "hello world"
+
From b9b9448c1599fe11039a07214d547f99392ef36e Mon Sep 17 00:00:00 2001
From: Jothsa <58094796+Jothsa@users.noreply.github.com>
Date: Thu, 14 Sep 2023 14:38:29 -0500
Subject: [PATCH 09/18] highlighting is working
---
pagefind_ui/default/svelte/result.svelte | 2 +-
.../default/svelte/result_with_subs.svelte | 4 +-
pagefind_ui/default/svelte/ui.svelte | 4 +-
pagefind_web_js/lib/highlight.ts | 41 +++++++++++++++----
4 files changed, 39 insertions(+), 12 deletions(-)
diff --git a/pagefind_ui/default/svelte/result.svelte b/pagefind_ui/default/svelte/result.svelte
index 1c895aed..86639611 100644
--- a/pagefind_ui/default/svelte/result.svelte
+++ b/pagefind_ui/default/svelte/result.svelte
@@ -38,7 +38,7 @@
{@html data.meta?.title}
diff --git a/pagefind_ui/default/svelte/result_with_subs.svelte b/pagefind_ui/default/svelte/result_with_subs.svelte
index dbb75480..5ee77ee9 100644
--- a/pagefind_ui/default/svelte/result_with_subs.svelte
+++ b/pagefind_ui/default/svelte/result_with_subs.svelte
@@ -65,8 +65,8 @@
{@html data.meta?.title}
diff --git a/pagefind_ui/default/svelte/ui.svelte b/pagefind_ui/default/svelte/ui.svelte
index dcb5b35a..e946398d 100644
--- a/pagefind_ui/default/svelte/ui.svelte
+++ b/pagefind_ui/default/svelte/ui.svelte
@@ -42,7 +42,9 @@
trigger_search_term = "";
}
- $: highlight_query_param = `${highlight_query_param_name}=${val}`;
+ $: highlight_query_param = new URLSearchParams([
+ [highlight_query_param_name, val],
+ ]).toString();
let pagefind;
let input_el,
clear_el,
diff --git a/pagefind_web_js/lib/highlight.ts b/pagefind_web_js/lib/highlight.ts
index ad30ce6f..2576c573 100644
--- a/pagefind_web_js/lib/highlight.ts
+++ b/pagefind_web_js/lib/highlight.ts
@@ -8,6 +8,26 @@ import Mark from "mark.js";
// waiting for DOMContentLoaded doesn't work if it already is loaded
// Ik I could work around, but this is simpler
+// TODO use browser api to read query param, make sure special chars get encoded/decoded
+
+// the separateWordSearch of mark options treats each space separated word as a separate search
+// I am not letting the user set it, because it should be handled on our side
+// if pagefind ever supports exact matches, including spaces ('hello world'), then this should be passed as an entry in the pagefind-highlight query param
+// see the tests for more examples
+
+// right now, since that isn't supported, to separateWordSearch should be false
+
+
+ type pagefindHighlightOptions = {
+ markContext: string | HTMLElement | HTMLElement[] | NodeList | null;
+ pagefindQueryParamName: string;
+ // ? should this be an option?
+ highlightNodeElementName: string;
+ highlightNodeClassName: string;
+ markOptions: Omit
| undefined;
+ addStyles: boolean;
+ };
+
export default class PagefindHighlight {
pagefindQueryParamName: string;
// ? should this be an option?
@@ -17,8 +37,10 @@ export default class PagefindHighlight {
markOptions: Mark.MarkOptions;
addStyles: boolean;
+ // TODO type constructor options better
+
constructor(
- options = {
+ options: pagefindHighlightOptions = {
markContext: null,
pagefindQueryParamName: "pagefind-highlight",
// ? should this be an option?
@@ -48,9 +70,10 @@ export default class PagefindHighlight {
} else {
this.markOptions = {
className: this.highlightNodeClassName,
- exclude: ["[data-pagefind-ignore]"],
+ exclude: ["*[data-pagefind-ignore]", "[data-pagefind-ignore] *"],
};
}
+ this.markOptions.separateWordSearch = false;
this.highlight();
}
@@ -60,9 +83,11 @@ export default class PagefindHighlight {
// find all occurrences of the query param in the DOM, respecting the data-pagefind attributes
// wrap the text in a mark with a class of pagefind__highlight
- getHighlightParam(paramName: string): string {
+ // TODO return array and get all params (to highlight multiple entitles (ex: 'hello world' and 'potato')))
+
+ getHighlightParams(paramName: string): string[] {
const urlParams = new URLSearchParams(window.location.search);
- return urlParams.get(paramName) || "";
+ return urlParams.getAll(paramName);
}
// Inline styles might be too hard to override
@@ -84,16 +109,16 @@ export default class PagefindHighlight {
}
}
- markText(instance: Mark, text: string) {
+ markText(instance: Mark, text: string[]) {
instance.mark(text, this.markOptions);
}
highlight() {
- const param = this.getHighlightParam(this.pagefindQueryParamName);
- if (!param) return;
+ const params = this.getHighlightParams(this.pagefindQueryParamName);
+ if (!params || params.length === 0) return;
this.addStyles && this.addHighlightStyles(this.highlightNodeClassName);
const markInstance = this.createMarkInstance();
- this.markText(markInstance, param);
+ this.markText(markInstance, params);
}
}
From d9b8263fc36b1fcff589250e10e07f5c2e0f6eb9 Mon Sep 17 00:00:00 2001
From: Jothsa <58094796+Jothsa@users.noreply.github.com>
Date: Thu, 14 Sep 2023 14:39:33 -0500
Subject: [PATCH 10/18] tests
---
pagefind/features/ui/ui_base.feature | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/pagefind/features/ui/ui_base.feature b/pagefind/features/ui/ui_base.feature
index c45bfe37..d062d440 100644
--- a/pagefind/features/ui/ui_base.feature
+++ b/pagefind/features/ui/ui_base.feature
@@ -49,7 +49,7 @@ Feature: Base UI Tests
# if the link doesn't exist, the check will fail
# see https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors#syntax:~:text=%5Battr%24%3Dvalue%5D,by%20value.
- Scenario: Pagefind UI adds highlight query param
+ Scenario: Pagefind UI adds highlight query params
Given I have a "public/cat/index.html" file with the body:
"""
hello world
@@ -77,7 +77,7 @@ Feature: Base UI Tests
}
"""
Then There should be no logs
- Then The selector ".pagefind-ui__result-link[href$='?pagefind-highlight=hello+world']" should contain "hello world"
+ Then The selector ".pagefind-ui__result-link[href$='?pagefind-highlight=hello&pagefind-highlight=world']" should contain "hello world"
When I evaluate:
"""
async function() {
@@ -86,5 +86,5 @@ Feature: Base UI Tests
}
"""
Then There should be no logs
- Then The selector ".pagefind-ui__result-link[href$='?pagefind-highlight=hello+world%21']" should contain "hello world"
+ Then The selector ".pagefind-ui__result-link[href$='?pagefind-highlight=hello&pagefind-highlight=world%21']" should contain "hello world"
From 9394fbfb435cda81f4c48086dd732656901be397 Mon Sep 17 00:00:00 2001
From: Jothsa <58094796+Jothsa@users.noreply.github.com>
Date: Thu, 14 Sep 2023 14:53:58 -0500
Subject: [PATCH 11/18] highlight params
---
pagefind_ui/default/svelte/ui.svelte | 13 ++++++++++---
1 file changed, 10 insertions(+), 3 deletions(-)
diff --git a/pagefind_ui/default/svelte/ui.svelte b/pagefind_ui/default/svelte/ui.svelte
index e946398d..7bbc8d66 100644
--- a/pagefind_ui/default/svelte/ui.svelte
+++ b/pagefind_ui/default/svelte/ui.svelte
@@ -42,9 +42,16 @@
trigger_search_term = "";
}
- $: highlight_query_param = new URLSearchParams([
- [highlight_query_param_name, val],
- ]).toString();
+ // this could be changed to not split if the value is quoted
+ // ex: val = `hello world "foo bar"` highlightWords = ["hello", "world", "foo bar"]
+
+ $: highlightWords = val.split(" ");
+
+ $: highlight_query_param = new URLSearchParams(
+ highlightWords.map((word) => {
+ return [highlight_query_param_name, word];
+ })
+ ).toString();
let pagefind;
let input_el,
clear_el,
From 97a80ed9e1bd39d869914eb92d438400bdde6964 Mon Sep 17 00:00:00 2001
From: Jothsa <58094796+Jothsa@users.noreply.github.com>
Date: Thu, 14 Sep 2023 19:55:35 -0500
Subject: [PATCH 12/18] disable highlighting
---
pagefind/features/ui/ui_hooks.feature | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/pagefind/features/ui/ui_hooks.feature b/pagefind/features/ui/ui_hooks.feature
index f0451a05..3c645c4a 100644
--- a/pagefind/features/ui/ui_hooks.feature
+++ b/pagefind/features/ui/ui_hooks.feature
@@ -14,6 +14,7 @@ Feature: UI Hooks
window.pui = new PagefindUI({
element: "#search",
processTerm: (t) => t.replace("word", "search"),
+ highlightQueryParamName: null
});
"""
@@ -31,7 +32,7 @@ Feature: UI Hooks
// TODO: Add more web test steps to humane instead of throwing js
let el = document.querySelector(".pagefind-ui__result-link");
if (el.getAttribute("href") !== "/") {
- throw new Error("Search term should have been normalized by processTerm");
+ throw new Error(`Search term should have been normalized by processTerm. href: ${el.getAttribute("href")}`);
}
}
"""
From 6f84eaedaaaef8f7af3059ee62c8e51604bee68c Mon Sep 17 00:00:00 2001
From: Jothsa <58094796+Jothsa@users.noreply.github.com>
Date: Thu, 14 Sep 2023 19:55:54 -0500
Subject: [PATCH 13/18] make sure highlight option works
---
pagefind_ui/default/svelte/ui.svelte | 9 +++++++--
pagefind_ui/default/ui-core.js | 8 ++++++--
2 files changed, 13 insertions(+), 4 deletions(-)
diff --git a/pagefind_ui/default/svelte/ui.svelte b/pagefind_ui/default/svelte/ui.svelte
index 7bbc8d66..fc58133a 100644
--- a/pagefind_ui/default/svelte/ui.svelte
+++ b/pagefind_ui/default/svelte/ui.svelte
@@ -34,6 +34,7 @@
export let trigger_search_term = "";
export let translations = {};
// this is the name of the query param which is used to highlight the search terms after the user has navigated to a result page
+ // consider exposing the prop in the constructor in camelCase if needed
export let highlight_query_param_name = "pagefind-highlight";
let val = "";
@@ -301,14 +302,18 @@
{show_images}
{process_result}
{result}
- {highlight_query_param}
+ highlight_query_param={highlight_query_param_name
+ ? highlight_query_param
+ : null}
/>
{:else}
{/if}
{/each}
diff --git a/pagefind_ui/default/ui-core.js b/pagefind_ui/default/ui-core.js
index e8a2c5f2..3ea82deb 100644
--- a/pagefind_ui/default/ui-core.js
+++ b/pagefind_ui/default/ui-core.js
@@ -34,8 +34,11 @@ export class PagefindUI {
let debounceTimeoutMs = opts.debounceTimeoutMs ?? 300;
let mergeIndex = opts.mergeIndex ?? [];
let translations = opts.translations ?? [];
- let pagefindQueryParamName =
- opts.pagefindQueryParamName ?? "pagefind-highlight";
+ // setting the param to null should disable highlighting, hence this more complicated check
+ let highlightQueryParamName = "pagefind-highlight";
+ if (opts.highlightQueryParamName !== undefined) {
+ highlightQueryParamName = opts.highlightQueryParamName;
+ }
// Remove the UI-specific config before passing it along to the Pagefind backend
delete opts["element"];
@@ -70,6 +73,7 @@ export class PagefindUI {
debounce_timeout_ms: debounceTimeoutMs,
merge_index: mergeIndex,
translations,
+ highlight_query_param_name: highlightQueryParamName,
pagefind_options: opts,
},
});
From 4cc31a9895cc22cd1b9b75a21b332027b991947c Mon Sep 17 00:00:00 2001
From: Jothsa <58094796+Jothsa@users.noreply.github.com>
Date: Thu, 14 Sep 2023 21:01:15 -0500
Subject: [PATCH 14/18] move highlight param tests
---
pagefind/features/ui/ui_base.feature | 44 -------
pagefind/features/ui/ui_highlight.feature | 143 ++++++++++++++++++++++
2 files changed, 143 insertions(+), 44 deletions(-)
create mode 100644 pagefind/features/ui/ui_highlight.feature
diff --git a/pagefind/features/ui/ui_base.feature b/pagefind/features/ui/ui_base.feature
index d062d440..7359452f 100644
--- a/pagefind/features/ui/ui_base.feature
+++ b/pagefind/features/ui/ui_base.feature
@@ -44,47 +44,3 @@ Feature: Base UI Tests
"""
Then There should be no logs
Then The selector ".pagefind-ui__result-link" should contain "world"
-
- # in this senario I use the css attribute selector to make sure the link has the query param as the end
- # if the link doesn't exist, the check will fail
- # see https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors#syntax:~:text=%5Battr%24%3Dvalue%5D,by%20value.
-
- Scenario: Pagefind UI adds highlight query params
- Given I have a "public/cat/index.html" file with the body:
- """
- hello world
- Hello world! How are you
- """
- When I run my program
- Then I should see "Running Pagefind" in stdout
- Then I should see the file "public/pagefind/pagefind.js"
- When I serve the "public" directory
- When I load "/"
- When I evaluate:
- """
- async function() {
- window.pui.triggerSearch("world");
- await new Promise(r => setTimeout(r, 1500)); // TODO: await el in humane
- }
- """
- Then There should be no logs
- Then The selector ".pagefind-ui__result-link[href$='?pagefind-highlight=world']" should contain "hello world"
- When I evaluate:
- """
- async function() {
- window.pui.triggerSearch("hello world");
- await new Promise(r => setTimeout(r, 1500)); // TODO: await el in humane
- }
- """
- Then There should be no logs
- Then The selector ".pagefind-ui__result-link[href$='?pagefind-highlight=hello&pagefind-highlight=world']" should contain "hello world"
- When I evaluate:
- """
- async function() {
- window.pui.triggerSearch("hello world!");
- await new Promise(r => setTimeout(r, 1500)); // TODO: await el in humane
- }
- """
- Then There should be no logs
- Then The selector ".pagefind-ui__result-link[href$='?pagefind-highlight=hello&pagefind-highlight=world%21']" should contain "hello world"
-
diff --git a/pagefind/features/ui/ui_highlight.feature b/pagefind/features/ui/ui_highlight.feature
new file mode 100644
index 00000000..642a2fbe
--- /dev/null
+++ b/pagefind/features/ui/ui_highlight.feature
@@ -0,0 +1,143 @@
+Feature: Base UI Tests
+ Background:
+ Given I have the environment variables:
+ | PAGEFIND_SITE | public |
+
+ # in this senario I use the css attribute selector to make sure the link has the query param as the end
+ # if the link doesn't exist, the check will fail
+ # see https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors#syntax:~:text=%5Battr%24%3Dvalue%5D,by%20value.
+
+ Scenario: Pagefind UI adds highlight query params
+ Given I have a "public/index.html" file with the body:
+ """
+
+
+
+
+ """
+ Given I have a "public/cat/index.html" file with the body:
+ """
+ hello world
+ Hello world! How are you
+ """
+ When I run my program
+ Then I should see "Running Pagefind" in stdout
+ Then I should see the file "public/pagefind/pagefind.js"
+ When I serve the "public" directory
+ When I load "/"
+ When I evaluate:
+ """
+ async function() {
+ window.pui.triggerSearch("world");
+ await new Promise(r => setTimeout(r, 1500)); // TODO: await el in humane
+ }
+ """
+ Then There should be no logs
+ Then The selector ".pagefind-ui__result-link[href$='?pagefind-highlight=world']" should contain "hello world"
+ When I evaluate:
+ """
+ async function() {
+ window.pui.triggerSearch("hello world");
+ await new Promise(r => setTimeout(r, 1500)); // TODO: await el in humane
+ }
+ """
+ Then There should be no logs
+ Then The selector ".pagefind-ui__result-link[href$='?pagefind-highlight=hello&pagefind-highlight=world']" should contain "hello world"
+ When I evaluate:
+ """
+ async function() {
+ window.pui.triggerSearch("hello world!");
+ await new Promise(r => setTimeout(r, 1500)); // TODO: await el in humane
+ }
+ """
+ Then There should be no logs
+ Then The selector ".pagefind-ui__result-link[href$='?pagefind-highlight=hello&pagefind-highlight=world%21']" should contain "hello world"
+
+ Scenario: Pagefind UI does not add highlight query params
+ Given I have a "public/index.html" file with the body:
+ """
+
+
+
+
+ """
+ Given I have a "public/cat/index.html" file with the body:
+ """
+ hello world
+ Hello world! How are you
+ """
+ When I run my program
+ Then I should see "Running Pagefind" in stdout
+ Then I should see the file "public/pagefind/pagefind.js"
+ When I serve the "public" directory
+ When I load "/"
+ When I evaluate:
+ """
+ async function() {
+ window.pui.triggerSearch("world");
+ await new Promise(r => setTimeout(r, 1500)); // TODO: await el in humane
+ }
+ """
+ Then There should be no logs
+ Then The selector ".pagefind-ui__result-link[href$='/']" should contain "hello world"
+ When I evaluate:
+ """
+ async function() {
+ window.pui.triggerSearch("hello world");
+ await new Promise(r => setTimeout(r, 1500)); // TODO: await el in humane
+ }
+ """
+ Then There should be no logs
+ Then The selector ".pagefind-ui__result-link[href$='/']" should contain "hello world"
+
+ Scenario: Pagefind UI uses custom highlight query param name
+ Given I have a "public/index.html" file with the body:
+ """
+
+
+
+
+ """
+ Given I have a "public/cat/index.html" file with the body:
+ """
+ hello world
+ Hello world! How are you
+ """
+ When I run my program
+ Then I should see "Running Pagefind" in stdout
+ Then I should see the file "public/pagefind/pagefind.js"
+ When I serve the "public" directory
+ When I load "/"
+ When I evaluate:
+ """
+ async function() {
+ window.pui.triggerSearch("world");
+ await new Promise(r => setTimeout(r, 1500)); // TODO: await el in humane
+ }
+ """
+ Then There should be no logs
+ Then The selector ".pagefind-ui__result-link[href$='?custom-param=world']" should contain "hello world"
+ When I evaluate:
+ """
+ async function() {
+ window.pui.triggerSearch("hello world");
+ await new Promise(r => setTimeout(r, 1500)); // TODO: await el in humane
+ }
+ """
+ Then There should be no logs
+ Then The selector ".pagefind-ui__result-link[href$='?custom-param=hello&custom-param=world']" should contain "hello world"
+ When I evaluate:
+ """
+ async function() {
+ window.pui.triggerSearch("hello world!");
+ await new Promise(r => setTimeout(r, 1500)); // TODO: await el in humane
+ }
+ """
+ Then There should be no logs
+ Then The selector ".pagefind-ui__result-link[href$='?custom-param=hello&custom-param=world%21']" should contain "hello world"
From c7d2c6689cfd16e310a974d2abe0f724c323884f Mon Sep 17 00:00:00 2001
From: Jothsa <58094796+Jothsa@users.noreply.github.com>
Date: Fri, 15 Sep 2023 11:26:06 -0500
Subject: [PATCH 15/18] cleanup
---
pagefind_web_js/lib/highlight.ts | 87 +++++++++++---------------------
1 file changed, 29 insertions(+), 58 deletions(-)
diff --git a/pagefind_web_js/lib/highlight.ts b/pagefind_web_js/lib/highlight.ts
index 2576c573..7ad92249 100644
--- a/pagefind_web_js/lib/highlight.ts
+++ b/pagefind_web_js/lib/highlight.ts
@@ -1,97 +1,67 @@
// this script should be imported on the result pages to enable highlighting
+// after a user clicks on a result, the linked page should have this script to enable highlighting
import Mark from "mark.js";
-// tbh not sure how to read this option
-
-// I think it's ok to let the user decide when to run this script
-// waiting for DOMContentLoaded doesn't work if it already is loaded
-// Ik I could work around, but this is simpler
-
-// TODO use browser api to read query param, make sure special chars get encoded/decoded
-
// the separateWordSearch of mark options treats each space separated word as a separate search
// I am not letting the user set it, because it should be handled on our side
-// if pagefind ever supports exact matches, including spaces ('hello world'), then this should be passed as an entry in the pagefind-highlight query param
+// if pagefind ever supports exact matches including spaces ('hello world'), then each sequence to be highlighted should be passed as an entry in the pagefind-highlight query param
+// so if the search is "'hello world' lorem" then the query param should be "pagefind-highlight=hello%20world&pagefind-highlight=lorem"
// see the tests for more examples
-
// right now, since that isn't supported, to separateWordSearch should be false
-
- type pagefindHighlightOptions = {
- markContext: string | HTMLElement | HTMLElement[] | NodeList | null;
- pagefindQueryParamName: string;
- // ? should this be an option?
- highlightNodeElementName: string;
- highlightNodeClassName: string;
- markOptions: Omit | undefined;
- addStyles: boolean;
- };
+type pagefindHighlightOptions = {
+ markContext: string | HTMLElement | HTMLElement[] | NodeList | null;
+ pagefindQueryParamName: string;
+ markOptions: Omit;
+ addStyles: boolean;
+};
export default class PagefindHighlight {
pagefindQueryParamName: string;
- // ? should this be an option?
- highlightNodeElementName: string;
- highlightNodeClassName: string;
markContext: string | HTMLElement | HTMLElement[] | NodeList | null;
markOptions: Mark.MarkOptions;
addStyles: boolean;
- // TODO type constructor options better
-
constructor(
options: pagefindHighlightOptions = {
markContext: null,
pagefindQueryParamName: "pagefind-highlight",
- // ? should this be an option?
- highlightNodeElementName: "mark",
- highlightNodeClassName: "pagefind__highlight",
- markOptions: undefined,
+ markOptions: {
+ className: "pagefind__highlight",
+ exclude: ["[data-pagefind-ignore]", "[data-pagefind-ignore] *"],
+ },
addStyles: true,
}
) {
- const {
- pagefindQueryParamName,
- highlightNodeElementName,
- highlightNodeClassName,
- markContext,
- markOptions,
- addStyles,
- } = options;
+ const { pagefindQueryParamName, markContext, markOptions, addStyles } =
+ options;
this.pagefindQueryParamName = pagefindQueryParamName;
- this.highlightNodeElementName = highlightNodeElementName || "mark";
- this.highlightNodeClassName = highlightNodeClassName;
this.addStyles = addStyles;
this.markContext = markContext;
-
- if (markOptions) {
- this.markOptions = markOptions;
- } else {
- this.markOptions = {
- className: this.highlightNodeClassName,
- exclude: ["*[data-pagefind-ignore]", "[data-pagefind-ignore] *"],
- };
- }
+ this.markOptions = markOptions;
+
+ // make sure these are always set
+ // if the user doesn't want to exclude anything, they should pass an empty array
+ // if the user doesn't want a className they should pass an empty string
+ this.markOptions.className ??= "pagefind__highlight";
+ this.markOptions.exclude ??= [
+ "[data-pagefind-ignore]",
+ "[data-pagefind-ignore] *",
+ ];
this.markOptions.separateWordSearch = false;
-
this.highlight();
}
- // wait for the DOM to be ready
- // read the query param
- // find all occurrences of the query param in the DOM, respecting the data-pagefind attributes
- // wrap the text in a mark with a class of pagefind__highlight
-
- // TODO return array and get all params (to highlight multiple entitles (ex: 'hello world' and 'potato')))
-
getHighlightParams(paramName: string): string[] {
const urlParams = new URLSearchParams(window.location.search);
return urlParams.getAll(paramName);
}
// Inline styles might be too hard to override
- addHighlightStyles(className: string) {
+ addHighlightStyles(className: string | undefined | null) {
+ if (!className) return;
const styleElement = document.createElement("style");
styleElement.innerText = `:where(.${className}) { background-color: yellow; color: black; }`;
document.head.appendChild(styleElement);
@@ -116,7 +86,8 @@ export default class PagefindHighlight {
highlight() {
const params = this.getHighlightParams(this.pagefindQueryParamName);
if (!params || params.length === 0) return;
- this.addStyles && this.addHighlightStyles(this.highlightNodeClassName);
+ this.addStyles &&
+ this.addHighlightStyles(this.markOptions.className as string);
const markInstance = this.createMarkInstance();
this.markText(markInstance, params);
}
From 08d2ed8aa2a7531d04752570c9ea69da0f423910 Mon Sep 17 00:00:00 2001
From: Jothsa <58094796+Jothsa@users.noreply.github.com>
Date: Fri, 15 Sep 2023 11:42:36 -0500
Subject: [PATCH 16/18] options
---
.../highlighting/highlighting_base.feature | 37 +++++++++++++++++++
pagefind_web_js/lib/highlight.ts | 5 ++-
2 files changed, 40 insertions(+), 2 deletions(-)
diff --git a/pagefind/features/highlighting/highlighting_base.feature b/pagefind/features/highlighting/highlighting_base.feature
index 8365645d..535770f9 100644
--- a/pagefind/features/highlighting/highlighting_base.feature
+++ b/pagefind/features/highlighting/highlighting_base.feature
@@ -41,6 +41,33 @@ Feature: Highlighting Tests
new PagefindHighlight();
"""
+ Given I have a "public/options/index.html" file with the body:
+ """
+
+ This should be highlighted
+ This should not be highlighted
+
+ This should not be highlighted
+
+
This should be highlighted
+
This should not be highlighted
+
This should not be highlighted
+
+
+ """
When I run my program
Then I should see "Running Pagefind" in stdout
When I serve the "public" directory
@@ -54,6 +81,7 @@ Feature: Highlighting Tests
When I load "/words/?pagefind-highlight=this"
Then There should be no logs
Then The selector "#has-highlight mark" should contain "this"
+ Then The selector "#has-highlight mark.pagefind-highlight" should contain "this"
Then The selector "p[data-pagefind-ignore]:not(:has(span))" should contain "This should not be highlighted"
Then The selector "p[data-pagefind-ignore]:has(span)" should contain "This should not be highlighted"
When I load "/words/?pagefind-highlight=this&pagefind-highlight=should"
@@ -82,4 +110,13 @@ Feature: Highlighting Tests
Then The selector "#has-highlight mark" should contain "This"
Then The selector "p[data-pagefind-ignore]" should contain "This should not be highlighted"
Then The selector "#no-highlight" should contain "This should not be highlighted"
+
+ Scenario: Highlight script options work
+ When I load "/options/?custom-name=this"
+ Then There should be no logs
+ Then The selector "#has-highlight mark" should contain "This"
+ Then The selector "#has-highlight mark.custom-class" should contain "This"
+ Then The selector "p[data-pagefind-ignore]" should contain "This should not be highlighted"
+ Then The selector "p.ignore" should contain "This should not be highlighted"
+ Then The selector "#no-highlight" should contain "This should not be highlighted"
diff --git a/pagefind_web_js/lib/highlight.ts b/pagefind_web_js/lib/highlight.ts
index 7ad92249..8a54a1ab 100644
--- a/pagefind_web_js/lib/highlight.ts
+++ b/pagefind_web_js/lib/highlight.ts
@@ -28,7 +28,7 @@ export default class PagefindHighlight {
markContext: null,
pagefindQueryParamName: "pagefind-highlight",
markOptions: {
- className: "pagefind__highlight",
+ className: "pagefind-highlight",
exclude: ["[data-pagefind-ignore]", "[data-pagefind-ignore] *"],
},
addStyles: true,
@@ -60,7 +60,8 @@ export default class PagefindHighlight {
}
// Inline styles might be too hard to override
- addHighlightStyles(className: string | undefined | null) {
+ addHighlightStyles(className: string) {
+ // class name could be ""
if (!className) return;
const styleElement = document.createElement("style");
styleElement.innerText = `:where(.${className}) { background-color: yellow; color: black; }`;
From aa502503ab1731dd1ba85180b4025532e2ddf613 Mon Sep 17 00:00:00 2001
From: Jothsa <58094796+Jothsa@users.noreply.github.com>
Date: Fri, 15 Sep 2023 11:52:25 -0500
Subject: [PATCH 17/18] types
---
pagefind_web_js/lib/highlight.ts | 31 ++++++++++++++++++++++---------
1 file changed, 22 insertions(+), 9 deletions(-)
diff --git a/pagefind_web_js/lib/highlight.ts b/pagefind_web_js/lib/highlight.ts
index 8a54a1ab..7feeabd5 100644
--- a/pagefind_web_js/lib/highlight.ts
+++ b/pagefind_web_js/lib/highlight.ts
@@ -11,10 +11,10 @@ import Mark from "mark.js";
// right now, since that isn't supported, to separateWordSearch should be false
type pagefindHighlightOptions = {
- markContext: string | HTMLElement | HTMLElement[] | NodeList | null;
- pagefindQueryParamName: string;
- markOptions: Omit;
- addStyles: boolean;
+ markContext?: string | HTMLElement | HTMLElement[] | NodeList | null;
+ pagefindQueryParamName?: string;
+ markOptions?: Omit;
+ addStyles?: boolean;
};
export default class PagefindHighlight {
@@ -37,12 +37,19 @@ export default class PagefindHighlight {
const { pagefindQueryParamName, markContext, markOptions, addStyles } =
options;
- this.pagefindQueryParamName = pagefindQueryParamName;
- this.addStyles = addStyles;
- this.markContext = markContext;
- this.markOptions = markOptions;
+ this.pagefindQueryParamName =
+ pagefindQueryParamName ?? "pagefind-highlight";
+ this.addStyles = addStyles ?? true;
+ this.markContext = markContext !== undefined ? markContext : null;
+ this.markOptions =
+ markOptions !== undefined
+ ? markOptions
+ : {
+ className: "pagefind-highlight",
+ exclude: ["[data-pagefind-ignore]", "[data-pagefind-ignore] *"],
+ };
- // make sure these are always set
+ // make sure these are always set (in case the user passes {} or {exclude: '.exclude'} to markOptions)
// if the user doesn't want to exclude anything, they should pass an empty array
// if the user doesn't want a className they should pass an empty string
this.markOptions.className ??= "pagefind__highlight";
@@ -94,4 +101,10 @@ export default class PagefindHighlight {
}
}
+declare global {
+ interface Window {
+ PagefindHighlight: typeof PagefindHighlight;
+ }
+}
+
window.PagefindHighlight = PagefindHighlight;
From 378d132984ead8dea1a7675fdd91a3537e51c397 Mon Sep 17 00:00:00 2001
From: Jothsa <58094796+Jothsa@users.noreply.github.com>
Date: Sun, 17 Sep 2023 13:02:25 -0500
Subject: [PATCH 18/18] some docs
---
docs/content/docs/_index.md | 13 ++++++++
docs/content/docs/highlight-config.md | 44 +++++++++++++++++++++++++++
docs/content/docs/highlighting.md | 17 +++++++++++
docs/content/docs/ui.md | 13 ++++++++
4 files changed, 87 insertions(+)
create mode 100644 docs/content/docs/highlight-config.md
create mode 100644 docs/content/docs/highlighting.md
diff --git a/docs/content/docs/_index.md b/docs/content/docs/_index.md
index 274c81fc..027c0c46 100644
--- a/docs/content/docs/_index.md
+++ b/docs/content/docs/_index.md
@@ -53,6 +53,19 @@ We can see that a bunch of content was indexed, and Pagefind will be running a p
Loading this in your browser, you should see a search input on your page. Try searching for some content and you will see results appear from your site.
+## Highlighting
+
+To highlight the search terms on results page, add the following snippet on every page that has been indexed
+
+```html
+
+
+