From 6bca12c9341ef59c54394f5564bb77ae6f3f8491 Mon Sep 17 00:00:00 2001 From: MassiveBox Date: Fri, 11 Apr 2025 18:42:45 +0200 Subject: [PATCH] File refactoring --- src/const.ts | 6 +- src/editor.ts | 45 ++++++++------- src/file.ts | 142 +++++++++++++++++++++++++++++++++-------------- src/helper.ts | 5 +- src/migration.ts | 16 ++++-- 5 files changed, 142 insertions(+), 72 deletions(-) diff --git a/src/const.ts b/src/const.ts index 2f8e88a..b7d60aa 100644 --- a/src/const.ts +++ b/src/const.ts @@ -2,8 +2,8 @@ export const SVG_MIME = "image/svg+xml"; export const JSON_MIME = "application/json"; export const DATA_PATH = "/data/"; export const ASSETS_PATH = "assets/"; -export const STORAGE_PATH = DATA_PATH + "storage/petal/siyuan-jsdraw-plugin"; -export const TOOLBAR_PATH = STORAGE_PATH + "/toolbar.json"; -export const CONFIG_PATH = STORAGE_PATH + "/conf.json"; +export const STORAGE_PATH = "/data/storage/petal/siyuan-jsdraw-plugin/"; +export const TOOLBAR_FILENAME = "toolbar.json"; +export const CONFIG_FILENAME = "conf.json"; export const EMBED_PATH = "/plugins/siyuan-jsdraw-plugin/webapp/?path="; export const DUMMY_HOST = "https://dummy.host/"; \ No newline at end of file diff --git a/src/editor.ts b/src/editor.ts index c744a2f..208bcc5 100644 --- a/src/editor.ts +++ b/src/editor.ts @@ -1,17 +1,18 @@ import {MaterialIconProvider} from "@js-draw/material-icons"; -import {getFile, saveFile, uploadAsset} from "@/file"; -import {DATA_PATH, JSON_MIME, SVG_MIME, TOOLBAR_PATH} from "@/const"; -import {IDsToAssetPath} from "@/helper"; +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 {removeFile} from "@/api"; export class PluginEditor { private readonly element: HTMLElement; private readonly editor: Editor; + private drawingFile: PluginAsset; + private toolbarFile: PluginFile; + private readonly fileID: string; private syncID: string; private readonly initialSyncID: string; @@ -34,12 +35,13 @@ export class PluginEditor { this.initialSyncID = initialSyncID; this.syncID = initialSyncID; - this.genToolbar() + this.genToolbar(); // restore drawing - getFile(DATA_PATH +IDsToAssetPath(fileID, initialSyncID)).then(svg => { - if(svg != null) { - this.editor.loadFromSVG(svg); + this.drawingFile = new PluginAsset(fileID, initialSyncID, SVG_MIME); + this.drawingFile.loadFromSiYuanFS().then(() => { + if(this.drawingFile.getContent() != null) { + this.editor.loadFromSVG(this.drawingFile.getContent()); } }); @@ -52,11 +54,13 @@ export class PluginEditor { const toolbar = this.editor.addToolbar(); - // restore toolbar state - const toolbarState = await getFile(TOOLBAR_PATH); - if (toolbarState != null) { - toolbar.deserializeState(toolbarState); - } + // restore toolbarFile state + this.toolbarFile = new PluginFile(STORAGE_PATH, TOOLBAR_FILENAME, JSON_MIME); + this.toolbarFile.loadFromSiYuanFS().then(() => { + if(this.toolbarFile.getContent() != null) { + toolbar.deserializeState(this.toolbarFile.getContent()); + } + }); // save button const saveButton = toolbar.addSaveButton(async () => { @@ -65,7 +69,8 @@ export class PluginEditor { // save toolbar config on tool change (toolbar state is not saved in SVGs!) this.editor.notifier.on(EditorEventType.ToolUpdated, () => { - saveFile(TOOLBAR_PATH, JSON_MIME, toolbar.serializeState()); + this.toolbarFile.setContent(toolbar.serializeState()); + this.toolbarFile.save(); }); } @@ -77,18 +82,20 @@ export class PluginEditor { const oldSyncID = this.syncID; try { - newSyncID = (await uploadAsset(this.fileID, SVG_MIME, svgElem.outerHTML)).syncID; - if(newSyncID != oldSyncID) { - const changed = await replaceSyncID(this.fileID, oldSyncID, newSyncID); + this.drawingFile.setContent(svgElem.outerHTML); + await this.drawingFile.save(); + newSyncID = this.drawingFile.getSyncID(); + if(newSyncID != oldSyncID) { // supposed to replace protyle + const changed = await replaceSyncID(this.fileID, oldSyncID, newSyncID); // try to change protyle if(!changed) { alert( "Error replacing old sync ID with new one! You may need to manually replace the file path." + "\nTry saving the drawing again. This is a bug, please open an issue as soon as you can." + "\nIf your document doesn't show the drawing, you can recover it from the SiYuan workspace directory." ); - return; + return; // don't delete old drawing if protyle unchanged (could cause confusion) } - await removeFile(DATA_PATH + IDsToAssetPath(this.fileID, oldSyncID)); + await this.drawingFile.removeOld(oldSyncID); } saveButton.setDisabled(true); setTimeout(() => { // @todo improve save button feedback diff --git a/src/file.ts b/src/file.ts index 79e0e48..dc2a86c 100644 --- a/src/file.ts +++ b/src/file.ts @@ -1,52 +1,108 @@ -import {getFileBlob, putFile, upload} from "@/api"; -import {ASSETS_PATH} from "@/const"; -import {assetPathToIDs} from "@/helper"; +import {getFileBlob, putFile, removeFile, upload} from "@/api"; +import {ASSETS_PATH, DATA_PATH} from "@/const"; +import {assetPathToIDs, IDsToAssetName} from "@/helper"; -function toFile(title: string, content: string, mimeType: string){ - const blob = new Blob([content], { type: mimeType }); - return new File([blob], title, { type: mimeType }); -} +abstract class PluginFileBase { -// upload asset to the assets folder, return fileID and syncID -export async function uploadAsset(fileID: string, mimeType: string, content: string) { + protected content: string | null; - const file = toFile(fileID + ".svg", content, mimeType); + protected fileName: string; + protected folderPath: string; + protected mimeType: string; - let r = await upload('/' + ASSETS_PATH, [file]); - if(r.errFiles) { - throw new Error("Failed to upload file"); - } - return assetPathToIDs(r.succMap[file.name]); + getContent() { return this.content; } + setContent(content: string) { this.content = content; } + setFileName(fileName: string) { this.fileName = fileName; } -} - -export function saveFile(path: string, mimeType: string, content: string) { - - const file = toFile(path.split('/').pop(), content, mimeType); - - try { - putFile(path, false, file); - } catch (error) { - console.error("Error saving file:", error); - throw error; - } - -} - -export async function getFile(path: string) { - - const blob = await getFileBlob(path); - const jsonText = await blob.text(); - - // if we got a 404 api response, we will return null - try { - const res = JSON.parse(jsonText); - if(res.code == 404) { - return null; + private setFolderPath(folderPath: string) { + if(folderPath.startsWith('/') && folderPath.endsWith('/')) { + this.folderPath = folderPath; + }else{ + throw new Error("folderPath must start and end with /"); } - }catch {} + } - // js-draw expects a string! - return jsonText; + // folderPath must start and end with / + constructor(folderPath: string, fileName: string, mimeType: string) { + this.setFolderPath(folderPath); + this.fileName = fileName; + this.mimeType = mimeType; + } + + async loadFromSiYuanFS() { + const blob = await getFileBlob(this.folderPath + this.fileName); + const text = await blob.text(); + + try { + const res = JSON.parse(text); + if(res.code == 404) { + this.content = null; + return; + } + }catch {} + + this.content = text; + } + + async remove(customFilename?: string) { + let filename = customFilename || this.fileName; + await removeFile(this.folderPath + filename); + } + + protected toFile(customFilename?: string): File { + let filename = customFilename || this.fileName; + const blob = new Blob([this.content], { type: this.mimeType }); + return new File([blob], filename, { type: this.mimeType }); + } } + +export class PluginFile extends PluginFileBase { + + async save() { + const file = this.toFile(); + try { + await putFile(this.folderPath + this.fileName, false, file); + } catch (error) { + console.error("Error saving file:", error); + throw error; + } + } + +} + +export class PluginAsset extends PluginFileBase { + + private fileID: string + private syncID: string + + getFileID() { return this.fileID; } + getSyncID() { return this.syncID; } + + constructor(fileID: string, syncID: string, mimeType: string) { + super(DATA_PATH + ASSETS_PATH, IDsToAssetName(fileID, syncID), mimeType); + this.fileID = fileID; + this.syncID = syncID; + } + + async save() { + + const file = this.toFile(this.fileID + '.svg'); + + let r = await upload('/' + ASSETS_PATH, [file]); + if (r.errFiles) { + throw new Error("Failed to upload file"); + } + const ids = assetPathToIDs(r.succMap[file.name]) + + this.fileID = ids.fileID; + this.syncID = ids.syncID; + super.setFileName(IDsToAssetName(this.fileID, this.syncID)); + + } + + async removeOld(oldSyncID: string) { + await super.remove(IDsToAssetName(this.fileID, oldSyncID)); + } + +} \ No newline at end of file diff --git a/src/helper.ts b/src/helper.ts index ea4524d..22c3e8c 100644 --- a/src/helper.ts +++ b/src/helper.ts @@ -47,8 +47,11 @@ export function generateRandomString() { } +export function IDsToAssetName(fileID: string, syncID: string) { + return `${fileID}-${syncID}.svg`; +} export function IDsToAssetPath(fileID: string, syncID: string) { - return `${ASSETS_PATH}${fileID}-${syncID}.svg` + return `${ASSETS_PATH}${IDsToAssetName(fileID, syncID)}` } export function assetPathToIDs(assetPath: string): { fileID: string; syncID: string } | null { diff --git a/src/migration.ts b/src/migration.ts index 6ec3eac..2829aad 100644 --- a/src/migration.ts +++ b/src/migration.ts @@ -1,5 +1,5 @@ import {sql} from "@/api"; -import {getFile, uploadAsset} from "@/file"; +import {PluginAsset, PluginFile} from "@/file"; import {ASSETS_PATH, DATA_PATH, SVG_MIME} from "@/const"; import {replaceBlockContent} from "@/protyle"; import {generateRandomString, getMarkdownBlock} from "@/helper"; @@ -13,11 +13,15 @@ export async function migrate() { for(const block of blocks) { const oldFileID = extractID(block.markdown); if(oldFileID) { - const newFileID = generateRandomString() + "-" + oldFileID; - const file = await getFile(DATA_PATH + ASSETS_PATH + oldFileID + ".svg"); - const r = await uploadAsset(newFileID, SVG_MIME, file); - const newMarkdown = getMarkdownBlock(r.fileID, r.syncID); - await replaceBlockContent(block.id, block.markdown, newMarkdown); + const oldFile = new PluginFile(DATA_PATH + ASSETS_PATH, oldFileID + '.svg', SVG_MIME); + await oldFile.loadFromSiYuanFS(); + const newFile = new PluginAsset(generateRandomString(), oldFileID, SVG_MIME); + newFile.setContent(oldFile.getContent()); + await newFile.save(); + const newMarkdown = getMarkdownBlock(newFile.getFileID(), newFile.getSyncID()); + if(await replaceBlockContent(block.id, block.markdown, newMarkdown)) { + await oldFile.remove(); + } } }