mirror of
https://github.com/zsviczian/obsidian-excalidraw-plugin.git
synced 2025-08-06 05:46:28 +00:00
Image click navigation. Image embeds finalized.
This commit is contained in:
6
TODO.md
6
TODO.md
@@ -4,9 +4,9 @@
|
||||
[x] update code to adopt change files moving from AppState to App
|
||||
- Add "files" to legacy excalidraw export
|
||||
|
||||
[ ] PNG preview
|
||||
[ ] markdown embed SVG 190
|
||||
[ ] markdown embed PNG
|
||||
[x] PNG preview
|
||||
[x] markdown embed SVG 190
|
||||
[x] markdown embed PNG
|
||||
[ ] embed Excalidraw into other Excalidraw
|
||||
|
||||
|
||||
|
||||
@@ -9,8 +9,8 @@ import {
|
||||
normalizePath,
|
||||
TFile
|
||||
} from "obsidian"
|
||||
import ExcalidrawView from "./ExcalidrawView";
|
||||
import { getJSON, getSVGString } from "./ExcalidrawData";
|
||||
import ExcalidrawView, { TextMode } from "./ExcalidrawView";
|
||||
import { ExcalidrawData, getJSON, getSVGString } from "./ExcalidrawData";
|
||||
import {
|
||||
FRONTMATTER,
|
||||
nanoid,
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
VIEW_TYPE_EXCALIDRAW,
|
||||
MAX_IMAGE_SIZE
|
||||
} from "./constants";
|
||||
import { generateSVGString, getObsidianImage, getPNG, getSVG, wrapText } from "./Utils";
|
||||
import { embedFontsInSVG, generateSVGString, getObsidianImage, getPNG, getSVG, loadSceneFiles, svgToBase64, wrapText } from "./Utils";
|
||||
import { AppState } from "@zsviczian/excalidraw/types/types";
|
||||
|
||||
declare type ConnectionPoint = "top"|"bottom"|"left"|"right";
|
||||
@@ -283,7 +283,7 @@ export async function initExcalidrawAutomate(plugin: ExcalidrawPlugin) {
|
||||
}
|
||||
}
|
||||
):Promise<string> {
|
||||
const template = params?.templatePath ? (await getTemplate(params.templatePath)) : null;
|
||||
const template = params?.templatePath ? (await getTemplate(params.templatePath,true)) : null;
|
||||
let elements = template ? template.elements : [];
|
||||
elements = elements.concat(this.getElements());
|
||||
let frontmatter:string;
|
||||
@@ -325,22 +325,24 @@ export async function initExcalidrawAutomate(plugin: ExcalidrawPlugin) {
|
||||
currentItemEndArrowhead: template?.appState?.currentItemEndArrowhead ?? this.style.endArrowHead,
|
||||
currentItemLinearStrokeSharpness: template?.appState?.currentItemLinearStrokeSharpness ?? this.style.strokeSharpness,
|
||||
gridSize: template?.appState?.gridSize ?? this.canvas.gridSize,
|
||||
files: template?.appState?.files ?? {},
|
||||
}
|
||||
},
|
||||
files: template?.files ?? {},
|
||||
};
|
||||
|
||||
return plugin.createDrawing(
|
||||
params?.filename ? params.filename + '.excalidraw.md' : this.plugin.getNextDefaultFilename(),
|
||||
params?.onNewPane ? params.onNewPane : false,
|
||||
params?.foldername ? params.foldername : this.plugin.settings.folder,
|
||||
frontmatter + await plugin.exportSceneToMD(
|
||||
JSON.stringify(scene,null,"\t"))
|
||||
this.plugin.settings.compatibilityMode
|
||||
? JSON.stringify(scene,null,"\t")
|
||||
: frontmatter + await plugin.exportSceneToMD(JSON.stringify(scene,null,"\t"))
|
||||
);
|
||||
},
|
||||
async createSVG(templatePath?:string,embedFont:boolean = false):Promise<SVGSVGElement> {
|
||||
const template = templatePath ? (await getTemplate(templatePath)) : null;
|
||||
const automateElements = this.getElements();
|
||||
const template = templatePath ? (await getTemplate(templatePath,true)) : null;
|
||||
let elements = template ? template.elements : [];
|
||||
elements = elements.concat(this.getElements());
|
||||
elements = elements.concat(automateElements);
|
||||
const svg = await getSVG(
|
||||
{//createDrawing
|
||||
type: "excalidraw",
|
||||
@@ -350,20 +352,21 @@ export async function initExcalidrawAutomate(plugin: ExcalidrawPlugin) {
|
||||
appState: {
|
||||
theme: template?.appState?.theme ?? this.canvas.theme,
|
||||
viewBackgroundColor: template?.appState?.viewBackgroundColor ?? this.canvas.viewBackgroundColor,
|
||||
files: template?.appState?.files ?? {}
|
||||
}
|
||||
},
|
||||
files: template?.files ?? {}
|
||||
},
|
||||
{
|
||||
withBackground: plugin.settings.exportWithBackground,
|
||||
withTheme: plugin.settings.exportWithTheme
|
||||
}
|
||||
)
|
||||
return embedFont ? ExcalidrawView.embedFontsInSVG(svg) : svg;
|
||||
return embedFont ? embedFontsInSVG(svg) : svg;
|
||||
},
|
||||
async createPNG(templatePath?:string, scale:number=1) {
|
||||
const template = templatePath ? (await getTemplate(templatePath)) : null;
|
||||
const automateElements = this.getElements();
|
||||
const template = templatePath ? (await getTemplate(templatePath,true)) : null;
|
||||
let elements = template ? template.elements : [];
|
||||
elements = elements.concat(this.getElements());
|
||||
elements = elements.concat(automateElements);
|
||||
return getPNG(
|
||||
{
|
||||
type: "excalidraw",
|
||||
@@ -373,8 +376,8 @@ export async function initExcalidrawAutomate(plugin: ExcalidrawPlugin) {
|
||||
appState: {
|
||||
theme: template?.appState?.theme ?? this.canvas.theme,
|
||||
viewBackgroundColor: template?.appState?.viewBackgroundColor ?? this.canvas.viewBackgroundColor,
|
||||
files: template?.appState?.files ?? {}
|
||||
}
|
||||
},
|
||||
files: template?.files ?? {}
|
||||
},
|
||||
{
|
||||
withBackground: plugin.settings.exportWithBackground,
|
||||
@@ -824,21 +827,40 @@ async function getTemplate(fileWithPath:string, loadFiles:boolean = false):Promi
|
||||
const vault = app.vault;
|
||||
const file = app.metadataCache.getFirstLinkpathDest(normalizePath(fileWithPath),'');
|
||||
if(file && file instanceof TFile) {
|
||||
const data = await vault.read(file);
|
||||
const data = (await vault.read(file)).replaceAll("\r\n","\n").replaceAll("\r","\n");
|
||||
const excalidrawData:ExcalidrawData = new ExcalidrawData(window.ExcalidrawAutomate.plugin);
|
||||
|
||||
if(file.extension === "excalidraw") {
|
||||
await excalidrawData.loadLegacyData(data,file);
|
||||
return {
|
||||
elements: excalidrawData.scene.elements,
|
||||
appState: excalidrawData.scene.appState,
|
||||
frontmatter: "",
|
||||
files: excalidrawData.scene.files,
|
||||
svgSnapshot: null,
|
||||
};
|
||||
}
|
||||
|
||||
const parsed = data.search("excalidraw-plugin: parsed\n")>-1 || data.search("excalidraw-plugin: locked\n")>-1; //locked for backward compatibility
|
||||
await excalidrawData.loadData(data,file,parsed ? TextMode.parsed : TextMode.raw)
|
||||
|
||||
let trimLocation = data.search("# Text Elements\n");
|
||||
if(trimLocation == -1) trimLocation = data.search("# Drawing\n");
|
||||
|
||||
const [scene,pos] = getJSON(data);
|
||||
const svgSnapshot = getSVGString(data.substr(pos+scene.length));
|
||||
if(loadFiles) {
|
||||
await loadSceneFiles(app,excalidrawData.files,(fileArray:any)=>{
|
||||
for(const f of fileArray) {
|
||||
excalidrawData.scene.files[f.id] = f;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const excalidrawData = JSON_parse(scene);
|
||||
return {
|
||||
elements: excalidrawData.elements,
|
||||
appState: excalidrawData.appState,
|
||||
elements: excalidrawData.scene.elements,
|
||||
appState: excalidrawData.scene.appState,
|
||||
frontmatter: data.substring(0,trimLocation),
|
||||
files: null,
|
||||
svgSnapshot: svgSnapshot
|
||||
files: excalidrawData.scene.files,
|
||||
svgSnapshot: excalidrawData.svgSnapshot
|
||||
};
|
||||
};
|
||||
return {
|
||||
|
||||
@@ -87,7 +87,7 @@ export function getSVGString(data:string):string {
|
||||
}
|
||||
|
||||
export class ExcalidrawData {
|
||||
public svgString: string = null;
|
||||
public svgSnapshot: string = null;
|
||||
private textElements:Map<string,{raw:string, parsed:string}> = null;
|
||||
public scene:any = null;
|
||||
private file:TFile = null;
|
||||
@@ -99,6 +99,7 @@ export class ExcalidrawData {
|
||||
private plugin: ExcalidrawPlugin;
|
||||
public loaded: boolean = false;
|
||||
public files:Map<FileId,string> = null; //fileId, path
|
||||
private compatibilityMode:boolean = false;
|
||||
|
||||
constructor(plugin: ExcalidrawPlugin) {
|
||||
this.plugin = plugin;
|
||||
@@ -116,6 +117,7 @@ export class ExcalidrawData {
|
||||
this.file = file;
|
||||
this.textElements = new Map<string,{raw:string, parsed:string}>();
|
||||
this.files.clear();
|
||||
this.compatibilityMode = false;
|
||||
|
||||
//I am storing these because if the settings change while a drawing is open parsing will run into errors during save
|
||||
//The drawing will use these values until next drawing is loaded or this drawing is re-loaded
|
||||
@@ -151,7 +153,7 @@ export class ExcalidrawData {
|
||||
this.scene.files = {}; //loading legacy scenes that do not yet have the files attribute.
|
||||
}
|
||||
|
||||
this.svgString = getSVGString(data.substr(pos+scene.length));
|
||||
this.svgSnapshot = getSVGString(data.substr(pos+scene.length));
|
||||
|
||||
data = data.substring(0,pos);
|
||||
|
||||
@@ -203,6 +205,7 @@ export class ExcalidrawData {
|
||||
}
|
||||
|
||||
public async loadLegacyData(data: string,file: TFile):Promise<boolean> {
|
||||
this.compatibilityMode = true;
|
||||
this.file = file;
|
||||
this.textElements = new Map<string,{raw:string, parsed:string}>();
|
||||
this.setShowLinkBrackets();
|
||||
@@ -485,7 +488,7 @@ export class ExcalidrawData {
|
||||
}
|
||||
outString += '\n';
|
||||
}
|
||||
return outString + this.plugin.getMarkdownDrawingSection(JSON.stringify(this.scene,null,"\t"),this.svgString);
|
||||
return outString + this.plugin.getMarkdownDrawingSection(JSON.stringify(this.scene,null,"\t"),this.svgSnapshot);
|
||||
}
|
||||
|
||||
private async syncFiles(scene:SceneDataWithFiles):Promise<boolean> {
|
||||
@@ -523,10 +526,12 @@ export class ExcalidrawData {
|
||||
}
|
||||
|
||||
public async syncElements(newScene:any):Promise<boolean> {
|
||||
//console.log("Excalidraw.Data.syncElements()");
|
||||
let result = await this.syncFiles(newScene);
|
||||
this.scene = newScene;//JSON_parse(newScene);
|
||||
this.scene.files = {};
|
||||
this.scene = newScene;
|
||||
let result = false;
|
||||
if(!this.compatibilityMode) {
|
||||
result = await this.syncFiles(newScene);
|
||||
this.scene.files = {};
|
||||
}
|
||||
result = result || this.setLinkPrefix() || this.setUrlPrefix() || this.setShowLinkBrackets();
|
||||
await this.updateTextElementsFromScene();
|
||||
return result || this.findNewTextElementsInScene();
|
||||
|
||||
@@ -36,7 +36,7 @@ import ExcalidrawPlugin from './main';
|
||||
import {ExcalidrawAutomate, repositionElementsToCursor} from './ExcalidrawAutomate';
|
||||
import { t } from "./lang/helpers";
|
||||
import { ExcalidrawData, REG_LINKINDEX_HYPERLINK, REGEX_LINK } from "./ExcalidrawData";
|
||||
import { checkAndCreateFolder, download, generateSVGString, getNewOrAdjacentLeaf, getNewUniqueFilepath, getObsidianImage, getPNG, getSVG, loadSceneFiles, rotatedDimensions, splitFolderAndFilename, svgToBase64, viewportCoordsToSceneCoords } from "./Utils";
|
||||
import { checkAndCreateFolder, download, embedFontsInSVG, generateSVGString, getNewOrAdjacentLeaf, getNewUniqueFilepath, getObsidianImage, getPNG, getSVG, loadSceneFiles, rotatedDimensions, splitFolderAndFilename, svgToBase64, viewportCoordsToSceneCoords } from "./Utils";
|
||||
import { Prompt } from "./Prompt";
|
||||
import { ClipboardData } from "@zsviczian/excalidraw/types/clipboard";
|
||||
|
||||
@@ -63,6 +63,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
private getScene: Function = null;
|
||||
public addElements: Function = null; //add elements to the active Excalidraw drawing
|
||||
private getSelectedTextElement: Function = null;
|
||||
private getSelectedImageElement: Function = null;
|
||||
public addText:Function = null;
|
||||
private refresh: Function = null;
|
||||
public excalidrawRef: React.MutableRefObject<any> = null;
|
||||
@@ -117,23 +118,12 @@ export default class ExcalidrawView extends TextFileView {
|
||||
const svg = await getSVG(scene,exportSettings);
|
||||
if(!svg) return;
|
||||
let serializer =new XMLSerializer();
|
||||
const svgString = serializer.serializeToString(ExcalidrawView.embedFontsInSVG(svg));
|
||||
const svgString = serializer.serializeToString(embedFontsInSVG(svg));
|
||||
if(file && file instanceof TFile) await this.app.vault.modify(file,svgString);
|
||||
else await this.app.vault.create(filepath,svgString);
|
||||
})();
|
||||
}
|
||||
|
||||
public static embedFontsInSVG(svg:SVGSVGElement):SVGSVGElement {
|
||||
//replace font references with base64 fonts
|
||||
const includesVirgil = svg.querySelector("text[font-family^='Virgil']") != null;
|
||||
const includesCascadia = svg.querySelector("text[font-family^='Cascadia']") != null;
|
||||
const defs = svg.querySelector("defs");
|
||||
if (defs && (includesCascadia || includesVirgil)) {
|
||||
defs.innerHTML = "<style>" + (includesVirgil ? VIRGIL_FONT : "") + (includesCascadia ? CASCADIA_FONT : "")+"</style>";
|
||||
}
|
||||
return svg;
|
||||
}
|
||||
|
||||
public savePNG(scene?: any) {
|
||||
if(!scene) {
|
||||
if (!this.getScene) return false;
|
||||
@@ -168,7 +158,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
await this.loadDrawing(false);
|
||||
}
|
||||
//generate SVG preview snapshot
|
||||
this.excalidrawData.svgString = await generateSVGString(this.getScene(),this.plugin.settings);
|
||||
this.excalidrawData.svgSnapshot = await generateSVGString(this.getScene(),this.plugin.settings);
|
||||
}
|
||||
await super.save();
|
||||
}
|
||||
@@ -180,12 +170,12 @@ export default class ExcalidrawView extends TextFileView {
|
||||
//console.log("ExcalidrawView.getViewData()");
|
||||
if(!this.getScene) return this.data;
|
||||
if(!this.excalidrawData.loaded) return this.data;
|
||||
const scene = this.getScene();
|
||||
if(!this.compatibilityMode) {
|
||||
let trimLocation = this.data.search(/(^%%\n)?# Text Elements\n/m);
|
||||
if(trimLocation == -1) trimLocation = this.data.search(/(%%\n)?# Drawing\n/);
|
||||
if(trimLocation == -1) return this.data;
|
||||
|
||||
const scene = this.excalidrawData.scene;
|
||||
if(!this.autosaving) {
|
||||
if(this.plugin.settings.autoexportSVG) this.saveSVG(scene);
|
||||
if(this.plugin.settings.autoexportPNG) this.savePNG(scene);
|
||||
@@ -197,7 +187,6 @@ export default class ExcalidrawView extends TextFileView {
|
||||
return header + this.excalidrawData.generateMD();
|
||||
}
|
||||
if(this.compatibilityMode) {
|
||||
const scene = this.excalidrawData.scene;
|
||||
if(!this.autosaving) {
|
||||
if(this.plugin.settings.autoexportSVG) this.saveSVG(scene);
|
||||
if(this.plugin.settings.autoexportPNG) this.savePNG(scene);
|
||||
@@ -208,62 +197,79 @@ export default class ExcalidrawView extends TextFileView {
|
||||
}
|
||||
|
||||
async handleLinkClick(view: ExcalidrawView, ev:MouseEvent) {
|
||||
let text:string = (this.textMode == TextMode.parsed)
|
||||
? this.excalidrawData.getRawText(this.getSelectedTextElement().id)
|
||||
: this.getSelectedTextElement().text;
|
||||
if(!text) {
|
||||
const selectedText = this.getSelectedTextElement();
|
||||
let file = null;
|
||||
let lineNum = 0;
|
||||
let linkText:string = null;
|
||||
|
||||
if(selectedText?.id) {
|
||||
linkText = (this.textMode == TextMode.parsed)
|
||||
? this.excalidrawData.getRawText(selectedText.id)
|
||||
: selectedText.text;
|
||||
|
||||
linkText = linkText.replaceAll("\n",""); //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/187
|
||||
if(linkText.match(REG_LINKINDEX_HYPERLINK)) {
|
||||
window.open(linkText,"_blank");
|
||||
return;
|
||||
}
|
||||
|
||||
const parts = REGEX_LINK.getRes(linkText).next();
|
||||
if(!parts.value) {
|
||||
const tags = linkText.matchAll(/#([\p{Letter}\p{Emoji_Presentation}\p{Number}\/_-]+)/ug).next();
|
||||
if(!tags.value || tags.value.length<2) {
|
||||
new Notice(t("TEXT_ELEMENT_EMPTY"),4000);
|
||||
return;
|
||||
}
|
||||
const search=this.app.workspace.getLeavesOfType("search");
|
||||
if(search.length==0) return;
|
||||
//@ts-ignore
|
||||
search[0].view.setQuery("tag:"+tags.value[1]);
|
||||
this.app.workspace.revealLeaf(search[0]);
|
||||
|
||||
if(document.fullscreenElement === this.contentEl) {
|
||||
document.exitFullscreen();
|
||||
this.zoomToFit();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
linkText = REGEX_LINK.getLink(parts);
|
||||
|
||||
if(linkText.match(REG_LINKINDEX_HYPERLINK)) {
|
||||
window.open(linkText,"_blank");
|
||||
return;
|
||||
}
|
||||
|
||||
if(linkText.search("#")>-1) {
|
||||
let t;
|
||||
[t,lineNum] = await this.excalidrawData.getTransclusion(linkText);
|
||||
linkText = linkText.substring(0,linkText.search("#"));
|
||||
}
|
||||
if(linkText.match(REG_LINKINDEX_INVALIDCHARS)) {
|
||||
new Notice(t("FILENAME_INVALID_CHARS"),4000);
|
||||
return;
|
||||
}
|
||||
file = view.app.metadataCache.getFirstLinkpathDest(linkText,view.file.path);
|
||||
if (!ev.altKey && !file) {
|
||||
new Notice(t("FILE_DOES_NOT_EXIST"), 4000);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
const selectedImage = this.getSelectedImageElement();
|
||||
if(selectedImage?.id) {
|
||||
await this.save(true); //in case pasted images haven't been saved yet
|
||||
if(this.excalidrawData.files.has(selectedImage.fileId)) {
|
||||
linkText = this.excalidrawData.files.get(selectedImage.fileId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!linkText) {
|
||||
new Notice(t("LINK_BUTTON_CLICK_NO_TEXT"),20000);
|
||||
return;
|
||||
}
|
||||
text = text.replaceAll("\n",""); //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/187
|
||||
if(text.match(REG_LINKINDEX_HYPERLINK)) {
|
||||
window.open(text,"_blank");
|
||||
return;
|
||||
}
|
||||
|
||||
const parts = REGEX_LINK.getRes(text).next();
|
||||
if(!parts.value) {
|
||||
const tags = text.matchAll(/#([\p{Letter}\p{Emoji_Presentation}\p{Number}\/_-]+)/ug).next();
|
||||
if(!tags.value || tags.value.length<2) {
|
||||
new Notice(t("TEXT_ELEMENT_EMPTY"),4000);
|
||||
return;
|
||||
}
|
||||
const search=this.app.workspace.getLeavesOfType("search");
|
||||
if(search.length==0) return;
|
||||
//@ts-ignore
|
||||
search[0].view.setQuery("tag:"+tags.value[1]);
|
||||
this.app.workspace.revealLeaf(search[0]);
|
||||
|
||||
if(document.fullscreenElement === this.contentEl) {
|
||||
document.exitFullscreen();
|
||||
this.zoomToFit();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
text = REGEX_LINK.getLink(parts);
|
||||
|
||||
if(text.match(REG_LINKINDEX_HYPERLINK)) {
|
||||
window.open(text,"_blank");
|
||||
return;
|
||||
}
|
||||
|
||||
let lineNum = null;
|
||||
if(text.search("#")>-1) {
|
||||
let t;
|
||||
[t,lineNum] = await this.excalidrawData.getTransclusion(text);
|
||||
text = text.substring(0,text.search("#"));
|
||||
}
|
||||
if(text.match(REG_LINKINDEX_INVALIDCHARS)) {
|
||||
new Notice(t("FILENAME_INVALID_CHARS"),4000);
|
||||
return;
|
||||
}
|
||||
const file = view.app.metadataCache.getFirstLinkpathDest(text,view.file.path);
|
||||
if (!ev.altKey && !file) {
|
||||
new Notice(t("FILE_DOES_NOT_EXIST"), 4000);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const f = view.file;
|
||||
if(ev.shiftKey && document.fullscreenElement === this.contentEl) {
|
||||
@@ -275,7 +281,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
if(file) {
|
||||
leaf.openFile(file,{eState: {line: lineNum-1}}); //if file exists open file and jump to reference
|
||||
} else {
|
||||
leaf.view.app.workspace.openLinkText(text,view.file.path);
|
||||
leaf.view.app.workspace.openLinkText(linkText,view.file.path);
|
||||
}
|
||||
} catch (e) {
|
||||
new Notice(e,4000);
|
||||
@@ -362,6 +368,10 @@ export default class ExcalidrawView extends TextFileView {
|
||||
this.preventReload = false;
|
||||
return;
|
||||
}
|
||||
if(this.compatibilityMode) {
|
||||
this.dirty = null;
|
||||
return;
|
||||
}
|
||||
if(!this.excalidrawRef) return;
|
||||
if(!this.file) return;
|
||||
if(file) this.data = await this.app.vault.cachedRead(file);
|
||||
@@ -383,7 +393,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
data = this.data = data.replaceAll("\r\n","\n").replaceAll("\r","\n");
|
||||
this.app.workspace.onLayoutReady(async ()=>{
|
||||
this.dirty = null;
|
||||
this.compatibilityMode = this.file.extension == "excalidraw";
|
||||
this.compatibilityMode = this.file.extension === "excalidraw";
|
||||
await this.plugin.loadSettings();
|
||||
this.plugin.opencount++;
|
||||
if(this.compatibilityMode) {
|
||||
@@ -430,6 +440,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
viewModeEnabled: viewModeEnabled,
|
||||
... excalidrawData.appState,
|
||||
},
|
||||
files: excalidrawData.files,
|
||||
commitToHistory: true,
|
||||
});
|
||||
if((this.app.workspace.activeLeaf === this.leaf) && this.excalidrawWrapperRef) {
|
||||
@@ -440,30 +451,11 @@ export default class ExcalidrawView extends TextFileView {
|
||||
this.instantiateExcalidraw({
|
||||
elements: excalidrawData.elements,
|
||||
appState: excalidrawData.appState,
|
||||
files: excalidrawData.files,
|
||||
libraryItems: await this.getLibrary(),
|
||||
});
|
||||
//files are loaded on excalidrawRef readyPromise
|
||||
}
|
||||
|
||||
/*
|
||||
//load files
|
||||
this.excalidrawData.files.forEach((value,key)=> {
|
||||
const file = this.app.vault.getAbstractFileByPath(value);
|
||||
if(file && file instanceof TFile) {
|
||||
getObsidianImage(this.plugin.app,file).then(async (data)=>{
|
||||
if(!this.excalidrawData) return;
|
||||
let files:BinaryFileData[] = [];
|
||||
files.push({
|
||||
mimeType : data.mimeType,
|
||||
id: key as FileId,
|
||||
dataURL: data.dataURL,
|
||||
created: data.created
|
||||
});
|
||||
this.excalidrawAPI.addFiles(files);
|
||||
});
|
||||
}
|
||||
});*/
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
//Compatibility mode with .excalidraw files
|
||||
@@ -583,7 +575,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
}
|
||||
let svg = await getSVG(this.getScene(),exportSettings);
|
||||
if(!svg) return null;
|
||||
svg = ExcalidrawView.embedFontsInSVG(svg);
|
||||
svg = embedFontsInSVG(svg);
|
||||
download(null,svgToBase64(svg.outerHTML),this.file.basename+'.svg');
|
||||
return;
|
||||
}
|
||||
@@ -671,7 +663,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
if(this.excalidrawAPI.getAppState().viewModeEnabled) {
|
||||
if(selectedTextElement) {
|
||||
const retval = selectedTextElement;
|
||||
selectedTextElement == null;
|
||||
selectedTextElement = null;
|
||||
return retval;
|
||||
}
|
||||
return {id:null,text:null};
|
||||
@@ -690,6 +682,30 @@ export default class ExcalidrawView extends TextFileView {
|
||||
return {id:selectedElement[0].id, text:selectedElement[0].text}; //return text element text
|
||||
};
|
||||
|
||||
this.getSelectedImageElement = ():{id: string, fileId:string} => {
|
||||
if(!excalidrawRef?.current) return {id:null,fileId:null};
|
||||
if(this.excalidrawAPI.getAppState().viewModeEnabled) {
|
||||
if(selectedImageElement) {
|
||||
const retval = selectedImageElement;
|
||||
selectedImageElement = null;
|
||||
return retval;
|
||||
}
|
||||
return {id:null,fileId:null};
|
||||
}
|
||||
const selectedElement = this.excalidrawAPI.getSceneElements().filter((el:any)=>el.id==Object.keys(this.excalidrawAPI.getAppState().selectedElementIds)[0]);
|
||||
if(selectedElement.length===0) return {id:null,fileId:null};
|
||||
if(selectedElement[0].type == "image") return {id:selectedElement[0].id, fileId:selectedElement[0].fileId}; //an image element was selected. Return fileId
|
||||
if(selectedElement[0].groupIds.length === 0) return {id:null,fileId:null}; //is the selected element part of a group?
|
||||
const group = selectedElement[0].groupIds[0]; //if yes, take the first group it is part of
|
||||
const imageElement = this
|
||||
.excalidrawAPI
|
||||
.getSceneElements()
|
||||
.filter((el:any)=>el.groupIds?.includes(group))
|
||||
.filter((el:any)=>el.type=="image"); //filter for Image elements of the group
|
||||
if(imageElement.length===0) return {id:null,fileId:null}; //the group had no image element member
|
||||
return {id:selectedElement[0].id, fileId:selectedElement[0].fileId}; //return image element fileId
|
||||
};
|
||||
|
||||
this.addText = (text:string, fontFamily?:1|2|3) => {
|
||||
if(!excalidrawRef?.current) {
|
||||
return;
|
||||
@@ -793,6 +809,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
|
||||
//variables used to handle click events in view mode
|
||||
let selectedTextElement:{id:string,text:string} = null;
|
||||
let selectedImageElement:{id:string,fileId:string} = null;
|
||||
let timestamp = 0;
|
||||
let blockOnMouseButtonDown = false;
|
||||
|
||||
@@ -822,6 +839,19 @@ export default class ExcalidrawView extends TextFileView {
|
||||
//if there are still multiple text elements with links on top of each other, return the first
|
||||
return {id:elementsWithLinks[0].id,text:elementsWithLinks[0].text};
|
||||
}
|
||||
|
||||
const getImageElementAtPointer = (pointer:any) => {
|
||||
const elements = this.excalidrawAPI.getSceneElements()
|
||||
.filter((e:ExcalidrawElement)=>{
|
||||
if (e.type !== "image") return false;
|
||||
const [x,y,w,h] = rotatedDimensions(e);
|
||||
return x<=pointer.x && x+w>=pointer.x
|
||||
&& y<=pointer.y && y+h>=pointer.y;
|
||||
});
|
||||
if(elements.length==0) return null;
|
||||
if(elements.length>1) return {id:elements[0].id,fileId:elements[0].fileId};
|
||||
//if more than 1 image elements are at the location, return the first
|
||||
}
|
||||
|
||||
let hoverPoint = {x:0,y:0};
|
||||
let hoverPreviewTarget:EventTarget = null;
|
||||
@@ -859,7 +889,13 @@ export default class ExcalidrawView extends TextFileView {
|
||||
const event = new MouseEvent("click", {ctrlKey: true, shiftKey: this.shiftKeyDown, altKey:this.altKeyDown});
|
||||
this.handleLinkClick(this,event);
|
||||
selectedTextElement = null;
|
||||
}
|
||||
}
|
||||
selectedImageElement = getImageElementAtPointer(currentPosition);
|
||||
if(selectedImageElement) {
|
||||
const event = new MouseEvent("click", {ctrlKey: true, shiftKey: this.shiftKeyDown, altKey:this.altKeyDown});
|
||||
this.handleLinkClick(this,event);
|
||||
selectedImageElement = null;
|
||||
}
|
||||
}
|
||||
|
||||
let mouseEvent:any = null;
|
||||
@@ -930,7 +966,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
//@ts-ignore
|
||||
if(!(e.ctrlKey||e.metaKey)) return;
|
||||
if(!(this.plugin.settings.allowCtrlClick)) return;
|
||||
if(!this.getSelectedTextElement().id) return;
|
||||
if(!(this.getSelectedTextElement().id || this.getSelectedImageElement().id)) return;
|
||||
this.handleLinkClick(this,e);
|
||||
},
|
||||
onMouseMove: (e:MouseEvent) => {
|
||||
|
||||
14
src/Utils.ts
14
src/Utils.ts
@@ -3,7 +3,7 @@ import { App, normalizePath, TAbstractFile, TFile, TFolder, Vault, WorkspaceLea
|
||||
import { Random } from "roughjs/bin/math";
|
||||
import { BinaryFileData, DataURL, Zoom } from "@zsviczian/excalidraw/types/types";
|
||||
import { nanoid } from "nanoid";
|
||||
import { IMAGE_TYPES } from "./constants";
|
||||
import { CASCADIA_FONT, IMAGE_TYPES, VIRGIL_FONT } from "./constants";
|
||||
import {ExcalidrawAutomate} from './ExcalidrawAutomate';
|
||||
import ExcalidrawPlugin from "./main";
|
||||
import { ExcalidrawElement, FileId } from "@zsviczian/excalidraw/types/element/types";
|
||||
@@ -352,6 +352,18 @@ export const getPNG = async (scene:any, exportSettings:ExportSettings, scale:num
|
||||
}
|
||||
}
|
||||
|
||||
export const embedFontsInSVG = (svg:SVGSVGElement):SVGSVGElement => {
|
||||
//replace font references with base64 fonts
|
||||
const includesVirgil = svg.querySelector("text[font-family^='Virgil']") != null;
|
||||
const includesCascadia = svg.querySelector("text[font-family^='Cascadia']") != null;
|
||||
const defs = svg.querySelector("defs");
|
||||
if (defs && (includesCascadia || includesVirgil)) {
|
||||
defs.innerHTML = "<style>" + (includesVirgil ? VIRGIL_FONT : "") + (includesCascadia ? CASCADIA_FONT : "")+"</style>";
|
||||
}
|
||||
return svg;
|
||||
}
|
||||
|
||||
|
||||
export const loadSceneFiles = async (app:App, filesMap: Map<FileId, string>,addFiles:Function) => {
|
||||
const entries = filesMap.entries();
|
||||
let entry;
|
||||
|
||||
@@ -32,10 +32,10 @@ export default {
|
||||
SAVE_AS_SVG: "Save as SVG into Vault (CTRL/META+CLICK to export)",
|
||||
OPEN_LINK: "Open selected text as link\n(SHIFT+CLICK to open in a new pane)",
|
||||
EXPORT_EXCALIDRAW: "Export to an .Excalidraw file",
|
||||
LINK_BUTTON_CLICK_NO_TEXT: 'Select a Text Element containing an internal or external link.\n'+
|
||||
LINK_BUTTON_CLICK_NO_TEXT: 'Select a an ImageElement, or select a TextElement that contains an internal or external link.\n'+
|
||||
'SHIFT CLICK this button to open the link in a new pane.\n'+
|
||||
'CTRL/META CLICK the Text Element on the canvas has the same effect!',
|
||||
TEXT_ELEMENT_EMPTY: "Text Element is empty, or [[valid-link|alias]] or [alias](valid-link) is not found",
|
||||
'CTRL/META CLICK the Image or TextElement on the canvas has the same effect!',
|
||||
TEXT_ELEMENT_EMPTY: "No ImageElement is selected or TextElement is empty, or [[valid-link|alias]] or [alias](valid-link) is not found",
|
||||
FILENAME_INVALID_CHARS: 'File name cannot contain any of the following characters: * " \\ < > : | ?',
|
||||
FILE_DOES_NOT_EXIST: "File does not exist. Hold down ALT (or ALT+SHIFT) and CLICK link button to create a new file.",
|
||||
FORCE_SAVE: "Force-save to update transclusions in adjacent panes.\n(Please note, that autosave is always on)",
|
||||
|
||||
@@ -56,7 +56,7 @@ import { Prompt } from "./Prompt";
|
||||
import { around } from "monkey-around";
|
||||
import { t } from "./lang/helpers";
|
||||
import { MigrationPrompt } from "./MigrationPrompt";
|
||||
import { checkAndCreateFolder, download, generateSVGString, getAttachmentsFolderAndFilePath, getIMGPathFromExcalidrawFile, getNewUniqueFilepath, getPNG, getSVG, splitFolderAndFilename, svgToBase64 } from "./Utils";
|
||||
import { checkAndCreateFolder, download, embedFontsInSVG, generateSVGString, getAttachmentsFolderAndFilePath, getIMGPathFromExcalidrawFile, getNewUniqueFilepath, getPNG, getSVG, splitFolderAndFilename, svgToBase64 } from "./Utils";
|
||||
|
||||
declare module "obsidian" {
|
||||
interface App {
|
||||
@@ -250,7 +250,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
svg = await getSVG(JSON_parse(scene),exportSettings);
|
||||
}
|
||||
if(!svg) return null;
|
||||
svg = ExcalidrawView.embedFontsInSVG(svg);
|
||||
svg = embedFontsInSVG(svg);
|
||||
svg.removeAttribute('width');
|
||||
svg.removeAttribute('height');
|
||||
img.setAttribute("src",svgToBase64(svg.outerHTML));
|
||||
@@ -789,7 +789,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
const filename = file.name.substr(0,file.name.lastIndexOf(".excalidraw")) + (replaceExtension ? ".md" : ".excalidraw.md");
|
||||
const fname = getNewUniqueFilepath(this.app.vault,filename,normalizePath(file.path.substr(0,file.path.lastIndexOf(file.name))));
|
||||
console.log(fname);
|
||||
const result = await this.app.vault.create(fname,FRONTMATTER + this.exportSceneToMD(data));
|
||||
const result = await this.app.vault.create(fname,FRONTMATTER + await this.exportSceneToMD(data));
|
||||
if (this.settings.keepInSync) {
|
||||
['.svg','.png'].forEach( (ext:string)=>{
|
||||
const oldIMGpath = file.path.substring(0,file.path.lastIndexOf(".excalidraw")) + ext;
|
||||
|
||||
Reference in New Issue
Block a user