Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion apps/roam/src/components/DiscoverSharedNodesDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
isFailedSharedNodeImport,
type SharedNodeImportItem,
} from "~/utils/importSharedNodes";
import { importSharedRelations } from "~/utils/importSharedRelations";
import internalError from "~/utils/internalError";
import { getLoggedInClient, getSupabaseContext } from "~/utils/supabaseContext";

Expand Down Expand Up @@ -146,6 +147,7 @@ const DiscoverSharedNodesDialog = ({ onClose }: { onClose: () => void }) => {
const [error, setError] = useState("");
const [searchTerm, setSearchTerm] = useState("");
const [selectedRids, setSelectedRids] = useState<Set<string>>(new Set());
const [spaceId, setSpaceId] = useState<number>(0);
const [importProgress, setImportProgress] = useState<{
current: number;
total: number;
Expand All @@ -163,6 +165,7 @@ const DiscoverSharedNodesDialog = ({ onClose }: { onClose: () => void }) => {
try {
const context = await getSupabaseContext();
if (!context) throw new Error("Could not connect to shared persistence.");
setSpaceId(context.spaceId);
const client = await getLoggedInClient();
if (!client) throw new Error("Could not connect to shared persistence.");
const { sharedNodes, importedSourceRids } = await discoverSharedNodes({
Expand Down Expand Up @@ -242,7 +245,6 @@ const DiscoverSharedNodesDialog = ({ onClose }: { onClose: () => void }) => {
sharedNodes: selectedNodes,
onProgress: (current, total) => setImportProgress({ current, total }),
});
setImportResults(results);
const newlyImportedRids = results
.filter((item) => item.status !== "failed")
.map((item) => item.sharedNode.rid);
Expand All @@ -251,6 +253,8 @@ const DiscoverSharedNodesDialog = ({ onClose }: { onClose: () => void }) => {
newlyImportedRids.forEach((rid) => next.add(rid));
return next;
});
await importSharedRelations(client, spaceId, [...importedRids]);
setImportResults(results);
const failedImports = results.filter(isFailedSharedNodeImport);
setSelectedRids(
new Set(failedImports.map((item) => item.sharedNode.rid)),
Expand Down
18 changes: 1 addition & 17 deletions apps/roam/src/components/settings/DiscourseNodeConfigPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,28 +59,12 @@ const DiscourseNodeConfigPanel: React.FC<DiscourseNodeConfigPanelProps> = ({
}
};

const getUnusedShortcut = (): string => {
const candidateShortcut = label.slice(0, 1).toUpperCase();
const existingShortcuts = new Set(
getDiscourseNodes()
.map((n) => n.shortcut.toUpperCase())
.filter(Boolean),
);
return existingShortcuts.has(candidateShortcut) ? "" : candidateShortcut;
};

const createNodeType = async (): Promise<void> => {
setIsCreating(true);
try {
const shortcut = getUnusedShortcut();
const format = `[[${label.slice(0, 3).toUpperCase()}]] - {content}`;
posthog.capture("Discourse Node: Type Created", { label });

const node = await createDiscourseNodeType({
text: label,
shortcut,
format,
});
const node = await createDiscourseNodeType({ label });

setNodes((prevNodes) => [...prevNodes, node]);
refreshConfigTree();
Expand Down
83 changes: 61 additions & 22 deletions apps/roam/src/components/settings/utils/accessors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,15 @@ import { getSubTree } from "roamjs-components/util";
import getSettingValueFromTree from "roamjs-components/util/getSettingValueFromTree";
import internalError from "~/utils/internalError";
import { getSetting } from "~/utils/extensionSettings";
import { getRoamMarkdownApi } from "~/utils/materializeSharedNode";

import type { RoamBasicNode } from "roamjs-components/types";
import discourseConfigRef from "~/utils/discourseConfigRef";
import { roamNodeToCondition } from "~/utils/parseQuery";
import type { DiscourseRelation } from "~/utils/getDiscourseRelations";
import type { DiscourseNode } from "~/utils/getDiscourseNodes";
import getDiscourseNodes, {
type DiscourseNode,
} from "~/utils/getDiscourseNodes";
import type { Condition } from "~/utils/types";
import { z } from "zod";
import {
Expand Down Expand Up @@ -266,7 +269,6 @@ const getLegacyPersonalLeftSidebarSetting = (): unknown[] => {
"Result-limit": section.settings?.resultLimit?.value ?? 0,
},
}));
/* eslint-enable @typescript-eslint/naming-convention */
};

const getLegacyPersonalSetting = (keys: string[]): unknown => {
Expand Down Expand Up @@ -533,7 +535,7 @@ const getLegacyDiscourseNodeSetting = (
"key-image-option": rawCanvas["key-image-option"] || "first-image",
"query-builder-alias": rawCanvas["query-builder-alias"] || "",
};
/* eslint-enable @typescript-eslint/naming-convention */

const attributes = Object.fromEntries(
getSubTree({ tree, key: "Attributes" }).children.map((c) => [
c.text,
Expand Down Expand Up @@ -717,7 +719,6 @@ const FEATURE_FLAG_LEGACY_MAP: Record<
text: "(BETA) Left Sidebar",
}).value,
};
/* eslint-enable @typescript-eslint/naming-convention */

export const getFeatureFlag = (key: keyof FeatureFlags): boolean => {
return bulkReadSettings().featureFlags[key];
Expand Down Expand Up @@ -954,7 +955,7 @@ const getRawDiscourseNodeBlockProps = (
}

return isRecord(blockProps) && Object.keys(blockProps).length > 0
? (blockProps as Record<string, json>)
? blockProps
: undefined;
};

Expand Down Expand Up @@ -1058,7 +1059,7 @@ const addConditionUids = (conditions: SchemaCondition[]): Condition[] =>
target: c.target,
not: c.not,
};
}) as Condition[];
});

