Skip to content

Commit 2ef4605

Browse files
committed
change the way discourse node search query works
1 parent 5ef439f commit 2ef4605

File tree

1 file changed

+58
-31
lines changed

1 file changed

+58
-31
lines changed

apps/roam/src/components/DiscourseNodeSearchMenu.tsx

Lines changed: 58 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ import { getCoordsFromTextarea } from "roamjs-components/components/CursorMenu";
2323
import { OnloadArgs } from "roamjs-components/types";
2424
import getDiscourseNodes, { DiscourseNode } from "~/utils/getDiscourseNodes";
2525
import getDiscourseNodeFormatExpression from "~/utils/getDiscourseNodeFormatExpression";
26-
import { escapeCljString } from "~/utils/formatUtils";
2726
import { Result } from "~/utils/types";
2827
import { getSetting } from "~/utils/extensionSettings";
28+
import fuzzy from "fuzzy";
2929

3030
type Props = {
3131
textarea: HTMLTextAreaElement;
@@ -34,19 +34,32 @@ type Props = {
3434
triggerText: string;
3535
};
3636

37-
const waitForBlock = (
38-
uid: string,
39-
text: string,
37+
const waitForBlock = ({
38+
uid,
39+
text,
4040
retries = 0,
4141
maxRetries = 30,
42-
): Promise<void> =>
42+
}: {
43+
uid: string;
44+
text: string;
45+
retries?: number;
46+
maxRetries?: number;
47+
}): Promise<void> =>
4348
getTextByBlockUid(uid) === text
4449
? Promise.resolve()
4550
: retries >= maxRetries
4651
? Promise.resolve()
4752
: new Promise((resolve) =>
4853
setTimeout(
49-
() => resolve(waitForBlock(uid, text, retries + 1, maxRetries)),
54+
() =>
55+
resolve(
56+
waitForBlock({
57+
uid,
58+
text,
59+
retries: retries + 1,
60+
maxRetries,
61+
}),
62+
),
5063
10,
5164
),
5265
);
@@ -63,6 +76,7 @@ const NodeSearchMenu = ({
6376
const [discourseTypes, setDiscourseTypes] = useState<DiscourseNode[]>([]);
6477
const [checkedTypes, setCheckedTypes] = useState<Record<string, boolean>>({});
6578
const [isLoading, setIsLoading] = useState(true);
79+
const [allNodes, setAllNodes] = useState<Record<string, Result[]>>({});
6680
const [searchResults, setSearchResults] = useState<Record<string, Result[]>>(
6781
{},
6882
);
@@ -89,10 +103,7 @@ const NodeSearchMenu = ({
89103
}, 300);
90104
}, []);
91105

92-
const searchNodesForType = (
93-
node: DiscourseNode,
94-
searchTerm: string,
95-
): Result[] => {
106+
const searchNodesForType = (node: DiscourseNode): Result[] => {
96107
if (!node.format) return [];
97108

98109
try {
@@ -102,19 +113,13 @@ const NodeSearchMenu = ({
102113
.replace(/\\/g, "\\\\")
103114
.replace(/"/g, '\\"');
104115

105-
const searchCondition = searchTerm
106-
? `[(re-pattern "(?i).*${escapeCljString(searchTerm.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"))}.*") ?search-regex]
107-
[(re-find ?search-regex ?node-title)]`
108-
: "";
109-
110116
const query = `[
111117
:find
112118
(pull ?node [:block/string :node/title :block/uid])
113119
:where
114120
[(re-pattern "${regexPattern}") ?title-regex]
115121
[?node :node/title ?node-title]
116122
[(re-find ?title-regex ?node-title)]
117-
${searchCondition}
118123
]`;
119124
const results = window.roamAlphaAPI.q(query);
120125

@@ -130,8 +135,22 @@ const NodeSearchMenu = ({
130135
}
131136
};
132137

138+
const filterNodesLocally = useCallback(
139+
(nodes: Result[], searchTerm: string): Result[] => {
140+
if (!searchTerm.trim()) return nodes;
141+
142+
return fuzzy
143+
.filter(searchTerm, nodes, {
144+
extract: (node) => node.text,
145+
})
146+
.map((result) => result.original)
147+
.filter((node): node is Result => !!node);
148+
},
149+
[],
150+
);
151+
133152
useEffect(() => {
134-
const fetchNodeTypes = async () => {
153+
const fetchNodeTypes = () => {
135154
setIsLoading(true);
136155

137156
const allNodeTypes = getDiscourseNodes().filter(
@@ -146,6 +165,12 @@ const NodeSearchMenu = ({
146165
});
147166
setCheckedTypes(initialCheckedTypes);
148167

168+
const allNodesCache: Record<string, Result[]> = {};
169+
allNodeTypes.forEach((type) => {
170+
allNodesCache[type.type] = searchNodesForType(type);
171+
});
172+
setAllNodes(allNodesCache);
173+
149174
const initialSearchResults = Object.fromEntries(
150175
allNodeTypes.map((type) => [type.type, []]),
151176
);
@@ -158,22 +183,25 @@ const NodeSearchMenu = ({
158183
}, []);
159184

160185
useEffect(() => {
161-
if (isLoading) return;
186+
if (isLoading || Object.keys(allNodes).length === 0) return;
162187

163188
const newResults: Record<string, Result[]> = {};
164189

165190
discourseTypes.forEach((type) => {
166-
newResults[type.type] = searchNodesForType(type, searchTerm);
191+
const cachedNodes = allNodes[type.type] || [];
192+
newResults[type.type] = filterNodesLocally(cachedNodes, searchTerm);
167193
});
168194

169195
setSearchResults(newResults);
170-
}, [searchTerm, isLoading, discourseTypes]);
196+
}, [searchTerm, isLoading, allNodes, discourseTypes, filterNodesLocally]);
171197

172198
const menuRef = useRef<HTMLUListElement>(null);
173199
const { ["block-uid"]: blockUid, ["window-id"]: windowId } = useMemo(
174200
() =>
175201
window.roamAlphaAPI.ui.getFocusedBlock() || {
202+
// eslint-disable-next-line @typescript-eslint/naming-convention
176203
"block-uid": "",
204+
// eslint-disable-next-line @typescript-eslint/naming-convention
177205
"window-id": "",
178206
},
179207
[],
@@ -204,7 +232,7 @@ const NodeSearchMenu = ({
204232

205233
const onSelect = useCallback(
206234
(item: Result) => {
207-
void waitForBlock(blockUid, textarea.value).then(() => {
235+
void waitForBlock({ uid: blockUid, text: textarea.value }).then(() => {
208236
onClose();
209237

210238
setTimeout(() => {
@@ -215,13 +243,15 @@ const NodeSearchMenu = ({
215243
const pageRef = `[[${item.text}]]`;
216244

217245
const newText = `${prefix}${pageRef}${suffix}`;
218-
updateBlock({ uid: blockUid, text: newText }).then(() => {
246+
void updateBlock({ uid: blockUid, text: newText }).then(() => {
219247
const newCursorPosition = triggerPosition + pageRef.length;
220248

221249
if (window.roamAlphaAPI.ui.setBlockFocusAndSelection) {
222-
window.roamAlphaAPI.ui.setBlockFocusAndSelection({
250+
void window.roamAlphaAPI.ui.setBlockFocusAndSelection({
223251
location: {
252+
// eslint-disable-next-line @typescript-eslint/naming-convention
224253
"block-uid": blockUid,
254+
// eslint-disable-next-line @typescript-eslint/naming-convention
225255
"window-id": windowId,
226256
},
227257
selection: { start: newCursorPosition },
@@ -230,14 +260,9 @@ const NodeSearchMenu = ({
230260
setTimeout(() => {
231261
const textareaElements = document.querySelectorAll("textarea");
232262
for (const el of textareaElements) {
233-
if (
234-
getUids(el as HTMLTextAreaElement).blockUid === blockUid
235-
) {
236-
(el as HTMLTextAreaElement).focus();
237-
(el as HTMLTextAreaElement).setSelectionRange(
238-
newCursorPosition,
239-
newCursorPosition,
240-
);
263+
if (getUids(el).blockUid === blockUid) {
264+
el.focus();
265+
el.setSelectionRange(newCursorPosition, newCursorPosition);
241266
break;
242267
}
243268
}
@@ -553,11 +578,13 @@ export const renderDiscourseNodeSearchMenu = (props: Props) => {
553578
parent.style.top = `${coords.top}px`;
554579
props.textarea.parentElement?.insertBefore(parent, props.textarea);
555580

581+
// eslint-disable-next-line react/no-deprecated
556582
ReactDOM.render(
557583
<NodeSearchMenu
558584
{...props}
559585
onClose={() => {
560586
props.onClose();
587+
// eslint-disable-next-line react/no-deprecated
561588
ReactDOM.unmountComponentAtNode(parent);
562589
parent.remove();
563590
}}

0 commit comments

Comments
 (0)