Skip to content

Commit 25541eb

Browse files
committed
Sync document title from root node and propagate to menubar; rename clipboard hook; fix logo skeleton sizing
- Derive document base title from the root node (resourceName/description) in Editor and set document.title. - Expose base title on window.__MINDMAPFLOW_BASE_TITLE__ and emit a "mindmapflow:title-changed" CustomEvent so Menubar can stay in sync; restore default title on unmount. - Add previous title tracking to avoid redundant updates. - Rename/move clipboard hook to hooks/use-mindmap-clipboard and update import. - Fix homepage logo skeleton sizing and set Image height to 0 until loaded to prevent layout shift. - Remove placeholder "Export (Coming later)" menu item.
1 parent b018362 commit 25541eb

File tree

4 files changed

+124
-11
lines changed

4 files changed

+124
-11
lines changed

src/app/(home)/page.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,9 @@ export default function Home() {
5454
<Skeleton
5555
aria-hidden
5656
baseColor="#e5e7eb"
57-
className="block h-full w-full"
57+
className="block h-auto w-full"
5858
containerClassName="absolute inset-0 pointer-events-none"
59+
height={260}
5960
highlightColor="#f3f4f6"
6061
/>
6162
) : null}
@@ -65,7 +66,7 @@ export default function Home() {
6566
"block h-auto w-full object-contain transition-opacity duration-300",
6667
loadedImages.logo ? "opacity-100" : "opacity-0",
6768
)}
68-
height={260}
69+
height={loadedImages.logo ? 260 : 0}
6970
sizes="(max-width: 640px) 80vw, 480px"
7071
src="/mindmapflow_logo_with_text.png"
7172
width={480}

src/components/editor/editor.tsx

Lines changed: 78 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"use client";
22

