Skip to content

Commit c27e20c

Browse files
authored
ENG-1115 - add command to toggle discourse context overlay from command (#579)
* Introduce a toggle command for enabling/disabling the overlay * Refactor overlay handling in initObservers: replace overlayPageRefHandler with getOverlayHandler and onPageRefObserverChange * Standardize command palette command labels for consistency in case formatting. * Enhance toggleDiscourseContextOverlay function with error handling for setting changes and improve toast message formatting. * .
1 parent 64d41c6 commit c27e20c

4 files changed

Lines changed: 123 additions & 15 deletions

File tree

apps/roam/src/components/DiscourseContextOverlay.tsx

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ type DiscourseContextOverlayBaseProps = {
9393
type DiscourseContextOverlayProps = DiscourseContextOverlayBaseProps &
9494
({ tag: string; uid?: never } | { tag?: never; uid: string });
9595

96+
const ICON_SIZE = 16;
97+
9698
const DiscourseContextOverlay = ({
9799
tag,
98100
id,
@@ -169,6 +171,7 @@ const DiscourseContextOverlay = ({
169171
icon={"diagram-tree"}
170172
color={iconColor}
171173
style={{ opacity: `${Number(opacity) / 100}` }}
174+
size={ICON_SIZE}
172175
/>
173176
<span
174177
className={`mr-1 leading-none opacity-${opacity}`}
@@ -180,6 +183,7 @@ const DiscourseContextOverlay = ({
180183
icon={"link"}
181184
color={iconColor}
182185
style={{ opacity: `${Number(opacity) / 100}` }}
186+
size={ICON_SIZE}
183187
/>
184188
<span
185189
className={`leading-none opacity-${opacity}`}
@@ -209,15 +213,20 @@ const Wrapper = ({ parent, tag }: { parent: HTMLElement; tag: string }) => {
209213
<Button
210214
small
211215
id={id}
216+
className={`roamjs-discourse-context-overlay`}
217+
style={{
218+
minHeight: "initial",
219+
paddingTop: ".25rem",
220+
paddingBottom: ".25rem",
221+
}}
212222
minimal
213-
className={"roamjs-discourse-context-overlay"}
214223
disabled={true}
215224
>
216225
<div className="flex items-center gap-1.5">
217-
<Icon icon={"diagram-tree"} />
218-
<span className="mr-1">-</span>
219-
<Icon icon={"link"} />
220-
<span>-</span>
226+
<Icon icon={"diagram-tree"} size={ICON_SIZE} />
227+
<span className={`mr-1 leading-none`}>-</span>
228+
<Icon icon={"link"} size={ICON_SIZE} />
229+
<span className={`leading-none`}>-</span>
221230
</div>
222231
</Button>
223232
);

apps/roam/src/utils/initializeObserversAndListeners.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ import {
2323
addPageRefObserver,
2424
getPageRefObserversSize,
2525
previewPageRefHandler,
26-
overlayPageRefHandler,
26+
getOverlayHandler,
27+
onPageRefObserverChange,
2728
getSuggestiveOverlayHandler,
2829
} from "~/utils/pageRefObserverHandlers";
2930
import getDiscourseNodes from "~/utils/getDiscourseNodes";
@@ -186,7 +187,8 @@ export const initObservers = async ({
186187
if (onloadArgs.extensionAPI.settings.get("page-preview"))
187188
addPageRefObserver(previewPageRefHandler);
188189
if (onloadArgs.extensionAPI.settings.get("discourse-context-overlay")) {
189-
addPageRefObserver((s) => overlayPageRefHandler(s, onloadArgs));
190+
const overlayHandler = getOverlayHandler(onloadArgs);
191+
onPageRefObserverChange(overlayHandler)(true);
190192
}
191193
if (!!getPageRefObserversSize()) enablePageRefObserver();
192194

apps/roam/src/utils/pageRefObserverHandlers.ts

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,24 @@ export const overlayPageRefHandler = (
3232
const tag =
3333
s.getAttribute("data-tag") ||
3434
s.parentElement.getAttribute("data-link-title");
35+
const hasOverlayAttribute = s.getAttribute("data-roamjs-discourse-overlay");
36+
const hasOverlayElement =
37+
(s.hasAttribute("data-tag") &&
38+
Array.from(s.children).some(
39+
(child) =>
40+
child instanceof HTMLSpanElement &&
41+
child.querySelector(".roamjs-discourse-context-overlay"),
42+
)) ||
43+
(s.parentElement &&
44+
Array.from(s.parentElement.children).some(
45+
(child) =>
46+
child instanceof HTMLSpanElement &&
47+
child.querySelector(".roamjs-discourse-context-overlay"),
48+
));
3549
if (
3650
tag &&
37-
!s.getAttribute("data-roamjs-discourse-overlay") &&
51+
!hasOverlayAttribute &&
52+
!hasOverlayElement &&
3853
isDiscourseNode(getPageUidByPageTitle(tag))
3954
) {
4055
s.setAttribute("data-roamjs-discourse-overlay", "true");
@@ -116,13 +131,64 @@ const disablePageRefObserver = () => {
116131
pageRefObserverRef.current = undefined;
117132
};
118133

134+
const applyHandlersToExistingPageRefs = (
135+
handler: (s: HTMLSpanElement) => void,
136+
) => {
137+
const existingPageRefs =
138+
document.querySelectorAll<HTMLSpanElement>("span.rm-page-ref");
139+
existingPageRefs.forEach((pageRef) => {
140+
handler(pageRef);
141+
});
142+
};
143+
144+
const removeOverlaysFromExistingPageRefs = () => {
145+
// Find all page refs that have the overlay attribute OR have overlay elements
146+
// This ensures we catch all cases, even if attribute is missing
147+
const allPageRefs =
148+
document.querySelectorAll<HTMLSpanElement>("span.rm-page-ref");
149+
allPageRefs.forEach((pageRef) => {
150+
// Check if overlay is a direct child of pageRef
151+
const directChildContainer = Array.from(pageRef.children).find(
152+
(child) =>
153+
child instanceof HTMLSpanElement &&
154+
child.querySelector(".roamjs-discourse-context-overlay"),
155+
) as HTMLSpanElement | undefined;
156+
if (directChildContainer) {
157+
directChildContainer.remove();
158+
pageRef.removeAttribute("data-roamjs-discourse-overlay");
159+
return;
160+
}
161+
162+
// Check if overlay is a direct child of pageRef's parent element
163+
if (pageRef.parentElement) {
164+
const parentDirectChildContainer = Array.from(
165+
pageRef.parentElement.children,
166+
).find(
167+
(child) =>
168+
child instanceof HTMLSpanElement &&
169+
child.querySelector(".roamjs-discourse-context-overlay"),
170+
) as HTMLSpanElement | undefined;
171+
if (parentDirectChildContainer) {
172+
parentDirectChildContainer.remove();
173+
pageRef.removeAttribute("data-roamjs-discourse-overlay");
174+
}
175+
}
176+
});
177+
};
178+
119179
export const onPageRefObserverChange =
120180
(handler: (s: HTMLSpanElement) => void) => (b: boolean) => {
121181
if (b) {
122182
if (!pageRefObservers.size) enablePageRefObserver();
123183
pageRefObservers.add(handler);
184+
// Apply handler to existing page refs when enabling
185+
applyHandlersToExistingPageRefs(handler);
124186
} else {
125187
pageRefObservers.delete(handler);
188+
// Remove overlays from existing page refs when disabling
189+
if (handler === cachedHandler) {
190+
removeOverlaysFromExistingPageRefs();
191+
}
126192
if (!pageRefObservers.size) disablePageRefObserver();
127193
}
128194
};

apps/roam/src/utils/registerCommandPaletteCommands.ts

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ import getDiscourseNodes from "./getDiscourseNodes";
1212
import fireQuery from "./fireQuery";
1313
import { excludeDefaultNodes } from "~/utils/getDiscourseNodes";
1414
import { render as renderSettings } from "~/components/settings/Settings";
15+
import {
16+
getOverlayHandler,
17+
onPageRefObserverChange,
18+
} from "./pageRefObserverHandlers";
1519

1620
export const registerCommandPaletteCommands = (onloadArgs: OnloadArgs) => {
1721
const { extensionAPI } = onloadArgs;
@@ -134,18 +138,45 @@ export const registerCommandPaletteCommands = (onloadArgs: OnloadArgs) => {
134138

135139
const renderSettingsPopup = () => renderSettings({ onloadArgs });
136140

141+
const toggleDiscourseContextOverlay = async () => {
142+
const currentValue =
143+
(extensionAPI.settings.get("discourse-context-overlay") as boolean) ??
144+
false;
145+
const newValue = !currentValue;
146+
try {
147+
await extensionAPI.settings.set("discourse-context-overlay", newValue);
148+
} catch (error) {
149+
const e = error as Error;
150+
renderToast({
151+
id: "discourse-context-overlay-toggle-error",
152+
content: `Failed to toggle discourse context overlay: ${e.message}`,
153+
});
154+
return;
155+
}
156+
const overlayHandler = getOverlayHandler(onloadArgs);
157+
onPageRefObserverChange(overlayHandler)(newValue);
158+
renderToast({
159+
id: "discourse-context-overlay-toggle",
160+
content: `Discourse context overlay ${newValue ? "enabled" : "disabled"}`,
161+
});
162+
};
163+
137164
const addCommand = (label: string, callback: () => void) => {
138165
return extensionAPI.ui.commandPalette.addCommand({
139166
label,
140167
callback,
141168
});
142169
};
143170

144-
// Roam organizes commands by alphabetically
145-
addCommand("DG: Export - Current Page", exportCurrentPage);
146-
addCommand("DG: Export - Discourse Graph", exportDiscourseGraph);
147-
addCommand("DG: Open - Discourse Settings", renderSettingsPopup);
148-
addCommand("DG: Open - Query Drawer", openQueryDrawerWithArgs);
149-
addCommand("DG: Query Block - Create", createQueryBlock);
150-
addCommand("DG: Query Block - Refresh", refreshCurrentQueryBuilder);
171+
// Roam organizes commands alphabetically
172+
void addCommand("DG: Export - Current page", exportCurrentPage);
173+
void addCommand("DG: Export - Discourse graph", exportDiscourseGraph);
174+
void addCommand("DG: Open - Discourse settings", renderSettingsPopup);
175+
void addCommand("DG: Open - Query drawer", openQueryDrawerWithArgs);
176+
void addCommand(
177+
"DG: Toggle - Discourse context overlay",
178+
toggleDiscourseContextOverlay,
179+
);
180+
void addCommand("DG: Query block - Create", createQueryBlock);
181+
void addCommand("DG: Query block - Refresh", refreshCurrentQueryBuilder);
151182
};

0 commit comments

Comments
 (0)