diff --git a/apps/roam/src/components/CreateRelationDialog.tsx b/apps/roam/src/components/CreateRelationDialog.tsx index 811995c6c..f6762cdba 100644 --- a/apps/roam/src/components/CreateRelationDialog.tsx +++ b/apps/roam/src/components/CreateRelationDialog.tsx @@ -24,9 +24,13 @@ import type { Result } from "~/utils/types"; import internalError from "~/utils/internalError"; import getDiscourseNodes from "~/utils/getDiscourseNodes"; import posthog from "posthog-js"; +import { refreshDiscourseContextsForMutatedUids } from "~/utils/discourseContextMutationRefresh"; export type CreateRelationDialogProps = { - onClose: () => void; + // `renderOverlay` replaces `onClose` with its own zero-argument wrapper, so a + // create result can only reach callers through a separate prop. + onClose?: () => void; + onCreated?: () => void; sourceNodeUid: string; }; @@ -42,6 +46,7 @@ type ExtendedCreateRelationDialogProps = CreateRelationDialogProps & { const CreateRelationDialog = ({ onClose, + onCreated, sourceNodeUid, relData, sourceNodeTitle, @@ -159,12 +164,18 @@ const CreateRelationDialog = ({ if (selectedTargetUid === "") return false; const relation = identifyRelationMatch(selectedTargetText); if (relation === null) return false; + const sourceUid = relation.forward ? sourceNodeUid : selectedTargetUid; + const destinationUid = relation.forward ? selectedTargetUid : sourceNodeUid; const result = await createReifiedRelation({ relationBlockUid: relation.id, - sourceUid: relation.forward ? sourceNodeUid : selectedTargetUid, - destinationUid: relation.forward ? selectedTargetUid : sourceNodeUid, + sourceUid, + destinationUid, }); - return result !== undefined; + if (result === undefined) return false; + refreshDiscourseContextsForMutatedUids({ + uids: [sourceNodeUid, selectedTargetUid], + }); + return true; }; const onCreateSync = (): void => { @@ -173,14 +184,15 @@ const CreateRelationDialog = ({ hasTarget: !!selectedTargetUid, }); onCreate() - .then((result: boolean) => { - if (result) { + .then((created) => { + if (created) { renderToast({ id: `discourse-relation-created-${Date.now()}`, intent: "success", timeout: 10000, content: Created relation, }); + onCreated?.(); } else { renderToast({ id: `discourse-relation-error-${Date.now()}`, @@ -188,7 +200,7 @@ const CreateRelationDialog = ({ content: Failed to create relation, }); } - onClose(); + onClose?.(); }) .catch(() => { renderToast({ @@ -196,7 +208,7 @@ const CreateRelationDialog = ({ intent: "danger", content: Failed to create relation, }); - onClose(); + onClose?.(); }); }; @@ -323,6 +335,7 @@ const prepareRelData = ( const extendProps = ({ sourceNodeUid, onClose, + onCreated, }: CreateRelationDialogProps): ExtendedCreateRelationDialogProps | null => { const nodeTitle = getPageTitleByPageUid(sourceNodeUid).trim(); const relData = prepareRelData(sourceNodeUid, nodeTitle); @@ -340,6 +353,7 @@ const extendProps = ({ return { sourceNodeUid, onClose, + onCreated, relData, sourceNodeTitle: nodeTitle, selectedSourceType, diff --git a/apps/roam/src/components/DiscourseContext.tsx b/apps/roam/src/components/DiscourseContext.tsx index 8cef59811..8b4b1114b 100644 --- a/apps/roam/src/components/DiscourseContext.tsx +++ b/apps/roam/src/components/DiscourseContext.tsx @@ -5,6 +5,10 @@ import getDiscourseContextResults from "~/utils/getDiscourseContextResults"; import ResultsView from "./results-view/ResultsView"; import posthog from "posthog-js"; import { CreateRelationButton } from "./CreateRelationDialog"; +import { + DISCOURSE_CONTEXT_MUTATION_REFRESH_EVENT, + type DiscourseContextMutationRefreshDetail, +} from "~/utils/discourseContextMutationRefresh"; export type DiscourseContextResults = Awaited< ReturnType @@ -13,7 +17,7 @@ export type DiscourseContextResults = Awaited< type Props = { uid: string; results?: DiscourseContextResults; - overlayRefresh?: () => void; + overlayRefresh?: (ignoreCache?: boolean) => void; }; const removeTargetFromResult = ( @@ -85,7 +89,7 @@ const ContextTab = ({ { + onCreated={() => { window.setTimeout(onRefresh, 150, true); }} /> @@ -158,7 +162,7 @@ export const ContextContent = ({ uid, results, overlayRefresh }: Props) => { onResult: addLabels, ignoreCache, }).finally(() => { - if (overlayRefresh) overlayRefresh(); + if (overlayRefresh) overlayRefresh(ignoreCache); setLoading(false); }); }, @@ -177,6 +181,27 @@ export const ContextContent = ({ uid, results, overlayRefresh }: Props) => { setLoading(false); } }, [onRefresh, results, setLoading, loading, addLabels]); + + useEffect(() => { + const onMutationRefresh = (event: Event) => { + const detail = ( + event as CustomEvent + ).detail; + if (!detail?.uids.includes(uid)) return; + onRefresh(true); + }; + + document.body.addEventListener( + DISCOURSE_CONTEXT_MUTATION_REFRESH_EVENT, + onMutationRefresh, + ); + return () => { + document.body.removeEventListener( + DISCOURSE_CONTEXT_MUTATION_REFRESH_EVENT, + onMutationRefresh, + ); + }; + }, [uid, onRefresh]); const [tabId, setTabId] = useState(0); const [groupByTarget, setGroupByTarget] = useState(false); return queryResults.length ? ( @@ -235,7 +260,7 @@ export const ContextContent = ({ uid, results, overlayRefresh }: Props) => { ) : (
No discourse relations found. - +
); }; diff --git a/apps/roam/src/components/DiscourseContextOverlay.tsx b/apps/roam/src/components/DiscourseContextOverlay.tsx index 0b7cd4d3d..6e956c4a9 100644 --- a/apps/roam/src/components/DiscourseContextOverlay.tsx +++ b/apps/roam/src/components/DiscourseContextOverlay.tsx @@ -7,7 +7,6 @@ import { Card, } from "@blueprintjs/core"; import React, { useCallback, useEffect, useMemo, useState } from "react"; -import ReactDOM from "react-dom"; import renderWithUnmount from "roamjs-components/util/renderWithUnmount"; import { ContextContent } from "./DiscourseContext"; import useInViewport from "react-in-viewport/dist/es/lib/useInViewport"; @@ -22,7 +21,6 @@ import getDiscourseContextResults from "~/utils/getDiscourseContextResults"; import findDiscourseNode from "~/utils/findDiscourseNode"; import getDiscourseNodes from "~/utils/getDiscourseNodes"; import getDiscourseRelations from "~/utils/getDiscourseRelations"; -import ExtensionApiContextProvider from "roamjs-components/components/ExtensionApiContext"; import { OnloadArgs } from "roamjs-components/types/native"; import getPageTitleByPageUid from "roamjs-components/queries/getPageTitleByPageUid"; import internalError from "~/utils/internalError"; diff --git a/apps/roam/src/components/results-view/ResultsTable.tsx b/apps/roam/src/components/results-view/ResultsTable.tsx index f5f9e1d8d..fb02137d9 100644 --- a/apps/roam/src/components/results-view/ResultsTable.tsx +++ b/apps/roam/src/components/results-view/ResultsTable.tsx @@ -23,6 +23,7 @@ import { ContextContent } from "~/components/DiscourseContext"; import DiscourseContextOverlay from "~/components/DiscourseContextOverlay"; import { CONTEXT_OVERLAY_SUGGESTION } from "~/utils/predefinedSelections"; import { strictQueryForReifiedBlocks } from "~/utils/createReifiedBlock"; +import { refreshDiscourseContextsForMutatedUids } from "~/utils/discourseContextMutationRefresh"; import { getStoredRelationsEnabled } from "~/utils/storedRelations"; import { RenderRoamBlock, @@ -286,7 +287,9 @@ const ResultRow = ({ content: "Relation deleted", intent: "success", }); - onRefresh(true); + refreshDiscourseContextsForMutatedUids({ + uids: [data.sourceUid, data.destinationUid], + }); }) .catch((e) => { // this one should be an internalError diff --git a/apps/roam/src/utils/discourseContextMutationRefresh.ts b/apps/roam/src/utils/discourseContextMutationRefresh.ts new file mode 100644 index 000000000..326b7e07c --- /dev/null +++ b/apps/roam/src/utils/discourseContextMutationRefresh.ts @@ -0,0 +1,25 @@ +export const DISCOURSE_CONTEXT_MUTATION_REFRESH_EVENT = + "roamjs:discourse-context:mutation-refresh"; + +export type DiscourseContextMutationRefreshDetail = { + uids: string[]; +}; + +export const refreshDiscourseContextsForMutatedUids = ({ + uids, +}: DiscourseContextMutationRefreshDetail): void => { + const uniqueUids = Array.from( + new Set(uids.map((uid) => uid?.trim()).filter(Boolean)), + ); + if (!uniqueUids.length) return; + document.body.dispatchEvent( + new CustomEvent( + DISCOURSE_CONTEXT_MUTATION_REFRESH_EVENT, + { + detail: { + uids: uniqueUids, + }, + }, + ), + ); +}; diff --git a/apps/roam/src/utils/matchDiscourseNode.ts b/apps/roam/src/utils/matchDiscourseNode.ts index d0f652497..f0e05522b 100644 --- a/apps/roam/src/utils/matchDiscourseNode.ts +++ b/apps/roam/src/utils/matchDiscourseNode.ts @@ -20,9 +20,10 @@ const matchDiscourseNode = ({ )) => { // Handle specification with single "has title" clause if ( - specification.length === 1 && - specification[0].type === "clause" && - specification[0].relation === "has title" + specification?.length === 1 && + specification[0]?.type === "clause" && + specification[0]?.relation === "has title" && + specification[0]?.target ) { const title = "title" in rest ? rest.title : getPageTitleByPageUid(rest.uid);