3-
import { useState, useCallback, useRef, useEffect } from "react";
3+
import { useState, useCallback, useRef, useEffect, useMemo } from "react";
44
import {
55
Edge,
66
MarkerType,
@@ -23,7 +23,7 @@ import { Menubar } from "./menubar";
2323
import { TextProperties } from "./nodes/base-node";
2424
import { GlobalSearchDialog } from "./global-search-dialog";
2525
import { useMindMapGlobalSearch } from "./hooks/use-global-search";
26-
import { useMindMapClipboard } from "./hooks/use-clipboard";
26+
import { useMindMapClipboard } from "./hooks/use-mindmap-clipboard";
2727
import { useMindMapNodeCreation } from "./hooks/use-node-creation";
2828
import { getDefaultTextProperties } from "./utils/text-properties";
2929

@@ -59,11 +59,19 @@ import {
5959
updateSelectedNodeData,
6060
} from "@/utils/editor-utils";
6161

62+
declare global {
63+
interface Window {
64+
__MINDMAPFLOW_BASE_TITLE__?: string;
65+
}
66+
}
67+
6268
const initialNodes: MindMapNode[] = sampleData.nodes;
6369
const initialEdges: Edge[] = sampleData.edges;
6470
const rootNodeId = "root";
6571

6672
const NODE_HORIZONTAL_OFFSET = 240;
73+
const DEFAULT_DOCUMENT_TITLE = "MindMapFlow";
74+
const TITLE_CHANGE_EVENT = "mindmapflow:title-changed";
6775

6876
export default function Editor() {
6977
const searchParams = useSearchParams();
@@ -194,6 +202,74 @@ export default function Editor() {
194202
// Derive selected node from selectedNodeId
195203
const selectedNode = nodes.find((node) => node.id === selectedNodeId);
196204

205+
const mindmapTitle = useMemo(() => {
206+
const rootNode =
207+
nodes.find((node) => node.id === rootNodeId) ||
208+
nodes.find((node) => (node.data?.depth ?? 0) === 0);
209+
210+
if (!rootNode || !rootNode.data) {
211+
return undefined;
212+
}
213+
214+
const candidate =
215+
rootNode.data.resourceName?.trim() ||
216+
rootNode.data.description?.trim() ||
217+
undefined;
218+
219+
if (!candidate) {
220+
return undefined;
221+
}
222+
223+
const normalized = candidate
224+
.split(/\r?\n/)
225+
.map((part) => part.trim())
226+
.filter((part) => part.length > 0)
227+
.join(" ");
228+
229+
return normalized.length > 0 ? normalized : undefined;
230+
}, [nodes]);
231+
232+
const previousBaseTitleRef = useRef<string | undefined>(undefined);
233+
234+
useEffect(() => {
235+
if (typeof window === "undefined") {
236+
return;
237+
}
238+
239+
const baseTitle = mindmapTitle
240+
? `${DEFAULT_DOCUMENT_TITLE} - ${mindmapTitle}`
241+
: DEFAULT_DOCUMENT_TITLE;
242+
243+
if (previousBaseTitleRef.current === baseTitle) {
244+
// Ensure global state stays in sync even if title is unchanged
245+
window.__MINDMAPFLOW_BASE_TITLE__ = baseTitle;
246+
247+
return;
248+
}
249+
250+
previousBaseTitleRef.current = baseTitle;
251+
window.__MINDMAPFLOW_BASE_TITLE__ = baseTitle;
252+
document.title = baseTitle;
253+
window.dispatchEvent(
254+
new CustomEvent<string>(TITLE_CHANGE_EVENT, { detail: baseTitle }),
255+
);
256+
}, [mindmapTitle]);
257+
258+
useEffect(() => {
259+
if (typeof window === "undefined") {
260+
return;
261+
}
262+
263+
return () => {
264+
window.__MINDMAPFLOW_BASE_TITLE__ = DEFAULT_DOCUMENT_TITLE;
265+
window.dispatchEvent(
266+
new CustomEvent<string>(TITLE_CHANGE_EVENT, {
267+
detail: DEFAULT_DOCUMENT_TITLE,
268+
}),
269+
);
270+
};
271+
}, []);
272+
197273
const applySelection = useCallback(
198274
(
199275
nextSelectedIds: string[],
File renamed without changes.

src/components/editor/menubar.tsx

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ export const Menubar = ({
5959
const [openProjectDialogOpen, setOpenProjectDialogOpen] = useState(false);
6060
const [manageSharesDialogOpen, setManageSharesDialogOpen] = useState(false);
6161
const [sharesRefreshToken, setSharesRefreshToken] = useState(0);
62+
const [baseDocumentTitle, setBaseDocumentTitle] = useState("MindMapFlow");
6263
const projectMenuOpen = Boolean(anchorEl);
6364
const editMenuOpen = Boolean(editAnchorEl);
6465
const profileMenuOpen = Boolean(profileAchorEl);
@@ -67,6 +68,42 @@ export const Menubar = ({
6768
const params = useParams();
6869
const mindMapId = (params?.id as string | undefined) ?? undefined;
6970

71+
useEffect(() => {
72+
if (typeof window === "undefined") {
73+
return;
74+
}
75+
76+
const readBaseTitle = () => {
77+
const current = window.__MINDMAPFLOW_BASE_TITLE__;
78+
79+
if (typeof current === "string" && current.trim().length > 0) {
80+
return current;
81+
}
82+
83+
return "MindMapFlow";
84+
};
85+
86+
const handleTitleChange = (event: Event) => {
87+
const customEvent = event as CustomEvent<string>;
88+
const detail =
89+
typeof customEvent.detail === "string" ? customEvent.detail : undefined;
90+
const nextTitle =
91+
detail && detail.trim().length > 0 ? detail : readBaseTitle();
92+
93+
setBaseDocumentTitle((prev) => (prev === nextTitle ? prev : nextTitle));
94+
};
95+
96+
setBaseDocumentTitle(readBaseTitle());
97+
window.addEventListener("mindmapflow:title-changed", handleTitleChange);
98+
99+
return () => {
100+
window.removeEventListener(
101+
"mindmapflow:title-changed",
102+
handleTitleChange,
103+
);
104+
};
105+
}, []);
106+
70107
// Update saved status whenever hasUnsavedChanges changes
71108
useEffect(() => {
72109
const updateSavedStatus = (event: Event) => {
@@ -115,14 +152,16 @@ export const Menubar = ({
115152

116153
// Update document title based on save status
117154
useEffect(() => {
118-
const baseTitle = "MindMapFlow";
155+
const title = !isSaved
156+
? `${baseDocumentTitle} (Unsaved Changes)`
157+
: baseDocumentTitle;
119158

120-
document.title = !isSaved ? `${baseTitle} (Unsaved Changes)` : baseTitle;
159+
document.title = title;
121160

122161
return () => {
123-
document.title = baseTitle;
162+
document.title = baseDocumentTitle;
124163
};
125-
}, [isSaved]);
164+
}, [isSaved, baseDocumentTitle]);
126165

127166
// Add beforeunload event handler
128167
useEffect(() => {
@@ -456,9 +495,6 @@ export const Menubar = ({
456495
</MenuItem>
457496
<Divider component="li" sx={{ my: 0.5 }} />
458497
<MenuItem onClick={handleCopyAsImage}>Export as image</MenuItem>
459-
<MenuItem disabled onClick={handleMenuClose}>
460-
Export (Coming later)
461-
</MenuItem>
462498
<Divider component="li" sx={{ my: 0.5 }} />
463499
<MenuItem
464500
aria-controls={

0 commit comments

Comments
 (0)