diff --git a/apps/roam/src/components/LeftSidebarView.tsx b/apps/roam/src/components/LeftSidebarView.tsx
index 44fae1668..244557c9e 100644
--- a/apps/roam/src/components/LeftSidebarView.tsx
+++ b/apps/roam/src/components/LeftSidebarView.tsx
@@ -290,7 +290,7 @@ const PersonalSections = ({ config }: { config: LeftSidebarConfig }) => {
return (
{sections.map((section, index) => (
-
+
))}
diff --git a/apps/roam/src/components/settings/DiscourseNodeAttributes.tsx b/apps/roam/src/components/settings/DiscourseNodeAttributes.tsx
index aa11d6161..66aa09721 100644
--- a/apps/roam/src/components/settings/DiscourseNodeAttributes.tsx
+++ b/apps/roam/src/components/settings/DiscourseNodeAttributes.tsx
@@ -67,17 +67,28 @@ const toRecord = (attrs: Attribute[]): Record
=>
const NodeAttributes = ({
uid,
nodeType,
+ defaultValue,
}: {
uid: string;
nodeType: string;
+ defaultValue?: Record;
}) => {
- const [attributes, setAttributes] = useState(() =>
- getBasicTreeByParentUid(uid).map((t) => ({
+ const [attributes, setAttributes] = useState(() => {
+ const tree = getBasicTreeByParentUid(uid);
+ if (defaultValue && Object.keys(defaultValue).length > 0) {
+ const treeByLabel = new Map(tree.map((t) => [t.text, t]));
+ return Object.entries(defaultValue).map(([label, value]) => ({
+ uid: treeByLabel.get(label)?.uid ?? "",
+ label,
+ value,
+ }));
+ }
+ return tree.map((t) => ({
uid: t.uid,
label: t.text,
value: t.children[0]?.text,
- })),
- );
+ }));
+ });
const attributesRef = useRef(attributes);
attributesRef.current = attributes;
const syncToBlockProps = () => {
@@ -93,7 +104,7 @@ const NodeAttributes = ({
{attributes.map((a) => (
setAttributes(
diff --git a/apps/roam/src/components/settings/DiscourseNodeSuggestiveRules.tsx b/apps/roam/src/components/settings/DiscourseNodeSuggestiveRules.tsx
index 3f31cec06..6fc756ff1 100644
--- a/apps/roam/src/components/settings/DiscourseNodeSuggestiveRules.tsx
+++ b/apps/roam/src/components/settings/DiscourseNodeSuggestiveRules.tsx
@@ -75,6 +75,7 @@ const DiscourseNodeSuggestiveRules = ({
description={`The template that auto fills ${node.text} page when generated.`}
settingKeys={TEMPLATE_SETTING_KEYS}
uid={templateUid}
+ defaultValue={node.template}
/>
}
@@ -343,6 +344,10 @@ const NodeConfig = ({
>(
+ node.type,
+ [DISCOURSE_NODE_KEYS.attributes],
+ )}
/>
@@ -33,9 +34,12 @@ const DualWriteBlocksPanel = ({
title,
description,
uid,
+ defaultValue,
}: DualWriteBlocksPanelProps) => {
const containerRef = useRef(null);
const debounceRef = useRef(0);
+ const defaultValueRef = useRef(defaultValue);
+ defaultValueRef.current = defaultValue;
const pullWatchArgsRef = useRef<
[string, string, (before: unknown, after: unknown) => void] | null
>(null);
@@ -62,17 +66,23 @@ const DualWriteBlocksPanel = ({
window.roamAlphaAPI.data.addPullWatch(pattern, entityId, callback);
};
- if (!getFirstChildUidByBlockUid(uid)) {
- void createBlock({ node: { text: " " }, parentUid: uid }).then(() => {
- el.innerHTML = "";
- void window.roamAlphaAPI.ui.components.renderBlock({ uid, el });
- registerPullWatch();
- });
- } else {
+ const dv = defaultValueRef.current;
+ const ensureChildren = getFirstChildUidByBlockUid(uid)
+ ? Promise.resolve()
+ : (dv && dv.length > 0
+ ? Promise.all(
+ dv.map((node, i) =>
+ createBlock({ node, parentUid: uid, order: i }),
+ ),
+ )
+ : createBlock({ node: { text: " " }, parentUid: uid })
+ ).then(() => {});
+
+ void ensureChildren.then(() => {
el.innerHTML = "";
void window.roamAlphaAPI.ui.components.renderBlock({ uid, el });
registerPullWatch();
- }
+ });
return () => {
window.clearTimeout(debounceRef.current);
diff --git a/apps/roam/src/components/settings/utils/settingKeys.ts b/apps/roam/src/components/settings/utils/settingKeys.ts
index 0dc0f2d42..8bc3c9a3e 100644
--- a/apps/roam/src/components/settings/utils/settingKeys.ts
+++ b/apps/roam/src/components/settings/utils/settingKeys.ts
@@ -79,6 +79,7 @@ export const DISCOURSE_NODE_KEYS = {
attributes: "attributes",
specification: "specification",
index: "index",
+ template: "template",
description: "description",
shortcut: "shortcut",
tag: "tag",
diff --git a/apps/roam/src/index.ts b/apps/roam/src/index.ts
index d8ae5ff3e..10ad6db4c 100644
--- a/apps/roam/src/index.ts
+++ b/apps/roam/src/index.ts
@@ -36,6 +36,7 @@ import { initPostHog } from "./utils/posthog";
import { initSchema } from "./components/settings/utils/init";
import {
bulkReadSettings,
+ isNewSettingsStoreEnabled,
isSyncEnabled,
} from "./components/settings/utils/accessors";
import { PERSONAL_KEYS } from "./components/settings/utils/settingKeys";
@@ -128,6 +129,45 @@ export default runExtension(async (onloadArgs) => {
}
const { extensionAPI } = onloadArgs;
+ if (isNewSettingsStoreEnabled()) {
+ const personalLegacyKeys = [
+ "discourse-context-overlay",
+ "text-selection-popup",
+ "disable-sidebar-open",
+ "page-preview",
+ "hide-feedback-button",
+ "auto-canvas-relations",
+ "discourse-context-overlay-in-canvas",
+ "streamline-styling",
+ "disallow-diagnostics",
+ "discourse-tool-shortcut",
+ "personal-node-menu-trigger",
+ "node-search-trigger",
+ "hide-metadata",
+ "default-page-size",
+ "query-pages",
+ "default-filters",
+ "use-reified-relations",
+ "canvas-page-format",
+ ];
+ void extensionAPI.ui.commandPalette.addCommand({
+ label: "DG: Dev - Nuke personal legacy settings",
+ callback: () => {
+ const before = Object.fromEntries(
+ personalLegacyKeys.map((k) => [k, extensionAPI.settings.get(k)]),
+ );
+ console.log("[dg] personal legacy BEFORE nuke:", before);
+ void Promise.all(
+ personalLegacyKeys.map((k) => extensionAPI.settings.set(k, null)),
+ ).then(() => {
+ const after = Object.fromEntries(
+ personalLegacyKeys.map((k) => [k, extensionAPI.settings.get(k)]),
+ );
+ console.log("[dg] personal legacy AFTER nuke:", after);
+ });
+ },
+ });
+ }
window.roamjs.extension.queryBuilder = {
runQuery: (parentUid: string) =>
runQuery({ parentUid, extensionAPI }).then(
diff --git a/apps/roam/src/utils/createDiscourseNode.ts b/apps/roam/src/utils/createDiscourseNode.ts
index f90e7ca3f..9d6a6702d 100644
--- a/apps/roam/src/utils/createDiscourseNode.ts
+++ b/apps/roam/src/utils/createDiscourseNode.ts
@@ -1,6 +1,5 @@
import { render as renderToast } from "roamjs-components/components/Toast";
import createBlock from "roamjs-components/writes/createBlock";
-import stripUid from "roamjs-components/util/stripUid";
import getPageUidByPageTitle from "roamjs-components/queries/getPageUidByPageTitle";
import createPage from "roamjs-components/writes/createPage";
import getFullTreeByParentUid from "roamjs-components/queries/getFullTreeByParentUid";
@@ -8,12 +7,18 @@ import getSubTree from "roamjs-components/util/getSubTree";
import openBlockInSidebar from "roamjs-components/writes/openBlockInSidebar";
import getDiscourseNodes from "./getDiscourseNodes";
import resolveQueryBuilderRef from "./resolveQueryBuilderRef";
-import { OnloadArgs, RoamBasicNode } from "roamjs-components/types";
+import { InputTextNode, OnloadArgs } from "roamjs-components/types";
import runQuery from "./runQuery";
import updateBlock from "roamjs-components/writes/updateBlock";
import posthog from "posthog-js";
-import { getPersonalSetting } from "~/components/settings/utils/accessors";
-import { PERSONAL_KEYS } from "~/components/settings/utils/settingKeys";
+import {
+ getDiscourseNodeSetting,
+ getPersonalSetting,
+} from "~/components/settings/utils/accessors";
+import {
+ DISCOURSE_NODE_KEYS,
+ PERSONAL_KEYS,
+} from "~/components/settings/utils/settingKeys";
type Props = {
text: string;
@@ -23,6 +28,18 @@ type Props = {
extensionAPI?: OnloadArgs["extensionAPI"];
};
+const stripTemplateUids = (nodes: InputTextNode[]): InputTextNode[] =>
+ nodes.map((node) => {
+ const nodeWithoutUid = { ...node };
+ const { children } = nodeWithoutUid;
+ delete nodeWithoutUid.uid;
+ delete nodeWithoutUid.children;
+ return {
+ ...nodeWithoutUid,
+ ...(children?.length ? { children: stripTemplateUids(children) } : {}),
+ };
+ });
+
const createDiscourseNode = async ({
text,
configPageUid,
@@ -141,15 +158,14 @@ const createDiscourseNode = async ({
return pageUid;
}
- const nodeTree = getFullTreeByParentUid(configPageUid).children;
- const templateNode = getSubTree({
- tree: nodeTree,
- key: "template",
- });
+ const templateChildren =
+ getDiscourseNodeSetting(configPageUid, [
+ DISCOURSE_NODE_KEYS.template,
+ ]) ?? [];
const createBlocksFromTemplate = async () => {
await Promise.all(
- stripUid(templateNode.children).map(({ uid, ...node }, order) =>
+ stripTemplateUids(templateChildren).map((node, order) =>
createBlock({
node,
order,
@@ -162,12 +178,17 @@ const createDiscourseNode = async ({
await handleImageCreation(pageUid);
};
- const hasSmartBlockSyntax = (node: RoamBasicNode) => {
+ const hasSmartBlockSyntax = (node: InputTextNode) => {
if (node.text.includes("<%")) return true;
if (node.children) return node.children.some(hasSmartBlockSyntax);
return false;
};
- const useSmartBlocks = hasSmartBlockSyntax(templateNode);
+ const useSmartBlocks = templateChildren.some(hasSmartBlockSyntax);
+ const legacyTemplateNode = getSubTree({
+ tree: getFullTreeByParentUid(configPageUid).children,
+ key: "template",
+ });
+ const canUseLegacySmartBlock = !!legacyTemplateNode.uid;
if (useSmartBlocks && !window.roamjs?.extension?.smartblocks) {
renderToast({
@@ -177,9 +198,13 @@ const createDiscourseNode = async ({
intent: "warning",
});
await createBlocksFromTemplate();
- } else if (useSmartBlocks && window.roamjs?.extension?.smartblocks) {
- window.roamjs.extension.smartblocks?.triggerSmartblock({
- srcUid: templateNode.uid,
+ } else if (
+ useSmartBlocks &&
+ canUseLegacySmartBlock &&
+ window.roamjs?.extension?.smartblocks
+ ) {
+ void window.roamjs.extension.smartblocks?.triggerSmartblock({
+ srcUid: legacyTemplateNode.uid,
targetUid: pageUid,
});
await handleImageCreation(pageUid);
diff --git a/apps/roam/src/utils/getExportSettings.ts b/apps/roam/src/utils/getExportSettings.ts
index c0d4dc9f6..0c53fe238 100644
--- a/apps/roam/src/utils/getExportSettings.ts
+++ b/apps/roam/src/utils/getExportSettings.ts
@@ -3,6 +3,8 @@ import getPageUidByPageTitle from "roamjs-components/queries/getPageUidByPageTit
import { RoamBasicNode } from "roamjs-components/types";
import { getSubTree } from "roamjs-components/util";
import { DISCOURSE_CONFIG_PAGE_TITLE } from "~/data/constants";
+import { bulkReadSettings } from "~/components/settings/utils/accessors";
+import { EXPORT_KEYS } from "~/components/settings/utils/settingKeys";
type UidPair = {
uid?: string;
@@ -110,16 +112,17 @@ export const getExportSettingsAndUids = (): ExportConfigWithUids => {
};
export const getExportSettings = (): Omit => {
- const settings = getExportSettingsAndUids();
+ const exportValues = bulkReadSettings().globalSettings.Export;
+ const legacy = getExportSettingsAndUids();
return {
- maxFilenameLength: settings.maxFilenameLength.value,
- openSidebar: settings.openSidebar.value,
- removeSpecialCharacters: settings.removeSpecialCharacters.value,
- simplifiedFilename: settings.simplifiedFilename.value,
- optsEmbeds: settings.optsEmbeds.value,
- optsRefs: settings.optsRefs.value,
- linkType: settings.linkType.value,
- appendRefNodeContext: settings.appendRefNodeContext.value,
- frontmatter: settings.frontmatter.values,
+ maxFilenameLength: exportValues[EXPORT_KEYS.maxFilenameLength],
+ removeSpecialCharacters: exportValues[EXPORT_KEYS.removeSpecialCharacters],
+ optsEmbeds: exportValues[EXPORT_KEYS.resolveBlockEmbeds],
+ optsRefs: exportValues[EXPORT_KEYS.resolveBlockReferences],
+ linkType: exportValues[EXPORT_KEYS.linkType],
+ appendRefNodeContext: exportValues[EXPORT_KEYS.appendReferencedNode],
+ frontmatter: exportValues[EXPORT_KEYS.frontmatter],
+ openSidebar: legacy.openSidebar.value,
+ simplifiedFilename: legacy.simplifiedFilename.value,
};
};
diff --git a/apps/roam/src/utils/getLeftSidebarSettings.ts b/apps/roam/src/utils/getLeftSidebarSettings.ts
index 928f6a067..af4e7a53c 100644
--- a/apps/roam/src/utils/getLeftSidebarSettings.ts
+++ b/apps/roam/src/utils/getLeftSidebarSettings.ts
@@ -204,28 +204,41 @@ export const getAllLeftSidebarPersonalSectionConfigs = (
return result;
};
-// TODO(ENG-1471): Remove when migration complete — just use accessor values directly.
-// During dual-read, we need old-system UIDs for block CRUD (fold toggle, reorder, delete)
-// but read setting VALUES from accessors (which route through the feature flag and log
-// mismatches). These helpers merge accessor values onto old-system config objects.
export const mergeGlobalSectionWithAccessor = (
config: LeftSidebarGlobalSectionConfig,
globalValues: LeftSidebarGlobalSettings | undefined,
): LeftSidebarGlobalSectionConfig => {
- if (!config.settings) return config;
+ const legacyChildByPageUid = new Map(config.children.map((c) => [c.text, c]));
+ const children: RoamBasicNode[] = (globalValues?.Children ?? []).map(
+ (targetPageUid) => {
+ const legacyChild = legacyChildByPageUid.get(targetPageUid);
+ return {
+ uid: legacyChild?.uid ?? "",
+ text: targetPageUid,
+ children: legacyChild?.children ?? [],
+ };
+ },
+ );
+
return {
- ...config,
+ uid: config.uid,
+ childrenUid: config.childrenUid,
+ children,
settings: {
- uid: config.settings.uid,
+ uid: config.settings?.uid ?? "",
collapsable: {
- uid: config.settings.collapsable.uid,
+ uid: config.settings?.collapsable.uid ?? "",
value:
globalValues?.Settings.Collapsable ??
- config.settings.collapsable.value,
+ config.settings?.collapsable.value ??
+ false,
},
folded: {
- uid: config.settings.folded.uid,
- value: globalValues?.Settings.Folded ?? config.settings.folded.value,
+ uid: config.settings?.folded.uid ?? "",
+ value:
+ globalValues?.Settings.Folded ??
+ config.settings?.folded.value ??
+ false,
},
},
};
@@ -235,22 +248,42 @@ export const mergePersonalSectionsWithAccessor = (
sections: LeftSidebarPersonalSectionConfig[],
personalValues: PersonalSection[] | undefined,
): LeftSidebarPersonalSectionConfig[] => {
- return sections.map((section, i) => {
- const newSection = personalValues?.[i];
- if (!section.settings || !newSection) return section;
+ const legacyByName = new Map(sections.map((s) => [s.text, s]));
+ return (personalValues ?? []).map((snap) => {
+ const legacy = legacyByName.get(snap.name);
+ const legacyChildByPageUid = new Map(
+ (legacy?.children ?? []).map((c) => [c.text, c]),
+ );
return {
- ...section,
+ uid: legacy?.uid ?? "",
+ text: snap.name,
settings: {
- ...section.settings,
+ uid: legacy?.settings?.uid ?? "",
truncateResult: {
- ...section.settings.truncateResult,
- value: newSection.Settings["Truncate-result?"],
+ uid: legacy?.settings?.truncateResult.uid ?? "",
+ value: snap.Settings["Truncate-result?"],
},
folded: {
- ...section.settings.folded,
- value: newSection.Settings.Folded,
+ uid: legacy?.settings?.folded.uid ?? "",
+ value: snap.Settings.Folded,
},
},
+ children:
+ snap.Children.length > 0
+ ? snap.Children.map((snapChild) => {
+ const legacyChild = legacyChildByPageUid.get(snapChild.uid);
+ return {
+ uid: legacyChild?.uid ?? "",
+ text: snapChild.uid,
+ children: legacyChild?.children ?? [],
+ alias: {
+ uid: legacyChild?.alias.uid,
+ value: snapChild.Alias,
+ },
+ };
+ })
+ : [],
+ childrenUid: legacy?.childrenUid,
};
});
};