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); } + } }