diff --git a/apps/roam/src/components/DiscourseNodeMenu.tsx b/apps/roam/src/components/DiscourseNodeMenu.tsx index 3b6f6e1b9..0c55f7d8c 100644 --- a/apps/roam/src/components/DiscourseNodeMenu.tsx +++ b/apps/roam/src/components/DiscourseNodeMenu.tsx @@ -23,7 +23,7 @@ import updateBlock from "roamjs-components/writes/updateBlock"; import { getCoordsFromTextarea } from "roamjs-components/components/CursorMenu"; import getDiscourseNodes from "~/utils/getDiscourseNodes"; import createDiscourseNode from "~/utils/createDiscourseNode"; -import { getNewDiscourseNodeText } from "~/utils/formatUtils"; +import { resolveNewDiscourseNodeText } from "~/utils/formatUtils"; import { OnloadArgs } from "roamjs-components/types"; import { formatHexColor } from "./settings/DiscourseNodeCanvasSettings"; import posthog from "posthog-js"; @@ -126,12 +126,13 @@ const NodeMenu = ({ if (document.activeElement === textarea) document.body.click(); const createNodeAndUpdateBlock = async () => { - const pageName = await getNewDiscourseNodeText({ - text: highlighted, - nodeType: nodeUid, - blockUid: targetBlockUid, - skipBlockUpdate: true, - }); + const { text: pageName, handledByDialog } = + await resolveNewDiscourseNodeText({ + text: highlighted, + nodeType: nodeUid, + blockUid: targetBlockUid, + skipBlockUpdate: true, + }); if (!pageName) return; const latestBlockText = getTextByBlockUid(targetBlockUid); @@ -141,11 +142,13 @@ const NodeMenu = ({ selectionStart, )}[[${pageName}]]${latestBlockText.substring(selectionEnd)}`; - await createDiscourseNode({ - text: pageName, - configPageUid: nodeUid, - extensionAPI, - }); + if (!handledByDialog) { + await createDiscourseNode({ + text: pageName, + configPageUid: nodeUid, + extensionAPI, + }); + } void updateBlock({ text: newText, uid: targetBlockUid }); posthog.capture("Discourse Node: Created via Node Menu", { nodeType: nodeUid, diff --git a/apps/roam/src/utils/__tests__/formatUtils.test.ts b/apps/roam/src/utils/__tests__/formatUtils.test.ts new file mode 100644 index 000000000..ef6a20965 --- /dev/null +++ b/apps/roam/src/utils/__tests__/formatUtils.test.ts @@ -0,0 +1,48 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { resolveNewDiscourseNodeText } from "~/utils/formatUtils"; +import type { ModifyNodeDialogProps } from "~/components/ModifyNodeDialog"; + +const { renderFormDialog } = vi.hoisted(() => ({ + renderFormDialog: vi.fn<(props: ModifyNodeDialogProps) => void>(), +})); + +vi.mock("roamjs-components/util/createOverlayRender", () => ({ + default: () => renderFormDialog, +})); +vi.mock("roamjs-components/util/extensionApiContext", () => ({ + default: vi.fn(() => undefined), +})); +vi.mock("~/components/ModifyNodeDialog", () => ({ + default: vi.fn(), +})); +vi.mock("~/utils/getDiscourseNodes", () => ({ + default: vi.fn(() => []), +})); + +describe("resolveNewDiscourseNodeText", () => { + beforeEach(() => { + renderFormDialog.mockReset(); + }); + + it("reports when an empty selection is handled by the creation dialog", async () => { + const resultPromise = resolveNewDiscourseNodeText({ + text: "", + nodeType: "issue", + blockUid: "source-block", + skipBlockUpdate: true, + }); + const dialogProps = renderFormDialog.mock.calls[0][0]; + + await dialogProps.onSuccess({ + text: "Test issue", + uid: "test-issue-uid", + action: "create", + }); + dialogProps.onClose(); + + await expect(resultPromise).resolves.toEqual({ + text: "Test issue", + handledByDialog: true, + }); + }); +}); diff --git a/apps/roam/src/utils/formatUtils.ts b/apps/roam/src/utils/formatUtils.ts index c807edfd6..5e184c869 100644 --- a/apps/roam/src/utils/formatUtils.ts +++ b/apps/roam/src/utils/formatUtils.ts @@ -20,23 +20,29 @@ const renderFormDialog = createOverlayRender( ModifyNodeDialog, ); -export const getNewDiscourseNodeText = async ({ - text, - nodeType, - blockUid, - skipBlockUpdate = false, -}: { +export type ResolvedDiscourseNodeText = { + text: string; + handledByDialog: boolean; +}; + +type GetNewDiscourseNodeTextArgs = { text: string; nodeType: string; blockUid?: string; skipBlockUpdate?: boolean; -}) => { +}; + +export const resolveNewDiscourseNodeText = async ({ + text, + nodeType, + blockUid, + skipBlockUpdate = false, +}: GetNewDiscourseNodeTextArgs): Promise => { const discourseNodes = getDiscourseNodes(); let newText = text; - let textFromDialog = false; + const handledByDialog = !text; - if (!text) { - textFromDialog = true; + if (handledByDialog) { newText = await new Promise((resolve) => { let resolvedText = ""; renderFormDialog({ @@ -57,10 +63,10 @@ export const getNewDiscourseNodeText = async ({ } if (!newText || !newText.trim()) { - return ""; + return { text: "", handledByDialog }; } - if (textFromDialog) { - return newText; + if (handledByDialog) { + return { text: newText, handledByDialog }; } const indexedByType = Object.fromEntries( @@ -90,7 +96,14 @@ export const getNewDiscourseNodeText = async ({ } return ""; }); - return formattedText; + return { text: formattedText, handledByDialog }; +}; + +export const getNewDiscourseNodeText = async ( + args: GetNewDiscourseNodeTextArgs, +): Promise => { + const { text } = await resolveNewDiscourseNodeText(args); + return text; }; export const getReferencedNodeInFormat = ({