mirror of
https://github.com/zsviczian/obsidian-excalidraw-plugin.git
synced 2025-08-06 05:46:28 +00:00
Compare commits
4 Commits
2.7.0-beta
...
2.7.0-beta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ee7fc3eddd | ||
|
|
639ccdf83e | ||
|
|
2b901c473b | ||
|
|
b419079734 |
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "obsidian-excalidraw-plugin",
|
||||
"name": "Excalidraw",
|
||||
"version": "2.7.0-beta-4",
|
||||
"version": "2.7.0-beta-5",
|
||||
"minAppVersion": "1.1.6",
|
||||
"description": "An Obsidian plugin to edit and view Excalidraw drawings",
|
||||
"author": "Zsolt Viczian",
|
||||
|
||||
465
src/Managers/FileManager.ts
Normal file
465
src/Managers/FileManager.ts
Normal file
@@ -0,0 +1,465 @@
|
||||
import { debug } from "src/utils/DebugHelper";
|
||||
import { App, FrontMatterCache, MarkdownView, normalizePath, Notice, TAbstractFile, TFile, WorkspaceLeaf } from "obsidian";
|
||||
import { BLANK_DRAWING, DARK_BLANK_DRAWING, DEVICE, EXPORT_TYPES, FRONTMATTER, FRONTMATTER_KEYS, JSON_parse, nanoid, VIEW_TYPE_EXCALIDRAW } from "src/constants/constants";
|
||||
import { Prompt, templatePromt } from "src/dialogs/Prompt";
|
||||
import { changeThemeOfExcalidrawMD, ExcalidrawData, getMarkdownDrawingSection } from "src/ExcalidrawData";
|
||||
import ExcalidrawView, { getTextMode } from "src/ExcalidrawView";
|
||||
import ExcalidrawPlugin from "src/main";
|
||||
import { DEBUGGING } from "src/utils/DebugHelper";
|
||||
import { checkAndCreateFolder, download, getIMGFilename, getLink, getListOfTemplateFiles, getNewUniqueFilepath } from "src/utils/FileUtils";
|
||||
import { PaneTarget } from "src/utils/ModifierkeyHelper";
|
||||
import { getExcalidrawViews, getNewOrAdjacentLeaf, isObsidianThemeDark, openLeaf } from "src/utils/ObsidianUtils";
|
||||
import { errorlog, getExportTheme } from "src/utils/Utils";
|
||||
|
||||
export class PluginFileManager {
|
||||
private plugin: ExcalidrawPlugin;
|
||||
private app: App;
|
||||
private excalidrawFiles: Set<TFile> = new Set<TFile>();
|
||||
|
||||
get settings() {
|
||||
return this.plugin.settings;
|
||||
}
|
||||
|
||||
constructor(plugin: ExcalidrawPlugin) {
|
||||
this.plugin = plugin;
|
||||
this.app = plugin.app;
|
||||
}
|
||||
|
||||
public isExcalidrawFile(f: TFile): boolean {
|
||||
if(!f) return false;
|
||||
if (f.extension === "excalidraw") {
|
||||
return true;
|
||||
}
|
||||
const fileCache = f ? this.plugin.app.metadataCache.getFileCache(f) : null;
|
||||
return !!fileCache?.frontmatter && !!fileCache.frontmatter[FRONTMATTER_KEYS["plugin"].name];
|
||||
}
|
||||
|
||||
//managing my own list of Excalidraw files because in the onDelete event handler
|
||||
//the file object is already gone from metadataCache, thus I can't check if it was an Excalidraw file
|
||||
public updateFileCache(
|
||||
file: TFile,
|
||||
frontmatter?: FrontMatterCache,
|
||||
deleted: boolean = false,
|
||||
) {
|
||||
if (frontmatter && typeof frontmatter[FRONTMATTER_KEYS["plugin"].name] !== "undefined") {
|
||||
this.excalidrawFiles.add(file);
|
||||
return;
|
||||
}
|
||||
if (!deleted && file.extension === "excalidraw") {
|
||||
this.excalidrawFiles.add(file);
|
||||
return;
|
||||
}
|
||||
this.excalidrawFiles.delete(file);
|
||||
}
|
||||
|
||||
public getExcalidrawFiles(): Set<TFile> {
|
||||
return this.excalidrawFiles;
|
||||
}
|
||||
|
||||
public destroy() {
|
||||
this.excalidrawFiles.clear();
|
||||
}
|
||||
|
||||
public async createDrawing(
|
||||
filename: string,
|
||||
foldername?: string,
|
||||
initData?: string,
|
||||
): Promise<TFile> {
|
||||
const folderpath = normalizePath(
|
||||
foldername ? foldername : this.settings.folder,
|
||||
);
|
||||
await checkAndCreateFolder(folderpath); //create folder if it does not exist
|
||||
const fname = getNewUniqueFilepath(this.app.vault, filename, folderpath);
|
||||
const file = await this.app.vault.create(
|
||||
fname,
|
||||
initData ?? (await this.plugin.getBlankDrawing()),
|
||||
);
|
||||
|
||||
//wait for metadata cache
|
||||
let counter = 0;
|
||||
while(file instanceof TFile && !this.isExcalidrawFile(file) && counter++<10) {
|
||||
await sleep(50);
|
||||
}
|
||||
|
||||
if(counter > 10) {
|
||||
errorlog({file, error: "new drawing not recognized as an excalidraw file", fn: this.createDrawing});
|
||||
}
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
public async getBlankDrawing(): Promise<string> {
|
||||
const templates = getListOfTemplateFiles(this.plugin);
|
||||
if(templates) {
|
||||
const template = await templatePromt(templates, this.app);
|
||||
if (template && template instanceof TFile) {
|
||||
if (
|
||||
(template.extension == "md" && !this.settings.compatibilityMode) ||
|
||||
(template.extension == "excalidraw" && this.settings.compatibilityMode)
|
||||
) {
|
||||
const data = await this.app.vault.read(template);
|
||||
if (data) {
|
||||
return this.settings.matchTheme
|
||||
? changeThemeOfExcalidrawMD(data)
|
||||
: data;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.settings.compatibilityMode) {
|
||||
return this.settings.matchTheme && isObsidianThemeDark()
|
||||
? DARK_BLANK_DRAWING
|
||||
: BLANK_DRAWING;
|
||||
}
|
||||
const blank =
|
||||
this.settings.matchTheme && isObsidianThemeDark()
|
||||
? DARK_BLANK_DRAWING
|
||||
: BLANK_DRAWING;
|
||||
return `${FRONTMATTER}\n${getMarkdownDrawingSection(
|
||||
blank,
|
||||
this.settings.compress,
|
||||
)}`;
|
||||
}
|
||||
|
||||
public async embedDrawing(file: TFile) {
|
||||
const activeView = this.app.workspace.getActiveViewOfType(MarkdownView);
|
||||
if (activeView && activeView.file) {
|
||||
const excalidrawRelativePath = this.app.metadataCache.fileToLinktext(
|
||||
file,
|
||||
activeView.file.path,
|
||||
this.settings.embedType === "excalidraw",
|
||||
);
|
||||
const editor = activeView.editor;
|
||||
|
||||
//embed Excalidraw
|
||||
if (this.settings.embedType === "excalidraw") {
|
||||
editor.replaceSelection(
|
||||
getLink(this.plugin, {path: excalidrawRelativePath}),
|
||||
);
|
||||
editor.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
//embed image
|
||||
let theme = this.settings.autoExportLightAndDark
|
||||
? getExportTheme (
|
||||
this.plugin,
|
||||
file,
|
||||
this.settings.exportWithTheme
|
||||
? isObsidianThemeDark() ? "dark":"light"
|
||||
: "light"
|
||||
)
|
||||
: "";
|
||||
|
||||
theme = (theme === "")
|
||||
? ""
|
||||
: theme + ".";
|
||||
|
||||
const imageRelativePath = getIMGFilename(
|
||||
excalidrawRelativePath,
|
||||
theme+this.settings.embedType.toLowerCase(),
|
||||
);
|
||||
const imageFullpath = getIMGFilename(
|
||||
file.path,
|
||||
theme+this.settings.embedType.toLowerCase(),
|
||||
);
|
||||
|
||||
//will hold incorrect value if theme==="", however in that case it won't be used
|
||||
const otherTheme = theme === "dark." ? "light." : "dark.";
|
||||
const otherImageRelativePath = theme === ""
|
||||
? null
|
||||
: getIMGFilename(
|
||||
excalidrawRelativePath,
|
||||
otherTheme+this.settings.embedType.toLowerCase(),
|
||||
);
|
||||
|
||||
const imgFile = this.app.vault.getAbstractFileByPath(imageFullpath);
|
||||
if (!imgFile) {
|
||||
await this.app.vault.create(imageFullpath, "");
|
||||
await sleep(200); //wait for metadata cache to update
|
||||
}
|
||||
|
||||
const inclCom = this.settings.embedMarkdownCommentLinks;
|
||||
|
||||
editor.replaceSelection(
|
||||
this.settings.embedWikiLink
|
||||
? `![[${imageRelativePath}]]\n` +
|
||||
(inclCom
|
||||
? `%%[[${excalidrawRelativePath}|🖋 Edit in Excalidraw]]${
|
||||
otherImageRelativePath
|
||||
? ", and the [["+otherImageRelativePath+"|"+otherTheme.split(".")[0]+" exported image]]"
|
||||
: ""
|
||||
}%%`
|
||||
: "")
|
||||
: `})\n` +
|
||||
(inclCom ? `%%[🖋 Edit in Excalidraw](${encodeURI(excalidrawRelativePath,
|
||||
)})${otherImageRelativePath?", and the ["+otherTheme.split(".")[0]+" exported image]("+encodeURI(otherImageRelativePath)+")":""}%%` : ""),
|
||||
);
|
||||
editor.focus();
|
||||
}
|
||||
}
|
||||
|
||||
public async exportLibrary() {
|
||||
if (DEVICE.isMobile) {
|
||||
const prompt = new Prompt(
|
||||
this.app,
|
||||
"Please provide a filename",
|
||||
"my-library",
|
||||
"filename, leave blank to cancel action",
|
||||
);
|
||||
prompt.openAndGetValue(async (filename: string) => {
|
||||
if (!filename) {
|
||||
return;
|
||||
}
|
||||
filename = `${filename}.excalidrawlib`;
|
||||
const folderpath = normalizePath(this.settings.folder);
|
||||
await checkAndCreateFolder(folderpath); //create folder if it does not exist
|
||||
const fname = getNewUniqueFilepath(
|
||||
this.app.vault,
|
||||
filename,
|
||||
folderpath,
|
||||
);
|
||||
this.app.vault.create(fname, this.settings.library);
|
||||
new Notice(`Exported library to ${fname}`, 6000);
|
||||
});
|
||||
return;
|
||||
}
|
||||
download(
|
||||
"data:text/plain;charset=utf-8",
|
||||
encodeURIComponent(JSON.stringify(this.settings.library2, null, "\t")),
|
||||
"my-obsidian-library.excalidrawlib",
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a drawing file
|
||||
* @param drawingFile
|
||||
* @param location
|
||||
* @param active
|
||||
* @param subpath
|
||||
* @param justCreated
|
||||
* @param popoutLocation
|
||||
*/
|
||||
public openDrawing(
|
||||
drawingFile: TFile,
|
||||
location: PaneTarget,
|
||||
active: boolean = false,
|
||||
subpath?: string,
|
||||
justCreated: boolean = false,
|
||||
popoutLocation?: {x?: number, y?: number, width?: number, height?: number},
|
||||
) {
|
||||
|
||||
const fnGetLeaf = ():WorkspaceLeaf => {
|
||||
if(location === "md-properties") {
|
||||
location = "new-tab";
|
||||
}
|
||||
let leaf: WorkspaceLeaf;
|
||||
if(location === "popout-window") {
|
||||
//@ts-ignore (the api does not include x,y)
|
||||
leaf = this.app.workspace.openPopoutLeaf(popoutLocation);
|
||||
}
|
||||
if(location === "new-tab") {
|
||||
leaf = this.app.workspace.getLeaf('tab');
|
||||
}
|
||||
if(!leaf) {
|
||||
leaf = this.app.workspace.getLeaf(false);
|
||||
if ((leaf.view.getViewType() !== 'empty') && (location === "new-pane")) {
|
||||
leaf = getNewOrAdjacentLeaf(this.plugin, leaf)
|
||||
}
|
||||
}
|
||||
return leaf;
|
||||
}
|
||||
|
||||
const {leaf, promise} = openLeaf({
|
||||
plugin: this.plugin,
|
||||
fnGetLeaf: () => fnGetLeaf(),
|
||||
file: drawingFile,
|
||||
openState:!subpath || subpath === ""
|
||||
? {active}
|
||||
: { active, eState: { subpath } }
|
||||
});
|
||||
|
||||
promise.then(()=>{
|
||||
const ea = this.plugin.ea;
|
||||
if(justCreated && ea.onFileCreateHook) {
|
||||
try {
|
||||
ea.onFileCreateHook({
|
||||
ea,
|
||||
excalidrawFile: drawingFile,
|
||||
view: leaf.view as ExcalidrawView,
|
||||
});
|
||||
} catch(e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the text elements from an Excalidraw scene into a string of ids as headers followed by the text contents
|
||||
* @param {string} data - Excalidraw scene JSON string
|
||||
* @returns {string} - Text starting with the "# Text Elements" header and followed by each "## id-value" and text
|
||||
*/
|
||||
public async exportSceneToMD(data: string, compressOverride?: boolean): Promise<string> {
|
||||
if (!data) {
|
||||
return "";
|
||||
}
|
||||
const excalidrawData = JSON_parse(data);
|
||||
const textElements = excalidrawData.elements?.filter(
|
||||
(el: any) => el.type == "text",
|
||||
);
|
||||
let outString = `# Excalidraw Data\n## Text Elements\n`;
|
||||
let id: string;
|
||||
for (const te of textElements) {
|
||||
id = te.id;
|
||||
//replacing Excalidraw text IDs with my own, because default IDs may contain
|
||||
//characters not recognized by Obsidian block references
|
||||
//also Excalidraw IDs are inconveniently long
|
||||
if (te.id.length > 8) {
|
||||
id = nanoid();
|
||||
data = data.replaceAll(te.id, id); //brute force approach to replace all occurrences.
|
||||
}
|
||||
outString += `${te.originalText ?? te.text} ^${id}\n\n`;
|
||||
}
|
||||
return (
|
||||
outString +
|
||||
getMarkdownDrawingSection(
|
||||
JSON.stringify(JSON_parse(data), null, "\t"),
|
||||
typeof compressOverride === "undefined"
|
||||
? this.settings.compress
|
||||
: compressOverride,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// -------------------------------------------------------
|
||||
// ------------------ Event Handlers ---------------------
|
||||
// -------------------------------------------------------
|
||||
|
||||
/**
|
||||
* watch filename change to rename .svg, .png; to sync to .md; to update links
|
||||
* @param file
|
||||
* @param oldPath
|
||||
* @returns
|
||||
*/
|
||||
public async renameEventHandler (file: TAbstractFile, oldPath: string) {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.renameEventHandler, `ExcalidrawPlugin.renameEventHandler`, file, oldPath);
|
||||
if (!(file instanceof TFile)) {
|
||||
return;
|
||||
}
|
||||
if (!this.isExcalidrawFile(file)) {
|
||||
return;
|
||||
}
|
||||
if (!this.settings.keepInSync) {
|
||||
return;
|
||||
}
|
||||
[EXPORT_TYPES, "excalidraw"].flat().forEach(async (ext: string) => {
|
||||
const oldIMGpath = getIMGFilename(oldPath, ext);
|
||||
const imgFile = app.vault.getAbstractFileByPath(
|
||||
normalizePath(oldIMGpath),
|
||||
);
|
||||
if (imgFile && imgFile instanceof TFile) {
|
||||
const newIMGpath = getIMGFilename(file.path, ext);
|
||||
await this.app.fileManager.renameFile(imgFile, newIMGpath);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async modifyEventHandler (file: TFile) {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.modifyEventHandler,`ExcalidrawPlugin.modifyEventHandler`, file);
|
||||
const excalidrawViews = getExcalidrawViews(this.app);
|
||||
excalidrawViews.forEach(async (excalidrawView) => {
|
||||
if(excalidrawView.semaphores?.viewunload) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
excalidrawView.file &&
|
||||
(excalidrawView.file.path === file.path ||
|
||||
(file.extension === "excalidraw" &&
|
||||
`${file.path.substring(
|
||||
0,
|
||||
file.path.lastIndexOf(".excalidraw"),
|
||||
)}.md` === excalidrawView.file.path))
|
||||
) {
|
||||
if(excalidrawView.semaphores?.preventReload) {
|
||||
excalidrawView.semaphores.preventReload = false;
|
||||
return;
|
||||
}
|
||||
//if the user hasn't touched the file for 5 minutes, don't synchronize, reload.
|
||||
//this is to avoid complex sync scenarios of multiple remote changes outside an active collaboration session
|
||||
const activeView = this.app.workspace.activeLeaf.view;
|
||||
const isEditingMarkdownSideInSplitView = (activeView !== excalidrawView) &&
|
||||
activeView instanceof MarkdownView && activeView.file === excalidrawView.file;
|
||||
|
||||
if(!isEditingMarkdownSideInSplitView && (excalidrawView.lastSaveTimestamp + 300000 < Date.now())) {
|
||||
excalidrawView.reload(true, excalidrawView.file);
|
||||
return;
|
||||
}
|
||||
if(file.extension==="md") {
|
||||
if(excalidrawView.semaphores?.embeddableIsEditingSelf) return;
|
||||
const inData = new ExcalidrawData(this.plugin);
|
||||
const data = await this.app.vault.read(file);
|
||||
await inData.loadData(data,file,getTextMode(data));
|
||||
excalidrawView.synchronizeWithData(inData);
|
||||
inData.destroy();
|
||||
if(excalidrawView?.isDirty()) {
|
||||
if(excalidrawView.autosaveTimer && excalidrawView.autosaveFunction) {
|
||||
clearTimeout(excalidrawView.autosaveTimer);
|
||||
}
|
||||
if(excalidrawView.autosaveFunction) {
|
||||
excalidrawView.autosaveFunction();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
excalidrawView.reload(true, excalidrawView.file);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* watch file delete and delete corresponding .svg and .png
|
||||
* @param file
|
||||
* @returns
|
||||
*/
|
||||
public async deleteEventHandler (file: TFile) {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.deleteEventHandler,`ExcalidrawPlugin.deleteEventHandler`, file);
|
||||
if (!(file instanceof TFile)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isExcalidarwFile = this.getExcalidrawFiles().has(file);
|
||||
this.updateFileCache(file, undefined, true);
|
||||
if (!isExcalidarwFile) {
|
||||
return;
|
||||
}
|
||||
|
||||
//close excalidraw view where this file is open
|
||||
const excalidrawViews = getExcalidrawViews(this.app);
|
||||
for (const excalidrawView of excalidrawViews) {
|
||||
if (excalidrawView.file.path === file.path) {
|
||||
await excalidrawView.leaf.setViewState({
|
||||
type: VIEW_TYPE_EXCALIDRAW,
|
||||
state: { file: null },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//delete PNG and SVG files as well
|
||||
if (this.settings.keepInSync) {
|
||||
window.setTimeout(() => {
|
||||
[EXPORT_TYPES, "excalidraw"].flat().forEach(async (ext: string) => {
|
||||
const imgPath = getIMGFilename(file.path, ext);
|
||||
const imgFile = this.app.vault.getAbstractFileByPath(
|
||||
normalizePath(imgPath),
|
||||
);
|
||||
if (imgFile && imgFile instanceof TFile) {
|
||||
await this.app.vault.delete(imgFile);
|
||||
}
|
||||
});
|
||||
}, 500);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
199
src/Managers/ObserverManager.ts
Normal file
199
src/Managers/ObserverManager.ts
Normal file
@@ -0,0 +1,199 @@
|
||||
import { debug, DEBUGGING } from "src/utils/DebugHelper";
|
||||
import ExcalidrawPlugin from "src/main";
|
||||
import { CustomMutationObserver } from "src/utils/DebugHelper";
|
||||
import { getExcalidrawViews, isObsidianThemeDark } from "src/utils/ObsidianUtils";
|
||||
import { App, TFile } from "obsidian";
|
||||
|
||||
export class ObserverManager {
|
||||
private plugin: ExcalidrawPlugin;
|
||||
private app: App;
|
||||
private themeObserver: MutationObserver | CustomMutationObserver;
|
||||
private fileExplorerObserver: MutationObserver | CustomMutationObserver;
|
||||
private modalContainerObserver: MutationObserver | CustomMutationObserver;
|
||||
private workspaceDrawerLeftObserver: MutationObserver | CustomMutationObserver;
|
||||
private workspaceDrawerRightObserver: MutationObserver | CustomMutationObserver;
|
||||
private activeViewDoc: Document;
|
||||
|
||||
constructor(plugin: ExcalidrawPlugin) {
|
||||
this.plugin = plugin;
|
||||
this.app = plugin.app;
|
||||
if(this.settings.matchThemeTrigger) this.addThemeObserver();
|
||||
this.experimentalFileTypeDisplayToggle(this.settings.experimentalFileType);
|
||||
this.addModalContainerObserver();
|
||||
}
|
||||
|
||||
get settings() {
|
||||
return this.plugin.settings;
|
||||
}
|
||||
|
||||
public addThemeObserver() {
|
||||
if(this.themeObserver) return;
|
||||
const { matchThemeTrigger } = this.settings;
|
||||
if (!matchThemeTrigger) return;
|
||||
|
||||
const themeObserverFn:MutationCallback = async (mutations: MutationRecord[]) => {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(themeObserverFn, `ExcalidrawPlugin.addThemeObserver`, mutations);
|
||||
const { matchThemeTrigger } = this.settings;
|
||||
if (!matchThemeTrigger) return;
|
||||
|
||||
const bodyClassList = document.body.classList;
|
||||
const mutation = mutations[0];
|
||||
if (mutation?.oldValue === bodyClassList.value) return;
|
||||
|
||||
const darkClass = bodyClassList.contains('theme-dark');
|
||||
if (mutation?.oldValue?.includes('theme-dark') === darkClass) return;
|
||||
|
||||
setTimeout(()=>{ //run async to avoid blocking the UI
|
||||
const theme = isObsidianThemeDark() ? "dark" : "light";
|
||||
const excalidrawViews = getExcalidrawViews(this.app);
|
||||
excalidrawViews.forEach(excalidrawView => {
|
||||
if (excalidrawView.file && excalidrawView.excalidrawAPI) {
|
||||
excalidrawView.setTheme(theme);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
this.themeObserver = DEBUGGING
|
||||
? new CustomMutationObserver(themeObserverFn, "themeObserver")
|
||||
: new MutationObserver(themeObserverFn);
|
||||
|
||||
this.themeObserver.observe(document.body, {
|
||||
attributeOldValue: true,
|
||||
attributeFilter: ["class"],
|
||||
});
|
||||
}
|
||||
|
||||
public removeThemeObserver() {
|
||||
if(!this.themeObserver) return;
|
||||
this.themeObserver.disconnect();
|
||||
this.themeObserver = null;
|
||||
}
|
||||
|
||||
public experimentalFileTypeDisplayToggle(enabled: boolean) {
|
||||
if (enabled) {
|
||||
this.experimentalFileTypeDisplay();
|
||||
return;
|
||||
}
|
||||
if (this.fileExplorerObserver) {
|
||||
this.fileExplorerObserver.disconnect();
|
||||
}
|
||||
this.fileExplorerObserver = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display characters configured in settings, in front of the filename, if the markdown file is an excalidraw drawing
|
||||
* Must be called after the workspace is ready
|
||||
* The function is called from onload()
|
||||
*/
|
||||
private async experimentalFileTypeDisplay() {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.experimentalFileTypeDisplay, `ExcalidrawPlugin.experimentalFileTypeDisplay`);
|
||||
const insertFiletype = (el: HTMLElement) => {
|
||||
if (el.childElementCount !== 1) {
|
||||
return;
|
||||
}
|
||||
const filename = el.getAttribute("data-path");
|
||||
if (!filename) {
|
||||
return;
|
||||
}
|
||||
const f = this.app.vault.getAbstractFileByPath(filename);
|
||||
if (!f || !(f instanceof TFile)) {
|
||||
return;
|
||||
}
|
||||
if (this.plugin.isExcalidrawFile(f)) {
|
||||
el.insertBefore(
|
||||
createDiv({
|
||||
cls: "nav-file-tag",
|
||||
text: this.settings.experimentalFileTag,
|
||||
}),
|
||||
el.firstChild,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const fileExplorerObserverFn:MutationCallback = (mutationsList) => {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(fileExplorerObserverFn, `ExcalidrawPlugin.experimentalFileTypeDisplay > fileExplorerObserverFn`, mutationsList);
|
||||
const mutationsWithNodes = mutationsList.filter((mutation) => mutation.addedNodes.length > 0);
|
||||
mutationsWithNodes.forEach((mutationNode) => {
|
||||
mutationNode.addedNodes.forEach((node) => {
|
||||
if (!(node instanceof Element)) {
|
||||
return;
|
||||
}
|
||||
node.querySelectorAll(".nav-file-title").forEach(insertFiletype);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
this.fileExplorerObserver = DEBUGGING
|
||||
? new CustomMutationObserver(fileExplorerObserverFn, "fileExplorerObserver")
|
||||
: new MutationObserver(fileExplorerObserverFn);
|
||||
|
||||
//the part that should only run after onLayoutReady
|
||||
document.querySelectorAll(".nav-file-title").forEach(insertFiletype); //apply filetype to files already displayed
|
||||
const container = document.querySelector(".nav-files-container");
|
||||
if (container) {
|
||||
this.fileExplorerObserver.observe(container, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Monitors if the user clicks outside the Excalidraw view, and saves the drawing if it's dirty
|
||||
* @returns
|
||||
*/
|
||||
public addModalContainerObserver() {
|
||||
if(!this.plugin.activeExcalidrawView) return;
|
||||
if(this.modalContainerObserver) {
|
||||
if(this.activeViewDoc === this.plugin.activeExcalidrawView.ownerDocument) {
|
||||
return;
|
||||
}
|
||||
this.removeModalContainerObserver();
|
||||
}
|
||||
//The user clicks settings, or "open another vault", or the command palette
|
||||
const modalContainerObserverFn: MutationCallback = async (m: MutationRecord[]) => {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(modalContainerObserverFn,`ExcalidrawPlugin.modalContainerObserverFn`, m);
|
||||
if (
|
||||
(m.length !== 1) ||
|
||||
(m[0].type !== "childList") ||
|
||||
(m[0].addedNodes.length !== 1) ||
|
||||
(!this.plugin.activeExcalidrawView) ||
|
||||
this.plugin.activeExcalidrawView?.semaphores?.viewunload ||
|
||||
(!this.plugin.activeExcalidrawView?.isDirty())
|
||||
) {
|
||||
return;
|
||||
}
|
||||
this.plugin.activeExcalidrawView.save();
|
||||
};
|
||||
|
||||
this.modalContainerObserver = DEBUGGING
|
||||
? new CustomMutationObserver(modalContainerObserverFn, "modalContainerObserver")
|
||||
: new MutationObserver(modalContainerObserverFn);
|
||||
this.activeViewDoc = this.plugin.activeExcalidrawView.ownerDocument;
|
||||
this.modalContainerObserver.observe(this.activeViewDoc.body, {
|
||||
childList: true,
|
||||
});
|
||||
}
|
||||
|
||||
public removeModalContainerObserver() {
|
||||
if(!this.modalContainerObserver) return;
|
||||
this.modalContainerObserver.disconnect();
|
||||
this.activeViewDoc = null;
|
||||
this.modalContainerObserver = null;
|
||||
}
|
||||
|
||||
public destroy() {
|
||||
this.removeThemeObserver();
|
||||
this.removeModalContainerObserver();
|
||||
if (this.workspaceDrawerLeftObserver) {
|
||||
this.workspaceDrawerLeftObserver.disconnect();
|
||||
}
|
||||
if (this.workspaceDrawerRightObserver) {
|
||||
this.workspaceDrawerRightObserver.disconnect();
|
||||
}
|
||||
if (this.fileExplorerObserver) {
|
||||
this.fileExplorerObserver.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
80
src/Managers/PackageManager.ts
Normal file
80
src/Managers/PackageManager.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import { ExcalidrawLib } from "../ExcalidrawLib";
|
||||
import { Packages } from "../types/types";
|
||||
import { debug, DEBUGGING } from "../utils/DebugHelper";
|
||||
|
||||
declare let REACT_PACKAGES:string;
|
||||
declare let react:any;
|
||||
declare let reactDOM:any;
|
||||
declare let excalidrawLib: typeof ExcalidrawLib;
|
||||
|
||||
export class PackageManager {
|
||||
private packageMap: Map<Window, Packages> = new Map<Window, Packages>();
|
||||
private EXCALIDRAW_PACKAGE: string;
|
||||
|
||||
constructor() {
|
||||
this.packageMap.set(window,{react, reactDOM, excalidrawLib});
|
||||
}
|
||||
|
||||
public setPackage(window: Window, pkg: Packages) {
|
||||
this.packageMap.set(window, pkg);
|
||||
}
|
||||
|
||||
public getPackageMap() {
|
||||
return this.packageMap;
|
||||
}
|
||||
|
||||
public getPackage(win:Window):Packages {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.getPackage, `ExcalidrawPlugin.getPackage`, win);
|
||||
|
||||
if(this.packageMap.has(win)) {
|
||||
return this.packageMap.get(win);
|
||||
}
|
||||
|
||||
const {react:r, reactDOM:rd, excalidrawLib:e} = win.eval.call(win,
|
||||
`(function() {
|
||||
${REACT_PACKAGES + this.EXCALIDRAW_PACKAGE};
|
||||
return {react:React,reactDOM:ReactDOM,excalidrawLib:ExcalidrawLib};
|
||||
})()`);
|
||||
this.packageMap.set(win,{react:r, reactDOM:rd, excalidrawLib:e});
|
||||
return {react:r, reactDOM:rd, excalidrawLib:e};
|
||||
}
|
||||
|
||||
public deletePackage(win: Window) {
|
||||
const { react, reactDOM, excalidrawLib } = this.getPackage(win);
|
||||
|
||||
if (win.ExcalidrawLib === excalidrawLib) {
|
||||
excalidrawLib.destroyObsidianUtils();
|
||||
delete win.ExcalidrawLib;
|
||||
}
|
||||
|
||||
if (win.React === react) {
|
||||
Object.keys(win.React).forEach((key) => {
|
||||
delete win.React[key];
|
||||
});
|
||||
delete win.React;
|
||||
}
|
||||
|
||||
if (win.ReactDOM === reactDOM) {
|
||||
Object.keys(win.ReactDOM).forEach((key) => {
|
||||
delete win.ReactDOM[key];
|
||||
});
|
||||
delete win.ReactDOM;
|
||||
}
|
||||
|
||||
this.packageMap.delete(win);
|
||||
}
|
||||
|
||||
public setExcalidrawPackage(pkg: string) {
|
||||
this.EXCALIDRAW_PACKAGE = pkg;
|
||||
}
|
||||
|
||||
public destroy() {
|
||||
REACT_PACKAGES = "";
|
||||
Object.values(this.packageMap).forEach((p: Packages) => {
|
||||
delete p.excalidrawLib;
|
||||
delete p.reactDOM;
|
||||
delete p.react;
|
||||
});
|
||||
this.packageMap.clear();
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import { COLOR_NAMES } from "src/constants/constants";
|
||||
import ExcalidrawView from "src/ExcalidrawView";
|
||||
import ExcalidrawPlugin from "src/main";
|
||||
import { setPen } from "src/menu/ObsidianMenu";
|
||||
import { ExtendedFillStyle, PenType } from "src/PenTypes";
|
||||
import { ExtendedFillStyle, PenType } from "src/types/PenTypes";
|
||||
import { getExcalidrawViews } from "src/utils/ObsidianUtils";
|
||||
import { PENS } from "src/utils/Pens";
|
||||
import { fragWithHTML } from "src/utils/Utils";
|
||||
|
||||
740
src/main.ts
740
src/main.ts
@@ -9,19 +9,16 @@ import {
|
||||
normalizePath,
|
||||
Menu,
|
||||
MenuItem,
|
||||
TAbstractFile,
|
||||
ViewState,
|
||||
Notice,
|
||||
request,
|
||||
MetadataCache,
|
||||
FrontMatterCache,
|
||||
Command,
|
||||
Workspace,
|
||||
Editor,
|
||||
MarkdownFileInfo,
|
||||
} from "obsidian";
|
||||
import {
|
||||
BLANK_DRAWING,
|
||||
VIEW_TYPE_EXCALIDRAW,
|
||||
EXCALIDRAW_ICON,
|
||||
ICON_NAME,
|
||||
@@ -31,8 +28,6 @@ import {
|
||||
FRONTMATTER_KEYS,
|
||||
FRONTMATTER,
|
||||
JSON_parse,
|
||||
nanoid,
|
||||
DARK_BLANK_DRAWING,
|
||||
SCRIPT_INSTALL_CODEBLOCK,
|
||||
SCRIPT_INSTALL_FOLDER,
|
||||
EXPORT_TYPES,
|
||||
@@ -47,11 +42,8 @@ import {
|
||||
CJK_STYLE_ID,
|
||||
updateExcalidrawLib,
|
||||
} from "./constants/constants";
|
||||
import ExcalidrawView, { TextMode, getTextMode } from "./ExcalidrawView";
|
||||
import ExcalidrawView, { TextMode } from "./ExcalidrawView";
|
||||
import {
|
||||
changeThemeOfExcalidrawMD,
|
||||
getMarkdownDrawingSection,
|
||||
ExcalidrawData,
|
||||
REGEX_LINK,
|
||||
} from "./ExcalidrawData";
|
||||
import {
|
||||
@@ -71,12 +63,11 @@ import {
|
||||
insertLaTeXToView,
|
||||
search,
|
||||
} from "./ExcalidrawAutomate";
|
||||
import { Prompt, templatePromt } from "./dialogs/Prompt";
|
||||
import { templatePromt } from "./dialogs/Prompt";
|
||||
import { around, dedupe } from "monkey-around";
|
||||
import { t } from "./lang/helpers";
|
||||
import {
|
||||
checkAndCreateFolder,
|
||||
download,
|
||||
fileShouldDefaultAsExcalidraw,
|
||||
getAliasWithSize,
|
||||
getAnnotationFileNameAndFolder,
|
||||
@@ -95,7 +86,6 @@ import {
|
||||
setLeftHandedMode,
|
||||
sleep,
|
||||
isVersionNewerThanOther,
|
||||
getExportTheme,
|
||||
isCallerFromTemplaterPlugin,
|
||||
decompress,
|
||||
getImageSize,
|
||||
@@ -144,23 +134,27 @@ import { getCJKDataURLs } from "./utils/CJKLoader";
|
||||
import { ExcalidrawLoading, switchToExcalidraw } from "./dialogs/ExcalidrawLoading";
|
||||
import { insertImageToView } from "./utils/ExcalidrawViewUtils";
|
||||
import { clearMathJaxVariables } from "./LaTeX";
|
||||
import { PluginFileManager } from "./Managers/FileManager";
|
||||
import { ObserverManager } from "./Managers/ObserverManager";
|
||||
import { PackageManager } from "./Managers/PackageManager";
|
||||
|
||||
declare let REACT_PACKAGES:string;
|
||||
//declare let EXCALIDRAW_PACKAGE:string;
|
||||
declare const unpackExcalidraw: Function;
|
||||
declare const PLUGIN_VERSION:string;
|
||||
declare const INITIAL_TIMESTAMP: number;
|
||||
declare let react:any;
|
||||
declare let reactDOM:any;
|
||||
declare let excalidrawLib: typeof ExcalidrawLib;
|
||||
declare const PLUGIN_VERSION:string;
|
||||
declare const INITIAL_TIMESTAMP: number;
|
||||
|
||||
export default class ExcalidrawPlugin extends Plugin {
|
||||
private fileManager: PluginFileManager;
|
||||
private observerManager: ObserverManager;
|
||||
private packageManager: PackageManager;
|
||||
private EXCALIDRAW_PACKAGE: string;
|
||||
public eaInstances = new WeakArray<ExcalidrawAutomate>();
|
||||
public fourthFontLoaded: boolean = false;
|
||||
public excalidrawConfig: ExcalidrawConfig;
|
||||
public taskbone: Taskbone;
|
||||
private excalidrawFiles: Set<TFile> = new Set<TFile>();
|
||||
public excalidrawFileModes: { [file: string]: string } = {};
|
||||
private _loaded: boolean = false;
|
||||
public settings: ExcalidrawSettings;
|
||||
@@ -177,9 +171,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
sourcePath: null,
|
||||
};
|
||||
private legacyExcalidrawPopoverObserver: MutationObserver | CustomMutationObserver;
|
||||
private themeObserver: MutationObserver | CustomMutationObserver;
|
||||
private fileExplorerObserver: MutationObserver | CustomMutationObserver;
|
||||
private modalContainerObserver: MutationObserver | CustomMutationObserver;
|
||||
private workspaceDrawerLeftObserver: MutationObserver | CustomMutationObserver;
|
||||
private workspaceDrawerRightObserver: MutationObserver | CustomMutationObserver;
|
||||
public opencount: number = 0;
|
||||
@@ -190,8 +182,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
public equationsMaster: Map<FileId, string> = null; //fileId, formula
|
||||
public mermaidsMaster: Map<FileId, string> = null; //fileId, mermaidText
|
||||
public scriptEngine: ScriptEngine;
|
||||
private packageMap: Map<Window,Packages> = new Map<Window,Packages>();
|
||||
public leafChangeTimeout: number = null;
|
||||
public leafChangeTimeout: number = null;
|
||||
private forceSaveCommand:Command;
|
||||
private removeEventLisnters:(()=>void)[] = [];
|
||||
private stylesManager:StylesManager;
|
||||
@@ -211,7 +202,8 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
super(app, manifest);
|
||||
this.loadTimestamp = INITIAL_TIMESTAMP;
|
||||
this.lastLogTimestamp = this.loadTimestamp;
|
||||
this.packageMap.set(window,{react, reactDOM, excalidrawLib});
|
||||
this.fileManager = new PluginFileManager(this);
|
||||
this.packageManager = new PackageManager();
|
||||
this.filesMaster = new Map<
|
||||
FileId,
|
||||
{ isHyperLink: boolean; isLocalLink: boolean; path: string; hasSVGwithBitmap: boolean; blockrefData: string; colorMapJSON?: string }
|
||||
@@ -246,60 +238,6 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
return document;
|
||||
};
|
||||
|
||||
public deletePackage(win:Window) {
|
||||
//window.console.log("ExcalidrawPlugin.deletePackage",win, this.packageMap.has(win));
|
||||
const {react, reactDOM, excalidrawLib} = this.getPackage(win);
|
||||
|
||||
//@ts-ignore
|
||||
if(win.ExcalidrawLib === excalidrawLib) {
|
||||
excalidrawLib.destroyObsidianUtils();
|
||||
//@ts-ignore
|
||||
delete win.ExcalidrawLib;
|
||||
}
|
||||
|
||||
//@ts-ignore
|
||||
if(win.React === react) {
|
||||
//@ts-ignore
|
||||
Object.keys(win.React).forEach((key) => {
|
||||
//@ts-ignore
|
||||
delete win.React[key];
|
||||
});
|
||||
//@ts-ignore
|
||||
delete win.React;
|
||||
}
|
||||
//@ts-ignore
|
||||
if(win.ReactDOM === reactDOM) {
|
||||
//@ts-ignore
|
||||
Object.keys(win.ReactDOM).forEach((key) => {
|
||||
//@ts-ignore
|
||||
delete win.ReactDOM[key];
|
||||
});
|
||||
//@ts-ignore
|
||||
delete win.ReactDOM;
|
||||
}
|
||||
if(this.packageMap.has(win)) {
|
||||
this.packageMap.delete(win);
|
||||
}
|
||||
}
|
||||
|
||||
public getPackage(win:Window):Packages {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.getPackage, `ExcalidrawPlugin.getPackage`, win);
|
||||
|
||||
if(this.packageMap.has(win)) {
|
||||
return this.packageMap.get(win);
|
||||
}
|
||||
|
||||
//@ts-ignore
|
||||
const {react:r, reactDOM:rd, excalidrawLib:e} = win.eval.call(win,
|
||||
`(function() {
|
||||
${REACT_PACKAGES + this.EXCALIDRAW_PACKAGE};
|
||||
return {react:React,reactDOM:ReactDOM,excalidrawLib:ExcalidrawLib};
|
||||
})()`);
|
||||
this.packageMap.set(win,{react:r, reactDOM:rd, excalidrawLib:e});
|
||||
return {react:r, reactDOM:rd, excalidrawLib:e};
|
||||
|
||||
}
|
||||
|
||||
// by adding the wrapper like this, likely in debug mode I am leaking memory because my code removes
|
||||
// the original event handlers, not the wrapped ones. I will only uncomment this if I need to debug
|
||||
/*public registerEvent(event: any) {
|
||||
@@ -451,7 +389,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
try {
|
||||
this.EXCALIDRAW_PACKAGE = unpackExcalidraw();
|
||||
excalidrawLib = window.eval.call(window,`(function() {${this.EXCALIDRAW_PACKAGE};return ExcalidrawLib;})()`);
|
||||
this.packageMap.set(window,{react, reactDOM, excalidrawLib});
|
||||
this.packageManager.setPackage(window,{react, reactDOM, excalidrawLib});
|
||||
updateExcalidrawLib();
|
||||
} catch (e) {
|
||||
new Notice("Error loading the Excalidraw package", 6000);
|
||||
@@ -476,12 +414,12 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
this.logStartupEvent("Excalidraw config initialized");
|
||||
|
||||
try {
|
||||
this.addThemeObserver();
|
||||
this.observerManager = new ObserverManager(this);
|
||||
} catch (e) {
|
||||
new Notice("Error adding theme observer", 6000);
|
||||
console.error("Error adding theme observer", e);
|
||||
new Notice("Error adding ObserverManager", 6000);
|
||||
console.error("Error adding ObserverManager", e);
|
||||
}
|
||||
this.logStartupEvent("Theme observer added");
|
||||
this.logStartupEvent("ObserverManager added");
|
||||
|
||||
try {
|
||||
//inspiration taken from kanban:
|
||||
@@ -590,14 +528,6 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
}
|
||||
this.logStartupEvent("Script install-codeblock processor registered");
|
||||
|
||||
try {
|
||||
this.experimentalFileTypeDisplayToggle(this.settings.experimentalFileType);
|
||||
} catch (e) {
|
||||
new Notice("Error setting up experimental file type display", 6000);
|
||||
console.error("Error setting up experimental file type display", e);
|
||||
}
|
||||
this.logStartupEvent("Experimental file type display set");
|
||||
|
||||
try {
|
||||
this.registerCommands();
|
||||
} catch (e) {
|
||||
@@ -706,7 +636,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
fontName: "Local Font",
|
||||
}
|
||||
}
|
||||
this.packageMap.forEach(({excalidrawLib}) => {
|
||||
this.packageManager.getPackageMap().forEach(({excalidrawLib}) => {
|
||||
(excalidrawLib as typeof ExcalidrawLib).registerLocalFont({metrics: fontMetrics as any, icon: null}, fourthFontDataURL);
|
||||
});
|
||||
// Add fonts to open Obsidian documents
|
||||
@@ -1013,119 +943,6 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
}
|
||||
}
|
||||
|
||||
public addThemeObserver() {
|
||||
if(this.themeObserver) return;
|
||||
const { matchThemeTrigger } = this.settings;
|
||||
if (!matchThemeTrigger) return;
|
||||
|
||||
const themeObserverFn:MutationCallback = async (mutations: MutationRecord[]) => {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(themeObserverFn, `ExcalidrawPlugin.addThemeObserver`, mutations);
|
||||
const { matchThemeTrigger } = this.settings;
|
||||
if (!matchThemeTrigger) return;
|
||||
|
||||
const bodyClassList = document.body.classList;
|
||||
const mutation = mutations[0];
|
||||
if (mutation?.oldValue === bodyClassList.value) return;
|
||||
|
||||
const darkClass = bodyClassList.contains('theme-dark');
|
||||
if (mutation?.oldValue?.includes('theme-dark') === darkClass) return;
|
||||
|
||||
setTimeout(()=>{ //run async to avoid blocking the UI
|
||||
const theme = isObsidianThemeDark() ? "dark" : "light";
|
||||
const excalidrawViews = getExcalidrawViews(this.app);
|
||||
excalidrawViews.forEach(excalidrawView => {
|
||||
if (excalidrawView.file && excalidrawView.excalidrawAPI) {
|
||||
excalidrawView.setTheme(theme);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
this.themeObserver = DEBUGGING
|
||||
? new CustomMutationObserver(themeObserverFn, "themeObserver")
|
||||
: new MutationObserver(themeObserverFn);
|
||||
|
||||
this.themeObserver.observe(document.body, {
|
||||
attributeOldValue: true,
|
||||
attributeFilter: ["class"],
|
||||
});
|
||||
}
|
||||
|
||||
public removeThemeObserver() {
|
||||
if(!this.themeObserver) return;
|
||||
this.themeObserver.disconnect();
|
||||
this.themeObserver = null;
|
||||
}
|
||||
|
||||
public experimentalFileTypeDisplayToggle(enabled: boolean) {
|
||||
if (enabled) {
|
||||
this.experimentalFileTypeDisplay();
|
||||
return;
|
||||
}
|
||||
if (this.fileExplorerObserver) {
|
||||
this.fileExplorerObserver.disconnect();
|
||||
}
|
||||
this.fileExplorerObserver = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display characters configured in settings, in front of the filename, if the markdown file is an excalidraw drawing
|
||||
* Must be called after the workspace is ready
|
||||
* The function is called from onload()
|
||||
*/
|
||||
private async experimentalFileTypeDisplay() {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.experimentalFileTypeDisplay, `ExcalidrawPlugin.experimentalFileTypeDisplay`);
|
||||
const insertFiletype = (el: HTMLElement) => {
|
||||
if (el.childElementCount !== 1) {
|
||||
return;
|
||||
}
|
||||
const filename = el.getAttribute("data-path");
|
||||
if (!filename) {
|
||||
return;
|
||||
}
|
||||
const f = this.app.vault.getAbstractFileByPath(filename);
|
||||
if (!f || !(f instanceof TFile)) {
|
||||
return;
|
||||
}
|
||||
if (this.isExcalidrawFile(f)) {
|
||||
el.insertBefore(
|
||||
createDiv({
|
||||
cls: "nav-file-tag",
|
||||
text: this.settings.experimentalFileTag,
|
||||
}),
|
||||
el.firstChild,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const fileExplorerObserverFn:MutationCallback = (mutationsList) => {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(fileExplorerObserverFn, `ExcalidrawPlugin.experimentalFileTypeDisplay > fileExplorerObserverFn`, mutationsList);
|
||||
const mutationsWithNodes = mutationsList.filter((mutation) => mutation.addedNodes.length > 0);
|
||||
mutationsWithNodes.forEach((mutationNode) => {
|
||||
mutationNode.addedNodes.forEach((node) => {
|
||||
if (!(node instanceof Element)) {
|
||||
return;
|
||||
}
|
||||
node.querySelectorAll(".nav-file-title").forEach(insertFiletype);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
this.fileExplorerObserver = DEBUGGING
|
||||
? new CustomMutationObserver(fileExplorerObserverFn, "fileExplorerObserver")
|
||||
: new MutationObserver(fileExplorerObserverFn);
|
||||
|
||||
//the part that should only run after onLayoutReady
|
||||
document.querySelectorAll(".nav-file-title").forEach(insertFiletype); //apply filetype to files already displayed
|
||||
const container = document.querySelector(".nav-files-container");
|
||||
if (container) {
|
||||
this.fileExplorerObserver.observe(container, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async actionRibbonClick(e: MouseEvent) {
|
||||
this.createAndOpenDrawing(
|
||||
getDrawingFilename(this.settings),
|
||||
@@ -1349,7 +1166,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
if(!excalidrawFile) return false;
|
||||
}
|
||||
if(checking) return true;
|
||||
this.openDrawing(excalidrawFile, "new-tab", true);
|
||||
this.fileManager.openDrawing(excalidrawFile, "new-tab", true);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1378,7 +1195,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
this.addCommand({
|
||||
id: "excalidraw-download-lib",
|
||||
name: t("DOWNLOAD_LIBRARY"),
|
||||
callback: this.exportLibrary,
|
||||
callback: ()=>this.fileManager.exportLibrary(),
|
||||
});
|
||||
|
||||
this.addCommand({
|
||||
@@ -1425,7 +1242,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
if (!(file instanceof TFile)) {
|
||||
return false;
|
||||
}
|
||||
this.embedDrawing(file);
|
||||
this.fileManager.embedDrawing(file);
|
||||
return true;
|
||||
},
|
||||
});
|
||||
@@ -1486,8 +1303,8 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
)
|
||||
).folder;
|
||||
const file = await this.createDrawing(filename, folder);
|
||||
await this.embedDrawing(file);
|
||||
this.openDrawing(file, location, true, undefined, true);
|
||||
await this.fileManager.embedDrawing(file);
|
||||
this.fileManager.openDrawing(file, location, true, undefined, true);
|
||||
};
|
||||
|
||||
this.addCommand({
|
||||
@@ -2031,7 +1848,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
y:Math.max(0,centerY - height/2 + view.ownerWindow.screenY),
|
||||
}
|
||||
|
||||
this.openDrawing(ef.file, DEVICE.isMobile ? "new-tab":"popout-window", true, undefined, false, {x,y,width,height});
|
||||
this.fileManager.openDrawing(ef.file, DEVICE.isMobile ? "new-tab":"popout-window", true, undefined, false, {x,y,width,height});
|
||||
}
|
||||
})
|
||||
|
||||
@@ -2816,7 +2633,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
log(fname);
|
||||
const result = await this.app.vault.create(
|
||||
fname,
|
||||
FRONTMATTER + (await this.exportSceneToMD(data, false)),
|
||||
FRONTMATTER + (await this.fileManager.exportSceneToMD(data, false)),
|
||||
);
|
||||
if (this.settings.keepInSync) {
|
||||
EXPORT_TYPES.forEach((ext: string) => {
|
||||
@@ -3073,10 +2890,10 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
this.activeExcalidrawView = newActiveviewEV;
|
||||
|
||||
if (newActiveviewEV) {
|
||||
this.addModalContainerObserver();
|
||||
this.observerManager.addModalContainerObserver();
|
||||
this.lastActiveExcalidrawFilePath = newActiveviewEV.file?.path;
|
||||
} else {
|
||||
this.removeModalContainerObserver();
|
||||
this.observerManager.removeModalContainerObserver();
|
||||
}
|
||||
|
||||
//!Temporary hack
|
||||
@@ -3254,127 +3071,9 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
};
|
||||
this.registerEvent(this.app.workspace.on("editor-paste", (evt, editor,info) => onPasteHandler(evt, editor, info)));
|
||||
|
||||
//watch filename change to rename .svg, .png; to sync to .md; to update links
|
||||
const renameEventHandler = async (
|
||||
file: TAbstractFile,
|
||||
oldPath: string,
|
||||
) => {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(renameEventHandler,`ExcalidrawPlugin.renameEventHandler`, file, oldPath);
|
||||
if (!(file instanceof TFile)) {
|
||||
return;
|
||||
}
|
||||
if (!this.isExcalidrawFile(file)) {
|
||||
return;
|
||||
}
|
||||
if (!this.settings.keepInSync) {
|
||||
return;
|
||||
}
|
||||
[EXPORT_TYPES, "excalidraw"].flat().forEach(async (ext: string) => {
|
||||
const oldIMGpath = getIMGFilename(oldPath, ext);
|
||||
const imgFile = app.vault.getAbstractFileByPath(
|
||||
normalizePath(oldIMGpath),
|
||||
);
|
||||
if (imgFile && imgFile instanceof TFile) {
|
||||
const newIMGpath = getIMGFilename(file.path, ext);
|
||||
await this.app.fileManager.renameFile(imgFile, newIMGpath);
|
||||
}
|
||||
});
|
||||
};
|
||||
this.registerEvent(this.app.vault.on("rename", (file,oldPath) => renameEventHandler(file,oldPath)));
|
||||
|
||||
const modifyEventHandler = async (file: TFile) => {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(modifyEventHandler,`ExcalidrawPlugin.modifyEventHandler`, file);
|
||||
const excalidrawViews = getExcalidrawViews(this.app);
|
||||
excalidrawViews.forEach(async (excalidrawView) => {
|
||||
if(excalidrawView.semaphores?.viewunload) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
excalidrawView.file &&
|
||||
(excalidrawView.file.path === file.path ||
|
||||
(file.extension === "excalidraw" &&
|
||||
`${file.path.substring(
|
||||
0,
|
||||
file.path.lastIndexOf(".excalidraw"),
|
||||
)}.md` === excalidrawView.file.path))
|
||||
) {
|
||||
if(excalidrawView.semaphores?.preventReload) {
|
||||
excalidrawView.semaphores.preventReload = false;
|
||||
return;
|
||||
}
|
||||
//if the user hasn't touched the file for 5 minutes, don't synchronize, reload.
|
||||
//this is to avoid complex sync scenarios of multiple remote changes outside an active collaboration session
|
||||
const activeView = this.app.workspace.activeLeaf.view;
|
||||
const isEditingMarkdownSideInSplitView = (activeView !== excalidrawView) &&
|
||||
activeView instanceof MarkdownView && activeView.file === excalidrawView.file;
|
||||
|
||||
if(!isEditingMarkdownSideInSplitView && (excalidrawView.lastSaveTimestamp + 300000 < Date.now())) {
|
||||
excalidrawView.reload(true, excalidrawView.file);
|
||||
return;
|
||||
}
|
||||
if(file.extension==="md") {
|
||||
if(excalidrawView.semaphores?.embeddableIsEditingSelf) return;
|
||||
const inData = new ExcalidrawData(this);
|
||||
const data = await this.app.vault.read(file);
|
||||
await inData.loadData(data,file,getTextMode(data));
|
||||
excalidrawView.synchronizeWithData(inData);
|
||||
inData.destroy();
|
||||
if(excalidrawView?.isDirty()) {
|
||||
if(excalidrawView.autosaveTimer && excalidrawView.autosaveFunction) {
|
||||
clearTimeout(excalidrawView.autosaveTimer);
|
||||
}
|
||||
if(excalidrawView.autosaveFunction) {
|
||||
excalidrawView.autosaveFunction();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
excalidrawView.reload(true, excalidrawView.file);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
this.registerEvent(this.app.vault.on("modify", (file:TFile) => modifyEventHandler(file)));
|
||||
|
||||
//watch file delete and delete corresponding .svg and .png
|
||||
const deleteEventHandler = async (file: TFile) => {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(deleteEventHandler,`ExcalidrawPlugin.deleteEventHandler`, file);
|
||||
if (!(file instanceof TFile)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isExcalidarwFile = this.excalidrawFiles.has(file);
|
||||
this.updateFileCache(file, undefined, true);
|
||||
if (!isExcalidarwFile) {
|
||||
return;
|
||||
}
|
||||
|
||||
//close excalidraw view where this file is open
|
||||
const excalidrawViews = getExcalidrawViews(this.app);
|
||||
for (const excalidrawView of excalidrawViews) {
|
||||
if (excalidrawView.file.path === file.path) {
|
||||
await excalidrawView.leaf.setViewState({
|
||||
type: VIEW_TYPE_EXCALIDRAW,
|
||||
state: { file: null },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//delete PNG and SVG files as well
|
||||
if (this.settings.keepInSync) {
|
||||
window.setTimeout(() => {
|
||||
[EXPORT_TYPES, "excalidraw"].flat().forEach(async (ext: string) => {
|
||||
const imgPath = getIMGFilename(file.path, ext);
|
||||
const imgFile = this.app.vault.getAbstractFileByPath(
|
||||
normalizePath(imgPath),
|
||||
);
|
||||
if (imgFile && imgFile instanceof TFile) {
|
||||
await this.app.vault.delete(imgFile);
|
||||
}
|
||||
});
|
||||
}, 500);
|
||||
}
|
||||
};
|
||||
this.registerEvent(this.app.vault.on("delete", (file:TFile) => deleteEventHandler(file)));
|
||||
this.registerEvent(this.app.vault.on("rename", (file,oldPath) => this.fileManager.renameEventHandler(file,oldPath)));
|
||||
this.registerEvent(this.app.vault.on("modify", (file:TFile) => this.fileManager.modifyEventHandler(file)));
|
||||
this.registerEvent(this.app.vault.on("delete", (file:TFile) => this.fileManager.deleteEventHandler(file)));
|
||||
|
||||
//save Excalidraw leaf and update embeds when switching to another leaf
|
||||
this.registerEvent(
|
||||
@@ -3394,7 +3093,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
(fm && typeof fm[FRONTMATTER_KEYS["plugin"].name] !== "undefined") ||
|
||||
filename.match(/\.excalidraw$/)
|
||||
) {
|
||||
this.updateFileCache(
|
||||
this.fileManager.updateFileCache(
|
||||
this.app.vault.getAbstractFileByPath(filename) as TFile,
|
||||
fm,
|
||||
);
|
||||
@@ -3402,7 +3101,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
});
|
||||
this.registerEvent(
|
||||
metaCache.on("changed", (file, _, cache) =>
|
||||
this.updateFileCache(file, cache?.frontmatter),
|
||||
this.fileManager.updateFileCache(file, cache?.frontmatter),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -3442,8 +3141,6 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
this.app.workspace.on("file-menu", onFileMenuEventSaveActiveDrawing),
|
||||
);
|
||||
|
||||
this.addModalContainerObserver();
|
||||
|
||||
//when the user activates the sliding drawers on Obsidian Mobile
|
||||
const leftWorkspaceDrawer = document.querySelector(
|
||||
".workspace-drawer.mod-left",
|
||||
@@ -3486,66 +3183,6 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
}
|
||||
}
|
||||
|
||||
private activeViewDoc: Document;
|
||||
private addModalContainerObserver() {
|
||||
if(!this.activeExcalidrawView) return;
|
||||
if(this.modalContainerObserver) {
|
||||
if(this.activeViewDoc === this.activeExcalidrawView.ownerDocument) {
|
||||
return;
|
||||
}
|
||||
this.removeModalContainerObserver();
|
||||
}
|
||||
//The user clicks settings, or "open another vault", or the command palette
|
||||
const modalContainerObserverFn: MutationCallback = async (m: MutationRecord[]) => {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(modalContainerObserverFn,`ExcalidrawPlugin.modalContainerObserverFn`, m);
|
||||
if (
|
||||
(m.length !== 1) ||
|
||||
(m[0].type !== "childList") ||
|
||||
(m[0].addedNodes.length !== 1) ||
|
||||
(!this.activeExcalidrawView) ||
|
||||
this.activeExcalidrawView?.semaphores?.viewunload ||
|
||||
(!this.activeExcalidrawView?.isDirty())
|
||||
) {
|
||||
return;
|
||||
}
|
||||
this.activeExcalidrawView.save();
|
||||
};
|
||||
|
||||
this.modalContainerObserver = DEBUGGING
|
||||
? new CustomMutationObserver(modalContainerObserverFn, "modalContainerObserver")
|
||||
: new MutationObserver(modalContainerObserverFn);
|
||||
this.activeViewDoc = this.activeExcalidrawView.ownerDocument;
|
||||
this.modalContainerObserver.observe(this.activeViewDoc.body, {
|
||||
childList: true,
|
||||
});
|
||||
}
|
||||
|
||||
private removeModalContainerObserver() {
|
||||
if(!this.modalContainerObserver) return;
|
||||
this.modalContainerObserver.disconnect();
|
||||
this.activeViewDoc = null;
|
||||
this.modalContainerObserver = null;
|
||||
}
|
||||
|
||||
//managing my own list of Excalidraw files because in the onDelete event handler
|
||||
//the file object is already gone from metadataCache, thus I can't check if it was an Excalidraw file
|
||||
updateFileCache(
|
||||
file: TFile,
|
||||
frontmatter?: FrontMatterCache,
|
||||
deleted: boolean = false,
|
||||
) {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.updateFileCache,`ExcalidrawPlugin.updateFileCache`, file, frontmatter, deleted);
|
||||
if (frontmatter && typeof frontmatter[FRONTMATTER_KEYS["plugin"].name] !== "undefined") {
|
||||
this.excalidrawFiles.add(file);
|
||||
return;
|
||||
}
|
||||
if (!deleted && file.extension === "excalidraw") {
|
||||
this.excalidrawFiles.add(file);
|
||||
return;
|
||||
}
|
||||
this.excalidrawFiles.delete(file);
|
||||
}
|
||||
|
||||
onunload() {
|
||||
const excalidrawViews = getExcalidrawViews(this.app);
|
||||
excalidrawViews.forEach(({leaf}) => {
|
||||
@@ -3591,8 +3228,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
if(this.legacyExcalidrawPopoverObserver) {
|
||||
this.legacyExcalidrawPopoverObserver.disconnect();
|
||||
}
|
||||
this.removeThemeObserver();
|
||||
this.removeModalContainerObserver();
|
||||
this.observerManager.destroy();
|
||||
if (this.workspaceDrawerLeftObserver) {
|
||||
this.workspaceDrawerLeftObserver.disconnect();
|
||||
}
|
||||
@@ -3606,11 +3242,6 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
this.taskbone.destroy();
|
||||
this.taskbone = null;
|
||||
}
|
||||
Object.values(this.packageMap).forEach((p:Packages)=>{
|
||||
delete p.excalidrawLib;
|
||||
delete p.reactDOM;
|
||||
delete p.react;
|
||||
});
|
||||
|
||||
this.excalidrawConfig = null;
|
||||
|
||||
@@ -3639,7 +3270,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
|
||||
this.hover = {linkText: null, sourcePath:null};
|
||||
|
||||
this.excalidrawFiles.clear();
|
||||
this.fileManager.destroy();
|
||||
this.equationsMaster.clear();
|
||||
this.filesMaster.clear();
|
||||
this.mermaidsMaster.clear();
|
||||
@@ -3655,96 +3286,17 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
this.settings = null;
|
||||
clearMathJaxVariables();
|
||||
this.EXCALIDRAW_PACKAGE = "";
|
||||
REACT_PACKAGES = "";
|
||||
//pluginPackages = null;
|
||||
//PLUGIN_VERSION = null;
|
||||
//@ts-ignore
|
||||
delete window.PolyBool;
|
||||
this.deletePackage(window);
|
||||
this.packageManager.destroy();
|
||||
react = null;
|
||||
reactDOM = null;
|
||||
excalidrawLib = null;
|
||||
terminateCompressionWorker();
|
||||
}
|
||||
|
||||
public async embedDrawing(file: TFile) {
|
||||
const activeView = this.app.workspace.getActiveViewOfType(MarkdownView);
|
||||
if (activeView && activeView.file) {
|
||||
const excalidrawRelativePath = this.app.metadataCache.fileToLinktext(
|
||||
file,
|
||||
activeView.file.path,
|
||||
this.settings.embedType === "excalidraw",
|
||||
);
|
||||
const editor = activeView.editor;
|
||||
|
||||
//embed Excalidraw
|
||||
if (this.settings.embedType === "excalidraw") {
|
||||
editor.replaceSelection(
|
||||
getLink(this, {path: excalidrawRelativePath}),
|
||||
);
|
||||
editor.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
//embed image
|
||||
let theme = this.settings.autoExportLightAndDark
|
||||
? getExportTheme (
|
||||
this,
|
||||
file,
|
||||
this.settings.exportWithTheme
|
||||
? isObsidianThemeDark() ? "dark":"light"
|
||||
: "light"
|
||||
)
|
||||
: "";
|
||||
|
||||
theme = (theme === "")
|
||||
? ""
|
||||
: theme + ".";
|
||||
|
||||
const imageRelativePath = getIMGFilename(
|
||||
excalidrawRelativePath,
|
||||
theme+this.settings.embedType.toLowerCase(),
|
||||
);
|
||||
const imageFullpath = getIMGFilename(
|
||||
file.path,
|
||||
theme+this.settings.embedType.toLowerCase(),
|
||||
);
|
||||
|
||||
//will hold incorrect value if theme==="", however in that case it won't be used
|
||||
const otherTheme = theme === "dark." ? "light." : "dark.";
|
||||
const otherImageRelativePath = theme === ""
|
||||
? null
|
||||
: getIMGFilename(
|
||||
excalidrawRelativePath,
|
||||
otherTheme+this.settings.embedType.toLowerCase(),
|
||||
);
|
||||
|
||||
const imgFile = this.app.vault.getAbstractFileByPath(imageFullpath);
|
||||
if (!imgFile) {
|
||||
await this.app.vault.create(imageFullpath, "");
|
||||
await sleep(200); //wait for metadata cache to update
|
||||
}
|
||||
|
||||
const inclCom = this.settings.embedMarkdownCommentLinks;
|
||||
|
||||
editor.replaceSelection(
|
||||
this.settings.embedWikiLink
|
||||
? `![[${imageRelativePath}]]\n` +
|
||||
(inclCom
|
||||
? `%%[[${excalidrawRelativePath}|🖋 Edit in Excalidraw]]${
|
||||
otherImageRelativePath
|
||||
? ", and the [["+otherImageRelativePath+"|"+otherTheme.split(".")[0]+" exported image]]"
|
||||
: ""
|
||||
}%%`
|
||||
: "")
|
||||
: `})\n` +
|
||||
(inclCom ? `%%[🖋 Edit in Excalidraw](${encodeURI(excalidrawRelativePath,
|
||||
)})${otherImageRelativePath?", and the ["+otherTheme.split(".")[0]+" exported image]("+encodeURI(otherImageRelativePath)+")":""}%%` : ""),
|
||||
);
|
||||
editor.focus();
|
||||
}
|
||||
}
|
||||
|
||||
public async loadSettings(opts: {
|
||||
applyLefthandedMode?: boolean,
|
||||
reEnableAutosave?: boolean
|
||||
@@ -3811,128 +3363,9 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
})
|
||||
}
|
||||
|
||||
public openDrawing(
|
||||
drawingFile: TFile,
|
||||
location: PaneTarget,
|
||||
active: boolean = false,
|
||||
subpath?: string,
|
||||
justCreated: boolean = false,
|
||||
popoutLocation?: {x?: number, y?: number, width?: number, height?: number},
|
||||
) {
|
||||
|
||||
const fnGetLeaf = ():WorkspaceLeaf => {
|
||||
if(location === "md-properties") {
|
||||
location = "new-tab";
|
||||
}
|
||||
let leaf: WorkspaceLeaf;
|
||||
if(location === "popout-window") {
|
||||
//@ts-ignore (the api does not include x,y)
|
||||
leaf = this.app.workspace.openPopoutLeaf(popoutLocation);
|
||||
}
|
||||
if(location === "new-tab") {
|
||||
leaf = this.app.workspace.getLeaf('tab');
|
||||
}
|
||||
if(!leaf) {
|
||||
leaf = this.app.workspace.getLeaf(false);
|
||||
if ((leaf.view.getViewType() !== 'empty') && (location === "new-pane")) {
|
||||
leaf = getNewOrAdjacentLeaf(this, leaf)
|
||||
}
|
||||
}
|
||||
return leaf;
|
||||
}
|
||||
|
||||
const {leaf, promise} = openLeaf({
|
||||
plugin: this,
|
||||
fnGetLeaf: () => fnGetLeaf(),
|
||||
file: drawingFile,
|
||||
openState:!subpath || subpath === ""
|
||||
? {active}
|
||||
: { active, eState: { subpath } }
|
||||
});
|
||||
|
||||
promise.then(()=>{
|
||||
if(justCreated && this.ea.onFileCreateHook) {
|
||||
try {
|
||||
this.ea.onFileCreateHook({
|
||||
ea: this.ea,
|
||||
excalidrawFile: drawingFile,
|
||||
view: leaf.view as ExcalidrawView,
|
||||
});
|
||||
} catch(e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//retained because some scripts make use of it
|
||||
public async getBlankDrawing(): Promise<string> {
|
||||
const templates = getListOfTemplateFiles(this);
|
||||
if(templates) {
|
||||
const template = await templatePromt(templates, this.app);
|
||||
if (template && template instanceof TFile) {
|
||||
if (
|
||||
(template.extension == "md" && !this.settings.compatibilityMode) ||
|
||||
(template.extension == "excalidraw" && this.settings.compatibilityMode)
|
||||
) {
|
||||
const data = await this.app.vault.read(template);
|
||||
if (data) {
|
||||
return this.settings.matchTheme
|
||||
? changeThemeOfExcalidrawMD(data)
|
||||
: data;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.settings.compatibilityMode) {
|
||||
return this.settings.matchTheme && isObsidianThemeDark()
|
||||
? DARK_BLANK_DRAWING
|
||||
: BLANK_DRAWING;
|
||||
}
|
||||
const blank =
|
||||
this.settings.matchTheme && isObsidianThemeDark()
|
||||
? DARK_BLANK_DRAWING
|
||||
: BLANK_DRAWING;
|
||||
return `${FRONTMATTER}\n${getMarkdownDrawingSection(
|
||||
blank,
|
||||
this.settings.compress,
|
||||
)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the text elements from an Excalidraw scene into a string of ids as headers followed by the text contents
|
||||
* @param {string} data - Excalidraw scene JSON string
|
||||
* @returns {string} - Text starting with the "# Text Elements" header and followed by each "## id-value" and text
|
||||
*/
|
||||
public async exportSceneToMD(data: string, compressOverride?: boolean): Promise<string> {
|
||||
if (!data) {
|
||||
return "";
|
||||
}
|
||||
const excalidrawData = JSON_parse(data);
|
||||
const textElements = excalidrawData.elements?.filter(
|
||||
(el: any) => el.type == "text",
|
||||
);
|
||||
let outString = `# Excalidraw Data\n## Text Elements\n`;
|
||||
let id: string;
|
||||
for (const te of textElements) {
|
||||
id = te.id;
|
||||
//replacing Excalidraw text IDs with my own, because default IDs may contain
|
||||
//characters not recognized by Obsidian block references
|
||||
//also Excalidraw IDs are inconveniently long
|
||||
if (te.id.length > 8) {
|
||||
id = nanoid();
|
||||
data = data.replaceAll(te.id, id); //brute force approach to replace all occurrences.
|
||||
}
|
||||
outString += `${te.originalText ?? te.text} ^${id}\n\n`;
|
||||
}
|
||||
return (
|
||||
outString +
|
||||
getMarkdownDrawingSection(
|
||||
JSON.stringify(JSON_parse(data), null, "\t"),
|
||||
typeof compressOverride === "undefined"
|
||||
? this.settings.compress
|
||||
: compressOverride,
|
||||
)
|
||||
);
|
||||
return await this.fileManager.getBlankDrawing();
|
||||
}
|
||||
|
||||
public async createDrawing(
|
||||
@@ -3940,25 +3373,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
foldername?: string,
|
||||
initData?: string,
|
||||
): Promise<TFile> {
|
||||
const folderpath = normalizePath(
|
||||
foldername ? foldername : this.settings.folder,
|
||||
);
|
||||
await checkAndCreateFolder(folderpath); //create folder if it does not exist
|
||||
const fname = getNewUniqueFilepath(this.app.vault, filename, folderpath);
|
||||
const file = await this.app.vault.create(
|
||||
fname,
|
||||
initData ?? (await this.getBlankDrawing()),
|
||||
);
|
||||
|
||||
//wait for metadata cache
|
||||
let counter = 0;
|
||||
while(file instanceof TFile && !this.isExcalidrawFile(file) && counter++<10) {
|
||||
await sleep(50);
|
||||
}
|
||||
|
||||
if(counter > 10) {
|
||||
errorlog({file, error: "new drawing not recognized as an excalidraw file", fn: this.createDrawing});
|
||||
}
|
||||
const file = await this.fileManager.createDrawing(filename, foldername, initData);
|
||||
|
||||
if(Date.now() - this.loadTimestamp > 1){//2000) {
|
||||
const filecount = this.app.vault.getFiles().filter(f=>this.isExcalidrawFile(f)).length;
|
||||
@@ -3985,7 +3400,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
initData?: string,
|
||||
): Promise<string> {
|
||||
const file = await this.createDrawing(filename, foldername, initData);
|
||||
this.openDrawing(file, location, true, undefined, true);
|
||||
this.fileManager.openDrawing(file, location, true, undefined, true);
|
||||
return file.path;
|
||||
}
|
||||
|
||||
@@ -4015,46 +3430,47 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
public isExcalidrawFile(f: TFile) {
|
||||
if(!f) return false;
|
||||
if (f.extension === "excalidraw") {
|
||||
return true;
|
||||
}
|
||||
const fileCache = f ? this.app.metadataCache.getFileCache(f) : null;
|
||||
return !!fileCache?.frontmatter && !!fileCache.frontmatter[FRONTMATTER_KEYS["plugin"].name];
|
||||
return this.fileManager.isExcalidrawFile(f);
|
||||
}
|
||||
|
||||
public openDrawing(
|
||||
drawingFile: TFile,
|
||||
location: PaneTarget,
|
||||
active: boolean = false,
|
||||
subpath?: string,
|
||||
justCreated: boolean = false,
|
||||
popoutLocation?: {x?: number, y?: number, width?: number, height?: number},
|
||||
) {
|
||||
this.fileManager.openDrawing(drawingFile, location, active, subpath, justCreated, popoutLocation);
|
||||
}
|
||||
|
||||
public async embedDrawing(file: TFile) {
|
||||
return await this.fileManager.embedDrawing(file);
|
||||
}
|
||||
|
||||
public async exportLibrary() {
|
||||
if (DEVICE.isMobile) {
|
||||
const prompt = new Prompt(
|
||||
this.app,
|
||||
"Please provide a filename",
|
||||
"my-library",
|
||||
"filename, leave blank to cancel action",
|
||||
);
|
||||
prompt.openAndGetValue(async (filename: string) => {
|
||||
if (!filename) {
|
||||
return;
|
||||
}
|
||||
filename = `${filename}.excalidrawlib`;
|
||||
const folderpath = normalizePath(this.settings.folder);
|
||||
await checkAndCreateFolder(folderpath); //create folder if it does not exist
|
||||
const fname = getNewUniqueFilepath(
|
||||
this.app.vault,
|
||||
filename,
|
||||
folderpath,
|
||||
);
|
||||
this.app.vault.create(fname, this.settings.library);
|
||||
new Notice(`Exported library to ${fname}`, 6000);
|
||||
});
|
||||
return;
|
||||
}
|
||||
download(
|
||||
"data:text/plain;charset=utf-8",
|
||||
encodeURIComponent(JSON.stringify(this.settings.library2, null, "\t")),
|
||||
"my-obsidian-library.excalidrawlib",
|
||||
);
|
||||
return await this.fileManager.exportLibrary();
|
||||
}
|
||||
|
||||
public addThemeObserver() {
|
||||
this.observerManager.addThemeObserver();
|
||||
}
|
||||
|
||||
public removeThemeObserver() {
|
||||
this.observerManager.removeThemeObserver();
|
||||
}
|
||||
|
||||
public experimentalFileTypeDisplayToggle(enabled: boolean) {
|
||||
this.observerManager.experimentalFileTypeDisplayToggle(enabled);
|
||||
}
|
||||
|
||||
public getPackage(win:Window):Packages {
|
||||
return this.packageManager.getPackage(win);
|
||||
}
|
||||
|
||||
public deletePackage(win: Window) {
|
||||
this.packageManager.deletePackage(win);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Copy, Crop, Globe, RotateCcw, Scan, Settings, TextSelect } from "lucide-react";
|
||||
import * as React from "react";
|
||||
import { PenStyle } from "src/PenTypes";
|
||||
import { PenStyle } from "src/types/PenTypes";
|
||||
|
||||
export const ICONS = {
|
||||
ExportImage: (
|
||||
|
||||
@@ -5,7 +5,7 @@ import * as React from "react";
|
||||
import { DEVICE } from "src/constants/constants";
|
||||
import { PenSettingsModal } from "src/dialogs/PenSettingsModal";
|
||||
import ExcalidrawView from "src/ExcalidrawView";
|
||||
import { PenStyle } from "src/PenTypes";
|
||||
import { PenStyle } from "src/types/PenTypes";
|
||||
import { PENS } from "src/utils/Pens";
|
||||
import ExcalidrawPlugin from "../main";
|
||||
import { ICONS, penIcon, stringToSVG } from "./ActionIcons";
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
import { GITHUB_RELEASES } from "./constants/constants";
|
||||
import { t } from "./lang/helpers";
|
||||
import type ExcalidrawPlugin from "./main";
|
||||
import { PenStyle } from "./PenTypes";
|
||||
import { PenStyle } from "./types/PenTypes";
|
||||
import { DynamicStyle, GridSettings } from "./types/types";
|
||||
import { PreviewImageType } from "./utils/UtilTypes";
|
||||
import { setDynamicStyle } from "./utils/DynamicStyling";
|
||||
|
||||
4
src/types/types.d.ts
vendored
4
src/types/types.d.ts
vendored
@@ -44,6 +44,10 @@ declare global {
|
||||
interface Window {
|
||||
ExcalidrawAutomate: ExcalidrawAutomate;
|
||||
pdfjsLib: any;
|
||||
eval: (x: string) => any;
|
||||
React?: any;
|
||||
ReactDOM?: any;
|
||||
ExcalidrawLib?: any;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PenStyle, PenType } from "src/PenTypes";
|
||||
import { PenStyle, PenType } from "src/types/PenTypes";
|
||||
|
||||
export const PENS:Record<PenType,PenStyle> = {
|
||||
"default": {
|
||||
|
||||
Reference in New Issue
Block a user