Skip to content

Commit c2ef4b9

Browse files
ENG-1736: Open active search result in main panel and sidebar. (#1059)
Rebased onto main so the PR only includes dialog open/sidebar behavior and footer shortcuts, without settings or duplicate utils from earlier stacked commits. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 0dd9e26 commit c2ef4b9

3 files changed

Lines changed: 107 additions & 30 deletions

File tree

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

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,32 @@ const AdvancedNodeSearchDialog = ({
315315
setSort(nextSort);
316316
}, []);
317317

318+
const onOpen = useCallback(async () => {
319+
if (!activeResult || contentState !== "results") return;
320+
321+
const uid = activeResult.uid;
322+
if (getPageTitleByPageUid(uid)) {
323+
await window.roamAlphaAPI.ui.mainWindow.openPage({ page: { uid } });
324+
} else {
325+
await window.roamAlphaAPI.ui.mainWindow.openBlock({ block: { uid } });
326+
}
327+
onClose();
328+
}, [activeResult, contentState, onClose]);
329+
330+
const onOpenInSidebar = useCallback(async () => {
331+
if (!activeResult || contentState !== "results") return;
332+
333+
await window.roamAlphaAPI.ui.rightSidebar.addWindow({
334+
window: {
335+
type: "outline",
336+
// @ts-expect-error - block-uid is valid for outline sidebar windows
337+
// eslint-disable-next-line @typescript-eslint/naming-convention
338+
"block-uid": activeResult.uid,
339+
},
340+
});
341+
onClose();
342+
}, [activeResult, contentState, onClose]);
343+
318344
const onKeyDown = useCallback(
319345
(event: React.KeyboardEvent<HTMLDivElement>) => {
320346
if (event.key === "ArrowDown" && results.length) {
@@ -323,6 +349,16 @@ const AdvancedNodeSearchDialog = ({
323349
} else if (event.key === "ArrowUp" && results.length) {
324350
event.preventDefault();
325351
setActiveIndex((index) => Math.max(index - 1, 0));
352+
} else if (
353+
event.key === "Enter" &&
354+
!event.metaKey &&
355+
!event.ctrlKey &&
356+
contentState === "results" &&
357+
activeResult
358+
) {
359+
event.preventDefault();
360+
if (event.shiftKey) void onOpenInSidebar();
361+
else void onOpen();
326362
} else if (
327363
event.key === "Enter" &&
328364
(event.metaKey || event.ctrlKey) &&
@@ -343,6 +379,8 @@ const AdvancedNodeSearchDialog = ({
343379
insertTarget,
344380
onClose,
345381
onInsert,
382+
onOpen,
383+
onOpenInSidebar,
346384
results.length,
347385
],
348386
);
@@ -441,6 +479,8 @@ const AdvancedNodeSearchDialog = ({
441479
hasActiveResult={!!activeResult}
442480
insertTarget={insertTarget}
443481
onInsert={() => void onInsert()}
482+
onOpen={() => void onOpen()}
483+
onOpenInSidebar={() => void onOpenInSidebar()}
444484
/>
445485
</div>
446486
</Dialog>

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

Lines changed: 57 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React from "react";
2-
import { Button } from "@blueprintjs/core";
2+
import { Icon, type IconName } from "@blueprintjs/core";
33
import type { InsertTarget } from "~/utils/advancedSearchFooterUtils";
44

55
export type AdvancedSearchContentState =
@@ -14,46 +14,77 @@ export type AdvancedSearchFooterProps = {
1414
hasActiveResult: boolean;
1515
insertTarget: InsertTarget | null;
1616
onInsert: () => void;
17+
onOpen: () => void;
18+
onOpenInSidebar: () => void;
1719
};
1820

1921
const footerKbdClassName =
20-
"rounded border border-gray-300 bg-white px-1.5 py-0.5 font-mono text-xs text-gray-600";
22+
"inline-flex items-center justify-center rounded border border-gray-300 bg-white px-1 py-0.5 text-gray-600";
2123

2224
const footerLabelClassName =
2325
"inline-flex shrink-0 items-center gap-1 text-xs lowercase text-gray-500";
2426

2527
type FooterShortcutHintProps = {
2628
disabled?: boolean;
27-
keys: string[];
29+
keyIcons: IconName[];
2830
label: string;
2931
onClick?: () => void;
3032
};
3133

32-
export const FooterShortcutHint = ({
34+
const FooterShortcutHint = ({
3335
disabled = false,
34-
keys,
36+
keyIcons,
3537
label,
3638
onClick,
3739
}: FooterShortcutHintProps) => (
38-
<Button
39-
className="inline-flex !min-h-0 items-center gap-2 p-0"
40+
<button
41+
className="inline-flex cursor-pointer items-center gap-2 border-0 bg-transparent p-0 disabled:cursor-not-allowed disabled:opacity-50"
4042
disabled={disabled}
41-
minimal
4243
onClick={onClick}
43-
small
44+
type="button"
4445
>
4546
<span className={footerLabelClassName}>
46-
{keys.map((key) => (
47-
<kbd className={footerKbdClassName} key={key}>
48-
{key}
47+
{keyIcons.map((icon) => (
48+
<kbd className={footerKbdClassName} key={icon}>
49+
<Icon icon={icon} iconSize={12} />
4950
</kbd>
5051
))}
5152
{label}
5253
</span>
53-
</Button>
54+
</button>
5455
);
5556

56-
export const InsertFooterAction = ({
57+
const OpenFooterAction = ({
58+
disabled,
59+
onOpen,
60+
}: {
61+
disabled: boolean;
62+
onOpen: () => void;
63+
}) => (
64+
<FooterShortcutHint
65+
disabled={disabled}
66+
keyIcons={["key-enter"]}
67+
label="open"
68+
onClick={() => void onOpen()}
69+
/>
70+
);
71+
72+
const OpenInSidebarFooterAction = ({
73+
disabled,
74+
onOpenInSidebar,
75+
}: {
76+
disabled: boolean;
77+
onOpenInSidebar: () => void;
78+
}) => (
79+
<FooterShortcutHint
80+
disabled={disabled}
81+
keyIcons={["key-shift", "key-enter"]}
82+
label="sidebar"
83+
onClick={() => void onOpenInSidebar()}
84+
/>
85+
);
86+
87+
const InsertFooterAction = ({
5788
disabled,
5889
onInsert,
5990
}: {
@@ -62,15 +93,17 @@ export const InsertFooterAction = ({
6293
}) => (
6394
<FooterShortcutHint
6495
disabled={disabled}
65-
keys={["", ""]}
96+
keyIcons={["key-command", "key-enter"]}
6697
label="insert"
6798
onClick={() => void onInsert()}
6899
/>
69100
);
70101

71102
const CloseFooterHint = () => (
72103
<span className={footerLabelClassName}>
73-
<kbd className={footerKbdClassName}>esc</kbd>
104+
<kbd className={footerKbdClassName}>
105+
<Icon icon="key-escape" iconSize={12} />
106+
</kbd>
74107
close
75108
</span>
76109
);
@@ -80,8 +113,11 @@ export const AdvancedSearchFooter = ({
80113
hasActiveResult,
81114
insertTarget,
82115
onInsert,
116+
onOpen,
117+
onOpenInSidebar,
83118
}: AdvancedSearchFooterProps) => {
84119
const hasResults = contentState === "results";
120+
const canOpen = hasActiveResult && hasResults;
85121
const canInsert = !!insertTarget && hasActiveResult && hasResults;
86122

87123
return (
@@ -90,6 +126,11 @@ export const AdvancedSearchFooter = ({
90126
{insertTarget && (
91127
<InsertFooterAction disabled={!canInsert} onInsert={onInsert} />
92128
)}
129+
<OpenFooterAction disabled={!canOpen} onOpen={onOpen} />
130+
<OpenInSidebarFooterAction
131+
disabled={!canOpen}
132+
onOpenInSidebar={onOpenInSidebar}
133+
/>
93134
</div>
94135
<CloseFooterHint />
95136
</div>

apps/roam/src/utils/registerCommandPaletteCommands.ts

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,10 @@ import refreshConfigTree from "~/utils/refreshConfigTree";
4747
import { refreshAndNotify } from "~/components/LeftSidebarView";
4848
import { sectionsToBlockProps } from "~/components/settings/LeftSidebarPersonalSettings";
4949
import { renderAdvancedNodeSearchDialog } from "~/components/AdvancedNodeSearchDialog/AdvancedSearchDialog";
50-
import { getBlockSelection } from "./advancedSearchFooterUtils";
50+
import {
51+
getBlockSelection,
52+
insertPageRefAtRange,
53+
} from "./advancedSearchFooterUtils";
5154

5255
export const createDiscourseNodeFromCommand = (
5356
extensionAPI: OnloadArgs["extensionAPI"],
@@ -74,19 +77,12 @@ export const createDiscourseNodeFromCommand = (
7477
});
7578
return;
7679
}
77-
const originalText = getTextByBlockUid(uid) || "";
78-
const pageRef = `[[${result.text}]]`;
79-
const newText = `${originalText.substring(0, selectionStart)}${pageRef}${originalText.substring(selectionEnd)}`;
80-
const newCursorPosition = selectionStart + pageRef.length;
81-
82-
await updateBlock({ uid, text: newText });
83-
84-
await window.roamAlphaAPI.ui.setBlockFocusAndSelection({
85-
location: {
86-
"block-uid": uid,
87-
"window-id": windowId,
88-
},
89-
selection: { start: newCursorPosition },
80+
await insertPageRefAtRange({
81+
blockUid: uid,
82+
pageTitle: result.text,
83+
selectionEnd,
84+
selectionStart,
85+
windowId,
9086
});
9187
return;
9288
},

0 commit comments

Comments
 (0)