Skip to content
Merged
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]);
Comment thread
maparent marked this conversation as resolved.
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
98 changes: 71 additions & 27 deletions apps/roam/src/components/settings/utils/accessors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,16 @@ import { getSubTree } from "roamjs-components/util";
import getSettingValueFromTree from "roamjs-components/util/getSettingValueFromTree";
import internalError from "~/utils/internalError";
import { getSetting } from "~/utils/extensionSettings";
import { getStoredRelationsEnabled } from "~/utils/storedRelations";
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 +270,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 +536,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 +720,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 @@ -829,16 +831,19 @@ export const getAllRelations = (
? settings.globalSettings
: getGlobalSettings();

return Object.entries(globalSettings.Relations).flatMap(([id, relation]) =>
relation.ifConditions.map((ifCondition) => ({
const storedRelationsEnabled = getStoredRelationsEnabled();
return Object.entries(globalSettings.Relations).flatMap(([id, relation]) => {
const base = {
id,
label: relation.label,
source: relation.source,
destination: relation.destination,
complement: relation.complement,
triples: ifCondition.triples,
})),
);
};
if (relation.ifConditions.length === 0 && storedRelationsEnabled)
return [{ ...base, triples: [] }];
return relation.ifConditions.map((c) => ({ ...base, triples: c.triples }));
});
};

export const getPersonalSettings = (): PersonalSettings => {
Expand Down Expand Up @@ -954,7 +959,7 @@ const getRawDiscourseNodeBlockProps = (
}

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

Expand Down Expand Up @@ -1058,7 +1063,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 +1090,75 @@ 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?: RoamBasicNode[] | string; // string would be markdown
}): 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 }],
},
];
let templateTree: RoamBasicNode[] | undefined;
if (template !== undefined) {
templateTree = Array.isArray(template) ? template : [];
tree.push({
text: "Template",
children: templateTree,
});
}
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,
});

if (typeof template === "string") {
const tree = getBasicTreeByParentUid(pageUid);
const templateUid = getSubTree({ tree, key: "Template" }).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 +1270,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: [],
Comment thread
maparent marked this conversation as resolved.
});
return blockUid;
};
1 change: 1 addition & 0 deletions apps/roam/src/utils/discoverSharedRelations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export const discoverSharedRelations = async (
.map((r) => r.concepts_of_relation)
.flat();
const spaceIds = new Set(relatedNodeInfo.map(({ space_id }) => space_id!));
spaceIds.add(spaceId);
const spaceMap = await getSpaceMap(client, [...spaceIds]);
const toRid = (spaceId: number, localId: string) =>
spaceId in spaceMap
Expand Down
Loading