Internal file paths are now Markdown image paths

In the last commit, file paths were implemented to be the full path for the API, however that is not necessary.
Before this change: /data/assets/filename.svg
After: assets/filename.svg
The new method is what SiYuan uses for Markdown images, like ![Drawing](assets/filename.svg)
This commit is contained in:
MassiveBox 2025-04-03 00:12:36 +02:00
parent a2503d5def
commit 5e51589ffa
Signed by: massivebox
GPG key ID: 9B74D3A59181947D
8 changed files with 66 additions and 14 deletions

View file

@ -2,7 +2,7 @@ function copyEditLink(path) {
navigator.clipboard.writeText(getEditLink(path)); navigator.clipboard.writeText(getEditLink(path));
} }
function copyImageLink(path) { function copyImageLink(path) {
navigator.clipboard.writeText(`![Drawing](${path.replace("/data/", "")})`); navigator.clipboard.writeText(`![Drawing](${path})`);
} }
function refreshPage() { function refreshPage() {

View file

@ -29,7 +29,7 @@ async function getFile(path) {
async function getSVG(path) { async function getSVG(path) {
const resp = await getFile(path); const resp = await getFile("/data/" + path);
if(resp == null) { if(resp == null) {
return FALLBACK; return FALLBACK;
} }

View file

@ -8,7 +8,7 @@
let path = urlParams.get('path'); let path = urlParams.get('path');
if(path === null) { if(path === null) {
const fileID = urlParams.get('id'); // legacy support const fileID = urlParams.get('id'); // legacy support
path = "/data/assets/" + fileID + ".svg"; path = "assets/" + fileID + ".svg";
} }
document.addEventListener('DOMContentLoaded', async () => { document.addEventListener('DOMContentLoaded', async () => {

View file

@ -1,7 +1,8 @@
export const SVG_MIME = "image/svg+xml"; export const SVG_MIME = "image/svg+xml";
export const JSON_MIME = "application/json"; export const JSON_MIME = "application/json";
export const DATA_PATH = "/data/assets/"; export const DATA_PATH = "/data/";
export const STORAGE_PATH = "/data/storage/petal/siyuan-jsdraw-plugin"; 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 TOOLBAR_PATH = STORAGE_PATH + "/toolbar.json";
export const CONFIG_PATH = STORAGE_PATH + "/conf.json"; export const CONFIG_PATH = STORAGE_PATH + "/conf.json";
export const EMBED_PATH = "/plugins/siyuan-jsdraw-plugin/webapp/?path="; export const EMBED_PATH = "/plugins/siyuan-jsdraw-plugin/webapp/?path=";

View file

@ -3,7 +3,7 @@ import Editor, {BaseWidget, EditorEventType} from "js-draw";
import { MaterialIconProvider } from '@js-draw/material-icons'; import { MaterialIconProvider } from '@js-draw/material-icons';
import 'js-draw/styles'; import 'js-draw/styles';
import {getFile, saveFile} from "@/file"; import {getFile, saveFile} from "@/file";
import {JSON_MIME, SVG_MIME, TOOLBAR_PATH} from "@/const"; import {DATA_PATH, JSON_MIME, SVG_MIME, TOOLBAR_PATH} from "@/const";
import {idToPath} from "@/helper"; import {idToPath} from "@/helper";
export function openEditorTab(p: Plugin, path: string) { export function openEditorTab(p: Plugin, path: string) {
@ -21,7 +21,7 @@ export function openEditorTab(p: Plugin, path: string) {
async function saveCallback(editor: Editor, path: string, saveButton: BaseWidget) { async function saveCallback(editor: Editor, path: string, saveButton: BaseWidget) {
const svgElem = editor.toSVG(); const svgElem = editor.toSVG();
try { try {
saveFile(path, SVG_MIME, svgElem.outerHTML); saveFile(DATA_PATH + path, SVG_MIME, svgElem.outerHTML);
saveButton.setDisabled(true); saveButton.setDisabled(true);
setTimeout(() => { // @todo improve save button feedback setTimeout(() => { // @todo improve save button feedback
saveButton.setDisabled(false); saveButton.setDisabled(false);
@ -59,7 +59,7 @@ export function createEditor(i: ITabModel) {
} }
}); });
// restore drawing // restore drawing
getFile(path).then(svg => { getFile(DATA_PATH + path).then(svg => {
if(svg != null) { if(svg != null) {
editor.loadFromSVG(svg); editor.loadFromSVG(svg);
} }

View file

@ -74,15 +74,20 @@ export function findImgSrc(element: HTMLElement): string | null {
return null; return null;
} }
export function imgSrcToAbsolutePath(imgSrc: string | null): string | null { export function imgSrcToPath(imgSrc: string | null): string | null {
if (!imgSrc) return null; if (!imgSrc) return null;
const url = new URL(imgSrc); const url = new URL(imgSrc);
imgSrc = decodeURIComponent(url.pathname); imgSrc = decodeURIComponent(url.pathname);
if(imgSrc.startsWith('/assets/')) { if(imgSrc.startsWith('/assets/')) {
return "/data" + imgSrc; return imgSrc.substring(1);
} }
return null return null
} }
// Helper to safely escape regex special characters
export function escapeRegExp(string: string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}

View file

@ -5,10 +5,10 @@ import {
getMenuHTML, getMenuHTML,
generateSiyuanId, generateSiyuanId,
findImgSrc, findImgSrc,
imgSrcToAbsolutePath imgSrcToPath
} from "@/helper"; } from "@/helper";
import {createEditor, openEditorTab} from "@/editorTab"; import {createEditor, openEditorTab} from "@/editorTab";
import {DATA_PATH} from "@/const"; import {ASSETS_PATH} from "@/const";
export default class DrawJSPlugin extends Plugin { export default class DrawJSPlugin extends Plugin {
onload() { onload() {
@ -26,14 +26,14 @@ export default class DrawJSPlugin extends Plugin {
filter: ["Insert Drawing", "Add drawing", "whiteboard", "freehand", "graphics", "jsdraw"], filter: ["Insert Drawing", "Add drawing", "whiteboard", "freehand", "graphics", "jsdraw"],
html: getMenuHTML("iconDraw", this.i18n.insertDrawing), html: getMenuHTML("iconDraw", this.i18n.insertDrawing),
callback: (protyle: Protyle) => { callback: (protyle: Protyle) => {
const path = DATA_PATH + generateSiyuanId() + ".svg"; const path = ASSETS_PATH + generateSiyuanId() + ".svg";
protyle.insert(getPreviewHTML(path), true, false); protyle.insert(getPreviewHTML(path), true, false);
openEditorTab(this, path); openEditorTab(this, path);
} }
}]; }];
this.eventBus.on("open-menu-image", (e: any) => { this.eventBus.on("open-menu-image", (e: any) => {
const path = imgSrcToAbsolutePath(findImgSrc(e.detail.element)); const path = imgSrcToPath(findImgSrc(e.detail.element));
if(path === null) { if(path === null) {
return; return;
} }

46
src/protyle.ts Normal file
View file

@ -0,0 +1,46 @@
import {getBlockByID, sql, updateBlock} from "@/api";
import {escapeRegExp} from "@/helper";
export async function findImageBlocks(src: string) {
const sqlQuery = `
SELECT id, markdown
FROM blocks
WHERE markdown like '%${src}%'
`;
try {
return await sql(sqlQuery);
} catch (error) {
console.error('Error searching for image blocks:', error);
return [];
}
}
export async function replaceBlockContent(
blockId: string,
searchStr: string,
replaceStr: string
): Promise<boolean> {
try {
const block = await getBlockByID(blockId);
if (!block) {
throw new Error('Block not found');
}
const originalContent = block.markdown;
const newContent = originalContent.replace(escapeRegExp(searchStr), replaceStr);
if (newContent === originalContent) {
return false;
}
await updateBlock('markdown', newContent, blockId);
return true;
} catch (error) {
console.error('Failed to replace block content:', error);
return false;
}
}