0.1.14 上线前更改
This commit is contained in:
@@ -155,9 +155,18 @@ const resolveAssetUrls = async (data: MindMapData): Promise<{ data: MindMapData;
|
||||
// 递归收集所有 asset:id
|
||||
const collectAssetIds = (node: any) => {
|
||||
if (!node) return;
|
||||
if (node.image?.url?.startsWith?.("asset:")) {
|
||||
assetIds.push(node.image.url.replace("asset:", ""));
|
||||
}
|
||||
const candidates: unknown[] = [
|
||||
node?.data?.image,
|
||||
node?.image,
|
||||
node?.image?.url,
|
||||
node?.data?.image?.url,
|
||||
];
|
||||
candidates.forEach((value) => {
|
||||
if (typeof value !== "string") return;
|
||||
if (!value.startsWith("asset:")) return;
|
||||
const id = value.replace(/^asset:/, "").trim();
|
||||
if (id) assetIds.push(id);
|
||||
});
|
||||
if (Array.isArray(node.children)) {
|
||||
node.children.forEach(collectAssetIds);
|
||||
}
|
||||
@@ -176,9 +185,7 @@ const resolveAssetUrls = async (data: MindMapData): Promise<{ data: MindMapData;
|
||||
const result = await response.json();
|
||||
urlMap.set(id, result.signedUrl);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`获取 asset ${id} 签名 URL 失败`, e);
|
||||
}
|
||||
} catch {}
|
||||
}));
|
||||
|
||||
// 递归替换 asset:id 为签名 URL,并建立反向映射
|
||||
@@ -186,15 +193,49 @@ const resolveAssetUrls = async (data: MindMapData): Promise<{ data: MindMapData;
|
||||
const replaceAssetIds = (node: any): any => {
|
||||
if (!node) return node;
|
||||
const newNode = { ...node };
|
||||
if (newNode.image?.url?.startsWith?.("asset:")) {
|
||||
const id = newNode.image.url.replace("asset:", "");
|
||||
|
||||
const replaceAssetString = (value: unknown): string | null => {
|
||||
if (typeof value !== "string") return null;
|
||||
if (!value.startsWith("asset:")) return null;
|
||||
const id = value.replace(/^asset:/, "").trim();
|
||||
if (!id) return null;
|
||||
const signedUrl = urlMap.get(id);
|
||||
if (signedUrl) {
|
||||
newNode.image = { ...newNode.image, url: signedUrl };
|
||||
// 建立反向映射:signedUrl -> asset:id
|
||||
urlToAssetId.set(signedUrl, id);
|
||||
return signedUrl;
|
||||
}
|
||||
// 即使签名失败,也保留 asset:id -> id 的映射,方便后续删除/撤销逻辑使用
|
||||
urlToAssetId.set(`asset:${id}`, id);
|
||||
return `asset:${id}`;
|
||||
};
|
||||
|
||||
// 常见:node.data.image = "asset:xxx"
|
||||
const nextDataImage = replaceAssetString(newNode?.data?.image);
|
||||
if (nextDataImage && newNode.data && typeof newNode.data === "object") {
|
||||
newNode.data = { ...newNode.data, image: nextDataImage };
|
||||
}
|
||||
|
||||
// 兼容:node.image = "asset:xxx"
|
||||
if (typeof newNode.image === "string") {
|
||||
const nextImage = replaceAssetString(newNode.image);
|
||||
if (nextImage) newNode.image = nextImage;
|
||||
}
|
||||
|
||||
// 兼容:node.image.url = "asset:xxx"
|
||||
const nextImageUrl = replaceAssetString(newNode?.image?.url);
|
||||
if (nextImageUrl && newNode.image && typeof newNode.image === "object") {
|
||||
newNode.image = { ...newNode.image, url: nextImageUrl };
|
||||
}
|
||||
|
||||
// 兼容:node.data.image.url = "asset:xxx"
|
||||
const nextDataImageUrl = replaceAssetString(newNode?.data?.image?.url);
|
||||
if (nextDataImageUrl && newNode.data && typeof newNode.data === "object") {
|
||||
const dataImage = (newNode.data as any).image;
|
||||
if (dataImage && typeof dataImage === "object") {
|
||||
(newNode.data as any).image = { ...(dataImage as any), url: nextDataImageUrl };
|
||||
}
|
||||
}
|
||||
|
||||
if (Array.isArray(newNode.children)) {
|
||||
newNode.children = newNode.children.map(replaceAssetIds);
|
||||
}
|
||||
@@ -209,12 +250,41 @@ const revertToAssetIds = (data: MindMapData, urlToAssetId: Map<string, string>):
|
||||
const revertNode = (node: any): any => {
|
||||
if (!node) return node;
|
||||
const newNode = { ...node };
|
||||
if (newNode.image?.url && typeof newNode.image.url === "string") {
|
||||
const assetId = urlToAssetId.get(newNode.image.url);
|
||||
if (assetId) {
|
||||
newNode.image = { ...newNode.image, url: `asset:${assetId}` };
|
||||
|
||||
const revertSigned = (value: unknown): string | null => {
|
||||
if (typeof value !== "string") return null;
|
||||
const assetId = urlToAssetId.get(value);
|
||||
if (!assetId) return null;
|
||||
return `asset:${assetId}`;
|
||||
};
|
||||
|
||||
// 常见:node.data.image = signedUrl
|
||||
const nextDataImage = revertSigned(newNode?.data?.image);
|
||||
if (nextDataImage && newNode.data && typeof newNode.data === "object") {
|
||||
newNode.data = { ...newNode.data, image: nextDataImage };
|
||||
}
|
||||
|
||||
// 兼容:node.image = signedUrl
|
||||
if (typeof newNode.image === "string") {
|
||||
const nextImage = revertSigned(newNode.image);
|
||||
if (nextImage) newNode.image = nextImage;
|
||||
}
|
||||
|
||||
// 兼容:node.image.url = signedUrl
|
||||
const nextImageUrl = revertSigned(newNode?.image?.url);
|
||||
if (nextImageUrl && newNode.image && typeof newNode.image === "object") {
|
||||
newNode.image = { ...newNode.image, url: nextImageUrl };
|
||||
}
|
||||
|
||||
// 兼容:node.data.image.url = signedUrl
|
||||
const nextDataImageUrl = revertSigned(newNode?.data?.image?.url);
|
||||
if (nextDataImageUrl && newNode.data && typeof newNode.data === "object") {
|
||||
const dataImage = (newNode.data as any).image;
|
||||
if (dataImage && typeof dataImage === "object") {
|
||||
(newNode.data as any).image = { ...(dataImage as any), url: nextDataImageUrl };
|
||||
}
|
||||
}
|
||||
|
||||
if (Array.isArray(newNode.children)) {
|
||||
newNode.children = newNode.children.map(revertNode);
|
||||
}
|
||||
@@ -297,10 +367,7 @@ const patchSvgRbox = async () => {
|
||||
cx: Math.max(0, viewportWidth / 2),
|
||||
cy: Math.max(0, viewportHeight / 2),
|
||||
};
|
||||
if (!warned) {
|
||||
console.warn("rbox 失败,使用 DOM 边界框降级避免崩溃", error, fallback);
|
||||
warned = true;
|
||||
}
|
||||
if (!warned) warned = true;
|
||||
return fallback;
|
||||
}
|
||||
};
|
||||
@@ -510,9 +577,7 @@ const MindmapBlockView = ({
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn("加载本地/远端思维导图失败", error);
|
||||
}
|
||||
} catch {}
|
||||
})();
|
||||
return () => {
|
||||
cancelled = true;
|
||||
@@ -1232,7 +1297,7 @@ const MindmapBlockView = ({
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((err) => console.warn("思维导图同步失败", err));
|
||||
.catch(() => {});
|
||||
}
|
||||
},
|
||||
[autosaveKey, block, docId, editor, mindmapId, effectiveFullscreen],
|
||||
@@ -1292,15 +1357,8 @@ const MindmapBlockView = ({
|
||||
body: JSON.stringify({ data, createOnly: true }),
|
||||
});
|
||||
if (!resp.ok) {
|
||||
console.warn(
|
||||
"初次创建思维导图文件失败",
|
||||
resp.status,
|
||||
await resp.text().catch(() => ""),
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn("初次创建思维导图文件失败", err);
|
||||
} finally {
|
||||
} catch {} finally {
|
||||
// 即便远端失败,也通知侧边栏刷新,保证本地文件树及时更新
|
||||
const fileName = `mindmap-${mindmapId}.json`;
|
||||
emitAssetsChanged(docId, {
|
||||
@@ -1479,7 +1537,6 @@ const MindmapBlockView = ({
|
||||
|
||||
plugins.forEach(({ name, plugin }) => {
|
||||
if (!plugin) {
|
||||
console.warn(`思维导图插件加载失败:${name}`);
|
||||
return;
|
||||
}
|
||||
const MindMapCtor = MindMap as unknown as {
|
||||
@@ -1491,7 +1548,6 @@ const MindmapBlockView = ({
|
||||
typeof hasPlugin === "function" ? hasPlugin(plugin) === -1 : true;
|
||||
const registerPlugin = MindMapCtor.usePlugin;
|
||||
if (notRegistered && typeof registerPlugin === "function") {
|
||||
console.log(`注册插件: ${name}`);
|
||||
registerPlugin(plugin);
|
||||
}
|
||||
});
|
||||
@@ -1673,11 +1729,9 @@ const MindmapBlockView = ({
|
||||
renderer.setRootNodeCenter();
|
||||
}
|
||||
instance.view?.fit?.();
|
||||
} catch (error) {
|
||||
} catch {
|
||||
if (retry < 3) {
|
||||
window.setTimeout(() => centerAndFit(retry + 1), 50);
|
||||
} else {
|
||||
console.warn("思维导图初始居中失败,已跳过", error);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -2089,9 +2143,7 @@ const MindmapBlockView = ({
|
||||
const data = await response.json();
|
||||
return data.signedUrl;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("获取签名 URL 失败", e);
|
||||
}
|
||||
} catch {}
|
||||
return urlOrAssetId;
|
||||
}
|
||||
// 如果是完整的 URL,直接返回
|
||||
@@ -2104,9 +2156,17 @@ const MindmapBlockView = ({
|
||||
const urls: string[] = [];
|
||||
const traverse = (node: any) => {
|
||||
if (!node) return;
|
||||
if (node.image && typeof node.image === "string" && node.image) {
|
||||
urls.push(node.image);
|
||||
}
|
||||
const candidates: unknown[] = [
|
||||
node?.data?.image,
|
||||
node?.image,
|
||||
node?.image?.url,
|
||||
node?.data?.image?.url,
|
||||
];
|
||||
candidates.forEach((value) => {
|
||||
if (typeof value === "string" && value) {
|
||||
urls.push(value);
|
||||
}
|
||||
});
|
||||
if (Array.isArray(node.children)) {
|
||||
node.children.forEach(traverse);
|
||||
}
|
||||
@@ -2119,7 +2179,6 @@ const MindmapBlockView = ({
|
||||
const deleteImageAssets = async (assetIds: string[]) => {
|
||||
if (!assetIds.length || !docId) return;
|
||||
try {
|
||||
console.log("[MindmapBlock] Deleting image assets:", assetIds);
|
||||
const response = await fetch("/api/media/batch", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
@@ -2128,12 +2187,11 @@ const MindmapBlockView = ({
|
||||
if (response.ok) {
|
||||
// 记录到已删除列表
|
||||
assetIds.forEach(id => deletedAssetIdsRef.current.add(id));
|
||||
console.log("[MindmapBlock] Image assets deleted successfully, emitting ASSETS_CHANGED_EVENT");
|
||||
// 通知文件树刷新,传递被删除的 assetIds
|
||||
emitAssetsChanged(docId, undefined, assetIds);
|
||||
} else {
|
||||
const payload = await response.json().catch(() => null);
|
||||
console.error("[MindmapBlock] Failed to delete image assets:", payload?.error);
|
||||
console.error("删除图片资源失败", payload?.error);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("删除图片资源失败", e);
|
||||
@@ -2280,20 +2338,15 @@ const MindmapBlockView = ({
|
||||
useEffect(() => {
|
||||
if (!mindmap || !docId) return;
|
||||
|
||||
console.log("[MindmapBlock] Setting up image deletion monitoring");
|
||||
|
||||
// 初始化:收集当前所有图片 URL
|
||||
const initialData = mindmap.getData?.();
|
||||
if (initialData) {
|
||||
const urls = collectImageUrls(initialData);
|
||||
currentImageUrlsRef.current = new Set(urls);
|
||||
console.log("[MindmapBlock] Initial image URLs:", urls.length, "Map size:", signedUrlToAssetIdRef.current.size);
|
||||
console.log("[MindmapBlock] Initial URLs:", urls);
|
||||
}
|
||||
|
||||
// 处理数据变化
|
||||
const handleDataChange = () => {
|
||||
console.log("[MindmapBlock] data_change event fired!");
|
||||
const newData = mindmap.getData?.();
|
||||
if (!newData) return;
|
||||
|
||||
@@ -2301,10 +2354,6 @@ const MindmapBlockView = ({
|
||||
const newUrlsSet = new Set(newUrls);
|
||||
const oldUrlsSet = currentImageUrlsRef.current;
|
||||
|
||||
console.log("[MindmapBlock] Old URLs:", Array.from(oldUrlsSet));
|
||||
console.log("[MindmapBlock] New URLs:", newUrls);
|
||||
console.log("[MindmapBlock] Map entries:", Array.from(signedUrlToAssetIdRef.current.entries()));
|
||||
|
||||
// 检测被删除的图片(在旧集合中但不在新集合中)
|
||||
const deletedUrls: string[] = [];
|
||||
oldUrlsSet.forEach(url => {
|
||||
@@ -2323,30 +2372,22 @@ const MindmapBlockView = ({
|
||||
|
||||
// 处理删除的图片
|
||||
if (deletedUrls.length > 0) {
|
||||
console.log("[MindmapBlock] Detected deleted URLs:", deletedUrls);
|
||||
const assetIdsToDelete: string[] = [];
|
||||
|
||||
deletedUrls.forEach(url => {
|
||||
const assetId = signedUrlToAssetIdRef.current.get(url);
|
||||
console.log("[MindmapBlock] URL:", url.substring(0, 100), "-> Asset ID:", assetId);
|
||||
if (assetId) {
|
||||
assetIdsToDelete.push(assetId);
|
||||
} else {
|
||||
console.warn("[MindmapBlock] Asset ID not found in map for URL:", url.substring(0, 100));
|
||||
}
|
||||
});
|
||||
|
||||
console.log("[MindmapBlock] Asset IDs to delete:", assetIdsToDelete);
|
||||
if (assetIdsToDelete.length > 0) {
|
||||
deleteImageAssets(assetIdsToDelete);
|
||||
} else {
|
||||
console.warn("[MindmapBlock] No asset IDs found for deleted URLs!");
|
||||
}
|
||||
}
|
||||
|
||||
// 处理恢复的图片(可能是撤销操作)
|
||||
if (addedUrls.length > 0) {
|
||||
console.log("[MindmapBlock] Detected added URLs:", addedUrls);
|
||||
const assetIdsToRestore: string[] = [];
|
||||
addedUrls.forEach(url => {
|
||||
const assetId = signedUrlToAssetIdRef.current.get(url);
|
||||
@@ -2354,7 +2395,6 @@ const MindmapBlockView = ({
|
||||
assetIdsToRestore.push(assetId);
|
||||
}
|
||||
});
|
||||
console.log("[MindmapBlock] Asset IDs to restore:", assetIdsToRestore);
|
||||
if (assetIdsToRestore.length > 0) {
|
||||
restoreImageAssets(assetIdsToRestore);
|
||||
}
|
||||
@@ -2369,10 +2409,7 @@ const MindmapBlockView = ({
|
||||
mindmap.on?.("back_forward", handleDataChange);
|
||||
mindmap.on?.("node_data_change", handleDataChange);
|
||||
|
||||
console.log("[MindmapBlock] Event listeners registered");
|
||||
|
||||
return () => {
|
||||
console.log("[MindmapBlock] Cleaning up event listeners");
|
||||
mindmap.off?.("data_change", handleDataChange);
|
||||
mindmap.off?.("back_forward", handleDataChange);
|
||||
mindmap.off?.("node_data_change", handleDataChange);
|
||||
@@ -2482,11 +2519,11 @@ const MindmapBlockView = ({
|
||||
let displayUrl = url;
|
||||
if (url.startsWith("asset:")) {
|
||||
displayUrl = await resolveImageUrl(url);
|
||||
// 记录映射关系供保存时使用
|
||||
if (displayUrl !== url) {
|
||||
const assetId = url.replace("asset:", "");
|
||||
// 记录映射关系供保存/删除/撤销使用(无论是否签名成功)
|
||||
const assetId = url.replace(/^asset:/, "").trim();
|
||||
if (assetId) {
|
||||
signedUrlToAssetIdRef.current.set(displayUrl, assetId);
|
||||
console.log("[MindmapBlock] New image mapped:", displayUrl.substring(0, 80), "-> Asset ID:", assetId);
|
||||
signedUrlToAssetIdRef.current.set(`asset:${assetId}`, assetId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2511,7 +2548,6 @@ const MindmapBlockView = ({
|
||||
if (newData) {
|
||||
const newUrls = collectImageUrls(newData);
|
||||
currentImageUrlsRef.current = new Set(newUrls);
|
||||
console.log("[MindmapBlock] Updated currentImageUrlsRef after insert:", newUrls);
|
||||
}
|
||||
}, 100);
|
||||
};
|
||||
@@ -2589,8 +2625,7 @@ const MindmapBlockView = ({
|
||||
}
|
||||
|
||||
window.alert("暂不支持该文件类型,请选择 .json / .smm / .xmind / .mmap / .md");
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} catch {
|
||||
window.alert("导入失败:文件格式或内容错误");
|
||||
} finally {
|
||||
reset();
|
||||
@@ -2627,8 +2662,7 @@ const MindmapBlockView = ({
|
||||
const handleExport = async (type: string, name = "mindmap") => {
|
||||
try {
|
||||
await mindmap?.doExport?.export(type, true, name);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} catch {
|
||||
window.alert(`导出 ${type.toUpperCase()} 失败,请稍后再试`);
|
||||
}
|
||||
};
|
||||
@@ -2861,6 +2895,9 @@ const MindmapBlockView = ({
|
||||
form.append("file", file);
|
||||
form.append("workspaceId", workspaceId);
|
||||
form.append("documentId", docId);
|
||||
if (mindmapId) {
|
||||
form.append("mindmapId", mindmapId);
|
||||
}
|
||||
const response = await fetch("/api/media/upload", {
|
||||
method: "POST",
|
||||
body: form,
|
||||
@@ -2999,26 +3036,12 @@ const MindmapBlockView = ({
|
||||
ref={wrapperRef}
|
||||
data-testid="mindmap-fullscreen"
|
||||
tabIndex={0}
|
||||
className="fixed inset-0 z-[9999] overflow-hidden bg-white"
|
||||
className="fixed inset-0 z-[9999] overflow-hidden bg-white"
|
||||
>
|
||||
<div className="fixed left-0 right-0 top-0 z-30 flex h-12 items-center justify-between border-b border-gray-200 bg-white/90 px-4 backdrop-blur">
|
||||
<div className="text-sm font-medium text-gray-700">
|
||||
思维导图编辑(全屏)
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
title="退出全屏"
|
||||
className="rounded p-2 text-gray-500 hover:bg-gray-100 hover:text-gray-700"
|
||||
onClick={exitLocalFullscreen}
|
||||
>
|
||||
<span className="text-lg leading-none">×</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="fixed left-1/2 top-14 z-20 flex w-fit -translate-x-1/2 transform items-center justify-center gap-4">
|
||||
<div className="fixed left-1/2 top-2 z-20 flex w-fit -translate-x-1/2 transform items-center justify-center gap-4">
|
||||
<MindmapToolbar {...toolbarProps} />
|
||||
</div>
|
||||
<div className="relative h-full w-full pt-12">
|
||||
<div className="relative h-full w-full pt-0">
|
||||
<div
|
||||
ref={containerRef}
|
||||
className="h-full w-full"
|
||||
|
||||
Reference in New Issue
Block a user