diff --git a/znai-docs/znai/release-notes/1.84/add-2025-11-23-search-highlights-improvements.md b/znai-docs/znai/release-notes/1.84/add-2025-11-23-search-highlights-improvements.md
new file mode 100644
index 000000000..c14a45b3d
--- /dev/null
+++ b/znai-docs/znai/release-notes/1.84/add-2025-11-23-search-highlights-improvements.md
@@ -0,0 +1 @@
+* Add: Remove search result highlights with Escape key
\ No newline at end of file
diff --git a/znai-docs/znai/release-notes/1.84/fix-2025-11-23-search-highlights-improvements.md b/znai-docs/znai/release-notes/1.84/fix-2025-11-23-search-highlights-improvements.md
new file mode 100644
index 000000000..4c1934436
--- /dev/null
+++ b/znai-docs/znai/release-notes/1.84/fix-2025-11-23-search-highlights-improvements.md
@@ -0,0 +1 @@
+* Fix: Search highlights apply highlights when user is already on the resulting page.
\ No newline at end of file
diff --git a/znai-reactjs/src/App.jsx b/znai-reactjs/src/App.jsx
index 7af17c9f1..44c4108c2 100644
--- a/znai-reactjs/src/App.jsx
+++ b/znai-reactjs/src/App.jsx
@@ -19,7 +19,7 @@ import "./App.css";
import "./layout/DocumentationLayout.css";
import "./doc-elements/search/Search.css";
-import React, { Component } from "react";
+import React, { Component, useEffect } from "react";
import { ComponentViewer, DropDowns, Registries } from "react-component-viewer";
import { tabsDemo } from "./doc-elements/tabs/Tabs.demo";
@@ -243,9 +243,17 @@ window.znaiSearchIdx = createLocalSearchIndex();
populateLocalSearchIndexWithData(window.znaiSearchIdx, window.znaiSearchData);
registries
.add("end to end")
- .registerAsMiniApp("full documentation navigation", /\/preview/, { root: "/preview" }, () => (
+ .registerAsMiniApp("test documentation page", /\/preview\/testpage/, { root: "/preview/testpage" }, () => (
- ));
+ ))
+ .registerAsMiniApp("full documentation navigation", /\/preview/, { root: "/preview" }, () => {
+ useEffect(() => {
+ const pageId = documentationNavigation.currentPageLocation();
+ documentationNavigation.navigateToPage(pageId);
+ }, []);
+
+ return ;
+ });
const dropDowns = new DropDowns();
dropDowns.add("Theme").addItem("Default", "Alt 1").addItem("Dark", "Alt 2").onSelect(selectTheme);
diff --git a/znai-reactjs/src/doc-elements/Documentation.jsx b/znai-reactjs/src/doc-elements/Documentation.jsx
index 354b092cf..c3761cc56 100644
--- a/znai-reactjs/src/doc-elements/Documentation.jsx
+++ b/znai-reactjs/src/doc-elements/Documentation.jsx
@@ -77,6 +77,8 @@ export class Documentation extends Component {
page: Documentation.processPage(page),
toc: tableOfContents.toc,
+ searchResult: null,
+
// previous version put footer inside props
// we check props for backward compatibility with deployed docs
// should be safe to remove props.footer after October 2021
@@ -115,8 +117,6 @@ export class Documentation extends Component {
this.keyDownHandler = this.keyDownHandler.bind(this);
this.mouseClickHandler = this.mouseClickHandler.bind(this);
- this.searchResult = null;
-
documentationNavigation.addUrlChangeListener(this.onUrlChange.bind(this));
}
@@ -139,12 +139,20 @@ export class Documentation extends Component {
}
}
+ removeSearchResult = () => {
+ this.setState({ searchResult: null });
+ };
+
componentDidUpdate(prevProps, prevState) {
const isTocItemChanged = !areTocItemEquals(this.state.page.tocItem, prevState.page.tocItem);
// reset searchResultId but only when navigating to a different page
- if (this.searchResult && isTocItemChanged && !areTocItemEquals(this.state.page.tocItem, this.searchResult.id)) {
- this.searchResult = null;
+ if (
+ this.state.searchResult &&
+ isTocItemChanged &&
+ !areTocItemEquals(this.state.page.tocItem, this.state.searchResult.id)
+ ) {
+ this.removeSearchResult();
}
}
@@ -159,6 +167,7 @@ export class Documentation extends Component {
tocCollapsed,
isSearchActive,
pageGenError,
+ searchResult,
} = this.state;
const theme = this.theme;
@@ -179,7 +188,8 @@ export class Documentation extends Component {
const renderedPage = (
{
+ if (highlight) {
+ setIsHighlighted(true);
+ }
+ }, [highlight]);
+
+ const handleAnimationEnd = () => {
+ setIsHighlighted(false);
+ };
+
+ const className = "section" + (isHighlighted ? " highlight" : "");
return (
-
+
{
- console.log("@@ Image fit", fit);
return (
void;
}
export function DefaultPageContent(props: Props) {
- const { elementsLibrary, content, searchResult, tocItem, contentRootDom } = props;
+ const { elementsLibrary, content, searchResult, tocItem, contentRootDom, removeSearchResult } = props;
const { PageTitle } = elementsLibrary;
const searchResultId = searchResult?.id;
@@ -42,7 +43,22 @@ export function DefaultPageContent(props: Props) {
if (searchSnippetsToHighlight && isSearchResultOnThisPage && contentRootDom) {
highlightSearchResultAndMaybeScroll(contentRootDom, searchSnippetsToHighlight, false);
}
- }, []);
+ }, [searchSnippetsToHighlight]);
+
+ useEffect(() => {
+ const handleKeyDown = (event: KeyboardEvent) => {
+ if (event.key === "Escape" && isSearchResultOnThisPage) {
+ removeSearchHighlight(contentRootDom);
+ removeSearchResult();
+ event.stopPropagation();
+ }
+ };
+
+ window.addEventListener("keydown", handleKeyDown);
+ return () => {
+ window.removeEventListener("keydown", handleKeyDown);
+ };
+ }, [isSearchResultOnThisPage]);
const renderedSections = content!.map((section) => {
// @ts-ignore
diff --git a/znai-reactjs/src/doc-elements/search/searchResultHighlighter.ts b/znai-reactjs/src/doc-elements/search/searchResultHighlighter.ts
index 7cb36caf2..7b56b86fc 100644
--- a/znai-reactjs/src/doc-elements/search/searchResultHighlighter.ts
+++ b/znai-reactjs/src/doc-elements/search/searchResultHighlighter.ts
@@ -40,3 +40,8 @@ export function highlightSearchResultAndMaybeScroll(root: HTMLElement, snippets:
},
});
}
+
+export function removeSearchHighlight(root: HTMLElement) {
+ const mark = new Mark(root);
+ mark.unmark({});
+}
diff --git a/znai-reactjs/src/screens/documentation-preparation/DocumentationPreparationScreen.jsx b/znai-reactjs/src/screens/documentation-preparation/DocumentationPreparationScreen.jsx
index 47896bd74..c841a8617 100644
--- a/znai-reactjs/src/screens/documentation-preparation/DocumentationPreparationScreen.jsx
+++ b/znai-reactjs/src/screens/documentation-preparation/DocumentationPreparationScreen.jsx
@@ -1,4 +1,5 @@
/*
+ * Copyright 2025 znai maintainers
* Copyright 2019 TWO SIGMA OPEN SOURCE, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -14,60 +15,56 @@
* limitations under the License.
*/
-import * as React from 'react'
+import * as React from "react";
-import DocumentationPreparation from './DocumentationPreparation'
-import {socketUrl} from '../../utils/socket'
+import DocumentationPreparation from "./DocumentationPreparation";
+import { socketUrl } from "../../utils/socket";
-import './DocumentationPreparationScreen.css'
+import "./DocumentationPreparationScreen.css";
export class DocumentationPreparationScreen extends React.Component {
- constructor(props) {
- super(props)
- this.state = props;
- }
+ constructor(props) {
+ super(props);
+ this.state = props;
+ }
- render() {
- return (
-
-
-
- )
- }
+ render() {
+ return (
+
+
+
+ );
+ }
- componentDidMount() {
- this._connect()
- }
+ componentDidMount() {
+ this._connect();
+ }
- componentWillUnmount() {
- this._disconnect()
- }
+ componentWillUnmount() {
+ this._disconnect();
+ }
- _connect() {
- this.ws = new WebSocket(socketUrl("_doc-update/" + this.props.docId))
+ _connect() {
+ this.ws = new WebSocket(socketUrl("_doc-update/" + this.props.docId));
- this.ws.onopen = () => {
- console.log('@@ open')
- }
+ this.ws.onopen = () => {};
- this.ws.onclose = () => {
- console.log('@@ close')
- }
+ this.ws.onclose = () => {};
- this.ws.onmessage = (message) => {
- const data = JSON.parse(message.data)
- this._update(data)
- };
- }
+ this.ws.onmessage = (message) => {
+ const data = JSON.parse(message.data);
+ this._update(data);
+ };
+ }
- _disconnect() {
- this.ws.close()
- }
+ _disconnect() {
+ this.ws.close();
+ }
- _update({message, keyValues, progress}) {
- this.setState({statusMessage: message, keyValues: keyValues || [], progressPercent: progress})
- if (progress >= 100) {
- setTimeout(() => window.location.reload(), 100)
- }
+ _update({ message, keyValues, progress }) {
+ this.setState({ statusMessage: message, keyValues: keyValues || [], progressPercent: progress });
+ if (progress >= 100) {
+ setTimeout(() => window.location.reload(), 100);
}
+ }
}