diff --git a/src/editorTab.ts b/src/editorTab.ts
index d2f89fb..8ebc0a0 100644
--- a/src/editorTab.ts
+++ b/src/editorTab.ts
@@ -2,37 +2,51 @@ import {Dialog, getFrontend, ITabModel, openTab, Plugin} from "siyuan"
 import Editor, {BaseWidget, EditorEventType} from "js-draw";
 import { MaterialIconProvider } from '@js-draw/material-icons';
 import 'js-draw/styles';
-import {getFile, saveFile} from "@/file";
+import {getFile, saveFile, uploadAsset} from "@/file";
 import {DATA_PATH, JSON_MIME, SVG_MIME, TOOLBAR_PATH} from "@/const";
-import {replaceAntiCacheID} from "@/protyle";
-import {idToPath} from "@/helper";
+import {replaceSyncID} from "@/protyle";
+import {IDsToAssetPath} from "@/helper";
+import {removeFile} from "@/api";
 
-export function openEditorTab(p: Plugin, path: string) {
+export function openEditorTab(p: Plugin, fileID: string, initialSyncID: string) {
     if(getFrontend() == "mobile") {
         const dialog = new Dialog({
             width: "100vw",
             height: "100vh",
             content: `<div id="DrawingPanel" style="width:100%; height: 100%;"></div>`,
         });
-        createEditor(dialog.element.querySelector("#DrawingPanel"), path);
+        createEditor(dialog.element.querySelector("#DrawingPanel"), fileID, initialSyncID);
         return;
     }
+    for(const tab of p.getOpenedTab()["whiteboard"]) {
+        if(tab.data.fileID == fileID) {
+            alert("File is already open in another editor tab!");
+            return;
+        }
+    }
     openTab({
         app: p.app,
         custom: {
             title: 'Drawing',
             icon: 'iconDraw',
             id: "siyuan-jsdraw-pluginwhiteboard",
-            data: { path: path }
+            data: {
+                fileID: fileID,
+                initialSyncID: initialSyncID
+            }
         }
     });
 }
 
-async function saveCallback(editor: Editor, path: string, saveButton: BaseWidget) {
+async function saveCallback(editor: Editor, fileID: string, oldSyncID: string, saveButton: BaseWidget): Promise<string> {
+
     const svgElem = editor.toSVG();
+    let newSyncID;
+
     try {
-        saveFile(DATA_PATH + path, SVG_MIME, svgElem.outerHTML);
-        await replaceAntiCacheID(path);
+        newSyncID = (await uploadAsset(fileID, SVG_MIME, svgElem.outerHTML)).syncID;
+        await replaceSyncID(fileID, oldSyncID, newSyncID);
+        await removeFile(DATA_PATH + IDsToAssetPath(fileID, oldSyncID));
         saveButton.setDisabled(true);
         setTimeout(() => { // @todo improve save button feedback
             saveButton.setDisabled(false);
@@ -41,11 +55,14 @@ async function saveCallback(editor: Editor, path: string, saveButton: BaseWidget
         alert("Error saving drawing! Enter developer mode to find the error, and a copy of the current status.");
         console.error(error);
         console.log("Couldn't save SVG: ", svgElem.outerHTML)
+        return oldSyncID;
     }
 
+    return newSyncID
+
 }
 
-export function createEditor(element: HTMLElement, path: string) {
+export function createEditor(element: HTMLElement, fileID: string, initialSyncID: string) {
 
     const editor = new Editor(element, {
         iconProvider: new MaterialIconProvider(),
@@ -60,14 +77,21 @@ export function createEditor(element: HTMLElement, path: string) {
         }
     });
     // restore drawing
-    getFile(DATA_PATH + path).then(svg => {
+    getFile(DATA_PATH +IDsToAssetPath(fileID, initialSyncID)).then(svg => {
         if(svg != null) {
             editor.loadFromSVG(svg);
         }
     });
 
+    let syncID = initialSyncID;
     // save logic
-    const saveButton = toolbar.addSaveButton(() => saveCallback(editor, path, saveButton));
+    const saveButton = toolbar.addSaveButton(() => {
+        saveCallback(editor, fileID, syncID, saveButton).then(
+            newSyncID => {
+                syncID = newSyncID
+            }
+        )
+    });
 
     // save toolbar config on tool change (toolbar state is not saved in SVGs!)
     editor.notifier.on(EditorEventType.ToolUpdated, () => {
@@ -81,15 +105,12 @@ export function createEditor(element: HTMLElement, path: string) {
 
 export function editorTabInit(tab: ITabModel) {
 
-    let path = tab.data.path;
-    if(path == null) {
-        const fileID = tab.data.id; // legacy compatibility
-        if (fileID == null) {
-            alert("File ID and path missing - couldn't open file.")
-            return;
-        }
-        path = idToPath(fileID);
+    const fileID = tab.data.fileID;
+    const initialSyncID = tab.data.initialSyncID;
+    if (fileID == null || initialSyncID == null) {
+        alert("File or Sync ID and path missing - couldn't open file.")
+        return;
     }
-    createEditor(tab.element, path);
+    createEditor(tab.element, fileID, initialSyncID);
 
 }
\ No newline at end of file
diff --git a/src/file.ts b/src/file.ts
index 5ce0d20..79e0e48 100644
--- a/src/file.ts
+++ b/src/file.ts
@@ -1,10 +1,25 @@
-import {getFileBlob, putFile} from "@/api";
+import {getFileBlob, putFile, upload} from "@/api";
+import {ASSETS_PATH} from "@/const";
+import {assetPathToIDs} from "@/helper";
 
 function toFile(title: string, content: string, mimeType: string){
     const blob = new Blob([content], { type: mimeType });
     return new File([blob], title, { type: mimeType });
 }
 
+// upload asset to the assets folder, return fileID and syncID
+export async function uploadAsset(fileID: string, mimeType: string, content: string) {
+
+    const file = toFile(fileID + ".svg", content, mimeType);
+
+    let r = await upload('/' + ASSETS_PATH, [file]);
+    if(r.errFiles) {
+        throw new Error("Failed to upload file");
+    }
+    return assetPathToIDs(r.succMap[file.name]);
+
+}
+
 export function saveFile(path: string, mimeType: string, content: string) {
 
     const file = toFile(path.split('/').pop(), content, mimeType);
diff --git a/src/helper.ts b/src/helper.ts
index d956158..ea4524d 100644
--- a/src/helper.ts
+++ b/src/helper.ts
@@ -1,5 +1,5 @@
 import { Plugin } from 'siyuan';
-import {DATA_PATH, EMBED_PATH} from "@/const";
+import {ASSETS_PATH} from "@/const";
 
 const drawIcon: string = `
 <symbol id="iconDraw" viewBox="0 0 28 28">
@@ -23,7 +23,7 @@ export function getMenuHTML(icon: string, text: string): string {
     `;
 }
 
-export function generateSiyuanId() {
+export function generateTimeString() {
     const now = new Date();
 
     const year = now.getFullYear().toString();
@@ -33,26 +33,45 @@ export function generateSiyuanId() {
     const minutes = now.getMinutes().toString().padStart(2, '0');
     const seconds = now.getSeconds().toString().padStart(2, '0');
 
-    const timestamp = `${year}${month}${day}${hours}${minutes}${seconds}`;
+    return `${year}${month}${day}${hours}${minutes}${seconds}`;
+}
+
+export function generateRandomString() {
 
     const characters = 'abcdefghijklmnopqrstuvwxyz';
     let random = '';
     for (let i = 0; i < 7; i++) {
         random += characters.charAt(Math.floor(Math.random() * characters.length));
     }
+    return random;
 
-    return `${timestamp}-${random}`;
 }
 
-export function idToPath(id: string) {
-    return DATA_PATH + id + '.svg';
+export function IDsToAssetPath(fileID: string, syncID: string) {
+    return `${ASSETS_PATH}${fileID}-${syncID}.svg`
+}
+export function assetPathToIDs(assetPath: string): { fileID: string; syncID: string } | null {
+
+    const filename = assetPath.split('/').pop() || '';
+    if (!filename.endsWith('.svg')) return null;
+
+    // Split into [basename, extension] and check format
+    const [basename] = filename.split('.');
+    const parts = basename.split('-');
+
+    // Must contain exactly 2 hyphens separating 3 non-empty parts
+    if (parts.length !== 3 || !parts[0] || !parts[1] || !parts[2]) return null;
+
+    return {
+        fileID: parts[0],
+        syncID: parts[1] + '-' + parts[2]
+    };
+
 }
 
-//    [Edit](siyuan://plugins/siyuan-jsdraw-pluginwhiteboard/?icon=iconDraw&title=Drawing&data={"id":"${id}"})
-//     ![Drawing](assets/${id}.svg)
-export function getPreviewHTML(path: string): string {
+export function getMarkdownBlock(fileID: string, syncID: string): string {
     return `
-    <iframe src="${EMBED_PATH + path}&antiCache=0"></iframe>
+    ![Drawing](${IDsToAssetPath(fileID, syncID)})
     `
 }
 
@@ -74,20 +93,13 @@ export function findImgSrc(element: HTMLElement): string | null {
     return null;
 }
 
-export function imgSrcToPath(imgSrc: string | null): string | null {
+export function imgSrcToIDs(imgSrc: string | null): { fileID: string; syncID: string } | null {
+
     if (!imgSrc) return null;
 
     const url = new URL(imgSrc);
     imgSrc = decodeURIComponent(url.pathname);
 
-    if(imgSrc.startsWith('/assets/')) {
-        return imgSrc.substring(1);
-    }
-    return null
+    return assetPathToIDs(imgSrc);
 
-}
-
-// Helper to safely escape regex special characters
-export function escapeRegExp(string: string) {
-    return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
 }
\ No newline at end of file
diff --git a/src/index.ts b/src/index.ts
index a3d8d43..78f1768 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,16 +1,15 @@
 import {Plugin, Protyle} from 'siyuan';
 import {
-    getPreviewHTML,
+    getMarkdownBlock,
     loadIcons,
     getMenuHTML,
-    generateSiyuanId,
     findImgSrc,
-    imgSrcToPath
+    imgSrcToIDs, generateTimeString, generateRandomString
 } from "@/helper";
 import {editorTabInit, openEditorTab} from "@/editorTab";
-import {ASSETS_PATH} from "@/const";
 
 export default class DrawJSPlugin extends Plugin {
+
     onload() {
 
         loadIcons(this);
@@ -24,22 +23,21 @@ export default class DrawJSPlugin extends Plugin {
             filter: ["Insert Drawing", "Add drawing", "whiteboard", "freehand", "graphics", "jsdraw"],
             html: getMenuHTML("iconDraw", this.i18n.insertDrawing),
             callback: (protyle: Protyle) => {
-                const path = ASSETS_PATH + generateSiyuanId() + ".svg";
-                protyle.insert(getPreviewHTML(path), true, false);
-                openEditorTab(this, path);
+                const fileID = generateRandomString();
+                const syncID = generateTimeString() + '-' + generateRandomString();
+                protyle.insert(getMarkdownBlock(fileID, syncID), true, false);
+                openEditorTab(this, fileID, syncID);
             }
         }];
 
         this.eventBus.on("open-menu-image", (e: any) => {
-            const path = imgSrcToPath(findImgSrc(e.detail.element));
-            if(path === null) {
-                return;
-            }
+            const ids = imgSrcToIDs(findImgSrc(e.detail.element));
+            if(ids === null) return;
             e.detail.menu.addItem({
                 icon: "iconDraw",
                 label: "Edit with js-draw",
                 click: () => {
-                    openEditorTab(this, path);
+                    openEditorTab(this, ids.fileID, ids.syncID);
                 }
             })
         })
diff --git a/src/protyle.ts b/src/protyle.ts
index cda24f9..292221c 100644
--- a/src/protyle.ts
+++ b/src/protyle.ts
@@ -1,5 +1,5 @@
 import {getBlockByID, sql, updateBlock} from "@/api";
-import {DUMMY_HOST} from "@/const";
+import {IDsToAssetPath} from "@/helper";
 
 export async function findImageBlocks(src: string) {
 
@@ -45,9 +45,9 @@ export async function replaceBlockContent(
     }
 }
 
-export async function replaceAntiCacheID(src: string) {
+export async function replaceSyncID(fileID: string, oldSyncID: string, newSyncID: string) {
 
-    const search = encodeURI(src); // the API uses URI-encoded
+    const search = encodeURI(IDsToAssetPath(fileID, oldSyncID)); // the API uses URI-encoded
     // find blocks containing that image
     const blocks = await findImageBlocks(search);
 
@@ -61,9 +61,7 @@ export async function replaceAntiCacheID(src: string) {
             .filter(source => source.startsWith(search)) // discard other images
 
         for(const source of sources) {
-            const url = new URL(source, DUMMY_HOST);
-            url.searchParams.set('antiCache', Date.now().toString()); // set or replace antiCache
-            const newSource =  url.href.replace(DUMMY_HOST, '');
+            const newSource = IDsToAssetPath(fileID, newSyncID);
             await replaceBlockContent(block.id, source, newSource);
         }
     }