From 7e4da82b821b3d02f4a3c0d4974c5adb83b1747e Mon Sep 17 00:00:00 2001 From: MassiveBox Date: Thu, 17 Apr 2025 15:16:07 +0200 Subject: [PATCH] Get initial Sync ID from protyle Related to issue #9 --- src/editor.ts | 50 ++++++++++++++++++++------------------------------ src/index.ts | 4 ++-- src/protyle.ts | 47 +++++++++++++++++++++++++++++++++++++++++------ 3 files changed, 63 insertions(+), 38 deletions(-) diff --git a/src/editor.ts b/src/editor.ts index 208bcc5..17f9c86 100644 --- a/src/editor.ts +++ b/src/editor.ts @@ -3,7 +3,7 @@ import {PluginAsset, PluginFile} from "@/file"; import {JSON_MIME, STORAGE_PATH, SVG_MIME, TOOLBAR_FILENAME} from "@/const"; import Editor, {BaseWidget, EditorEventType} from "js-draw"; import {Dialog, Plugin, openTab, getFrontend} from "siyuan"; -import {replaceSyncID} from "@/protyle"; +import {findSyncIDInProtyle, replaceSyncID} from "@/protyle"; export class PluginEditor { @@ -15,15 +15,15 @@ export class PluginEditor { private readonly fileID: string; private syncID: string; - private readonly initialSyncID: string; getElement(): HTMLElement { return this.element; } getEditor(): Editor { return this.editor; } getFileID(): string { return this.fileID; } getSyncID(): string { return this.syncID; } - getInitialSyncID(): string { return this.initialSyncID; } - constructor(fileID: string, initialSyncID: string) { + constructor(fileID: string) { + + this.fileID = fileID; this.element = document.createElement("div"); this.element.style.height = '100%'; @@ -31,22 +31,20 @@ export class PluginEditor { iconProvider: new MaterialIconProvider(), }); - this.fileID = fileID; - this.initialSyncID = initialSyncID; - this.syncID = initialSyncID; - - this.genToolbar(); - - // restore drawing - this.drawingFile = new PluginAsset(fileID, initialSyncID, SVG_MIME); - this.drawingFile.loadFromSiYuanFS().then(() => { - if(this.drawingFile.getContent() != null) { - this.editor.loadFromSVG(this.drawingFile.getContent()); - } + this.genToolbar().then(() => { + this.editor.dispatch(this.editor.setBackgroundStyle({ autoresize: true }), false); + this.editor.getRootElement().style.height = '100%'; }); - this.editor.dispatch(this.editor.setBackgroundStyle({ autoresize: true }), false); - this.editor.getRootElement().style.height = '100%'; + findSyncIDInProtyle(this.fileID).then(async (syncID) => { + this.syncID = syncID; + // restore drawing + this.drawingFile = new PluginAsset(this.fileID, syncID, SVG_MIME); + await this.drawingFile.loadFromSiYuanFS(); + if(this.drawingFile.getContent() != null) { + await this.editor.loadFromSVG(this.drawingFile.getContent()); + } + }); } @@ -127,24 +125,17 @@ export class EditorManager { 'type': "whiteboard", init() { const fileID = this.data.fileID; - const initialSyncID = this.data.initialSyncID; - if (fileID == null || initialSyncID == null) { - alert("File or Sync ID and path missing - couldn't open file.") + if (fileID == null) { + alert("File ID missing - couldn't open file.") return; } - const editor = new PluginEditor(fileID, initialSyncID); + const editor = new PluginEditor(fileID); this.element.appendChild(editor.getElement()); } }); } toTab(p: Plugin) { - for(const tab of p.getOpenedTab()["whiteboard"]) { - if(tab.data.fileID == this.editor.getFileID()) { - alert("File is already open in another editor tab!"); - return; - } - } openTab({ app: p.app, custom: { @@ -153,7 +144,6 @@ export class EditorManager { id: "siyuan-jsdraw-pluginwhiteboard", data: { fileID: this.editor.getFileID(), - initialSyncID: this.editor.getInitialSyncID() } } }); @@ -168,7 +158,7 @@ export class EditorManager { dialog.element.querySelector("#DrawingPanel").appendChild(this.editor.getElement()); } - open(p: Plugin) { + async open(p: Plugin) { if(getFrontend() != "mobile") { this.toTab(p); } else { diff --git a/src/index.ts b/src/index.ts index 2bd05c8..1e0777a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -34,7 +34,7 @@ export default class DrawJSPlugin extends Plugin { const fileID = generateRandomString(); const syncID = generateTimeString() + '-' + generateRandomString(); protyle.insert(getMarkdownBlock(fileID, syncID), true, false); - new EditorManager(new PluginEditor(fileID, syncID)).open(this) + new EditorManager(new PluginEditor(fileID)).open(this); } }]; @@ -46,7 +46,7 @@ export default class DrawJSPlugin extends Plugin { label: "Edit with js-draw", click: () => { void this.analytics.sendEvent('edit'); - new EditorManager(new PluginEditor(ids.fileID, ids.syncID)).open(this) + new EditorManager(new PluginEditor(ids.fileID)).open(this) } }) }) diff --git a/src/protyle.ts b/src/protyle.ts index b3b8905..2914b3c 100644 --- a/src/protyle.ts +++ b/src/protyle.ts @@ -1,5 +1,37 @@ import {getBlockByID, sql, updateBlock} from "@/api"; -import {IDsToAssetPath} from "@/helper"; +import {assetPathToIDs, IDsToAssetPath} from "@/helper"; + +export async function findSyncIDInProtyle(fileID: string, iter?: number): Promise { + + const search = `assets/${fileID}-`; + const blocks = await findImageBlocks(search); + + let syncID = null; + + for(const block of blocks) { + const sources = extractImageSourcesFromMarkdown(block.markdown, search); + for(const source of sources) { + const ids = assetPathToIDs(source); + if(syncID == null) { + syncID = ids.syncID; + }else if(ids.syncID !== syncID) { + throw new Error("Multiple syncIDs found"); + } + } + } + + if(!iter) iter = 0; + if(syncID == null) { + // when the block has just been created, we need to wait a bit before it can be found + if(iter < 4) { // cap max time at 2s, it should be ok by then + await new Promise(resolve => setTimeout(resolve, 500)); + return await findSyncIDInProtyle(fileID, iter + 1); + } + } + + return syncID; + +} export async function findImageBlocks(src: string) { @@ -45,6 +77,13 @@ export async function replaceBlockContent( } } +function extractImageSourcesFromMarkdown(markdown: string, mustStartWith?: string) { + const imageRegex = /!\[.*?\]\((.*?)\)/g; // only get images + return Array.from(markdown.matchAll(imageRegex)) + .map(match => match[1]) + .filter(source => source.startsWith(mustStartWith)) // discard other images +} + export async function replaceSyncID(fileID: string, oldSyncID: string, newSyncID: string) { const search = encodeURI(IDsToAssetPath(fileID, oldSyncID)); // the API uses URI-encoded @@ -56,12 +95,8 @@ export async function replaceSyncID(fileID: string, oldSyncID: string, newSyncID // get all the image sources, with parameters const markdown = block.markdown; - const imageRegex = /!\[.*?\]\((.*?)\)/g; // only get images - const sources = Array.from(markdown.matchAll(imageRegex)) - .map(match => match[1]) - .filter(source => source.startsWith(search)) // discard other images - for(const source of sources) { + for(const source of extractImageSourcesFromMarkdown(markdown, search)) { const newSource = IDsToAssetPath(fileID, newSyncID); const changed = await replaceBlockContent(block.id, source, newSource); if(!changed) return false