const toDiscourseNode = (settings: DiscourseNodeSettings): DiscourseNode => ({
text: settings.text,
Expand All @@ -1085,34 +1086,74 @@ const toDiscourseNode = (settings: DiscourseNodeSettings): DiscourseNode => ({
: undefined,
});

const getUnusedShortcut = (label: string): string => {
const candidateShortcut = label.slice(0, 1).toUpperCase();
const existingShortcuts = new Set(
getDiscourseNodes()
.map((n) => n.shortcut.toUpperCase())
.filter(Boolean),
);
return existingShortcuts.has(candidateShortcut) ? "" : candidateShortcut;
};

// getAllDiscourseNodes skips prop-less pages, so invalidate only after the props write settles.
export const createDiscourseNodeType = async ({
text,
label,
shortcut,
format,
template,
}: {
text: string;
shortcut: string;
format: string;
label: string;
shortcut?: string;
format?: string;
template?: string;
}): Promise<DiscourseNode> => {
if (shortcut === undefined) shortcut = getUnusedShortcut(label);
format = format ?? `[[${label.slice(0, 3).toUpperCase()}]] - {content}`;
const tree = [
{
text: "Shortcut",
children: [{ text: shortcut }],
},
{
text: "Tag",
children: [{ text: "" }],
},
{
text: "Format",
children: [{ text: format }],
},
];
if (template !== undefined) {
tree.push({
text: "Template",
children: [],
});
}
const pageUid = await createPage({
title: `${DISCOURSE_NODE_PAGE_PREFIX}${text}`,
tree: [
{ text: "Shortcut", children: [{ text: shortcut }] },
{ text: "Tag", children: [{ text: "" }] },
{ text: "Format", children: [{ text: format }] },
],
title: `discourse-graph/nodes/${label}`,
tree,
});

let templateTree: RoamBasicNode[] | undefined;
if (template !== undefined) {
const tree = getBasicTreeByParentUid(pageUid);
const templateUid = tree[3].uid;
await getRoamMarkdownApi().block.fromMarkdown({
location: { "parent-uid": templateUid, order: "last" },
"markdown-string": template,
});
templateTree = getBasicTreeByParentUid(templateUid);
}
const settings = DiscourseNodeSchema.parse({
text,
text: label,
type: pageUid,
shortcut,
format,
template: templateTree,
});
setBlockProps(pageUid, settings);
await setBlockPropsAsync(pageUid, settings);
invalidateDiscourseNodeTypeCaches();

return toDiscourseNode(settings);
};

Expand Down Expand Up @@ -1224,9 +1265,7 @@ export const getAllDiscourseNodes = (): DiscourseNode[] => {
);
} else {
// Try migrating legacy field shapes before dropping the node.
const migrated = migrateNodeBlockProps(
blockProps as Record<string, json>,
);
const migrated = migrateNodeBlockProps(blockProps);
const retryResult = DiscourseNodeSchema.safeParse(migrated);
if (retryResult.success) {
setBlockProps(pageUid, retryResult.data, false);
Expand Down
8 changes: 8 additions & 0 deletions apps/roam/src/utils/__tests__/nodeSharingFeatureFlag.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ vi.mock("~/utils/internalError", () => ({ default: vi.fn() }));
vi.mock("~/utils/extensionSettings", () => ({ getSetting: vi.fn() }));
vi.mock("~/utils/parseQuery", () => ({ roamNodeToCondition: vi.fn() }));

// Runs before the imports below: getDiscourseNodes calls generateUID at module load.
vi.hoisted(() => {
(globalThis as { window?: unknown }).window = {
roamAlphaAPI: { util: { generateUID: () => "someUid" } },
};
});

import {
isNodeSharingEnabled,
isSyncEnabled,
Expand All @@ -13,6 +20,7 @@ const seedWindow = (featureFlags: Record<string, boolean>) => {
(globalThis as { window: unknown }).window = {
roamAlphaAPI: {
user: { uid: () => "user-1" },
util: { generateUID: () => "someUid" },
pull: () => ({
":block/children": [
{
Expand Down
6 changes: 6 additions & 0 deletions apps/roam/src/utils/__tests__/queryParsing.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ vi.mock("roamjs-components/util/getSettingValueFromTree", () => ({
vi.mock("roamjs-components/writes/createBlock", () => ({
default: vi.fn(),
}));
// Runs before the imports below: getDiscourseNodes calls generateUID at module load.
vi.hoisted(() => {
(globalThis as { window?: unknown }).window = {
roamAlphaAPI: { util: { generateUID: () => "someUid" } },
};
});

import getSubTree from "roamjs-components/util/getSubTree";
import createBlock from "roamjs-components/writes/createBlock";
Expand Down
14 changes: 9 additions & 5 deletions apps/roam/src/utils/createReifiedBlock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,17 +136,21 @@ export const createReifiedRelation = async ({
sourceUid,
relationBlockUid,
destinationUid,
tentative,
}: {
sourceUid: string;
relationBlockUid: string;
destinationUid: string;
}): Promise<string | undefined> => {
tentative?: boolean;

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

adding the tentative flag

}): Promise<string> => {
const parameterUids: Record<string, string> = {
sourceUid,
destinationUid,
...(tentative !== undefined && { tentative: String(tentative) }),
};
return await createReifiedBlock({
destinationBlockUid: await getOrCreateRelationPageUid(),
schemaUid: relationBlockUid,
parameterUids: {
sourceUid,
destinationUid,
},
parameterUids,
});
};
53 changes: 53 additions & 0 deletions apps/roam/src/utils/createRelationSchema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import discourseConfigRef from "~/utils/discourseConfigRef";
import createBlock from "roamjs-components/writes/createBlock";
import { setGlobalSetting } from "~/components/settings/utils/accessors";
import { GLOBAL_KEYS } from "~/components/settings/utils/settingKeys";

export const createRelationSchema = async ({
label,
complement,
source,
destination,
}: {
label: string;
complement: string;
source: string;
destination: string;
}) => {
const grammarNode = discourseConfigRef.tree.find(
(node) => node.text === "grammar",
);
const relationsNode = grammarNode?.children.find(
(node) => node.text === "relations",
);
if (!relationsNode) throw new Error("Cannot find the relation grammar");
const blockUid = await createBlock({
parentUid: relationsNode.uid,
Comment thread
maparent marked this conversation as resolved.
order: "last",
node: {
text: label,
children: [
{
text: "source",
children: [{ text: source }],
},
{
text: "destination",
children: [{ text: destination }],
},
{
text: "complement",
children: [{ text: complement }],
},
],
},
});
setGlobalSetting([GLOBAL_KEYS.relations, blockUid], {
label,
source,
destination,
complement,
ifConditions: [],
});
return blockUid;
};
5 changes: 5 additions & 0 deletions apps/roam/src/utils/getDiscourseRelations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
type SettingsSnapshot,
} from "~/components/settings/utils/accessors";
import discourseConfigRef from "./discourseConfigRef";
import { getStoredRelationsEnabled } from "~/utils/storedRelations";

export type Triple = readonly [string, string, string];
export type DiscourseRelation = {
Expand Down Expand Up @@ -47,6 +48,7 @@ const getDiscourseRelations = (snapshot?: SettingsSnapshot) => {
const grammarNode = getGrammarNode();
const relationsNode = getRelationsNode(grammarNode);
const relationNodes = relationsNode?.children || DEFAULT_RELATION_VALUES;
const storedRelationsEnabled = getStoredRelationsEnabled();
const discourseRelations = relationNodes.flatMap(
(r: InputTextNode, i: number) => {
const tree = (r?.children || []) as TextNode[];
Expand All @@ -58,6 +60,9 @@ const getDiscourseRelations = (snapshot?: SettingsSnapshot) => {
complement: getSettingValueFromTree({ tree, key: "Complement" }),
};
const ifNode = tree.find(matchNodeText("if"))?.children || [];
if (ifNode.length === 0 && storedRelationsEnabled) {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No triples on a new-style relation created from scratch

return [{ ...data, triples: [] }];
}
return ifNode.map((node) => ({
...data,
triples: node.children
Expand Down
Loading