Skip to content

Commit e527149

Browse files
ENG-1730: Add node type filter dropdown to advanced node search (#1054)
* basic filter * Address PR review feedback for node type filter. Fix indeterminate checkbox sizing, use Blueprint controls, improve Escape handling when the filter popover is open, disable the trigger while types load, colocate click-outside handling, and add unit tests for filter state helpers. Co-authored-by: Cursor <cursoragent@cursor.com> * Fix eslint no-floating-promises in filter unit tests. Co-authored-by: Cursor <cursoragent@cursor.com> * remove test * cleanup styling * final clean * lint fix * cleanup * Address review feedback on node type filter UI. Use Roam-safe hover styles, inline padding instead of important utilities, filter-keep icon, and clearer filter-state comment. Co-authored-by: Cursor <cursoragent@cursor.com> * fix filter icon janky --------- Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent fb9898a commit e527149

4 files changed

Lines changed: 413 additions & 11 deletions

File tree

apps/roam/src/components/AdvancedNodeSearchDialog/AdvancedSearchDialog.tsx

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import {
4343
splitWithHighlights,
4444
stripTypePrefix,
4545
} from "./utils";
46+
import { DiscourseNodeTypeFilter } from "~/components/AdvancedNodeSearchDialog/DiscourseNodeTypeFilter";
4647
import { RenderRoamBlock, RenderRoamPage } from "~/utils/roamReactComponents";
4748
import { AdvancedSearchFooter } from "./AdvancedSearchFooter";
4849

@@ -157,6 +158,8 @@ const AdvancedNodeSearchDialog = ({
157158
const [activeIndex, setActiveIndex] = useState(0);
158159
const [results, setResults] = useState<SearchResult[]>([]);
159160
const [sort, setSort] = useState<SortConfig>(DEFAULT_SORT_CONFIG);
161+
const [discourseNodes, setDiscourseNodes] = useState<DiscourseNode[]>([]);
162+
const [selectedNodeTypeIds, setSelectedNodeTypeIds] = useState<string[]>([]);
160163
const miniSearchRef = useRef<MiniSearch<
161164
SearchResult & { id: string }
162165
> | null>(null);
@@ -165,14 +168,9 @@ const AdvancedNodeSearchDialog = ({
165168
const inputRef = useRef<HTMLInputElement | null>(null);
166169
const [insertTarget, setInsertTarget] = useState<InsertTarget | null>(null);
167170

168-
const nodeConfigByType = useMemo(() => {
169-
const discourseNodes = getDiscourseNodes().filter(
170-
(node) => node.backedBy === "user",
171-
);
172-
return Object.fromEntries(
173-
discourseNodes.map((node) => [node.type, node]),
174-
) as Record<string, DiscourseNode>;
175-
}, []);
171+
const nodeConfigByType = Object.fromEntries(
172+
discourseNodes.map((node) => [node.type, node]),
173+
);
176174

177175
const activeResult = results[activeIndex] ?? null;
178176
const keywords = debouncedSearchTerm.split(/\s+/).filter(Boolean);
@@ -200,6 +198,7 @@ const AdvancedNodeSearchDialog = ({
200198
setDebouncedSearchTerm("");
201199
setActiveIndex(0);
202200
setSort(DEFAULT_SORT_CONFIG);
201+
setSelectedNodeTypeIds([]);
203202
setResults([]);
204203
setIndexError(false);
205204
}
@@ -221,10 +220,18 @@ const AdvancedNodeSearchDialog = ({
221220
miniSearch: miniSearchRef.current,
222221
allResults: allResultsRef.current,
223222
searchTerm: debouncedSearchTerm,
223+
typeFilter: selectedNodeTypeIds.length ? selectedNodeTypeIds : undefined,
224224
});
225225

226226
setResults(sortSearchResults({ hits: scoredHits, sort }));
227-
}, [debouncedSearchTerm, indexError, isIndexLoading, isOpen, sort]);
227+
}, [
228+
debouncedSearchTerm,
229+
indexError,
230+
isIndexLoading,
231+
isOpen,
232+
selectedNodeTypeIds,
233+
sort,
234+
]);
228235

229236
useEffect(() => {
230237
let cancelled = false;
@@ -234,6 +241,7 @@ const AdvancedNodeSearchDialog = ({
234241
const discourseNodes = getDiscourseNodes().filter(
235242
(node) => node.backedBy === "user",
236243
);
244+
setDiscourseNodes(discourseNodes);
237245

238246
void buildSearchIndex(discourseNodes)
239247
.then(({ miniSearch, results: indexedResults }) => {
@@ -270,7 +278,7 @@ const AdvancedNodeSearchDialog = ({
270278

271279
useEffect(() => {
272280
setActiveIndex(0);
273-
}, [debouncedSearchTerm, sort]);
281+
}, [debouncedSearchTerm, selectedNodeTypeIds, sort]);
274282

275283
useEffect(() => {
276284
const panel = resultsPanelRef.current;
@@ -419,7 +427,11 @@ const AdvancedNodeSearchDialog = ({
419427
placeholder="Search discourse nodes..."
420428
value={searchTerm}
421429
/>
422-
430+
<DiscourseNodeTypeFilter
431+
nodeTypes={discourseNodes}
432+
onSelectedTypeIdsChange={setSelectedNodeTypeIds}
433+
selectedTypeIds={selectedNodeTypeIds}
434+
/>
423435
<DiscourseNodeSortControl
424436
disabled={isIndexLoading || indexError}
425437
onSortChange={handleSortChange}

0 commit comments

Comments
 (0)