From c9f812f53ec93f2c649865fcda8ae2b40119a9f2 Mon Sep 17 00:00:00 2001 From: Michael Gartner Date: Tue, 2 Dec 2025 15:40:03 -0600 Subject: [PATCH 1/5] Introduce a toggle command for enabling/disabling the overlay --- .../components/DiscourseContextOverlay.tsx | 19 ++++-- .../roam/src/utils/pageRefObserverHandlers.ts | 68 ++++++++++++++++++- .../utils/registerCommandPaletteCommands.ts | 36 ++++++++-- 3 files changed, 110 insertions(+), 13 deletions(-) diff --git a/apps/roam/src/components/DiscourseContextOverlay.tsx b/apps/roam/src/components/DiscourseContextOverlay.tsx index 88aacee46..eae6bb1a6 100644 --- a/apps/roam/src/components/DiscourseContextOverlay.tsx +++ b/apps/roam/src/components/DiscourseContextOverlay.tsx @@ -93,6 +93,8 @@ type DiscourseContextOverlayBaseProps = { type DiscourseContextOverlayProps = DiscourseContextOverlayBaseProps & ({ tag: string; uid?: never } | { tag?: never; uid: string }); +const ICON_SIZE = 16; + const DiscourseContextOverlay = ({ tag, id, @@ -169,6 +171,7 @@ const DiscourseContextOverlay = ({ icon={"diagram-tree"} color={iconColor} style={{ opacity: `${Number(opacity) / 100}` }} + size={ICON_SIZE} /> { ); diff --git a/apps/roam/src/utils/pageRefObserverHandlers.ts b/apps/roam/src/utils/pageRefObserverHandlers.ts index 6acc9623c..f00689801 100644 --- a/apps/roam/src/utils/pageRefObserverHandlers.ts +++ b/apps/roam/src/utils/pageRefObserverHandlers.ts @@ -32,9 +32,24 @@ export const overlayPageRefHandler = ( const tag = s.getAttribute("data-tag") || s.parentElement.getAttribute("data-link-title"); + const hasOverlayAttribute = s.getAttribute("data-roamjs-discourse-overlay"); + const hasOverlayElement = + (s.hasAttribute("data-tag") && + Array.from(s.children).some( + (child) => + child instanceof HTMLSpanElement && + child.querySelector(".roamjs-discourse-context-overlay"), + )) || + (s.parentElement && + Array.from(s.parentElement.children).some( + (child) => + child instanceof HTMLSpanElement && + child.querySelector(".roamjs-discourse-context-overlay"), + )); if ( tag && - !s.getAttribute("data-roamjs-discourse-overlay") && + !hasOverlayAttribute && + !hasOverlayElement && isDiscourseNode(getPageUidByPageTitle(tag)) ) { s.setAttribute("data-roamjs-discourse-overlay", "true"); @@ -116,13 +131,64 @@ const disablePageRefObserver = () => { pageRefObserverRef.current = undefined; }; +const applyHandlersToExistingPageRefs = ( + handler: (s: HTMLSpanElement) => void, +) => { + const existingPageRefs = + document.querySelectorAll("span.rm-page-ref"); + existingPageRefs.forEach((pageRef) => { + handler(pageRef); + }); +}; + +const removeOverlaysFromExistingPageRefs = () => { + // Find all page refs that have the overlay attribute OR have overlay elements + // This ensures we catch all cases, even if attribute is missing + const allPageRefs = + document.querySelectorAll("span.rm-page-ref"); + allPageRefs.forEach((pageRef) => { + // Check if overlay is a direct child of pageRef + const directChildContainer = Array.from(pageRef.children).find( + (child) => + child instanceof HTMLSpanElement && + child.querySelector(".roamjs-discourse-context-overlay"), + ) as HTMLSpanElement | undefined; + if (directChildContainer) { + directChildContainer.remove(); + pageRef.removeAttribute("data-roamjs-discourse-overlay"); + return; + } + + // Check if overlay is a direct child of pageRef's parent element + if (pageRef.parentElement) { + const parentDirectChildContainer = Array.from( + pageRef.parentElement.children, + ).find( + (child) => + child instanceof HTMLSpanElement && + child.querySelector(".roamjs-discourse-context-overlay"), + ) as HTMLSpanElement | undefined; + if (parentDirectChildContainer) { + parentDirectChildContainer.remove(); + pageRef.removeAttribute("data-roamjs-discourse-overlay"); + } + } + }); +}; + export const onPageRefObserverChange = (handler: (s: HTMLSpanElement) => void) => (b: boolean) => { if (b) { if (!pageRefObservers.size) enablePageRefObserver(); pageRefObservers.add(handler); + // Apply handler to existing page refs when enabling + applyHandlersToExistingPageRefs(handler); } else { pageRefObservers.delete(handler); + // Remove overlays from existing page refs when disabling + if (handler === cachedHandler) { + removeOverlaysFromExistingPageRefs(); + } if (!pageRefObservers.size) disablePageRefObserver(); } }; diff --git a/apps/roam/src/utils/registerCommandPaletteCommands.ts b/apps/roam/src/utils/registerCommandPaletteCommands.ts index a963e0c3a..65b268562 100644 --- a/apps/roam/src/utils/registerCommandPaletteCommands.ts +++ b/apps/roam/src/utils/registerCommandPaletteCommands.ts @@ -12,6 +12,10 @@ import getDiscourseNodes from "./getDiscourseNodes"; import fireQuery from "./fireQuery"; import { excludeDefaultNodes } from "~/utils/getDiscourseNodes"; import { render as renderSettings } from "~/components/settings/Settings"; +import { + getOverlayHandler, + onPageRefObserverChange, +} from "./pageRefObserverHandlers"; export const registerCommandPaletteCommands = (onloadArgs: OnloadArgs) => { const { extensionAPI } = onloadArgs; @@ -134,6 +138,20 @@ export const registerCommandPaletteCommands = (onloadArgs: OnloadArgs) => { const renderSettingsPopup = () => renderSettings({ onloadArgs }); + const toggleDiscourseContextOverlay = () => { + const currentValue = + (extensionAPI.settings.get("discourse-context-overlay") as boolean) ?? + false; + const newValue = !currentValue; + void extensionAPI.settings.set("discourse-context-overlay", newValue); + const overlayHandler = getOverlayHandler(onloadArgs); + onPageRefObserverChange(overlayHandler)(newValue); + renderToast({ + id: "discourse-context-overlay-toggle", + content: `Discourse Context Overlay ${newValue ? "enabled" : "disabled"}`, + }); + }; + const addCommand = (label: string, callback: () => void) => { return extensionAPI.ui.commandPalette.addCommand({ label, @@ -141,11 +159,15 @@ export const registerCommandPaletteCommands = (onloadArgs: OnloadArgs) => { }); }; - // Roam organizes commands by alphabetically - addCommand("DG: Export - Current Page", exportCurrentPage); - addCommand("DG: Export - Discourse Graph", exportDiscourseGraph); - addCommand("DG: Open - Discourse Settings", renderSettingsPopup); - addCommand("DG: Open - Query Drawer", openQueryDrawerWithArgs); - addCommand("DG: Query Block - Create", createQueryBlock); - addCommand("DG: Query Block - Refresh", refreshCurrentQueryBuilder); + // Roam organizes commands alphabetically + void addCommand("DG: Export - Current Page", exportCurrentPage); + void addCommand("DG: Export - Discourse Graph", exportDiscourseGraph); + void addCommand("DG: Open - Discourse Settings", renderSettingsPopup); + void addCommand("DG: Open - Query Drawer", openQueryDrawerWithArgs); + void addCommand( + "DG: Toggle - Discourse Context Overlay", + toggleDiscourseContextOverlay, + ); + void addCommand("DG: Query Block - Create", createQueryBlock); + void addCommand("DG: Query Block - Refresh", refreshCurrentQueryBuilder); }; From b313d1319d9bbe7ccd90a7a831d11c591ef4b77f Mon Sep 17 00:00:00 2001 From: Michael Gartner Date: Tue, 2 Dec 2025 15:42:25 -0600 Subject: [PATCH 2/5] Refactor overlay handling in initObservers: replace overlayPageRefHandler with getOverlayHandler and onPageRefObserverChange --- apps/roam/src/utils/initializeObserversAndListeners.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/roam/src/utils/initializeObserversAndListeners.ts b/apps/roam/src/utils/initializeObserversAndListeners.ts index b6fb54dbf..e10d05adf 100644 --- a/apps/roam/src/utils/initializeObserversAndListeners.ts +++ b/apps/roam/src/utils/initializeObserversAndListeners.ts @@ -23,7 +23,8 @@ import { addPageRefObserver, getPageRefObserversSize, previewPageRefHandler, - overlayPageRefHandler, + getOverlayHandler, + onPageRefObserverChange, getSuggestiveOverlayHandler, } from "~/utils/pageRefObserverHandlers"; import getDiscourseNodes from "~/utils/getDiscourseNodes"; @@ -186,7 +187,8 @@ export const initObservers = async ({ if (onloadArgs.extensionAPI.settings.get("page-preview")) addPageRefObserver(previewPageRefHandler); if (onloadArgs.extensionAPI.settings.get("discourse-context-overlay")) { - addPageRefObserver((s) => overlayPageRefHandler(s, onloadArgs)); + const overlayHandler = getOverlayHandler(onloadArgs); + onPageRefObserverChange(overlayHandler)(true); } if (!!getPageRefObserversSize()) enablePageRefObserver(); From e9284f3e612d15d3dd92cf7f1551d5b36e1395c2 Mon Sep 17 00:00:00 2001 From: Michael Gartner Date: Tue, 2 Dec 2025 16:04:57 -0600 Subject: [PATCH 3/5] Standardize command palette command labels for consistency in case formatting. --- .../src/utils/registerCommandPaletteCommands.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/roam/src/utils/registerCommandPaletteCommands.ts b/apps/roam/src/utils/registerCommandPaletteCommands.ts index 65b268562..169afdbe4 100644 --- a/apps/roam/src/utils/registerCommandPaletteCommands.ts +++ b/apps/roam/src/utils/registerCommandPaletteCommands.ts @@ -148,7 +148,7 @@ export const registerCommandPaletteCommands = (onloadArgs: OnloadArgs) => { onPageRefObserverChange(overlayHandler)(newValue); renderToast({ id: "discourse-context-overlay-toggle", - content: `Discourse Context Overlay ${newValue ? "enabled" : "disabled"}`, + content: `Discourse context overlay ${newValue ? "enabled" : "disabled"}`, }); }; @@ -160,14 +160,14 @@ export const registerCommandPaletteCommands = (onloadArgs: OnloadArgs) => { }; // Roam organizes commands alphabetically - void addCommand("DG: Export - Current Page", exportCurrentPage); - void addCommand("DG: Export - Discourse Graph", exportDiscourseGraph); - void addCommand("DG: Open - Discourse Settings", renderSettingsPopup); - void addCommand("DG: Open - Query Drawer", openQueryDrawerWithArgs); + void addCommand("DG: Export - Current page", exportCurrentPage); + void addCommand("DG: Export - Discourse graph", exportDiscourseGraph); + void addCommand("DG: Open - Discourse settings", renderSettingsPopup); + void addCommand("DG: Open - Query drawer", openQueryDrawerWithArgs); void addCommand( - "DG: Toggle - Discourse Context Overlay", + "DG: Toggle - Discourse context overlay", toggleDiscourseContextOverlay, ); - void addCommand("DG: Query Block - Create", createQueryBlock); - void addCommand("DG: Query Block - Refresh", refreshCurrentQueryBuilder); + void addCommand("DG: Query block - Create", createQueryBlock); + void addCommand("DG: Query block - Refresh", refreshCurrentQueryBuilder); }; From 1bdfcd0f37c504e19f47d4a946a8aed269e044e1 Mon Sep 17 00:00:00 2001 From: Michael Gartner Date: Tue, 2 Dec 2025 16:06:58 -0600 Subject: [PATCH 4/5] Enhance toggleDiscourseContextOverlay function with error handling for setting changes and improve toast message formatting. --- .../src/utils/registerCommandPaletteCommands.ts | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/apps/roam/src/utils/registerCommandPaletteCommands.ts b/apps/roam/src/utils/registerCommandPaletteCommands.ts index 169afdbe4..2aa42cc46 100644 --- a/apps/roam/src/utils/registerCommandPaletteCommands.ts +++ b/apps/roam/src/utils/registerCommandPaletteCommands.ts @@ -138,17 +138,26 @@ export const registerCommandPaletteCommands = (onloadArgs: OnloadArgs) => { const renderSettingsPopup = () => renderSettings({ onloadArgs }); - const toggleDiscourseContextOverlay = () => { + const toggleDiscourseContextOverlay = async () => { const currentValue = (extensionAPI.settings.get("discourse-context-overlay") as boolean) ?? false; const newValue = !currentValue; - void extensionAPI.settings.set("discourse-context-overlay", newValue); + try { + await extensionAPI.settings.set("discourse-context-overlay", newValue); + } catch (error) { + const e = error as Error; + renderToast({ + id: "discourse-context-overlay-toggle-error", + content: `Failed to toggle discourse context overlay: ${e.message}`, + }); + return; + } const overlayHandler = getOverlayHandler(onloadArgs); onPageRefObserverChange(overlayHandler)(newValue); renderToast({ id: "discourse-context-overlay-toggle", - content: `Discourse context overlay ${newValue ? "enabled" : "disabled"}`, + content: `Discourse Context Overlay ${newValue ? "enabled" : "disabled"}`, }); }; From ef308274e4f8325b72ffdf46f643c4d7b39d98e2 Mon Sep 17 00:00:00 2001 From: Michael Gartner Date: Tue, 2 Dec 2025 16:09:19 -0600 Subject: [PATCH 5/5] . --- apps/roam/src/utils/registerCommandPaletteCommands.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/roam/src/utils/registerCommandPaletteCommands.ts b/apps/roam/src/utils/registerCommandPaletteCommands.ts index 2aa42cc46..28ee9923a 100644 --- a/apps/roam/src/utils/registerCommandPaletteCommands.ts +++ b/apps/roam/src/utils/registerCommandPaletteCommands.ts @@ -157,7 +157,7 @@ export const registerCommandPaletteCommands = (onloadArgs: OnloadArgs) => { onPageRefObserverChange(overlayHandler)(newValue); renderToast({ id: "discourse-context-overlay-toggle", - content: `Discourse Context Overlay ${newValue ? "enabled" : "disabled"}`, + content: `Discourse context overlay ${newValue ? "enabled" : "disabled"}`, }); };