)
+ const commonProps = {
+ elementsLibrary,
+ docMeta: docMeta(),
+ title: "Section title",
+ id: "section-title",
+ };
+ registry
+ .add("before snippet", () => )
+ .add("after snippet", () => )
+ .add("surrounded by text", () => );
- function beforeSnippetContent() {
- return [
- {
- type: "Columns",
- config: {},
- columns: [
- {content: [contentSnippet(false)]},
- {content: [contentSnippet(false)]}
- ]
- },
- contentSnippet(false),
- ]
- }
+ function beforeSnippetContent() {
+ return [
+ {
+ type: "Columns",
+ config: {},
+ columns: [{ content: [contentSnippet(false)] }, { content: [contentSnippet(false)] }],
+ },
+ contentSnippet(false),
+ ];
+ }
- function afterSnippetContent() {
- return [
- contentSnippet(false),
- {
- type: "Columns",
- config: {},
- columns: [
- {content: [contentSnippet(false)]},
- {content: [contentSnippet(false)]}
- ]
- }
- ]
- }
+ function afterSnippetContent() {
+ return [
+ contentSnippet(false),
+ {
+ type: "Columns",
+ config: {},
+ columns: [{ content: [contentSnippet(false)] }, { content: [contentSnippet(false)] }],
+ },
+ ];
+ }
- function surroundedByTextContent() {
- return [
- contentParagraph(false),
- {
- type: "Columns",
- config: {},
- columns: [
- {content: [contentSnippet(false)]},
- {content: [contentSnippet(false)]}
- ]
- },
- contentParagraph(false),
- ]
- }
+ function surroundedByTextContent() {
+ return [
+ contentParagraph(false),
+ {
+ type: "Columns",
+ config: {},
+ columns: [{ content: [contentSnippet(false)] }, { content: [contentSnippet(false)] }],
+ },
+ contentParagraph(false),
+ ];
+ }
}
function htmlCode() {
- return '\n'
+ return (
+ '\n"
+ );
}
function wideCode() {
- return 'class InternationalPriceService implements PriceService { // classic\n' +
- ' private static void LongJavaInterfaceNameWithSuperFactory createMegaAbstractFactory(final ExchangeCalendarLongerThanLife calendar) {\n' +
- ' ...\n' +
- ' }\n' +
- '}\n'
+ return (
+ "class InternationalPriceService implements PriceService { // classic\n" +
+ " private static void LongJavaInterfaceNameWithSuperFactory createMegaAbstractFactory(final ExchangeCalendarLongerThanLife calendar) {\n" +
+ " ...\n" +
+ " }\n" +
+ "}\n"
+ );
}
function methodCallReferences() {
- return {
- 'http.header': {
- pageUrl: '#http-header'
- },
- 'body.get': {
- pageUrl: '/chapter/page'
- },
- 'should': {
- pageUrl: 'http://example.com'
- }
- }
+ return {
+ "http.header": {
+ pageUrl: "#http-header",
+ },
+ "body.get": {
+ pageUrl: "/chapter/page",
+ },
+ should: {
+ pageUrl: "http://example.com",
+ },
+ };
}
function markdownCode() {
- return '# Server Configuration\n\n' +
- ':include-file: config/server.config\n\nNote: hello world\n'
+ return "# Server Configuration\n\n" + ":include-file: config/server.config\n\nNote: hello world\n";
}
function longCode() {
- let lines = []
- for (let i = 1; i <= 30; i++) {
- lines.push('line of text number ' + i)
- }
+ let lines = [];
+ for (let i = 1; i <= 30; i++) {
+ lines.push("line of text number " + i);
+ }
- return lines.join('\n')
+ return lines.join("\n");
}
-function tabsContent({label, wide}) {
- return {
- tabsContent: [{
- name: label, content: [{
- type: "Snippet",
- wide: wide,
- lang: "java",
- snippet: wideCode()
- }]
- }]
- }
+function tabsContent({ label, wide }) {
+ return {
+ tabsContent: [
+ {
+ name: label,
+ content: [
+ {
+ type: "Snippet",
+ wide: wide,
+ lang: "java",
+ snippet: wideCode(),
+ },
+ ],
+ },
+ ],
+ };
}
function twoSidesPage(content) {
- return pageContent("two-sides", content)
+ return pageContent("two-sides", content);
}
function pageContent(type, content) {
- return {
- "type": "Page",
- "content": content,
- "lastModifiedTime": 1527473295000,
- "tocItem": {
- "sectionTitle": "Demo",
- "pageTitle": "Demo",
- "pageMeta": {
- "type": [
- type
- ]
- },
- "fileName": "demo",
- "dirName": "demo",
- "pageSectionIdTitles": []
- }
- }
+ return {
+ type: "Page",
+ content: content,
+ lastModifiedTime: 1527473295000,
+ tocItem: {
+ sectionTitle: "Demo",
+ pageTitle: "Demo",
+ pageMeta: {
+ type: [type],
+ },
+ fileName: "demo",
+ dirName: "demo",
+ pageSectionIdTitles: [],
+ },
+ };
}
function snippetAfterSection() {
- return [
- {
- "title": "Primary Use Case",
- "id": "primary-use-case",
- "type": "Section",
- "content": [contentSnippet(false)]
- }
- ]
+ return [
+ {
+ title: "Primary Use Case",
+ id: "primary-use-case",
+ type: "Section",
+ content: [contentSnippet(false)],
+ },
+ ];
}
function textBetweenSnippetsMultipleBlocks() {
- return [
- {
- "title": "Primary Use Case",
- "id": "primary-use-case",
- "type": "Section",
- "content": [
- contentSnippet(false),
- contentSnippet(true),
- contentParagraph(false),
- contentParagraph(true),
- contentSnippet(false),
- ]
- }
- ]
+ return [
+ {
+ title: "Primary Use Case",
+ id: "primary-use-case",
+ type: "Section",
+ content: [
+ contentSnippet(false),
+ contentSnippet(true),
+ contentParagraph(false),
+ contentParagraph(true),
+ contentSnippet(false),
+ ],
+ },
+ ];
}
-function compactContentSnippet(title, {noGap, noGapBorder} = {}) {
- return {
- "type": "Snippet",
- "lang": "java",
- "title": title,
- "noGap": noGap,
- "noGapBorder": noGapBorder,
- "snippet": codeWithMethodCalls(),
- }
+function compactContentSnippet(title, { noGap, noGapBorder } = {}) {
+ return {
+ type: "Snippet",
+ lang: "java",
+ title: title,
+ noGap: noGap,
+ noGapBorder: noGapBorder,
+ snippet: codeWithMethodCalls(),
+ };
}
function pythonCode() {
- return "import market\n" +
- "import flight\n" +
- "\n" +
- "def main():\n" +
- " id = market.book_trade('symbol', market.CURRENT_PRICE, 100)\n" +
- "\n" +
- " market.cancel_trade('id')\n" +
- "\n" +
- "if __name__ == \"__main__\":\n" +
- " main() "
+ return (
+ "import market\n" +
+ "import flight\n" +
+ "\n" +
+ "def main():\n" +
+ " id = market.book_trade('symbol', market.CURRENT_PRICE, 100)\n" +
+ "\n" +
+ " market.cancel_trade('id')\n" +
+ "\n" +
+ 'if __name__ == "__main__":\n' +
+ " main() "
+ );
}
function largeJavaCodeWithJavaDocs() {
- return "/**\n" +
- " * Top level conceptual description of a Domain problem.\n" +
- " * \n" +
- " * To avoid copy & paste of the content consider to re-use information.\n" +
- " */\n" +
- "class HelloWorld {\n" +
- " /**\n" +
- " * Each year we hire students from different universities to increase\n" +
- " * diversity\n" +
- " */\n" +
- " private int numberOfStudents;\n" +
- "\n" +
- " /**\n" +
- " * Conceptual description of a Domain problem.\n" +
- " *
\n" +
- " * It will work only if you put high level description here and\n" +
- " * not implementation details.\n" +
- " *\n" +
- " * @param p1 important parameter of something\n" +
- " * @param p2 sample offset according to the rules of the universe\n" +
- " * @return name of the best sample\n" +
- " */\n" +
- " public String sampleMethod(String p1, int p2) {\n" +
- " validate();\n" +
- " process(p2); // important comment\n" +
- " notifyAll(p1); // very important\n" +
- "\n" +
- " return bestSample();\n" +
- " }\n" +
- "\n" +
- " public void sampleMethod(Map p1, int p2, boolean isActive) {\n" +
- " // overloaded method\n" +
- " }\n" +
- "\n" +
- " public void importantAction() {\n" +
- " // TODO important\n" +
- " }\n" +
- "\n" +
- " public Data createData() {\n" +
- " // create data\n" +
- " }\n" +
- "}"
-}
\ No newline at end of file
+ return (
+ "/**\n" +
+ " * Top level conceptual description of a Domain problem.\n" +
+ " * \n" +
+ " * To avoid copy & paste of the content consider to re-use information.\n" +
+ " */\n" +
+ "class HelloWorld {\n" +
+ " /**\n" +
+ " * Each year we hire students from different universities to increase\n" +
+ " * diversity\n" +
+ " */\n" +
+ " private int numberOfStudents;\n" +
+ "\n" +
+ " /**\n" +
+ " * Conceptual description of a Domain problem.\n" +
+ " *
\n" +
+ " * It will work only if you put high level description here and\n" +
+ " * not implementation details.\n" +
+ " *\n" +
+ " * @param p1 important parameter of something\n" +
+ " * @param p2 sample offset according to the rules of the universe\n" +
+ " * @return name of the best sample\n" +
+ " */\n" +
+ " public String sampleMethod(String p1, int p2) {\n" +
+ " validate();\n" +
+ " process(p2); // important comment\n" +
+ " notifyAll(p1); // very important\n" +
+ "\n" +
+ " return bestSample();\n" +
+ " }\n" +
+ "\n" +
+ " public void sampleMethod(Map p1, int p2, boolean isActive) {\n" +
+ " // overloaded method\n" +
+ " }\n" +
+ "\n" +
+ " public void importantAction() {\n" +
+ " // TODO important\n" +
+ " }\n" +
+ "\n" +
+ " public Data createData() {\n" +
+ " // create data\n" +
+ " }\n" +
+ "}"
+ );
+}
diff --git a/znai-reactjs/src/doc-elements/demo-utils/ButtonToForceTextHighlight.tsx b/znai-reactjs/src/doc-elements/demo-utils/ButtonToForceTextHighlight.tsx
new file mode 100644
index 000000000..689d58480
--- /dev/null
+++ b/znai-reactjs/src/doc-elements/demo-utils/ButtonToForceTextHighlight.tsx
@@ -0,0 +1,23 @@
+import React from "react";
+
+interface Props {
+ textToHighlight: string;
+}
+
+export function ButtonToForceTextHighlight({ textToHighlight }: Props) {
+ return (
+
+ );
+}
diff --git a/znai-reactjs/src/doc-elements/demo-utils/contentGenerators.ts b/znai-reactjs/src/doc-elements/demo-utils/contentGenerators.ts
index b6d96a7a1..2f5c6e339 100644
--- a/znai-reactjs/src/doc-elements/demo-utils/contentGenerators.ts
+++ b/znai-reactjs/src/doc-elements/demo-utils/contentGenerators.ts
@@ -30,6 +30,22 @@ export function contentParagraph(isRightSide: boolean) {
};
}
+export function anotherContentParagraph(isRightSide: boolean) {
+ return {
+ type: "Paragraph",
+ content: [
+ {
+ text:
+ "This is the first line of test text that demonstrates how content flows in a paragraph format. The second line continues the sample content while maintaining readability and proper spacing throughout. Finally, the third line completes this example paragraph with additional placeholder text for testing purposes.",
+ type: "SimpleText",
+ },
+ ],
+ meta: {
+ rightSide: isRightSide,
+ },
+ };
+}
+
export function contentSingleLink(text: string) {
return {
type: "Paragraph",
diff --git a/znai-reactjs/src/doc-elements/read-more/ReadMore.demo.tsx b/znai-reactjs/src/doc-elements/read-more/ReadMore.demo.tsx
index 81df4ef16..9e1603edf 100644
--- a/znai-reactjs/src/doc-elements/read-more/ReadMore.demo.tsx
+++ b/znai-reactjs/src/doc-elements/read-more/ReadMore.demo.tsx
@@ -17,14 +17,17 @@
import { Registry } from "react-component-viewer";
import * as React from "react";
import { elementsLibrary } from "../DefaultElementsLibrary";
-import { contentParagraph } from "../demo-utils/contentGenerators";
+import { anotherContentParagraph, contentParagraph } from "../demo-utils/contentGenerators";
import { ReadMore } from "./ReadMore";
+import { AllTextHighlights } from "../text-selection/AllTextHighlights";
+import { documentationNavigation } from "../../structure/DocumentationNavigation";
+import { ButtonToForceTextHighlight } from "../demo-utils/ButtonToForceTextHighlight";
export function readMoreDemo(registry: Registry) {
registry.add("regular text", () =>
surroundWithText(
elementsLibrary,
-
+
)
);
}
@@ -36,6 +39,8 @@ function surroundWithText(elementsLibrary: any, rendered: any) {
{rendered}
+
+
);
}
diff --git a/znai-reactjs/src/doc-elements/read-more/ReadMore.tsx b/znai-reactjs/src/doc-elements/read-more/ReadMore.tsx
index 156e406c3..db113f480 100644
--- a/znai-reactjs/src/doc-elements/read-more/ReadMore.tsx
+++ b/znai-reactjs/src/doc-elements/read-more/ReadMore.tsx
@@ -14,10 +14,12 @@
* limitations under the License.
*/
-import React, { useState } from "react";
+import React, { useRef, useState } from "react";
import { DocElementContent, ElementsLibraryMap } from "../default-elements/DocElement";
import { Icon } from "../icons/Icon";
+
+import { useHighlightOfHiddenElement } from "../text-selection/componentsHighlightUtils";
import "./ReadMore.css";
interface Props {
@@ -28,27 +30,29 @@ interface Props {
export function ReadMore({ title, content, elementsLibrary }: Props) {
const [expanded, setExpanded] = useState(false);
+ const containerRef = useRef(null);
+ const hiddenContainerRef = useRef(null);
+ const hasHiddenHighlightedElement = useHighlightOfHiddenElement(containerRef, hiddenContainerRef, expanded);
+
const expandedClassName = expanded ? "expanded" : "collapsed";
const topClassName = "znai-read-more content-block " + expandedClassName;
- const summaryClassName = "znai-read-more-title-block content-block " + expandedClassName;
+ const summaryClassName =
+ "znai-read-more-title-block content-block " +
+ expandedClassName +
+ (hasHiddenHighlightedElement && !expanded ? " " + "znai-highlight single" : "");
const summary = (
setExpanded((prev) => !prev)}>
{title}
);
+ const style = expanded ? { display: "block" } : { display: "none" };
return (
-
- {expanded ? (
- <>
- {summary}
-
-
-
- >
- ) : (
- summary
- )}
+
);
}
diff --git a/znai-reactjs/src/doc-elements/testDocumentation.jsx b/znai-reactjs/src/doc-elements/testDocumentation.jsx
index 731b7d200..b0ee75719 100644
--- a/znai-reactjs/src/doc-elements/testDocumentation.jsx
+++ b/znai-reactjs/src/doc-elements/testDocumentation.jsx
@@ -111,6 +111,69 @@ export const testDocumentation = {
},
],
},
+ {
+ title: "Title of the Hidden Details",
+ content: [
+ {
+ type: "Paragraph",
+ content: [
+ {
+ text: "Information you don't want users to see right away goes here.",
+ type: "SimpleText",
+ },
+ {
+ type: "SoftLineBreak",
+ },
+ {
+ text: "Can use all the markdown inside",
+ type: "SimpleText",
+ },
+ ],
+ },
+ {
+ delimiter: ".",
+ startNumber: 1,
+ type: "OrderedList",
+ content: [
+ {
+ type: "ListItem",
+ content: [
+ {
+ type: "Paragraph",
+ content: [
+ {
+ text: "item A",
+ type: "SimpleText",
+ },
+ ],
+ },
+ ],
+ },
+ {
+ type: "ListItem",
+ content: [
+ {
+ type: "Paragraph",
+ content: [
+ {
+ text: "item B",
+ type: "SimpleText",
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ {
+ lang: "",
+ snippet: "let list = [1, 2, 3]\nlet value = 0",
+ lineNumber: "",
+ type: "Snippet",
+ },
+ ],
+ type: "ReadMore",
+ },
{
lang: "javascript",
snippet: "class JsClass {\n constructor() {\n usefulAction()\n }\n}\n\nexport default JsClass",
@@ -125,6 +188,14 @@ export const testDocumentation = {
},
],
},
+ {
+ lang: "java",
+ snippet:
+ 'public class DocScaffolding {\n private final Path workingDir;\n private Map
> fileNameByDirName;\n\n public DocScaffolding(Path workingDir) {\n this.workingDir = workingDir;\n this.fileNameByDirName = new LinkedHashMap<>();\n }\n\n public void create() {\n createPages();\n createToc();\n createMeta();\n createIndex();\n createLookupPaths();\n }\n\n private void createLookupPaths() {\n createFileFromResource("lookup-paths");\n }\n\n private void createMeta() {\n createFileFromResource("meta.json");\n }\n}',
+ readMore: true,
+ readMoreVisibleLines: 3,
+ type: "Snippet",
+ },
{
bulletMarker: "*",
tight: true,
diff --git a/znai-reactjs/src/doc-elements/text-selection/AllTextHighlights.tsx b/znai-reactjs/src/doc-elements/text-selection/AllTextHighlights.tsx
new file mode 100644
index 000000000..6e909aca6
--- /dev/null
+++ b/znai-reactjs/src/doc-elements/text-selection/AllTextHighlights.tsx
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2025 znai maintainers
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { HighlightUrlText } from "./HighlightUrlText";
+import { SlackActiveQuestions } from "./SlackActiveQuestions";
+import React, { useEffect, useState } from "react";
+import { TocItem } from "../../structure/TocItem";
+
+export function reapplyTextHighlights() {
+ listeners.forEach((listener) => listener());
+}
+
+export type ReapplyTextHighlightListener = () => void;
+
+const listeners: ReapplyTextHighlightListener[] = [];
+
+export function addReapplyTextHighlightsListener(listener: ReapplyTextHighlightListener) {
+ listeners.push(listener);
+}
+
+export function removeReapplyTextHighlightsListener(listener: ReapplyTextHighlightListener) {
+ listeners.splice(listeners.indexOf(listener), 1);
+}
+
+export function AllTextHighlights({ containerNode, tocItem }: { containerNode: HTMLElement; tocItem: TocItem }) {
+ const [keyToForceReHighlight, setKeyToForceReHighlight] = useState(0);
+ // TODO move load questions from slack here
+
+ useEffect(() => {
+ function forceReapply() {
+ setKeyToForceReHighlight((prev) => prev + 1);
+ }
+
+ addReapplyTextHighlightsListener(forceReapply);
+ return () => removeReapplyTextHighlightsListener(forceReapply);
+ }, []);
+
+ return (
+
+
+
+
+ );
+}
diff --git a/znai-reactjs/src/doc-elements/text-selection/HighlightUrlText.tsx b/znai-reactjs/src/doc-elements/text-selection/HighlightUrlText.tsx
index c2a9e0890..92edf5b26 100644
--- a/znai-reactjs/src/doc-elements/text-selection/HighlightUrlText.tsx
+++ b/znai-reactjs/src/doc-elements/text-selection/HighlightUrlText.tsx
@@ -17,7 +17,7 @@
import { extractHighlightParams } from "./highlightUrl";
import { HighlightedText } from "./HighlightedText";
-export function HighlightUrlText({ containerNode }: { containerNode: HTMLDivElement }) {
+export function HighlightUrlText({ containerNode }: { containerNode: HTMLElement }) {
const params = extractHighlightParams();
return params ? (
diff --git a/znai-reactjs/src/doc-elements/text-selection/HighlightedText.tsx b/znai-reactjs/src/doc-elements/text-selection/HighlightedText.tsx
index 6f2e6d465..464a8f05f 100644
--- a/znai-reactjs/src/doc-elements/text-selection/HighlightedText.tsx
+++ b/znai-reactjs/src/doc-elements/text-selection/HighlightedText.tsx
@@ -22,11 +22,25 @@ import { createPortal } from "react-dom";
import { afterTitleId } from "../../layout/classNamesAndIds";
-import "./HighlightedText.css";
import { removeTrailingSlashFromQueryParam } from "./queryParamUtils";
+import "./HighlightedText.css";
+
+export interface HighlightedTextListener {
+ onUserDrivenTextHighlight(firstElement: HTMLElement): void;
+}
+
+const highlightedTextListeners: HighlightedTextListener[] = [];
+
+export function addHighlightedTextListener(listener: HighlightedTextListener) {
+ highlightedTextListeners.push(listener);
+}
+
+export function removeHighlightedTextListener(listener: HighlightedTextListener) {
+ highlightedTextListeners.splice(highlightedTextListeners.indexOf(listener), 1);
+}
interface Props {
- containerNode: HTMLDivElement;
+ containerNode: HTMLElement;
selectedText: string;
selectedPrefix: string;
selectedSuffix: string;
@@ -154,14 +168,13 @@ export function HighlightedText({
} else {
console.warn("can't find element with id: " + afterTitleId);
}
- }
-
- if (firstHighlightedElement) {
+ } else {
if (question && bubbleRef.current && displayBubbleAndScrollIntoView) {
showBubbleAndCalcPositionIfHasQuestion(firstHighlightedElement, highlights[highlights.length - 1]);
}
scrollToBubbleIfRequired(firstHighlightedElement);
+ highlightedTextListeners.forEach((listener) => listener.onUserDrivenTextHighlight(firstHighlightedElement));
}
addTextMenuListener(textMenuListener);
diff --git a/znai-reactjs/src/doc-elements/text-selection/SlackActiveQuestions.tsx b/znai-reactjs/src/doc-elements/text-selection/SlackActiveQuestions.tsx
index 60c7b53b3..d2505881f 100644
--- a/znai-reactjs/src/doc-elements/text-selection/SlackActiveQuestions.tsx
+++ b/znai-reactjs/src/doc-elements/text-selection/SlackActiveQuestions.tsx
@@ -37,7 +37,7 @@ interface Question {
resolved: boolean;
}
-export function SlackActiveQuestions({ containerNode, tocItem }: { containerNode: HTMLDivElement; tocItem: TocItem }) {
+export function SlackActiveQuestions({ containerNode, tocItem }: { containerNode: HTMLElement; tocItem: TocItem }) {
const [questions, setQuestions] = useState([]);
const [notification, setNotification] = useState<{ type: "success" | "error"; message: string } | null>(null);
diff --git a/znai-reactjs/src/doc-elements/text-selection/componentsHighlightUtils.ts b/znai-reactjs/src/doc-elements/text-selection/componentsHighlightUtils.ts
new file mode 100644
index 000000000..fb040a170
--- /dev/null
+++ b/znai-reactjs/src/doc-elements/text-selection/componentsHighlightUtils.ts
@@ -0,0 +1,97 @@
+import { MutableRefObject, useEffect, useRef, useState } from "react";
+import { addHighlightedTextListener, removeHighlightedTextListener } from "./HighlightedText";
+import { reapplyTextHighlights } from "./AllTextHighlights";
+
+/*
+
+Use can highlight text inside hidden by default blocks of text.
+When a page with the highlight is opened, we want the browser to scroll
+to the element that contains the hidden text.
+
+To achieve that, components with hidden content should still
+render the content but with display:none. We need the content
+to be part of the DOM so the highlight engine can find them.
+
+Components can subscribe to "highlight" event and receive the firstHighlightElement
+in the callback.
+
+If the element is part of the hidden section, components can
+highlight their toggles to let users know there is a hidden, highlighted text.
+But they also need to temporary move the "firstHighlightElement" somehwere in the
+visible zone for the scroll to happen.
+
+Hook below attempts to encapsulate this logic.
+
+*/
+
+export function useHighlightOfHiddenElement(
+ containerRef: MutableRefObject,
+ hiddenContainerRef: MutableRefObject,
+ contentVisibilityTrigger: any
+) {
+ const restoreFirstHighlightElementFunRef = useRef<(() => void) | null>(null);
+ const [hasHiddenHighlightedElement, setHasHiddenHighlightedElement] = useState(false);
+ const onlyOnce = useRef(false);
+
+ useEffect(() => {
+ const listener = {
+ onUserDrivenTextHighlight: (firstHighlightElement: HTMLElement) => {
+ if (
+ containerRef.current &&
+ hiddenContainerRef.current &&
+ hiddenContainerRef.current.contains(firstHighlightElement) &&
+ !onlyOnce.current
+ ) {
+ restoreFirstHighlightElementFunRef.current = moveAndHideHtmlElementForAutoScroll(
+ firstHighlightElement,
+ containerRef.current
+ );
+ setHasHiddenHighlightedElement(true);
+ onlyOnce.current = true;
+ }
+ },
+ };
+ addHighlightedTextListener(listener);
+ return () => {
+ removeHighlightedTextListener(listener);
+ };
+ }, []);
+
+ // this may not work with Tabs
+ useEffect(() => {
+ if (restoreFirstHighlightElementFunRef.current && onlyOnce.current) {
+ restoreFirstHighlightElementFunRef.current();
+ restoreFirstHighlightElementFunRef.current = null;
+ }
+ // TODO only reapply for the specific highlight(s) that are affected maybe somehow(?)
+ // or make sure that the scroll to the selected question is triggered everytime someone toggles read more
+ reapplyTextHighlights();
+ }, [contentVisibilityTrigger]);
+
+ return hasHiddenHighlightedElement;
+}
+
+export function moveAndHideHtmlElementForAutoScroll(element: HTMLElement, newParent: HTMLElement) {
+ const originalParent = element.parentNode;
+ const originalNextSibling = element.nextSibling;
+ const originalDisplay = element.style.display;
+ const originalHeight = element.style.height;
+ const originalOverflow = element.style.overflow;
+
+ newParent.appendChild(element);
+ element.style.display = "block";
+ element.style.height = "0";
+ element.style.overflow = "hidden";
+
+ return function restore() {
+ element.style.display = originalDisplay;
+ element.style.height = originalHeight;
+ element.style.overflow = originalOverflow;
+
+ if (originalNextSibling) {
+ originalParent!.insertBefore(element, originalNextSibling);
+ } else {
+ originalParent!.appendChild(element);
+ }
+ };
+}
diff --git a/znai-reactjs/src/layout/DocumentationLayout.tsx b/znai-reactjs/src/layout/DocumentationLayout.tsx
index 5dd6db5e3..9f22e40d2 100644
--- a/znai-reactjs/src/layout/DocumentationLayout.tsx
+++ b/znai-reactjs/src/layout/DocumentationLayout.tsx
@@ -32,9 +32,7 @@ import { mainPanelClassName } from "./classNamesAndIds";
import { TopHeader } from "./TopHeader";
import { TextSelectionMenu } from "../doc-elements/text-selection/TextSelectionMenu";
-import { HighlightUrlText } from "../doc-elements/text-selection/HighlightUrlText";
-import { SlackActiveQuestions } from "../doc-elements/text-selection/SlackActiveQuestions";
-
+import { AllTextHighlights } from "../doc-elements/text-selection/AllTextHighlights";
import "./DocumentationLayout.css";
import "./mobile/MobileLayoutOverrides.css";
@@ -101,8 +99,7 @@ export function DocumentationLayout({
return (
{contentRef.current &&
}
- {contentRef.current &&
}
- {contentRef.current &&
}
+ {contentRef.current &&
}
{renderedPage}