From eab69b3ba055f3f9cf07198efe04751fc8e17dea Mon Sep 17 00:00:00 2001 From: sid597 Date: Sun, 11 Jan 2026 12:01:02 +0530 Subject: [PATCH 1/2] pull watchers for prop settings --- .../components/settings/utils/pullWatchers.ts | 276 ++++++++++++++++++ 1 file changed, 276 insertions(+) create mode 100644 apps/roam/src/components/settings/utils/pullWatchers.ts diff --git a/apps/roam/src/components/settings/utils/pullWatchers.ts b/apps/roam/src/components/settings/utils/pullWatchers.ts new file mode 100644 index 000000000..9666392b0 --- /dev/null +++ b/apps/roam/src/components/settings/utils/pullWatchers.ts @@ -0,0 +1,276 @@ +import { type json, normalizeProps } from "~/utils/getBlockProps"; +import { + TOP_LEVEL_BLOCK_PROP_KEYS, + DISCOURSE_NODE_PAGE_PREFIX, +} from "../data/blockPropsSettingsConfig"; +import { getPersonalSettingsKey } from "./init"; +import { + FeatureFlagsSchema, + GlobalSettingsSchema, + PersonalSettingsSchema, + DiscourseNodeSchema, + type FeatureFlags, + type GlobalSettings, + type PersonalSettings, + type DiscourseNodeSettings, +} from "./zodSchema"; + +type PullWatchCallback = (before: unknown, after: unknown) => void; + +type PullWatchEntry = { + pattern: string; + entityId: string; + callback: PullWatchCallback; +}; + +const getNormalizedProps = (data: unknown): Record => { + return normalizeProps( + ((data as Record)?.[":block/props"] || {}) as json, + ) as Record; +}; + +const hasPropChanged = ( + before: unknown, + after: unknown, + key?: string, +): boolean => { + const beforeProps = getNormalizedProps(before); + const afterProps = getNormalizedProps(after); + + if (key) { + return JSON.stringify(beforeProps[key]) !== JSON.stringify(afterProps[key]); + } + + return JSON.stringify(beforeProps) !== JSON.stringify(afterProps); +}; + +const createCleanupFn = (watches: PullWatchEntry[]): (() => void) => { + return () => { + watches.forEach(({ pattern, entityId, callback }) => { + window.roamAlphaAPI.data.removePullWatch(pattern, entityId, callback); + }); + }; +}; + +const addPullWatch = ( + watches: PullWatchEntry[], + blockUid: string, + callback: PullWatchCallback, +): void => { + const pattern = "[:block/props]"; + const entityId = `[:block/uid "${blockUid}"]`; + + window.roamAlphaAPI.data.addPullWatch(pattern, entityId, callback); + watches.push({ pattern, entityId, callback }); +}; + +type FeatureFlagHandler = ( + newValue: boolean, + oldValue: boolean, + allSettings: FeatureFlags, +) => void; + +type GlobalSettingHandler = ( + newValue: GlobalSettings[K], + oldValue: GlobalSettings[K], + allSettings: GlobalSettings, +) => void; + +type PersonalSettingHandler = ( + newValue: PersonalSettings[K], + oldValue: PersonalSettings[K], + allSettings: PersonalSettings, +) => void; + +type DiscourseNodeHandler = ( + nodeType: string, + newSettings: DiscourseNodeSettings, + oldSettings: DiscourseNodeSettings | null, +) => void; + +export const featureFlagHandlers: Partial< + Record +> = { + // Add handlers as needed: + // "Enable Left Sidebar": (newValue) => { ... }, + // "Suggestive Mode Enabled": (newValue) => { ... }, + // "Reified Relation Triples": (newValue) => { ... }, +}; + +export const globalSettingsHandlers: Partial< + Record +> = { + // Add handlers as needed: + // "Trigger": (newValue) => { ... }, + // "Canvas Page Format": (newValue) => { ... }, + // "Left Sidebar": (newValue) => { ... }, + // "Export": (newValue) => { ... }, + // "Suggestive Mode": (newValue) => { ... }, +}; + +export const personalSettingsHandlers: Partial< + Record +> = { + // Add handlers as needed: + // "Left Sidebar": (newValue) => { ... }, + // "Discourse Context Overlay": (newValue) => { ... }, + // "Page Preview": (newValue) => { ... }, + // etc. +}; + + +export const discourseNodeHandlers: DiscourseNodeHandler[] = [ + // Add handlers as needed: + // (nodeType, newSettings, oldSettings) => { ... }, +]; + + +export const setupPullWatchSettings = ( + blockUids: Record, +): (() => void) => { + const watches: PullWatchEntry[] = []; + + const featureFlagsBlockUid = + blockUids[TOP_LEVEL_BLOCK_PROP_KEYS.featureFlags]; + const globalSettingsBlockUid = blockUids[TOP_LEVEL_BLOCK_PROP_KEYS.global]; + const personalSettingsKey = getPersonalSettingsKey(); + const personalSettingsBlockUid = blockUids[personalSettingsKey]; + + if (featureFlagsBlockUid && Object.keys(featureFlagHandlers).length > 0) { + addPullWatch(watches, featureFlagsBlockUid, (before, after) => { + if (!hasPropChanged(before, after)) return; + + const beforeProps = getNormalizedProps(before); + const afterProps = getNormalizedProps(after); + const beforeResult = FeatureFlagsSchema.safeParse(beforeProps); + const afterResult = FeatureFlagsSchema.safeParse(afterProps); + + if (!afterResult.success) return; + + const oldSettings = beforeResult.success ? beforeResult.data : null; + const newSettings = afterResult.data; + + for (const [key, handler] of Object.entries(featureFlagHandlers)) { + const typedKey = key as keyof FeatureFlags; + if (hasPropChanged(before, after, key) && handler) { + handler( + newSettings[typedKey], + oldSettings?.[typedKey] ?? false, + newSettings, + ); + } + } + }); + } + + if (globalSettingsBlockUid && Object.keys(globalSettingsHandlers).length > 0) { + addPullWatch(watches, globalSettingsBlockUid, (before, after) => { + if (!hasPropChanged(before, after)) return; + + const beforeProps = getNormalizedProps(before); + const afterProps = getNormalizedProps(after); + const beforeResult = GlobalSettingsSchema.safeParse(beforeProps); + const afterResult = GlobalSettingsSchema.safeParse(afterProps); + + if (!afterResult.success) return; + + const oldSettings = beforeResult.success ? beforeResult.data : null; + const newSettings = afterResult.data; + + for (const [key, handler] of Object.entries(globalSettingsHandlers)) { + const typedKey = key as keyof GlobalSettings; + if (hasPropChanged(before, after, key) && handler) { + handler( + newSettings[typedKey], + oldSettings?.[typedKey] as GlobalSettings[typeof typedKey], + newSettings, + ); + } + } + }); + } + + if (personalSettingsBlockUid && Object.keys(personalSettingsHandlers).length > 0) { + addPullWatch(watches, personalSettingsBlockUid, (before, after) => { + if (!hasPropChanged(before, after)) return; + + const beforeProps = getNormalizedProps(before); + const afterProps = getNormalizedProps(after); + const beforeResult = PersonalSettingsSchema.safeParse(beforeProps); + const afterResult = PersonalSettingsSchema.safeParse(afterProps); + + if (!afterResult.success) return; + + const oldSettings = beforeResult.success ? beforeResult.data : null; + const newSettings = afterResult.data; + + for (const [key, handler] of Object.entries(personalSettingsHandlers)) { + const typedKey = key as keyof PersonalSettings; + if (hasPropChanged(before, after, key) && handler) { + handler( + newSettings[typedKey], + oldSettings?.[typedKey] as PersonalSettings[typeof typedKey], + newSettings, + ); + } + } + }); + } + + return createCleanupFn(watches); +}; + + +export const setupPullWatchDiscourseNodes = ( + nodePageUids: Record, +): (() => void) => { + const watches: PullWatchEntry[] = []; + + if (discourseNodeHandlers.length === 0) { + return () => {}; + } + + Object.entries(nodePageUids).forEach(([nodeType, pageUid]) => { + addPullWatch(watches, pageUid, (before, after) => { + if (!hasPropChanged(before, after)) return; + + const beforeProps = getNormalizedProps(before); + const afterProps = getNormalizedProps(after); + const beforeResult = DiscourseNodeSchema.safeParse(beforeProps); + const afterResult = DiscourseNodeSchema.safeParse(afterProps); + + if (!afterResult.success) return; + + const oldSettings = beforeResult.success ? beforeResult.data : null; + const newSettings = afterResult.data; + + for (const handler of discourseNodeHandlers) { + handler(nodeType, newSettings, oldSettings); + } + }); + }); + + return createCleanupFn(watches); +}; + + +export const queryAllDiscourseNodePageUids = (): Record => { + const results = window.roamAlphaAPI.q(` + [:find ?uid ?title + :where + [?page :node/title ?title] + [?page :block/uid ?uid] + [(clojure.string/starts-with? ?title "${DISCOURSE_NODE_PAGE_PREFIX}")]] + `) as [string, string][]; + + const nodePageUids: Record = {}; + + for (const [pageUid, title] of results) { + const nodeLabel = title.replace(DISCOURSE_NODE_PAGE_PREFIX, ""); + nodePageUids[nodeLabel] = pageUid; + } + + return nodePageUids; +}; + +export { hasPropChanged, getNormalizedProps }; From bd6a3cfeb8ed4c11cfd3190ab62875579da2de04 Mon Sep 17 00:00:00 2001 From: sid597 Date: Sun, 11 Jan 2026 12:52:24 +0530 Subject: [PATCH 2/2] remove redundatnt function --- .../components/settings/utils/pullWatchers.ts | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/apps/roam/src/components/settings/utils/pullWatchers.ts b/apps/roam/src/components/settings/utils/pullWatchers.ts index 9666392b0..e188e7c82 100644 --- a/apps/roam/src/components/settings/utils/pullWatchers.ts +++ b/apps/roam/src/components/settings/utils/pullWatchers.ts @@ -254,23 +254,4 @@ export const setupPullWatchDiscourseNodes = ( }; -export const queryAllDiscourseNodePageUids = (): Record => { - const results = window.roamAlphaAPI.q(` - [:find ?uid ?title - :where - [?page :node/title ?title] - [?page :block/uid ?uid] - [(clojure.string/starts-with? ?title "${DISCOURSE_NODE_PAGE_PREFIX}")]] - `) as [string, string][]; - - const nodePageUids: Record = {}; - - for (const [pageUid, title] of results) { - const nodeLabel = title.replace(DISCOURSE_NODE_PAGE_PREFIX, ""); - nodePageUids[nodeLabel] = pageUid; - } - - return nodePageUids; -}; - export { hasPropChanged, getNormalizedProps };