import { TFile, Plugin, WorkspaceLeaf, addIcon, App, PluginManifest, MarkdownView, normalizePath, Menu, MenuItem, TAbstractFile, ViewState, Notice, request, MetadataCache, FrontMatterCache, Command, Workspace, Editor, MarkdownFileInfo, loadMermaid, } from "obsidian"; import { BLANK_DRAWING, VIEW_TYPE_EXCALIDRAW, EXCALIDRAW_ICON, ICON_NAME, SCRIPTENGINE_ICON, SCRIPTENGINE_ICON_NAME, RERENDER_EVENT, FRONTMATTER_KEYS, FRONTMATTER, JSON_parse, nanoid, DARK_BLANK_DRAWING, SCRIPT_INSTALL_CODEBLOCK, SCRIPT_INSTALL_FOLDER, EXPORT_TYPES, EXPORT_IMG_ICON_NAME, EXPORT_IMG_ICON, LOCALE, IMAGE_TYPES, setExcalidrawPlugin, DEVICE, sceneCoordsToViewportCoords } from "./constants/constants"; import { VIRGIL_FONT, VIRGIL_DATAURL, FONTS_STYLE_ID, } from "./constants/constFonts"; import ExcalidrawView, { TextMode, getTextMode } from "./ExcalidrawView"; import { changeThemeOfExcalidrawMD, getMarkdownDrawingSection, ExcalidrawData, REGEX_LINK, } from "./ExcalidrawData"; import { ExcalidrawSettings, DEFAULT_SETTINGS, ExcalidrawSettingTab, } from "./settings"; import { openDialogAction, OpenFileDialog } from "./dialogs/OpenDrawing"; import { InsertLinkDialog } from "./dialogs/InsertLinkDialog"; import { InsertCommandDialog } from "./dialogs/InsertCommandDialog"; import { InsertImageDialog } from "./dialogs/InsertImageDialog"; import { ImportSVGDialog } from "./dialogs/ImportSVGDialog"; import { InsertMDDialog } from "./dialogs/InsertMDDialog"; import { initExcalidrawAutomate, destroyExcalidrawAutomate, ExcalidrawAutomate, insertLaTeXToView, search, } from "./ExcalidrawAutomate"; import { Prompt, templatePromt } from "./dialogs/Prompt"; import { around, dedupe } from "monkey-around"; import { t } from "./lang/helpers"; import { checkAndCreateFolder, download, fileShouldDefaultAsExcalidraw, getAliasWithSize, getAnnotationFileNameAndFolder, getCropFileNameAndFolder, getDrawingFilename, getEmbedFilename, getIMGFilename, getLink, getListOfTemplateFiles, getNewUniqueFilepath, getURLImageExtension, } from "./utils/FileUtils"; import { getFontDataURL, errorlog, setLeftHandedMode, sleep, isVersionNewerThanOther, getExportTheme, isCallerFromTemplaterPlugin, decompress, getImageSize, } from "./utils/Utils"; import { editorInsertText, extractSVGPNGFileName, foldExcalidrawSection, getActivePDFPageNumberFromPDFView, getAttachmentsFolderAndFilePath, getNewOrAdjacentLeaf, getParentOfClass, isObsidianThemeDark, mergeMarkdownFiles, openLeaf } from "./utils/ObsidianUtils"; import { ExcalidrawElement, ExcalidrawEmbeddableElement, ExcalidrawImageElement, ExcalidrawTextElement, FileId } from "@zsviczian/excalidraw/types/excalidraw/element/types"; import { ScriptEngine } from "./Scripts"; import { hoverEvent, initializeMarkdownPostProcessor, markdownPostProcessor, legacyExcalidrawPopoverObserver, } from "./MarkdownPostProcessor"; import { FieldSuggester } from "./dialogs/FieldSuggester"; import { ReleaseNotes } from "./dialogs/ReleaseNotes"; import { Packages } from "./types"; import { PreviewImageType } from "./utils/UtilTypes"; import { ScriptInstallPrompt } from "./dialogs/ScriptInstallPrompt"; import Taskbone from "./ocr/Taskbone"; import { emulateCTRLClickForLinks, linkClickModifierType, PaneTarget } from "./utils/ModifierkeyHelper"; import { InsertPDFModal } from "./dialogs/InsertPDFModal"; import { ExportDialog } from "./dialogs/ExportDialog"; import { UniversalInsertFileModal } from "./dialogs/UniversalInsertFileModal"; import { imageCache } from "./utils/ImageCache"; import { StylesManager } from "./utils/StylesManager"; import { PublishOutOfDateFilesDialog } from "./dialogs/PublishOutOfDateFiles"; import { EmbeddableSettings } from "./dialogs/EmbeddableSettings"; import { processLinkText } from "./utils/CustomEmbeddableUtils"; import { getEA } from "src"; import { ExcalidrawImperativeAPI } from "@zsviczian/excalidraw/types/excalidraw/types"; import { Mutable } from "@zsviczian/excalidraw/types/excalidraw/utility-types"; import { CustomMutationObserver, debug, durationTreshold, log, DEBUGGING, setDebugging } from "./utils/DebugHelper"; import { carveOutImage, carveOutPDF, createImageCropperFile } from "./utils/CarveOut"; import { ExcalidrawConfig } from "./utils/ExcalidrawConfig"; import { EditorHandler } from "./CodeMirrorExtension/EditorHandler"; declare const EXCALIDRAW_PACKAGES:string; declare const react:any; declare const reactDOM:any; declare const excalidrawLib: any; declare const PLUGIN_VERSION:string; declare var LZString: any; export default class ExcalidrawPlugin extends Plugin { public excalidrawConfig: ExcalidrawConfig; public taskbone: Taskbone; private excalidrawFiles: Set = new Set(); public excalidrawFileModes: { [file: string]: string } = {}; private _loaded: boolean = false; public settings: ExcalidrawSettings; private openDialog: OpenFileDialog; public insertLinkDialog: InsertLinkDialog; public insertCommandDialog: InsertCommandDialog; public insertImageDialog: InsertImageDialog; public importSVGDialog: ImportSVGDialog; public insertMDDialog: InsertMDDialog; public activeExcalidrawView: ExcalidrawView = null; public lastActiveExcalidrawFilePath: string = null; public hover: { linkText: string; sourcePath: string } = { linkText: null, 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; public ea: ExcalidrawAutomate; //A master list of fileIds to facilitate copy / paste public filesMaster: Map = null; //fileId, path public equationsMaster: Map = null; //fileId, formula public mermaidsMaster: Map = null; //fileId, mermaidText public scriptEngine: ScriptEngine; public fourthFontDef: string = VIRGIL_FONT; private packageMap: WeakMap = new WeakMap(); public leafChangeTimeout: NodeJS.Timeout = null; private forceSaveCommand:Command; private removeEventLisnters:(()=>void)[] = []; private stylesManager:StylesManager; private textMeasureDiv:HTMLDivElement = null; public editorHandler: EditorHandler; public activeLeafChangeEventHandler: (leaf: WorkspaceLeaf) => Promise; //if set, the next time this file is opened it will be opened as markdown public forceToOpenInMarkdownFilepath: string = null; constructor(app: App, manifest: PluginManifest) { super(app, manifest); this.filesMaster = new Map< FileId, { isHyperLink: boolean; isLocalLink: boolean; path: string; hasSVGwithBitmap: boolean; blockrefData: string; colorMapJSON?: string } >(); this.equationsMaster = new Map(); this.mermaidsMaster = new Map(); setExcalidrawPlugin(this); } get locale() { return LOCALE; } get window(): Window { return window; }; get document(): Document { return document; }; public getPackage(win:Window):Packages { (process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.getPackage, `ExcalidrawPlugin.getPackage`, win); if(win===window) { return {react, reactDOM, excalidrawLib}; } if(this.packageMap.has(win)) { return this.packageMap.get(win); } //@ts-ignore const {react:r, reactDOM:rd, excalidrawLib:e} = win.eval.call(win, `(function() { ${LZString.decompressFromBase64(EXCALIDRAW_PACKAGES)}; 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 registerEvent(event: any) { if (process.env.NODE_ENV !== 'development') { super.registerEvent(event); return; } else { if(!DEBUGGING) { super.registerEvent(event); return; } const originalHandler = event.fn; // Wrap the original event handler const wrappedHandler = async (...args: any[]) => { const startTime = performance.now(); // Get start time // Invoke the original event handler const result = await originalHandler(...args); const endTime = performance.now(); // Get end time const executionTime = endTime - startTime; if(executionTime > durationTreshold) { console.log(`Excalidraw Event '${event.name}' took ${executionTime}ms to execute`); } return result; } // Replace the original event handler with the wrapped one event.fn = wrappedHandler; // Register the modified event super.registerEvent(event); }; } async onload() { addIcon(ICON_NAME, EXCALIDRAW_ICON); addIcon(SCRIPTENGINE_ICON_NAME, SCRIPTENGINE_ICON); addIcon(EXPORT_IMG_ICON_NAME, EXPORT_IMG_ICON); await this.loadSettings({reEnableAutosave:true}); if(!this.settings.onceOffCompressFlagReset) { this.settings.compress = true; this.settings.onceOffCompressFlagReset = true; await this.saveSettings(); } this.excalidrawConfig = new ExcalidrawConfig(this); await loadMermaid(); this.editorHandler = new EditorHandler(this); this.editorHandler.setup(); this.addSettingTab(new ExcalidrawSettingTab(this.app, this)); this.ea = await initExcalidrawAutomate(this); this.textMeasureDiv = document.createElement("div"); this.textMeasureDiv.setAttribute("id", "excalidraw-measure-text"); document.body.appendChild(this.textMeasureDiv); this.registerView( VIEW_TYPE_EXCALIDRAW, (leaf: WorkspaceLeaf) => new ExcalidrawView(leaf, this), ); //Compatibility mode with .excalidraw files this.registerExtensions(["excalidraw"], VIEW_TYPE_EXCALIDRAW); this.addMarkdownPostProcessor(); this.registerInstallCodeblockProcessor(); this.addThemeObserver(); this.experimentalFileTypeDisplayToggle(this.settings.experimentalFileType); this.registerCommands(); this.registerEventListeners(); this.runStartupScript(); this.initializeFonts(); this.registerEditorSuggest(new FieldSuggester(this)); this.setPropertyTypes(); //inspiration taken from kanban: //https://github.com/mgmeyers/obsidian-kanban/blob/44118e25661bff9ebfe54f71ae33805dc88ffa53/src/main.ts#L267 this.registerMonkeyPatches(); this.stylesManager = new StylesManager(this); // const patches = new OneOffs(this); if (this.settings.showReleaseNotes) { //I am repurposing imageElementNotice, if the value is true, this means the plugin was just newly installed to Obsidian. const obsidianJustInstalled = this.settings.previousRelease === "0.0.0" if (isVersionNewerThanOther(PLUGIN_VERSION, this.settings.previousRelease)) { new ReleaseNotes( this.app, this, obsidianJustInstalled ? null : PLUGIN_VERSION, ).open(); } } this.switchToExcalidarwAfterLoad(); const self = this; this.app.workspace.onLayoutReady(() => { (process.env.NODE_ENV === 'development') && DEBUGGING && debug(self.onload,"ExcalidrawPlugin.onload > app.workspace.onLayoutReady"); this.scriptEngine = new ScriptEngine(self); imageCache.initializeDB(self); }); this.taskbone = new Taskbone(this); } private setPropertyTypes() { const app = this.app; const self = this; this.app.workspace.onLayoutReady(() => { (process.env.NODE_ENV === 'development') && DEBUGGING && debug(self.setPropertyTypes, `ExcalidrawPlugin.setPropertyTypes > app.workspace.onLayoutReady`); Object.keys(FRONTMATTER_KEYS).forEach((key) => { if(FRONTMATTER_KEYS[key].depricated === true) return; const {name, type} = FRONTMATTER_KEYS[key]; app.metadataTypeManager.setType(name,type); }); }); } public initializeFonts() { const self = this; this.app.workspace.onLayoutReady(async () => { (process.env.NODE_ENV === 'development') && DEBUGGING && debug(self.initializeFonts,`ExcalidrawPlugin.initializeFonts > app.workspace.onLayoutReady`); const font = await getFontDataURL( this.app, this.settings.experimantalFourthFont, "", "LocalFont", ); const fourthFontDataURL = font.dataURL === "" ? VIRGIL_DATAURL : font.dataURL; this.fourthFontDef = font.fontDef; this.getOpenObsidianDocuments().forEach((ownerDocument) => { this.addFonts([ `@font-face{font-family:'LocalFont';src:url("${fourthFontDataURL}");font-display: swap;`, ],ownerDocument); }) }); } public addFonts(declarations: string[],ownerDocument:Document = document) { // replace the old local font