From 4281d94feb9150a23120fcfe04cffa9cfaeb33a9 Mon Sep 17 00:00:00 2001 From: Trang Doan Date: Mon, 5 May 2025 15:04:46 -0400 Subject: [PATCH 1/9] cur progress --- apps/roam/src/components/DiscourseNodeSearchMenu.tsx | 2 +- apps/roam/src/utils/initializeObserversAndListeners.ts | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/roam/src/components/DiscourseNodeSearchMenu.tsx b/apps/roam/src/components/DiscourseNodeSearchMenu.tsx index 83206c262..0e5dd318a 100644 --- a/apps/roam/src/components/DiscourseNodeSearchMenu.tsx +++ b/apps/roam/src/components/DiscourseNodeSearchMenu.tsx @@ -135,6 +135,7 @@ const NodeSearchMenu = ({ const onSelect = useCallback( (item: { id: string; text: string }) => { + // Wait for block to stabilize before making changes waitForBlock(blockUid, textarea.value).then(() => { onClose(); @@ -175,7 +176,6 @@ const NodeSearchMenu = ({ }, 50); } }); - posthog.capture("Discourse Node: Selected from Search Menu", { id: item.id, text: item.text, diff --git a/apps/roam/src/utils/initializeObserversAndListeners.ts b/apps/roam/src/utils/initializeObserversAndListeners.ts index e5bb9b728..2970cfa08 100644 --- a/apps/roam/src/utils/initializeObserversAndListeners.ts +++ b/apps/roam/src/utils/initializeObserversAndListeners.ts @@ -191,11 +191,13 @@ export const initObservers = async ({ const target = evt.target as HTMLElement; if (document.querySelector(".discourse-node-search-menu")) return; + // if (!menuOpen && (evt.key === "@" || (evt.key === "2" && evt.shiftKey))) { if (evt.key === "@" || (evt.key === "2" && evt.shiftKey)) { if ( target.tagName === "TEXTAREA" && target.classList.contains("rm-block-input") ) { + // menuOpen = true; const textarea = target as HTMLTextAreaElement; const location = window.roamAlphaAPI.ui.getFocusedBlock(); if (!location) return; @@ -205,7 +207,7 @@ export const initObservers = async ({ cursorPos === 0 || textarea.value.charAt(cursorPos - 1) === " " || textarea.value.charAt(cursorPos - 1) === "\n"; - + console.log("cursorPos", cursorPos); if (isBeginningOrAfterSpace) { renderDiscourseNodeSearchMenu({ onClose: () => {}, From b0a1d0660d3e63fadfe96d818d98d48a720d2278 Mon Sep 17 00:00:00 2001 From: Trang Doan Date: Mon, 5 May 2025 19:37:47 -0400 Subject: [PATCH 2/9] current progress --- apps/roam/src/components/DiscourseNodeSearchMenu.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/roam/src/components/DiscourseNodeSearchMenu.tsx b/apps/roam/src/components/DiscourseNodeSearchMenu.tsx index 0e5dd318a..940921e40 100644 --- a/apps/roam/src/components/DiscourseNodeSearchMenu.tsx +++ b/apps/roam/src/components/DiscourseNodeSearchMenu.tsx @@ -135,7 +135,6 @@ const NodeSearchMenu = ({ const onSelect = useCallback( (item: { id: string; text: string }) => { - // Wait for block to stabilize before making changes waitForBlock(blockUid, textarea.value).then(() => { onClose(); From d60ab28c0b306127b431a539abb981e4e5b4d6eb Mon Sep 17 00:00:00 2001 From: Trang Doan Date: Tue, 6 May 2025 13:17:23 -0400 Subject: [PATCH 3/9] rm logs --- apps/roam/src/utils/initializeObserversAndListeners.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/apps/roam/src/utils/initializeObserversAndListeners.ts b/apps/roam/src/utils/initializeObserversAndListeners.ts index 2970cfa08..9c2479116 100644 --- a/apps/roam/src/utils/initializeObserversAndListeners.ts +++ b/apps/roam/src/utils/initializeObserversAndListeners.ts @@ -191,13 +191,11 @@ export const initObservers = async ({ const target = evt.target as HTMLElement; if (document.querySelector(".discourse-node-search-menu")) return; - // if (!menuOpen && (evt.key === "@" || (evt.key === "2" && evt.shiftKey))) { if (evt.key === "@" || (evt.key === "2" && evt.shiftKey)) { if ( target.tagName === "TEXTAREA" && target.classList.contains("rm-block-input") ) { - // menuOpen = true; const textarea = target as HTMLTextAreaElement; const location = window.roamAlphaAPI.ui.getFocusedBlock(); if (!location) return; @@ -207,7 +205,6 @@ export const initObservers = async ({ cursorPos === 0 || textarea.value.charAt(cursorPos - 1) === " " || textarea.value.charAt(cursorPos - 1) === "\n"; - console.log("cursorPos", cursorPos); if (isBeginningOrAfterSpace) { renderDiscourseNodeSearchMenu({ onClose: () => {}, From afb7c2bcc607349aa60c2b53325889dea056e759 Mon Sep 17 00:00:00 2001 From: Trang Doan Date: Fri, 9 May 2025 13:35:37 -0400 Subject: [PATCH 4/9] address PR comments --- apps/roam/src/components/DiscourseNodeSearchMenu.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/roam/src/components/DiscourseNodeSearchMenu.tsx b/apps/roam/src/components/DiscourseNodeSearchMenu.tsx index 940921e40..db8db4e44 100644 --- a/apps/roam/src/components/DiscourseNodeSearchMenu.tsx +++ b/apps/roam/src/components/DiscourseNodeSearchMenu.tsx @@ -289,7 +289,6 @@ const NodeSearchMenu = ({ setIsFilterMenuOpen((prev) => !prev); - // Restore focus after toggle setTimeout(() => { if (textarea) { textarea.focus(); From 96005701d425a6a9638c8fd27be76b6faae3425a Mon Sep 17 00:00:00 2001 From: Trang Doan Date: Mon, 12 May 2025 11:57:38 -0400 Subject: [PATCH 5/9] address PR comments --- apps/roam/src/components/DiscourseNodeSearchMenu.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/roam/src/components/DiscourseNodeSearchMenu.tsx b/apps/roam/src/components/DiscourseNodeSearchMenu.tsx index db8db4e44..940921e40 100644 --- a/apps/roam/src/components/DiscourseNodeSearchMenu.tsx +++ b/apps/roam/src/components/DiscourseNodeSearchMenu.tsx @@ -289,6 +289,7 @@ const NodeSearchMenu = ({ setIsFilterMenuOpen((prev) => !prev); + // Restore focus after toggle setTimeout(() => { if (textarea) { textarea.focus(); From cad7f1372f4642879f790d53402abbb708b5a7ca Mon Sep 17 00:00:00 2001 From: Trang Doan Date: Tue, 6 May 2025 15:55:09 -0400 Subject: [PATCH 6/9] query works --- .../components/DiscourseNodeSearchMenu.tsx | 321 +++++++++++------- 1 file changed, 203 insertions(+), 118 deletions(-) diff --git a/apps/roam/src/components/DiscourseNodeSearchMenu.tsx b/apps/roam/src/components/DiscourseNodeSearchMenu.tsx index 940921e40..4b5acad4c 100644 --- a/apps/roam/src/components/DiscourseNodeSearchMenu.tsx +++ b/apps/roam/src/components/DiscourseNodeSearchMenu.tsx @@ -20,6 +20,8 @@ import getTextByBlockUid from "roamjs-components/queries/getTextByBlockUid"; import updateBlock from "roamjs-components/writes/updateBlock"; import posthog from "posthog-js"; import { getCoordsFromTextarea } from "roamjs-components/components/CursorMenu"; +import getDiscourseNodes, { DiscourseNode } from "~/utils/getDiscourseNodes"; +import getDiscourseNodeFormatExpression from "~/utils/getDiscourseNodeFormatExpression"; type Props = { textarea: HTMLTextAreaElement; @@ -27,41 +29,12 @@ type Props = { onClose: () => void; }; -type DiscourseType = { - type: string; - title: string; - items: { id: string; text: string }[]; +type NodeSearchResult = { + id: string; + text: string; + uid?: string; }; -// Hardcoded discourse types for testing -// TODO: replace with actual discourse types -const DISCOURSE_TYPES: DiscourseType[] = [ - { - type: "claims", - title: "Claims", - items: [ - { id: "clm1", text: "[[CLM]] - Claim 1" }, - { id: "clm2", text: "[[CLM]] - Claim 1" }, - ], - }, - { - type: "evidence", - title: "Evidence", - items: [ - { id: "evd1", text: "[[EVD]] - Evidence 1" }, - { id: "evd2", text: "[[EVD]] - Evidence 2" }, - ], - }, - { - type: "results", - title: "Results", - items: [ - { id: "res1", text: "[[RES]] - Result 1" }, - { id: "res2", text: "[[RES]] - Result 1" }, - ], - }, -]; - const waitForBlock = ( uid: string, text: string, @@ -86,10 +59,91 @@ const NodeSearchMenu = ({ }: { onClose: () => void } & Props) => { const [activeIndex, setActiveIndex] = useState(0); const [searchTerm, setSearchTerm] = useState(""); - const [checkedTypes, setCheckedTypes] = useState>( - DISCOURSE_TYPES.reduce((acc, type) => ({ ...acc, [type.type]: true }), {}), - ); const [isFilterMenuOpen, setIsFilterMenuOpen] = useState(false); + const [discourseTypes, setDiscourseTypes] = useState([]); + const [checkedTypes, setCheckedTypes] = useState>({}); + const [isLoading, setIsLoading] = useState(true); + const [searchResults, setSearchResults] = useState< + Record + >({}); + const scrollContainerRef = useRef(null); + + const searchNodesForType = ( + node: DiscourseNode, + searchTerm: string, + ): NodeSearchResult[] => { + if (!node.format) return []; + + try { + const regex = getDiscourseNodeFormatExpression(node.format); + + const regexPattern = regex.source + .replace(/^\^/, "") + .replace(/\$$/, "") + .replace(/\\/g, "\\\\"); + + const query = `[ + :find + (pull ?node [:block/string :node/title :block/uid]) + :where + [(re-pattern "${regexPattern}") ?title-regex] + [?node :node/title ?node-title] + ${searchTerm ? `[(clojure.string/includes? ?node-title "${searchTerm.toLowerCase()}")]` : ""} + [(re-find ?title-regex ?node-title)] + ]`; + const results = window.roamAlphaAPI.q(query); + + return results.map(([result]: any) => ({ + id: result.uid, + text: result.title || result.string, + uid: result.uid, + })); + } catch (error) { + console.error(`Error querying for node type ${node.type}:`, error); + return []; + } + }; + + useEffect(() => { + const fetchNodes = async () => { + setIsLoading(true); + + const allNodeTypes = getDiscourseNodes().filter( + (n) => n.backedBy === "user", + ); + + setDiscourseTypes(allNodeTypes); + + const initialCheckedTypes = allNodeTypes.reduce( + (acc, type) => ({ ...acc, [type.type]: true }), + {}, + ); + setCheckedTypes(initialCheckedTypes); + + const initialSearchResults = allNodeTypes.reduce( + (acc, type) => ({ ...acc, [type.type]: [] }), + {}, + ); + setSearchResults(initialSearchResults); + + setIsLoading(false); + }; + + fetchNodes(); + }, []); + + useEffect(() => { + if (isLoading) return; + + const newResults: Record = {}; + + discourseTypes.forEach((type) => { + newResults[type.type] = searchNodesForType(type, searchTerm); + }); + + setSearchResults(newResults); + }, [searchTerm, isLoading, discourseTypes]); + const menuRef = useRef(null); const { ["block-uid"]: blockUid, ["window-id"]: windowId } = useMemo( () => @@ -101,40 +155,30 @@ const NodeSearchMenu = ({ ); const filteredTypes = useMemo(() => { - const typesToShow = DISCOURSE_TYPES.filter( - (type) => checkedTypes[type.type], - ); - - if (!searchTerm.trim()) return typesToShow; - - return typesToShow - .map((type) => ({ - ...type, - items: type.items.filter((item) => - item.text.toLowerCase().includes(searchTerm.toLowerCase()), - ), - })) - .filter((type) => type.items.length > 0); - }, [searchTerm, checkedTypes]); + return discourseTypes + .filter((type) => checkedTypes[type.type]) + .filter((type) => searchResults[type.type]?.length > 0); + }, [discourseTypes, checkedTypes, searchResults]); const allItems = useMemo(() => { const items: { typeIndex: number; itemIndex: number; - item: { id: string; text: string }; + item: NodeSearchResult; }[] = []; filteredTypes.forEach((type, typeIndex) => { - type.items.forEach((item, itemIndex) => { + const typeResults = searchResults[type.type] || []; + typeResults.forEach((item, itemIndex) => { items.push({ typeIndex, itemIndex, item }); }); }); return items; - }, [filteredTypes]); + }, [filteredTypes, searchResults]); const onSelect = useCallback( - (item: { id: string; text: string }) => { + (item: NodeSearchResult) => { waitForBlock(blockUid, textarea.value).then(() => { onClose(); @@ -182,7 +226,7 @@ const NodeSearchMenu = ({ }, 10); }); }, - [blockUid, onClose, searchTerm, textarea], + [blockUid, onClose, textarea, triggerPosition, windowId], ); const handleTextAreaInput = useCallback(() => { @@ -248,7 +292,7 @@ const NodeSearchMenu = ({ return () => { listeningEl?.removeEventListener("keydown", keydownListener); }; - }, [keydownListener]); + }, [keydownListener, textarea]); useEffect(() => { setTimeout(() => { @@ -256,6 +300,30 @@ const NodeSearchMenu = ({ }, 50); }, [handleTextAreaInput]); + useEffect(() => { + if (scrollContainerRef.current) { + const activeItem = scrollContainerRef.current.querySelector( + '[data-active="true"]', + ) as HTMLElement; + + if (activeItem) { + const container = scrollContainerRef.current; + const containerRect = container.getBoundingClientRect(); + const itemRect = activeItem.getBoundingClientRect(); + + if ( + itemRect.bottom > containerRect.bottom || + itemRect.top < containerRect.top + ) { + activeItem.scrollIntoView({ + block: "nearest", + behavior: "auto", + }); + } + } + } + }, [activeIndex]); + let currentGlobalIndex = -1; const handleTypeCheckChange = useCallback( @@ -321,71 +389,88 @@ const NodeSearchMenu = ({ onMouseDown={remainFocusOnTextarea} onClick={remainFocusOnTextarea} > -
-
Search Results
-
- - {isFilterMenuOpen && ( -
-
Filter by type:
-
- {DISCOURSE_TYPES.map((type) => ( -
handleTypeCheckChange(type.type, e)} - > - {}} - className="m-0" - /> -
- ))} -
-
- )} - -
- {filteredTypes.map((type, typeIndex) => ( -
-
- {type.title} + {isLoading ? ( +
Loading...
+ ) : ( + <> +
+
+
Search Results
+
- - {type.items.map((item) => { - currentGlobalIndex++; - const isActive = currentGlobalIndex === activeIndex; - return ( - setActiveIndex(currentGlobalIndex)} - onClick={() => onSelect(item)} - /> - ); - })} - + + {isFilterMenuOpen && ( +
+
+ Filter by type: +
+
+ {discourseTypes.map((type) => ( +
handleTypeCheckChange(type.type, e)} + > + {}} + className="m-0" + /> +
+ ))} +
+
+ )}
- ))} +
+ {filteredTypes.map((type, typeIndex) => ( +
+
+ {type.text} +
+ + {searchResults[type.type]?.map((item) => { + currentGlobalIndex++; + const isActive = currentGlobalIndex === activeIndex; + return ( + + setActiveIndex(currentGlobalIndex) + } + onClick={() => onSelect(item)} + /> + ); + })} + +
+ ))} - {allItems.length === 0 && ( -
- No matches found + {allItems.length === 0 && ( +
+ No matches found +
+ )}
- )} -
+ + )}
} /> From f7479036680955ac83cf95112bf6e966447ac58e Mon Sep 17 00:00:00 2001 From: Trang Doan Date: Wed, 7 May 2025 14:24:50 -0400 Subject: [PATCH 7/9] address PR comments --- .../components/DiscourseNodeSearchMenu.tsx | 35 +++++++++++++------ apps/roam/src/utils/formatUtils.ts | 4 +++ 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/apps/roam/src/components/DiscourseNodeSearchMenu.tsx b/apps/roam/src/components/DiscourseNodeSearchMenu.tsx index 4b5acad4c..79a225ec1 100644 --- a/apps/roam/src/components/DiscourseNodeSearchMenu.tsx +++ b/apps/roam/src/components/DiscourseNodeSearchMenu.tsx @@ -22,6 +22,7 @@ import posthog from "posthog-js"; import { getCoordsFromTextarea } from "roamjs-components/components/CursorMenu"; import getDiscourseNodes, { DiscourseNode } from "~/utils/getDiscourseNodes"; import getDiscourseNodeFormatExpression from "~/utils/getDiscourseNodeFormatExpression"; +import { escapeCljString } from "~/utils/formatUtils"; type Props = { textarea: HTMLTextAreaElement; @@ -88,7 +89,7 @@ const NodeSearchMenu = ({ :where [(re-pattern "${regexPattern}") ?title-regex] [?node :node/title ?node-title] - ${searchTerm ? `[(clojure.string/includes? ?node-title "${searchTerm.toLowerCase()}")]` : ""} + ${searchTerm ? `[(clojure.string/includes? ?node-title "${escapeCljString(searchTerm)}")]` : ""} [(re-find ?title-regex ?node-title)] ]`; const results = window.roamAlphaAPI.q(query); @@ -114,10 +115,10 @@ const NodeSearchMenu = ({ setDiscourseTypes(allNodeTypes); - const initialCheckedTypes = allNodeTypes.reduce( - (acc, type) => ({ ...acc, [type.type]: true }), - {}, - ); + const initialCheckedTypes: Record = {}; + allNodeTypes.forEach((t) => { + initialCheckedTypes[t.type] = true; + }); setCheckedTypes(initialCheckedTypes); const initialSearchResults = allNodeTypes.reduce( @@ -246,11 +247,11 @@ const NodeSearchMenu = ({ const keydownListener = useCallback( (e: KeyboardEvent) => { - if (e.key === "ArrowDown") { + if (e.key === "ArrowDown" && allItems.length) { setActiveIndex((prev) => (prev + 1) % allItems.length); e.preventDefault(); e.stopPropagation(); - } else if (e.key === "ArrowUp") { + } else if (e.key === "ArrowUp" && allItems.length) { setActiveIndex( (prev) => (prev - 1 + allItems.length) % allItems.length, ); @@ -285,14 +286,26 @@ const NodeSearchMenu = ({ }, [handleTextAreaInput, textarea]); useEffect(() => { - const listeningEl = !!textarea.closest(".rm-reference-item") + const listeningEl = textarea.closest(".rm-reference-item") ? textarea.parentElement : textarea; - listeningEl?.addEventListener("keydown", keydownListener); + + if (listeningEl) { + listeningEl.addEventListener("keydown", keydownListener); + listeningEl.addEventListener("input", handleTextAreaInput); + listeningEl.addEventListener("blur", handleTextAreaInput); + listeningEl.addEventListener("click", handleTextAreaInput); + } + return () => { - listeningEl?.removeEventListener("keydown", keydownListener); + if (listeningEl) { + listeningEl.removeEventListener("keydown", keydownListener); + listeningEl.removeEventListener("input", handleTextAreaInput); + listeningEl.removeEventListener("blur", handleTextAreaInput); + listeningEl.removeEventListener("click", handleTextAreaInput); + } }; - }, [keydownListener, textarea]); + }, [textarea, keydownListener, handleTextAreaInput]); useEffect(() => { setTimeout(() => { diff --git a/apps/roam/src/utils/formatUtils.ts b/apps/roam/src/utils/formatUtils.ts index 814d67027..580ba501a 100644 --- a/apps/roam/src/utils/formatUtils.ts +++ b/apps/roam/src/utils/formatUtils.ts @@ -195,3 +195,7 @@ export const findReferencedNodeInText = ({ text: pageTitle, } as Result; }; + +export const escapeCljString = (str: string) => { + return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"').toLowerCase(); +}; From a6fbaafbe81d79fca72b29305926f0e3838f8255 Mon Sep 17 00:00:00 2001 From: Trang Doan Date: Tue, 13 May 2025 18:24:52 -0400 Subject: [PATCH 8/9] address PR comments --- .../components/DiscourseNodeSearchMenu.tsx | 55 +++++++++++-------- 1 file changed, 32 insertions(+), 23 deletions(-) diff --git a/apps/roam/src/components/DiscourseNodeSearchMenu.tsx b/apps/roam/src/components/DiscourseNodeSearchMenu.tsx index 79a225ec1..c978cfeb2 100644 --- a/apps/roam/src/components/DiscourseNodeSearchMenu.tsx +++ b/apps/roam/src/components/DiscourseNodeSearchMenu.tsx @@ -12,7 +12,6 @@ import { Position, Checkbox, Button, - Icon, } from "@blueprintjs/core"; import ReactDOM from "react-dom"; import getUids from "roamjs-components/dom/getUids"; @@ -23,6 +22,7 @@ import { getCoordsFromTextarea } from "roamjs-components/components/CursorMenu"; import getDiscourseNodes, { DiscourseNode } from "~/utils/getDiscourseNodes"; import getDiscourseNodeFormatExpression from "~/utils/getDiscourseNodeFormatExpression"; import { escapeCljString } from "~/utils/formatUtils"; +import { Result } from "~/utils/types"; type Props = { textarea: HTMLTextAreaElement; @@ -30,12 +30,6 @@ type Props = { onClose: () => void; }; -type NodeSearchResult = { - id: string; - text: string; - uid?: string; -}; - const waitForBlock = ( uid: string, text: string, @@ -64,15 +58,26 @@ const NodeSearchMenu = ({ const [discourseTypes, setDiscourseTypes] = useState([]); const [checkedTypes, setCheckedTypes] = useState>({}); const [isLoading, setIsLoading] = useState(true); - const [searchResults, setSearchResults] = useState< - Record - >({}); + const [searchResults, setSearchResults] = useState>( + {}, + ); const scrollContainerRef = useRef(null); + const searchTimeoutRef = useRef(null); + + const debouncedSearchTerm = useCallback((term: string) => { + if (searchTimeoutRef.current) { + clearTimeout(searchTimeoutRef.current); + } + + searchTimeoutRef.current = setTimeout(() => { + setSearchTerm(term); + }, 300); + }, []); const searchNodesForType = ( node: DiscourseNode, searchTerm: string, - ): NodeSearchResult[] => { + ): Result[] => { if (!node.format) return []; try { @@ -106,7 +111,7 @@ const NodeSearchMenu = ({ }; useEffect(() => { - const fetchNodes = async () => { + const fetchNodeTypes = async () => { setIsLoading(true); const allNodeTypes = getDiscourseNodes().filter( @@ -130,13 +135,13 @@ const NodeSearchMenu = ({ setIsLoading(false); }; - fetchNodes(); + fetchNodeTypes(); }, []); useEffect(() => { if (isLoading) return; - const newResults: Record = {}; + const newResults: Record = {}; discourseTypes.forEach((type) => { newResults[type.type] = searchNodesForType(type, searchTerm); @@ -165,7 +170,7 @@ const NodeSearchMenu = ({ const items: { typeIndex: number; itemIndex: number; - item: NodeSearchResult; + item: Result; }[] = []; filteredTypes.forEach((type, typeIndex) => { @@ -179,7 +184,7 @@ const NodeSearchMenu = ({ }, [filteredTypes, searchResults]); const onSelect = useCallback( - (item: NodeSearchResult) => { + (item: Result) => { waitForBlock(blockUid, textarea.value).then(() => { onClose(); @@ -238,12 +243,12 @@ const NodeSearchMenu = ({ ); const match = atTriggerRegex.exec(textBeforeCursor); if (match) { - setSearchTerm(match[1]); + debouncedSearchTerm(match[1]); } else { onClose(); return; } - }, [textarea, onClose, setSearchTerm, triggerPosition]); + }, [textarea, onClose, debouncedSearchTerm, triggerPosition]); const keydownListener = useCallback( (e: KeyboardEvent) => { @@ -293,16 +298,12 @@ const NodeSearchMenu = ({ if (listeningEl) { listeningEl.addEventListener("keydown", keydownListener); listeningEl.addEventListener("input", handleTextAreaInput); - listeningEl.addEventListener("blur", handleTextAreaInput); - listeningEl.addEventListener("click", handleTextAreaInput); } return () => { if (listeningEl) { listeningEl.removeEventListener("keydown", keydownListener); listeningEl.removeEventListener("input", handleTextAreaInput); - listeningEl.removeEventListener("blur", handleTextAreaInput); - listeningEl.removeEventListener("click", handleTextAreaInput); } }; }, [textarea, keydownListener, handleTextAreaInput]); @@ -313,6 +314,14 @@ const NodeSearchMenu = ({ }, 50); }, [handleTextAreaInput]); + useEffect(() => { + return () => { + if (searchTimeoutRef.current) { + clearTimeout(searchTimeoutRef.current); + } + }; + }, []); + useEffect(() => { if (scrollContainerRef.current) { const activeItem = scrollContainerRef.current.querySelector( @@ -461,7 +470,7 @@ const NodeSearchMenu = ({ const isActive = currentGlobalIndex === activeIndex; return ( Date: Wed, 14 May 2025 17:17:47 -0400 Subject: [PATCH 9/9] sm change --- apps/roam/src/components/DiscourseNodeSearchMenu.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/roam/src/components/DiscourseNodeSearchMenu.tsx b/apps/roam/src/components/DiscourseNodeSearchMenu.tsx index c978cfeb2..b5acc8ab0 100644 --- a/apps/roam/src/components/DiscourseNodeSearchMenu.tsx +++ b/apps/roam/src/components/DiscourseNodeSearchMenu.tsx @@ -459,7 +459,7 @@ const NodeSearchMenu = ({ )}
- {filteredTypes.map((type, typeIndex) => ( + {filteredTypes.map((type) => (
{type.text}