Skip to content

Commit 7ea650f

Browse files
committed
ENG-1189: Prod: Init block prop based schema
1 parent 0dbcfa6 commit 7ea650f

File tree

2 files changed

+355
-0
lines changed

2 files changed

+355
-0
lines changed
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/* eslint-disable @typescript-eslint/naming-convention */
2+
3+
export const DG_BLOCK_PROP_SETTINGS_PAGE_TITLE =
4+
"roam/js/discourse-graph/block-prop-settings";
5+
6+
export const DISCOURSE_NODE_PAGE_PREFIX = "discourse-graph/nodes/";
7+
8+
export const TOP_LEVEL_BLOCK_PROP_KEYS = {
9+
featureFlags: "Feature Flags",
10+
global: "Global",
11+
} as const;
12+
13+
export const DISCOURSE_NODE_BLOCK_KEYS = {
14+
template: "Template",
15+
index: "Index",
16+
specification: "Specification",
17+
} as const;
18+
19+
/* eslint-enable @typescript-eslint/naming-convention */
Lines changed: 336 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,336 @@
1+
import getPageUidByPageTitle from "roamjs-components/queries/getPageUidByPageTitle";
2+
import getShallowTreeByParentUid from "roamjs-components/queries/getShallowTreeByParentUid";
3+
import { createPage, createBlock } from "roamjs-components/writes";
4+
import setBlockProps from "~/utils/setBlockProps";
5+
import getBlockProps, { type json } from "~/utils/getBlockProps";
6+
// eslint-disable-next-line @typescript-eslint/naming-convention
7+
import INITIAL_NODE_VALUES from "~/data/defaultDiscourseNodes";
8+
import {
9+
DiscourseNodeSchema,
10+
FeatureFlagsSchema,
11+
GlobalSettingsSchema,
12+
PersonalSettingsSchema,
13+
} from "./zodSchema";
14+
import type { ZodSchema } from "zod";
15+
import {
16+
DG_BLOCK_PROP_SETTINGS_PAGE_TITLE,
17+
DISCOURSE_NODE_PAGE_PREFIX,
18+
TOP_LEVEL_BLOCK_PROP_KEYS,
19+
DISCOURSE_NODE_BLOCK_KEYS,
20+
} from "../data/blockPropsSettingsConfig";
21+
22+
let cachedPersonalSettingsKey: string | null = null;
23+
24+
const getPersonalSettingsKey = (): string => {
25+
if (cachedPersonalSettingsKey !== null) {
26+
return cachedPersonalSettingsKey;
27+
}
28+
cachedPersonalSettingsKey = window.roamAlphaAPI.user.uid() || "";
29+
return cachedPersonalSettingsKey;
30+
};
31+
32+
const getDiscourseNodePageTitle = (nodeLabel: string): string => {
33+
return `${DISCOURSE_NODE_PAGE_PREFIX}${nodeLabel}`;
34+
};
35+
36+
const ensurePageExists = async (pageTitle: string): Promise<string> => {
37+
let pageUid = getPageUidByPageTitle(pageTitle);
38+
39+
if (!pageUid) {
40+
pageUid = window.roamAlphaAPI.util.generateUID();
41+
await createPage({
42+
title: pageTitle,
43+
uid: pageUid,
44+
});
45+
}
46+
47+
return pageUid;
48+
};
49+
50+
const ensureBlocksExist = async (
51+
pageUid: string,
52+
blockTexts: string[],
53+
existingBlockMap: Record<string, string>,
54+
): Promise<Record<string, string>> => {
55+
const missingBlocks = blockTexts.filter(
56+
(blockText) => !existingBlockMap[blockText],
57+
);
58+
59+
if (missingBlocks.length > 0) {
60+
const createdBlocks = await Promise.all(
61+
missingBlocks.map(async (blockText) => {
62+
const uid = await createBlock({
63+
parentUid: pageUid,
64+
node: { text: blockText },
65+
});
66+
return { text: blockText, uid };
67+
}),
68+
);
69+
70+
createdBlocks.forEach((block) => {
71+
existingBlockMap[block.text] = block.uid;
72+
});
73+
}
74+
75+
return existingBlockMap;
76+
};
77+
78+
const ensurePersonalBlockExists = async (
79+
pageUid: string,
80+
existingBlockMap: Record<string, string>,
81+
): Promise<{ key: string; uid: string }> => {
82+
const personalKey = getPersonalSettingsKey();
83+
84+
if (existingBlockMap[personalKey]) {
85+
return { key: personalKey, uid: existingBlockMap[personalKey] };
86+
}
87+
88+
const uid = await createBlock({
89+
parentUid: pageUid,
90+
node: { text: personalKey },
91+
});
92+
93+
return { key: personalKey, uid };
94+
};
95+
96+
const buildBlockMap = (pageUid: string): Record<string, string> => {
97+
const existingChildren = getShallowTreeByParentUid(pageUid);
98+
const blockMap: Record<string, string> = {};
99+
existingChildren.forEach((child) => {
100+
blockMap[child.text] = child.uid;
101+
});
102+
return blockMap;
103+
};
104+
105+
const initBlockPropsIfEmpty = (uid: string, schema: ZodSchema): void => {
106+
const existingProps = getBlockProps(uid);
107+
if (!existingProps || Object.keys(existingProps).length === 0) {
108+
const defaults = schema.parse({}) as Record<string, json>;
109+
setBlockProps(uid, defaults, false);
110+
}
111+
};
112+
113+
const initializeSettingsBlockProps = (
114+
blockMap: Record<string, string>,
115+
): void => {
116+
const configs = [
117+
{ key: TOP_LEVEL_BLOCK_PROP_KEYS.featureFlags, schema: FeatureFlagsSchema },
118+
{ key: TOP_LEVEL_BLOCK_PROP_KEYS.global, schema: GlobalSettingsSchema },
119+
{ key: getPersonalSettingsKey(), schema: PersonalSettingsSchema },
120+
];
121+
122+
for (const { key, schema } of configs) {
123+
const uid = blockMap[key];
124+
if (uid) {
125+
initBlockPropsIfEmpty(uid, schema);
126+
}
127+
}
128+
};
129+
130+
const initSettingsPageBlocks = async (): Promise<Record<string, string>> => {
131+
const pageUid = await ensurePageExists(DG_BLOCK_PROP_SETTINGS_PAGE_TITLE);
132+
const blockMap = buildBlockMap(pageUid);
133+
134+
const topLevelBlocks = Object.values(TOP_LEVEL_BLOCK_PROP_KEYS);
135+
await ensureBlocksExist(pageUid, topLevelBlocks, blockMap);
136+
137+
const personalBlock = await ensurePersonalBlockExists(pageUid, blockMap);
138+
blockMap[personalBlock.key] = personalBlock.uid;
139+
140+
initializeSettingsBlockProps(blockMap);
141+
142+
return blockMap;
143+
};
144+
145+
const ensureDiscourseNodePageExists = async (
146+
nodeLabel: string,
147+
): Promise<string> => {
148+
const pageTitle = getDiscourseNodePageTitle(nodeLabel);
149+
return ensurePageExists(pageTitle);
150+
};
151+
152+
const initSingleDiscourseNode = async (
153+
node: (typeof INITIAL_NODE_VALUES)[number],
154+
): Promise<{ label: string; pageUid: string } | null> => {
155+
if (!node.text) return null;
156+
157+
const pageUid = await ensureDiscourseNodePageExists(node.text);
158+
const existingProps = getBlockProps(pageUid);
159+
const blockMap = buildBlockMap(pageUid);
160+
161+
for (const key of Object.values(DISCOURSE_NODE_BLOCK_KEYS)) {
162+
if (!blockMap[key]) {
163+
blockMap[key] = await createBlock({
164+
parentUid: pageUid,
165+
node: { text: key },
166+
});
167+
}
168+
}
169+
170+
const templateUid = blockMap[DISCOURSE_NODE_BLOCK_KEYS.template];
171+
const indexUid = blockMap[DISCOURSE_NODE_BLOCK_KEYS.index];
172+
const specificationUid = blockMap[DISCOURSE_NODE_BLOCK_KEYS.specification];
173+
174+
if (!existingProps || Object.keys(existingProps).length === 0) {
175+
const nodeData = DiscourseNodeSchema.parse({
176+
text: node.text,
177+
type: node.type,
178+
format: node.format || "",
179+
shortcut: node.shortcut || "",
180+
tag: node.tag || "",
181+
graphOverview: node.graphOverview ?? false,
182+
canvasSettings: node.canvasSettings || {},
183+
templateUid,
184+
indexUid,
185+
specificationUid,
186+
backedBy: "user",
187+
});
188+
189+
setBlockProps(pageUid, nodeData as Record<string, json>, false);
190+
} else if (
191+
!existingProps.templateUid ||
192+
!existingProps.indexUid ||
193+
!existingProps.specificationUid
194+
) {
195+
setBlockProps(pageUid, { templateUid, indexUid, specificationUid }, true);
196+
}
197+
198+
return { label: node.text, pageUid };
199+
};
200+
201+
const initDiscourseNodePages = async (): Promise<Record<string, string>> => {
202+
const results = await Promise.all(
203+
INITIAL_NODE_VALUES.map((node) => initSingleDiscourseNode(node)),
204+
);
205+
206+
const nodePageUids: Record<string, string> = {};
207+
for (const result of results) {
208+
if (result) {
209+
nodePageUids[result.label] = result.pageUid;
210+
}
211+
}
212+
213+
return nodePageUids;
214+
};
215+
216+
const printAllSettings = (blockMap: Record<string, string>): void => {
217+
const featureFlagsUid = blockMap[TOP_LEVEL_BLOCK_PROP_KEYS.featureFlags];
218+
const globalUid = blockMap[TOP_LEVEL_BLOCK_PROP_KEYS.global];
219+
const personalKey = getPersonalSettingsKey();
220+
const personalUid = blockMap[personalKey];
221+
222+
const featureFlags = featureFlagsUid ? getBlockProps(featureFlagsUid) : null;
223+
const globalSettings = globalUid ? getBlockProps(globalUid) : null;
224+
const personalSettings = personalUid ? getBlockProps(personalUid) : null;
225+
226+
console.group("🔧 Discourse Graph Settings Initialized");
227+
228+
if (featureFlags) {
229+
console.group("🚩 Feature Flags");
230+
console.table(featureFlags);
231+
console.groupEnd();
232+
}
233+
234+
if (globalSettings) {
235+
console.group("🌍 Global Settings");
236+
console.log("Trigger:", globalSettings?.Trigger || "(empty)");
237+
console.log(
238+
"Canvas Page Format:",
239+
globalSettings?.["Canvas Page Format"] || "(empty)",
240+
);
241+
242+
if (globalSettings?.["Left Sidebar"]) {
243+
console.group("📂 Left Sidebar");
244+
console.log(globalSettings["Left Sidebar"]);
245+
console.groupEnd();
246+
}
247+
248+
if (globalSettings?.Export) {
249+
console.group("📤 Export Settings");
250+
console.table(globalSettings.Export);
251+
console.groupEnd();
252+
}
253+
254+
if (globalSettings?.["Suggestive Mode"]) {
255+
console.group("💡 Suggestive Mode");
256+
console.log(globalSettings["Suggestive Mode"]);
257+
console.groupEnd();
258+
}
259+
260+
console.groupEnd();
261+
}
262+
263+
if (personalSettings) {
264+
console.group("👤 Personal Settings");
265+
console.log(
266+
"Personal Node Menu Trigger:",
267+
personalSettings?.["Personal Node Menu Trigger"] || "(empty)",
268+
);
269+
console.log(
270+
"Node Search Menu Trigger:",
271+
personalSettings?.["Node Search Menu Trigger"] || "(empty)",
272+
);
273+
console.log(
274+
"Discourse Tool Shortcut:",
275+
personalSettings?.["Discourse Tool Shortcut"] || "(empty)",
276+
);
277+
278+
console.group("🎛️ Toggles");
279+
const toggles = {
280+
// eslint-disable-next-line @typescript-eslint/naming-convention
281+
"Discourse Context Overlay":
282+
personalSettings?.["Discourse Context Overlay"],
283+
// eslint-disable-next-line @typescript-eslint/naming-convention
284+
"Suggestive Mode Overlay": personalSettings?.["Suggestive Mode Overlay"],
285+
// eslint-disable-next-line @typescript-eslint/naming-convention
286+
"Overlay in Canvas": personalSettings?.["Overlay in Canvas"],
287+
// eslint-disable-next-line @typescript-eslint/naming-convention
288+
"Text Selection Popup": personalSettings?.["Text Selection Popup"],
289+
// eslint-disable-next-line @typescript-eslint/naming-convention
290+
"Disable Sidebar Open": personalSettings?.["Disable Sidebar Open"],
291+
// eslint-disable-next-line @typescript-eslint/naming-convention
292+
"Page Preview": personalSettings?.["Page Preview"],
293+
// eslint-disable-next-line @typescript-eslint/naming-convention
294+
"Hide Feedback Button": personalSettings?.["Hide Feedback Button"],
295+
// eslint-disable-next-line @typescript-eslint/naming-convention
296+
"Streamline Styling": personalSettings?.["Streamline Styling"],
297+
// eslint-disable-next-line @typescript-eslint/naming-convention
298+
"Auto Canvas Relations": personalSettings?.["Auto Canvas Relations"],
299+
// eslint-disable-next-line @typescript-eslint/naming-convention
300+
"Disable Product Diagnostics":
301+
personalSettings?.["Disable Product Diagnostics"],
302+
};
303+
console.table(toggles);
304+
console.groupEnd();
305+
306+
if (personalSettings?.["Left Sidebar"]) {
307+
console.group("📂 Personal Left Sidebar");
308+
console.log(personalSettings["Left Sidebar"]);
309+
console.groupEnd();
310+
}
311+
312+
if (personalSettings?.Query) {
313+
console.group("🔍 Query Settings");
314+
console.table(personalSettings.Query);
315+
console.groupEnd();
316+
}
317+
318+
console.groupEnd();
319+
}
320+
321+
console.groupEnd();
322+
};
323+
324+
export type InitSchemaResult = {
325+
blockUids: Record<string, string>;
326+
nodePageUids: Record<string, string>;
327+
};
328+
329+
export const initSchema = async (): Promise<InitSchemaResult> => {
330+
const blockUids = await initSettingsPageBlocks();
331+
const nodePageUids = await initDiscourseNodePages();
332+
333+
printAllSettings(blockUids);
334+
335+
return { blockUids, nodePageUids };
336+
};

0 commit comments

Comments
 (0)