Skip to content

Commit 5c0576d

Browse files
ENG-1136: Notify user when suggestive mode suggestion is adopted
When a user adopts a suggestion in Suggestive Mode: - Shows a toast "Added to [[Page Name]]" if the target page is already open in the main window or if sidebar navigation is disabled - Opens the target page in the right sidebar (or brings it to the top if already open) otherwise - Shows a toast confirming the created relation when a reified relation is adopted instead of a block Adds a new `notifySuggestiveModeAdoption` utility to keep notification logic separate from `SuggestionsBody`. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 84cc227 commit 5c0576d

2 files changed

Lines changed: 172 additions & 31 deletions

File tree

apps/roam/src/components/SuggestionsBody.tsx

Lines changed: 42 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import type { PageGroup } from "~/components/settings/utils/zodSchema";
3434
import { createReifiedRelation } from "~/utils/createReifiedBlock";
3535
import { getStoredRelationsEnabled } from "~/utils/storedRelations";
3636
import posthog from "posthog-js";
37+
import { notifySuggestiveModeAdopted } from "~/utils/notifySuggestiveModeAdoption";
3738

3839
export type DiscourseData = {
3940
results: Awaited<ReturnType<typeof getDiscourseContextResults>>;
@@ -330,50 +331,60 @@ const SuggestionsBody = ({
330331
rel.destination === discourseNode.type) ||
331332
(rel.destination === node.type && rel.source === discourseNode.type),
332333
);
333-
if (relevantRelns.length) {
334-
if (relevantRelns.length > 1) {
335-
// I don't want to panick the user with this.
336-
// TODO: Maybe think of adding a relation type picker?
337-
console.warn("Picking an arbitrary relation");
338-
}
339-
const rel = relevantRelns[0];
340-
try {
341-
if (rel.destination === node.type)
342-
await createReifiedRelation({
343-
sourceUid: tagUid,
344-
destinationUid: node.uid,
345-
relationBlockUid: rel.id,
346-
});
347-
else
348-
await createReifiedRelation({
349-
sourceUid: node.uid,
350-
destinationUid: tagUid,
351-
relationBlockUid: rel.id,
352-
});
353-
} catch (error) {
354-
console.error("Failed to create reified relation:", error);
355-
renderToast({
356-
id: "suggestions-create-block-error",
357-
content: "Failed to create relation",
358-
intent: "danger",
359-
timeout: 5000,
360-
});
361-
return;
362-
}
363-
} else {
334+
if (!relevantRelns.length) {
364335
renderToast({
365336
id: "suggestions-create-block-error",
366337
content: "Could not identify a relevant relation",
367338
intent: "danger",
368339
timeout: 5000,
369340
});
341+
return;
370342
}
343+
if (relevantRelns.length > 1) {
344+
// I don't want to panick the user with this.
345+
// TODO: Maybe think of adding a relation type picker?
346+
console.warn("Picking an arbitrary relation");
347+
}
348+
const rel = relevantRelns[0];
349+
try {
350+
if (rel.destination === node.type)
351+
await createReifiedRelation({
352+
sourceUid: tagUid,
353+
destinationUid: node.uid,
354+
relationBlockUid: rel.id,
355+
});
356+
else
357+
await createReifiedRelation({
358+
sourceUid: node.uid,
359+
destinationUid: tagUid,
360+
relationBlockUid: rel.id,
361+
});
362+
} catch (error) {
363+
console.error("Failed to create reified relation:", error);
364+
renderToast({
365+
id: "suggestions-create-block-error",
366+
content: "Failed to create relation",
367+
intent: "danger",
368+
timeout: 5000,
369+
});
370+
return;
371+
}
372+
await notifySuggestiveModeAdopted({
373+
adoptionType: "relation",
374+
sourceTitle: tag,
375+
destinationTitle: node.text,
376+
});
371377
} else {
372378
await createBlock({
373379
parentUid: blockUid,
374380
node: { text: `[[${node.text}]]` },
375381
});
382+
await notifySuggestiveModeAdopted({
383+
adoptionType: "block",
384+
targetBlockUid: blockUid,
385+
});
376386
}
387+
377388
posthog.capture("Suggestive Mode: Suggestion Adopted", {
378389
tag,
379390
nodeType: node.type,
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import { render as renderToast } from "roamjs-components/components/Toast";
2+
import getPageTitleByPageUid from "roamjs-components/queries/getPageTitleByPageUid";
3+
import getPageUidByBlockUid from "roamjs-components/queries/getPageUidByBlockUid";
4+
import getCurrentPageUid from "roamjs-components/dom/getCurrentPageUid";
5+
import openBlockInSidebar from "roamjs-components/writes/openBlockInSidebar";
6+
import { getPersonalSetting } from "~/components/settings/utils/accessors";
7+
import { PERSONAL_KEYS } from "~/components/settings/utils/settingKeys";
8+
import { isPageUid } from "~/utils/isPageUid";
9+
10+
type RoamSidebarWindow = {
11+
type: string;
12+
"window-id": string;
13+
"block-uid"?: string;
14+
};
15+
16+
export type NotifySuggestiveModeAdoptedParams =
17+
| { adoptionType: "block"; targetBlockUid: string }
18+
| { adoptionType: "relation"; sourceTitle: string; destinationTitle: string };
19+
20+
const resolveTargetPageUid = (blockUid: string): string => {
21+
if (isPageUid(blockUid)) return blockUid;
22+
return getPageUidByBlockUid(blockUid) || blockUid;
23+
};
24+
25+
const isTargetOpenInMainWindow = (targetBlockUid: string): boolean => {
26+
const mainWindowUid = getCurrentPageUid();
27+
if (!mainWindowUid) return false;
28+
if (mainWindowUid === targetBlockUid) return true;
29+
return mainWindowUid === resolveTargetPageUid(targetBlockUid);
30+
};
31+
32+
const getSidebarWindows = (): RoamSidebarWindow[] => {
33+
try {
34+
const windows = window.roamAlphaAPI.ui.rightSidebar.getWindows();
35+
return windows ?? [];
36+
} catch {
37+
return [];
38+
}
39+
};
40+
41+
const findOutlineSidebarWindowId = ({
42+
blockUid,
43+
pageUid,
44+
windows,
45+
}: {
46+
blockUid: string;
47+
pageUid: string;
48+
windows: RoamSidebarWindow[];
49+
}): string | undefined => {
50+
return windows.find(
51+
(w) =>
52+
w.type === "outline" &&
53+
(w["block-uid"] === blockUid || w["block-uid"] === pageUid),
54+
)?.["window-id"];
55+
};
56+
57+
const bringSidebarWindowToTop = (windowId: string): void => {
58+
const sidebarWindow = document.querySelector<HTMLElement>(
59+
`[data-sidebar-window-id="${windowId}"]`,
60+
);
61+
const titleDisplay =
62+
sidebarWindow?.querySelector<HTMLElement>(".rm-title-display") ??
63+
document.querySelector<HTMLElement>(
64+
".rm-sidebar-outline .rm-title-display",
65+
);
66+
67+
titleDisplay?.dispatchEvent(new MouseEvent("mousedown", { bubbles: true }));
68+
};
69+
70+
const focusTopSidebarOutline = (): void => {
71+
setTimeout(() => {
72+
document
73+
.querySelector<HTMLElement>(".rm-sidebar-outline .rm-title-display")
74+
?.dispatchEvent(new MouseEvent("mousedown", { bubbles: true }));
75+
}, 100);
76+
};
77+
78+
const showBlockAdoptedToast = (pageUid: string): void => {
79+
const pageTitle = getPageTitleByPageUid(pageUid);
80+
renderToast({
81+
id: "suggestive-mode-adopted",
82+
content: pageTitle ? `Added to [[${pageTitle}]]` : "Added to outline",
83+
intent: "success",
84+
timeout: 4000,
85+
});
86+
};
87+
88+
const notifyBlockAdopted = async (targetBlockUid: string): Promise<void> => {
89+
const pageUid = resolveTargetPageUid(targetBlockUid);
90+
91+
if (isTargetOpenInMainWindow(targetBlockUid)) {
92+
showBlockAdoptedToast(pageUid);
93+
return;
94+
}
95+
96+
if (getPersonalSetting<boolean>([PERSONAL_KEYS.disableSidebarOpen])) {
97+
showBlockAdoptedToast(pageUid);
98+
return;
99+
}
100+
101+
const existingWindowId = findOutlineSidebarWindowId({
102+
blockUid: targetBlockUid,
103+
pageUid,
104+
windows: getSidebarWindows(),
105+
});
106+
107+
if (existingWindowId) {
108+
bringSidebarWindowToTop(existingWindowId);
109+
return;
110+
}
111+
112+
await openBlockInSidebar(targetBlockUid);
113+
focusTopSidebarOutline();
114+
};
115+
116+
export const notifySuggestiveModeAdopted = async (
117+
params: NotifySuggestiveModeAdoptedParams,
118+
): Promise<void> => {
119+
if (params.adoptionType === "relation") {
120+
renderToast({
121+
id: "suggestive-mode-adopted",
122+
content: `Added relation between [[${params.sourceTitle}]] and [[${params.destinationTitle}]]`,
123+
intent: "success",
124+
timeout: 4000,
125+
});
126+
return;
127+
}
128+
129+
await notifyBlockAdopted(params.targetBlockUid);
130+
};

0 commit comments

Comments
 (0)