diff --git a/apps/roam/src/components/settings/AdminPanel.tsx b/apps/roam/src/components/settings/AdminPanel.tsx
index 6606cf20e..85ba13269 100644
--- a/apps/roam/src/components/settings/AdminPanel.tsx
+++ b/apps/roam/src/components/settings/AdminPanel.tsx
@@ -38,6 +38,7 @@ import createBlock from "roamjs-components/writes/createBlock";
import deleteBlock from "roamjs-components/writes/deleteBlock";
import { USE_REIFIED_RELATIONS } from "~/data/userSettings";
import posthog from "posthog-js";
+import { setFeatureFlag } from "~/components/settings/utils/accessors";
const NodeRow = ({ node }: { node: PConceptFull }) => {
return (
@@ -377,6 +378,7 @@ const FeatureFlagsTab = (): React.ReactElement => {
setSuggestiveModeUid(undefined);
}
setSuggestiveModeEnabled(false);
+ setFeatureFlag("Suggestive mode enabled", false);
}
}}
labelElement={
@@ -399,6 +401,7 @@ const FeatureFlagsTab = (): React.ReactElement => {
}).then((uid) => {
setSuggestiveModeUid(uid);
setSuggestiveModeEnabled(true);
+ setFeatureFlag("Suggestive mode enabled", true);
setIsAlertOpen(false);
setIsInstructionOpen(true);
});
@@ -447,6 +450,7 @@ const FeatureFlagsTab = (): React.ReactElement => {
void setSetting(USE_REIFIED_RELATIONS, target.checked).catch(
() => undefined,
);
+ setFeatureFlag("Reified relation triples", target.checked);
posthog.capture("Reified Relations: Toggled", {
enabled: target.checked,
});
diff --git a/apps/roam/src/components/settings/DefaultFilters.tsx b/apps/roam/src/components/settings/DefaultFilters.tsx
index f9daeb85f..693e98f74 100644
--- a/apps/roam/src/components/settings/DefaultFilters.tsx
+++ b/apps/roam/src/components/settings/DefaultFilters.tsx
@@ -3,6 +3,7 @@ import React, { useEffect, useState } from "react";
import type { OnloadArgs } from "roamjs-components/types";
import type { Filters } from "roamjs-components/components/Filter";
import posthog from "posthog-js";
+import { setPersonalSetting } from "~/components/settings/utils/accessors";
//
// TODO - REWORK THIS COMPONENT
@@ -124,28 +125,27 @@ const DefaultFilters = ({
);
useEffect(() => {
- extensionAPI.settings.set(
- "default-filters",
- Object.fromEntries(
- Object.entries(filters).map(([k, v]) => [
- k,
- {
- includes: Object.fromEntries(
- Object.entries(v.includes || {}).map(([k, v]) => [
- k,
- Array.from(v),
- ]),
- ),
- excludes: Object.fromEntries(
- Object.entries(v.excludes || {}).map(([k, v]) => [
- k,
- Array.from(v),
- ]),
- ),
- },
- ]),
- ),
+ const serialized = Object.fromEntries(
+ Object.entries(filters).map(([k, v]) => [
+ k,
+ {
+ includes: Object.fromEntries(
+ Object.entries(v.includes || {}).map(([k, v]) => [
+ k,
+ Array.from(v),
+ ]),
+ ),
+ excludes: Object.fromEntries(
+ Object.entries(v.excludes || {}).map(([k, v]) => [
+ k,
+ Array.from(v),
+ ]),
+ ),
+ },
+ ]),
);
+ void extensionAPI.settings.set("default-filters", serialized);
+ setPersonalSetting(["Query", "Default filters"], serialized);
}, [filters]);
return (
void; onDelete: () => void }) => {
+ onSync,
+}: Attribute & {
+ onChange: (v: string) => void;
+ onDelete: () => void;
+ onSync?: () => void;
+}) => {
const timeoutRef = useRef(0);
return (
@@ -53,7 +60,16 @@ const NodeAttribute = ({
);
};
-const NodeAttributes = ({ uid }: { uid: string }) => {
+const toRecord = (attrs: Attribute[]): Record
=>
+ Object.fromEntries(attrs.map((a) => [a.label, a.value]));
+
+const NodeAttributes = ({
+ uid,
+ nodeType,
+}: {
+ uid: string;
+ nodeType: string;
+}) => {
const [attributes, setAttributes] = useState(() =>
getBasicTreeByParentUid(uid).map((t) => ({
uid: t.uid,
@@ -61,6 +77,15 @@ const NodeAttributes = ({ uid }: { uid: string }) => {
value: t.children[0]?.text,
})),
);
+ const attributesRef = useRef(attributes);
+ attributesRef.current = attributes;
+ const syncToBlockProps = () => {
+ setDiscourseNodeSetting(
+ nodeType,
+ ["attributes"],
+ toRecord(attributesRef.current),
+ );
+ };
const [newAttribute, setNewAttribute] = useState("");
return (
@@ -77,10 +102,17 @@ const NodeAttributes = ({ uid }: { uid: string }) => {
)
}
onDelete={() =>
- deleteBlock(a.uid).then(() =>
- setAttributes(attributes.filter((aa) => a.uid !== aa.uid)),
- )
+ deleteBlock(a.uid).then(() => {
+ const updated = attributes.filter((aa) => a.uid !== aa.uid);
+ setAttributes(updated);
+ setDiscourseNodeSetting(
+ nodeType,
+ ["attributes"],
+ toRecord(updated),
+ );
+ })
}
+ onSync={syncToBlockProps}
/>
))}
@@ -105,11 +137,17 @@ const NodeAttributes = ({ uid }: { uid: string }) => {
parentUid: uid,
order: attributes.length,
}).then((uid) => {
- setAttributes([
+ const updated = [
...attributes,
{ uid, label: newAttribute, value: DEFAULT },
- ]);
+ ];
+ setAttributes(updated);
setNewAttribute("");
+ setDiscourseNodeSetting(
+ nodeType,
+ ["attributes"],
+ toRecord(updated),
+ );
});
}}
/>
diff --git a/apps/roam/src/components/settings/DiscourseNodeCanvasSettings.tsx b/apps/roam/src/components/settings/DiscourseNodeCanvasSettings.tsx
index ebc47d1ad..287f4194b 100644
--- a/apps/roam/src/components/settings/DiscourseNodeCanvasSettings.tsx
+++ b/apps/roam/src/components/settings/DiscourseNodeCanvasSettings.tsx
@@ -11,7 +11,11 @@ import React, { useState, useMemo } from "react";
import getBasicTreeByParentUid from "roamjs-components/queries/getBasicTreeByParentUid";
import getSettingValueFromTree from "roamjs-components/util/getSettingValueFromTree";
import setInputSetting from "roamjs-components/util/setInputSetting";
-import { DiscourseNodeFlagPanel } from "./components/BlockPropSettingPanels";
+import {
+ DiscourseNodeFlagPanel,
+ DiscourseNodeTextPanel,
+} from "./components/BlockPropSettingPanels";
+import { setDiscourseNodeSetting } from "~/components/settings/utils/accessors";
export const formatHexColor = (color: string) => {
if (!color) return "";
@@ -37,9 +41,7 @@ const DiscourseNodeCanvasSettings = ({
const color = getSettingValueFromTree({ tree, key: "color" });
return formatHexColor(color);
});
- const [alias, setAlias] = useState(() =>
- getSettingValueFromTree({ tree, key: "alias" }),
- );
+ const alias = getSettingValueFromTree({ tree, key: "alias" });
const [queryBuilderAlias, setQueryBuilderAlias] = useState(() =>
getSettingValueFromTree({ tree, key: "query-builder-alias" }),
);
@@ -60,12 +62,18 @@ const DiscourseNodeCanvasSettings = ({
type={"color"}
value={color}
onChange={(e) => {
+ const colorValue = e.target.value.replace("#", ""); // remove hash to not create roam link
setColor(e.target.value);
void setInputSetting({
blockUid: uid,
key: "color",
- value: e.target.value.replace("#", ""), // remove hash to not create roam link
+ value: colorValue,
});
+ setDiscourseNodeSetting(
+ nodeType,
+ ["canvasSettings", "color"],
+ colorValue,
+ );
}}
/>
@@ -79,25 +87,30 @@ const DiscourseNodeCanvasSettings = ({
key: "color",
value: "",
});
+ setDiscourseNodeSetting(
+ nodeType,
+ ["canvasSettings", "color"],
+ "",
+ );
}}
/>
-
+ {
+ void setInputSetting({
+ blockUid: uid,
+ key: "alias",
+ value: val,
+ });
+ }}
+ />
@@ -145,12 +163,18 @@ const DiscourseNodeCanvasSettings = ({
disabled={keyImageOption !== "query-builder" || !isKeyImage}
value={queryBuilderAlias}
onChange={(e) => {
- setQueryBuilderAlias(e.target.value);
+ const val = e.target.value;
+ setQueryBuilderAlias(val);
void setInputSetting({
blockUid: uid,
key: "query-builder-alias",
- value: e.target.value,
+ value: val,
});
+ setDiscourseNodeSetting(
+ nodeType,
+ ["canvasSettings", "query-builder-alias"],
+ val,
+ );
}}
/>
diff --git a/apps/roam/src/components/settings/DiscourseNodeConfigPanel.tsx b/apps/roam/src/components/settings/DiscourseNodeConfigPanel.tsx
index ea5db06cc..8d388189e 100644
--- a/apps/roam/src/components/settings/DiscourseNodeConfigPanel.tsx
+++ b/apps/roam/src/components/settings/DiscourseNodeConfigPanel.tsx
@@ -13,9 +13,14 @@ import refreshConfigTree from "~/utils/refreshConfigTree";
import createPage from "roamjs-components/writes/createPage";
import type { CustomField } from "roamjs-components/components/ConfigPanels/types";
import posthog from "posthog-js";
-import getDiscourseRelations from "~/utils/getDiscourseRelations";
+import getDiscourseRelations, {
+ type DiscourseRelation,
+} from "~/utils/getDiscourseRelations";
import { deleteBlock } from "roamjs-components/writes";
import { formatHexColor } from "./DiscourseNodeCanvasSettings";
+import setBlockProps from "~/utils/setBlockProps";
+import { DiscourseNodeSchema } from "./utils/zodSchema";
+import { getGlobalSettings, setGlobalSetting } from "./utils/accessors";
type DiscourseNodeConfigPanelProps = React.ComponentProps<
CustomField["options"]["component"]
@@ -38,7 +43,9 @@ const DiscourseNodeConfigPanel: React.FC = ({
const [isAlertOpen, setIsAlertOpen] = useState(false);
const [alertMessage, setAlertMessage] = useState("");
- const [affectedRelations, setAffectedRelations] = useState([]);
+ const [affectedRelations, setAffectedRelations] = useState<
+ DiscourseRelation[]
+ >([]);
const [nodeTypeIdToDelete, setNodeTypeIdToDelete] = useState("");
const navigateToNode = (uid: string) => {
if (isPopup) {
@@ -72,13 +79,15 @@ const DiscourseNodeConfigPanel: React.FC = ({
className="select-none"
disabled={!label}
onClick={() => {
+ const shortcut = label.slice(0, 1).toUpperCase();
+ const format = `[[${label.slice(0, 3).toUpperCase()}]] - {content}`;
posthog.capture("Discourse Node: Type Created", { label: label });
- createPage({
+ void createPage({
title: `discourse-graph/nodes/${label}`,
tree: [
{
text: "Shortcut",
- children: [{ text: label.slice(0, 1).toUpperCase() }],
+ children: [{ text: shortcut }],
},
{
text: "Tag",
@@ -86,14 +95,20 @@ const DiscourseNodeConfigPanel: React.FC = ({
},
{
text: "Format",
- children: [
- {
- text: `[[${label.slice(0, 3).toUpperCase()}]] - {content}`,
- },
- ],
+ children: [{ text: format }],
},
],
}).then((valueUid) => {
+ setBlockProps(
+ valueUid,
+ DiscourseNodeSchema.parse({
+ text: label,
+ type: valueUid,
+ shortcut,
+ format,
+ backedBy: "user",
+ }),
+ );
setNodes([
...nodes,
{
@@ -222,6 +237,9 @@ const DiscourseNodeConfigPanel: React.FC = ({
throw error;
});
}
+ const relations = { ...getGlobalSettings().Relations };
+ for (const rel of affectedRelations) delete relations[rel.id];
+ setGlobalSetting(["Relations"], relations);
deleteNodeType(nodeTypeIdToDelete);
} catch (error) {
console.error(
diff --git a/apps/roam/src/components/settings/NodeConfig.tsx b/apps/roam/src/components/settings/NodeConfig.tsx
index 0c0e5ff7d..20447a426 100644
--- a/apps/roam/src/components/settings/NodeConfig.tsx
+++ b/apps/roam/src/components/settings/NodeConfig.tsx
@@ -1,30 +1,22 @@
-import React, {
- useState,
- useCallback,
- useRef,
- useEffect,
- useMemo,
-} from "react";
+import React, { useState, useCallback, useEffect, useMemo } from "react";
import { DiscourseNode } from "~/utils/getDiscourseNodes";
-import SelectPanel from "roamjs-components/components/ConfigPanels/SelectPanel";
import DualWriteBlocksPanel from "./components/EphemeralBlocksPanel";
import { getSubTree } from "roamjs-components/util";
import Description from "roamjs-components/components/Description";
-import { Label, Tabs, Tab, TabId, InputGroup } from "@blueprintjs/core";
+import { Label, Tabs, Tab, TabId } from "@blueprintjs/core";
import DiscourseNodeSpecification from "./DiscourseNodeSpecification";
import DiscourseNodeAttributes from "./DiscourseNodeAttributes";
import DiscourseNodeCanvasSettings from "./DiscourseNodeCanvasSettings";
import DiscourseNodeIndex from "./DiscourseNodeIndex";
import { OnloadArgs } from "roamjs-components/types";
import getBasicTreeByParentUid from "roamjs-components/queries/getBasicTreeByParentUid";
-import createBlock from "roamjs-components/writes/createBlock";
-import updateBlock from "roamjs-components/writes/updateBlock";
import DiscourseNodeSuggestiveRules from "./DiscourseNodeSuggestiveRules";
import { getFormattedConfigTree } from "~/utils/discourseConfigRef";
import refreshConfigTree from "~/utils/refreshConfigTree";
import {
DiscourseNodeTextPanel,
DiscourseNodeFlagPanel,
+ DiscourseNodeSelectPanel,
} from "./components/BlockPropSettingPanels";
const TEMPLATE_SETTING_KEYS = ["template"];
@@ -33,91 +25,6 @@ export const getCleanTagText = (tag: string): string => {
return tag.replace(/^#+/, "").trim().toUpperCase();
};
-const ValidatedInputPanel = ({
- label,
- description,
- value,
- onChange,
- onBlur,
- error,
- placeholder,
-}: {
- label: string;
- description: string;
- value: string;
- onChange: (e: React.ChangeEvent) => void;
- onBlur: () => void;
- error: string;
- placeholder?: string;
-}) => (
-
-
- {error && (
-
{error}
- )}
-
-);
-
-const useDebouncedRoamUpdater = <
- T extends HTMLInputElement | HTMLTextAreaElement,
->(
- uid: string,
- initialValue: string,
- isValid: boolean,
-) => {
- const [value, setValue] = useState(initialValue);
- const debounceRef = useRef(0);
- const isValidRef = useRef(isValid);
- isValidRef.current = isValid;
-
- const saveToRoam = useCallback(
- (text: string, timeout: boolean) => {
- window.clearTimeout(debounceRef.current);
- debounceRef.current = window.setTimeout(
- () => {
- if (!isValidRef.current) {
- return;
- }
- const existingBlock = getBasicTreeByParentUid(uid)[0];
- if (existingBlock) {
- if (existingBlock.text !== text) {
- void updateBlock({ uid: existingBlock.uid, text });
- }
- } else if (text) {
- void createBlock({ parentUid: uid, node: { text } });
- }
- },
- timeout ? 500 : 0,
- );
- },
- [uid],
- );
-
- const handleChange = useCallback(
- (e: React.ChangeEvent) => {
- const newValue = e.target.value;
- setValue(newValue);
- saveToRoam(newValue, true);
- },
- [saveToRoam],
- );
-
- const handleBlur = useCallback(() => {
- saveToRoam(value, false);
- }, [value, saveToRoam]);
-
- return { value, handleChange, handleBlur };
-};
-
const generateTagPlaceholder = (node: DiscourseNode): string => {
// Extract first reference from format like [[CLM]], [[QUE]], [[EVD]]
const referenceMatch = node.format.match(/\[\[([A-Z]+)\]\]/);
@@ -166,18 +73,9 @@ const NodeConfig = ({
const [selectedTabId, setSelectedTabId] = useState("general");
const [tagError, setTagError] = useState("");
const [formatError, setFormatError] = useState("");
- const isConfigurationValid = !tagError && !formatError;
const [tagValue, setTagValue] = useState(node.tag || "");
- const {
- value: formatValue,
- handleChange: handleFormatChange,
- handleBlur: handleFormatBlurFromHook,
- } = useDebouncedRoamUpdater(
- formatUid,
- node.format,
- isConfigurationValid,
- );
+ const [formatValue, setFormatValue] = useState(node.format || "");
const validate = useCallback(
({
tag,
@@ -237,11 +135,6 @@ const NodeConfig = ({
validate({ tag: tagValue, format: formatValue });
}, [tagValue, formatValue, validate]);
- const handleFormatBlur = useCallback(() => {
- handleFormatBlurFromHook();
- validate({ tag: tagValue, format: formatValue });
- }, [handleFormatBlurFromHook, tagValue, formatValue, validate]);
-
return (
<>
-