diff --git a/apps/roam/src/components/settings/block-prop/utils/accessors.ts b/apps/roam/src/components/settings/block-prop/utils/accessors.ts index 61eaf581b..a558c4b89 100644 --- a/apps/roam/src/components/settings/block-prop/utils/accessors.ts +++ b/apps/roam/src/components/settings/block-prop/utils/accessors.ts @@ -10,7 +10,9 @@ import { FeatureFlags, FeatureFlagsSchema, GlobalSettingsSchema, + PersonalSettingsSchema, } from "~/components/settings/block-prop/utils/zodSchema"; +import { getPersonalSettingsKey } from "~/components/settings/block-prop/utils/init"; const isRecord = (value: unknown): value is Record => typeof value === "object" && value !== null && !Array.isArray(value); @@ -162,3 +164,27 @@ export const setGlobalSetting = (keys: string[], value: json): void => { value, }); }; + +export const getPersonalSetting = (keys: string[]): unknown => { + const personalKey = getPersonalSettingsKey(); + + const { blockProps } = getBlockPropBasedSettings({ + keys: [personalKey], + }); + + const settings = PersonalSettingsSchema.parse(blockProps || {}); + + return keys.reduce((current, key) => { + if (!isRecord(current) || !(key in current)) return undefined; + return current[key]; + }, settings); +}; + +export const setPersonalSetting = (keys: string[], value: json): void => { + const personalKey = getPersonalSettingsKey(); + + void setBlockPropBasedSettings({ + keys: [personalKey, ...keys], + value, + }); +}; diff --git a/apps/roam/src/components/settings/block-prop/utils/init.ts b/apps/roam/src/components/settings/block-prop/utils/init.ts new file mode 100644 index 000000000..5b57a3fab --- /dev/null +++ b/apps/roam/src/components/settings/block-prop/utils/init.ts @@ -0,0 +1,89 @@ +import { + TOP_LEVEL_BLOCK_PROP_KEYS, + DG_BLOCK_PROP_SETTINGS_PAGE_TITLE, +} from "~/components/settings/block-prop/data/blockPropsSettingsConfig"; +import getPageUidByPageTitle from "roamjs-components/queries/getPageUidByPageTitle"; +import getShallowTreeByParentUid from "roamjs-components/queries/getShallowTreeByParentUid"; +import { createPage, createBlock } from "roamjs-components/writes"; + +const ensurePageExists = async (pageTitle: string): Promise => { + let pageUid = getPageUidByPageTitle(pageTitle); + + if (!pageUid) { + pageUid = window.roamAlphaAPI.util.generateUID(); + await createPage({ + title: pageTitle, + uid: pageUid, + }); + } + + return pageUid; +}; + +const ensureBlocksExist = async ( + pageUid: string, + blockMap: Record, +): Promise> => { + const blockTexts = Object.values(TOP_LEVEL_BLOCK_PROP_KEYS); + + const missingBlocks = blockTexts.filter((blockText) => !blockMap[blockText]); + + if (missingBlocks.length > 0) { + const createdBlocks = await Promise.all( + missingBlocks.map(async (blockText) => { + const uid = await createBlock({ + parentUid: pageUid, + node: { text: blockText }, + }); + return { text: blockText, uid }; + }), + ); + + createdBlocks.forEach((block) => { + blockMap[block.text] = block.uid; + }); + } + + return blockMap; +}; + +const ensurePersonalBlockExists = async ( + pageUid: string, + blockMap: Record, +): Promise<{ key: string; uid: string }> => { + const userUid = window.roamAlphaAPI.user.uid(); + const personalKey = `${userUid}/Personal-Section`; + + if (blockMap[personalKey]) { + return { key: personalKey, uid: blockMap[personalKey] }; + } + + const uid = await createBlock({ + parentUid: pageUid, + node: { text: personalKey }, + }); + + return { key: personalKey, uid }; +}; + +export const getPersonalSettingsKey = (): string => { + const userUid = window.roamAlphaAPI.user.uid(); + return `${userUid}/Personal-Section`; +}; + +export const initSchema = async (): Promise> => { + const pageUid = await ensurePageExists(DG_BLOCK_PROP_SETTINGS_PAGE_TITLE); + const existingChildren = getShallowTreeByParentUid(pageUid); + + const blockMap: Record = {}; + existingChildren.forEach((child) => { + blockMap[child.text] = child.uid; + }); + + await ensureBlocksExist(pageUid, blockMap); + + const personalBlock = await ensurePersonalBlockExists(pageUid, blockMap); + blockMap[personalBlock.key] = personalBlock.uid; + + return blockMap; +}; diff --git a/apps/roam/src/components/settings/block-prop/utils/pullWatch.ts b/apps/roam/src/components/settings/block-prop/utils/pullWatch.ts index 4dc7f5960..ad19c4c71 100644 --- a/apps/roam/src/components/settings/block-prop/utils/pullWatch.ts +++ b/apps/roam/src/components/settings/block-prop/utils/pullWatch.ts @@ -1,5 +1,21 @@ import { TOP_LEVEL_BLOCK_PROP_KEYS } from "~/components/settings/block-prop/data/blockPropsSettingsConfig"; import { type json, normalizeProps } from "~/utils/getBlockProps"; +import { getPersonalSettingsKey } from "~/components/settings/block-prop/utils/init"; + +const hasPropChanged = ( + before: unknown, + after: unknown, + key: string, +): boolean => { + const beforeProps = normalizeProps( + ((before as Record)?.[":block/props"] || {}) as json, + ) as Record; + const afterProps = normalizeProps( + ((after as Record)?.[":block/props"] || {}) as json, + ) as Record; + + return JSON.stringify(beforeProps[key]) !== JSON.stringify(afterProps[key]); +}; export const setupPullWatchBlockPropsBasedSettings = ( blockUids: Record, @@ -16,22 +32,7 @@ export const setupPullWatchBlockPropsBasedSettings = ( "[:block/props]", `[:block/uid "${featureFlagsBlockUid}"]`, (before, after) => { - const beforeProps = normalizeProps( - (before?.[":block/props"] || {}) as json, - ) as Record; - const afterProps = normalizeProps( - (after?.[":block/props"] || {}) as json, - ) as Record; - - const beforeEnabled = beforeProps["Enable Left Sidebar"] as - | boolean - | undefined; - const afterEnabled = afterProps["Enable Left Sidebar"] as - | boolean - | undefined; - - // Only update if the flag actually changed - if (beforeEnabled !== afterEnabled) { + if (hasPropChanged(before, after, "Enable Left Sidebar")) { updateLeftSidebar(leftSidebarContainer); } }, @@ -42,8 +43,25 @@ export const setupPullWatchBlockPropsBasedSettings = ( window.roamAlphaAPI.data.addPullWatch( "[:block/props]", `[:block/uid "${globalSettingsBlockUid}"]`, - () => { - updateLeftSidebar(leftSidebarContainer); + (before, after) => { + if (hasPropChanged(before, after, "Left Sidebar")) { + updateLeftSidebar(leftSidebarContainer); + } + }, + ); + } + + const personalSettingsKey = getPersonalSettingsKey(); + const personalSettingsBlockUid = blockUids[personalSettingsKey]; + + if (personalSettingsBlockUid) { + window.roamAlphaAPI.data.addPullWatch( + "[:block/props]", + `[:block/uid "${personalSettingsBlockUid}"]`, + (before, after) => { + if (hasPropChanged(before, after, "Left Sidebar")) { + updateLeftSidebar(leftSidebarContainer); + } }, ); } diff --git a/apps/roam/src/components/settings/block-prop/utils/zodSchema.ts b/apps/roam/src/components/settings/block-prop/utils/zodSchema.ts new file mode 100644 index 000000000..109dfb767 --- /dev/null +++ b/apps/roam/src/components/settings/block-prop/utils/zodSchema.ts @@ -0,0 +1,47 @@ +import { z } from "zod"; + +/* eslint-disable @typescript-eslint/naming-convention */ +export const FeatureFlagsSchema = z.object({ + "Enable Left Sidebar": z.boolean().default(false), +}); + +export const GlobalSettingsSchema = z.object({ + "Left Sidebar": z + .object({ + Children: z.array(z.string()).default([]), + Settings: z + .object({ + Collapsable: z.boolean().default(false), + Folded: z.boolean().default(false), + }) + .default({}), + }) + .default({}), +}); + +export const PersonalSectionSchema = z.object({ + Children: z + .array( + z.object({ + Page: z.string(), + Alias: z.string().default(""), + }), + ) + .default([]), + Settings: z + .object({ + "Truncate-result?": z.number().default(75), + Folded: z.boolean().default(false), + }) + .default({}), +}); + +export const PersonalSettingsSchema = z.object({ + "Left Sidebar": z.record(z.string(), PersonalSectionSchema).default({}), +}); +/* eslint-enable @typescript-eslint/naming-convention */ + +export type FeatureFlags = z.infer; +export type GlobalSettings = z.infer; +export type PersonalSection = z.infer; +export type PersonalSettings = z.infer;