import { TFile, Plugin, WorkspaceLeaf, addIcon, App, PluginManifest, MarkdownView, normalizePath, Menu, MenuItem, TAbstractFile, ViewState, Notice, loadMathJax, request, MetadataCache, FrontMatterCache, } from "obsidian"; import { BLANK_DRAWING, VIEW_TYPE_EXCALIDRAW, EXCALIDRAW_ICON, ICON_NAME, SCRIPTENGINE_ICON, SCRIPTENGINE_ICON_NAME, DISK_ICON, DISK_ICON_NAME, PNG_ICON, PNG_ICON_NAME, SVG_ICON, SVG_ICON_NAME, RERENDER_EVENT, FRONTMATTER_KEY, FRONTMATTER, JSON_parse, nanoid, DARK_BLANK_DRAWING, CTRL_OR_CMD, SCRIPT_INSTALL_CODEBLOCK, SCRIPT_INSTALL_FOLDER, VIRGIL_FONT, VIRGIL_DATAURL, } from "./Constants"; import ExcalidrawView, { TextMode, getTextMode } from "./ExcalidrawView"; import { changeThemeOfExcalidrawMD, getMarkdownDrawingSection, ExcalidrawData } from "./ExcalidrawData"; import { ExcalidrawSettings, DEFAULT_SETTINGS, ExcalidrawSettingTab, } from "./Settings"; import { openDialogAction, OpenFileDialog } from "./dialogs/OpenDrawing"; import { InsertLinkDialog } from "./dialogs/InsertLinkDialog"; import { InsertImageDialog } from "./dialogs/InsertImageDialog"; import { InsertMDDialog } from "./dialogs/InsertMDDialog"; import { initExcalidrawAutomate, destroyExcalidrawAutomate, ExcalidrawAutomate, insertLaTeXToView, search, } from "./ExcalidrawAutomate"; import { Prompt } from "./dialogs/Prompt"; import { around } from "monkey-around"; import { t } from "./lang/helpers"; import { checkAndCreateFolder, download, getDrawingFilename, getEmbedFilename, getIMGFilename, getIMGPathFromExcalidrawFile, getNewUniqueFilepath, } from "./utils/FileUtils"; import { getFontDataURL, errorlog, log, setLeftHandedMode, sleep, } from "./utils/Utils"; import { getAttachmentsFolderAndFilePath, getNewOrAdjacentLeaf, isObsidianThemeDark } from "./utils/ObsidianUtils"; //import { OneOffs } from "./OneOffs"; import { FileId } from "@zsviczian/excalidraw/types/element/types"; import { ScriptEngine } from "./Scripts"; import { hoverEvent, initializeMarkdownPostProcessor, markdownPostProcessor, observer, } from "./MarkdownPostProcessor"; import { FieldSuggester } from "./dialogs/FieldSuggester"; import { ReleaseNotes } from "./dialogs/ReleaseNotes"; declare module "obsidian" { interface App { isMobile(): boolean; } interface Keymap { getRootScope(): Scope; } interface Scope { keys: any[]; } interface Workspace { on( name: "hover-link", callback: (e: MouseEvent) => any, ctx?: any, ): EventRef; } } export default class ExcalidrawPlugin extends Plugin { private excalidrawFiles: Set = new Set(); public excalidrawFileModes: { [file: string]: string } = {}; private _loaded: boolean = false; public settings: ExcalidrawSettings; private openDialog: OpenFileDialog; public insertLinkDialog: InsertLinkDialog; public insertImageDialog: InsertImageDialog; public insertMDDialog: InsertMDDialog; public activeExcalidrawView: ExcalidrawView = null; public lastActiveExcalidrawFilePath: string = null; public hover: { linkText: string; sourcePath: string } = { linkText: null, sourcePath: null, }; private observer: MutationObserver; private themeObserver: MutationObserver; private fileExplorerObserver: MutationObserver; private modalContainerObserver: MutationObserver; private workspaceDrawerLeftObserver: MutationObserver; private workspaceDrawerRightObserver: MutationObserver; 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 mathjax: any = null; private mathjaxDiv: HTMLDivElement = null; public mathjaxLoaderFinished: boolean = false; public scriptEngine: ScriptEngine; public fourthFontDef: string = VIRGIL_FONT; constructor(app: App, manifest: PluginManifest) { super(app, manifest); this.filesMaster = new Map< FileId, { path: string; hasSVGwithBitmap: boolean } >(); this.equationsMaster = new Map(); } async onload() { addIcon(ICON_NAME, EXCALIDRAW_ICON); addIcon(SCRIPTENGINE_ICON_NAME, SCRIPTENGINE_ICON); addIcon(DISK_ICON_NAME, DISK_ICON); addIcon(PNG_ICON_NAME, PNG_ICON); addIcon(SVG_ICON_NAME, SVG_ICON); await this.loadSettings(); this.addSettingTab(new ExcalidrawSettingTab(this.app, this)); this.ea = await initExcalidrawAutomate(this); 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.initializeFourthFont(); this.registerEditorSuggest(new FieldSuggester(this)); //inspiration taken from kanban: //https://github.com/mgmeyers/obsidian-kanban/blob/44118e25661bff9ebfe54f71ae33805dc88ffa53/src/main.ts#L267 this.registerMonkeyPatches(); if (!this.app.isMobile) { const electron: string = process?.versions?.electron; if (electron && electron?.startsWith("8.")) { new Notice( `You are running an older version of the electron Browser (${electron}). If Excalidraw does not start up, please reinstall Obsidian with the latest installer and try again.`, 10000, ); } } // 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.imageElementNotice; const version: string = //@ts-ignore this.app.plugins.manifests["obsidian-excalidraw-plugin"].version; if (version > this.settings.previousRelease) { new ReleaseNotes( this.app, this, obsidianJustInstalled ? null : version, ).open(); } } this.switchToExcalidarwAfterLoad(); this.loadMathJax(); const self = this; this.app.workspace.onLayoutReady(() => { this.scriptEngine = new ScriptEngine(self); }); } public initializeFourthFont() { this.app.workspace.onLayoutReady(async () => { const font = await getFontDataURL( this.app, this.settings.experimantalFourthFont, "", "LocalFont", ); const fourthFontDataURL = font.dataURL === "" ? VIRGIL_DATAURL : font.dataURL; this.fourthFontDef = font.fontDef; const newStylesheet = document.createElement("style"); newStylesheet.id = "local-font-stylesheet"; newStylesheet.textContent = ` @font-face { font-family: 'LocalFont'; src: url("${fourthFontDataURL}"); font-display: swap; } `; // replace the old local font