feat: add xmlsvg export option (#761)

* feat: add xmlsvg export option

* fix: avoid atob for xmlsvg export to prevent UTF-8 corruption

Pass the data URL directly (like PNG export) instead of decoding
with atob(). atob() + Blob([string]) double-encodes non-ASCII
characters (Chinese, Japanese, emoji), corrupting the output file.

---------

Co-authored-by: dayuan.jiang <jdy.toh@gmail.com>
This commit is contained in:
zongxi1115
2026-04-03 07:50:57 +08:00
committed by GitHub
parent 29538782fc
commit cb8127920c
6 changed files with 24 additions and 8 deletions

View File

@@ -20,7 +20,7 @@ import {
} from "@/components/ui/select"
import { useDictionary } from "@/hooks/use-dictionary"
export type ExportFormat = "drawio" | "png" | "svg"
export type ExportFormat = "drawio" | "png" | "svg" | "xmlsvg"
interface SaveDialogProps {
open: boolean
@@ -74,6 +74,11 @@ export function SaveDialog({
label: dict.save.formats.svg,
extension: ".svg",
},
{
value: "xmlsvg" as const,
label: dict.save.formats.xmlsvg,
extension: ".drawio.svg",
},
]
const currentFormat = FORMAT_OPTIONS.find((f) => f.value === format)

View File

@@ -233,7 +233,8 @@ export function DiagramProvider({ children }: { children: React.ReactNode }) {
saveResolverRef.current = { resolver: null, format: null }
// For non-xmlsvg formats, skip XML extraction as it will fail
// Only drawio (which uses xmlsvg internally) has the content attribute
if (format === "png" || format === "svg") {
// xmlsvg is saved directly as SVG file, no need for extraction
if (format === "png" || format === "svg" || format === "xmlsvg") {
return
}
}
@@ -296,7 +297,8 @@ export function DiagramProvider({ children }: { children: React.ReactNode }) {
}
// Map format to draw.io export format
const drawioFormat = format === "drawio" ? "xmlsvg" : format
const drawioFormat =
format === "drawio" || format === "xmlsvg" ? "xmlsvg" : format
// Set up the resolver before triggering export
saveResolverRef.current = {
@@ -320,8 +322,13 @@ export function DiagramProvider({ children }: { children: React.ReactNode }) {
fileContent = exportData
mimeType = "image/png"
extension = ".png"
} else if (format === "xmlsvg") {
// Editable SVG: pass data URL directly (like PNG)
fileContent = exportData
mimeType = "image/svg+xml"
extension = ".drawio.svg"
} else {
// SVG format
// SVG format (view-only)
fileContent = exportData
mimeType = "image/svg+xml"
extension = ".svg"

View File

@@ -140,7 +140,8 @@
"formats": {
"drawio": "Draw.io XML",
"png": "PNG Image",
"svg": "SVG Image"
"svg": "SVG Image",
"xmlsvg": "Editable SVG"
},
"savedSuccessfully": "Saved successfully!"
},

View File

@@ -140,7 +140,8 @@
"formats": {
"drawio": "Draw.io XML",
"png": "PNG 画像",
"svg": "SVG 画像"
"svg": "SVG 画像",
"xmlsvg": "編集可能 SVG"
},
"savedSuccessfully": "保存完了!"
},

View File

@@ -140,7 +140,8 @@
"formats": {
"drawio": "Draw.io XML",
"png": "PNG 圖片",
"svg": "SVG 圖片"
"svg": "SVG 圖片",
"xmlsvg": "可編輯 SVG"
},
"savedSuccessfully": "儲存成功!"
},

View File

@@ -140,7 +140,8 @@
"formats": {
"drawio": "Draw.io XML",
"png": "PNG 图片",
"svg": "SVG 图片"
"svg": "SVG 图片",
"xmlsvg": "可编辑 SVG"
},
"savedSuccessfully": "保存成功!"
},