diff --git a/apps/roam/src/components/DiscourseContextOverlay.tsx b/apps/roam/src/components/DiscourseContextOverlay.tsx
index 88aacee4..eae6bb1a 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/initializeObserversAndListeners.ts b/apps/roam/src/utils/initializeObserversAndListeners.ts
index b6fb54db..e10d05ad 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();
diff --git a/apps/roam/src/utils/pageRefObserverHandlers.ts b/apps/roam/src/utils/pageRefObserverHandlers.ts
index 6acc9623..f0068980 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 a963e0c3..28ee9923 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,29 @@ export const registerCommandPaletteCommands = (onloadArgs: OnloadArgs) => {
const renderSettingsPopup = () => renderSettings({ onloadArgs });
+ const toggleDiscourseContextOverlay = async () => {
+ const currentValue =
+ (extensionAPI.settings.get("discourse-context-overlay") as boolean) ??
+ false;
+ const newValue = !currentValue;
+ 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"}`,
+ });
+ };
+
const addCommand = (label: string, callback: () => void) => {
return extensionAPI.ui.commandPalette.addCommand({
label,
@@ -141,11 +168,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);
};