mirror of
https://github.com/zsviczian/obsidian-excalidraw-plugin.git
synced 2025-08-06 05:46:28 +00:00
Compare commits
13 Commits
2.6.8
...
2.7.0-beta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5c4d37cce4 | ||
|
|
7b5f701f8f | ||
|
|
0eca97bf18 | ||
|
|
f620263fc6 | ||
|
|
4e299677bd | ||
|
|
b8655cff5e | ||
|
|
be452fee6d | ||
|
|
90589dd075 | ||
|
|
9c5b48c037 | ||
|
|
4406709920 | ||
|
|
b7ba0f8909 | ||
|
|
c28911c739 | ||
|
|
28088754ad |
96
MathjaxToSVG/index.ts
Normal file
96
MathjaxToSVG/index.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
import {mathjax} from "mathjax-full/js/mathjax";
|
||||
import {TeX} from 'mathjax-full/js/input/tex.js';
|
||||
import {SVG} from 'mathjax-full/js/output/svg.js';
|
||||
import {LiteAdaptor, liteAdaptor} from 'mathjax-full/js/adaptors/liteAdaptor.js';
|
||||
import {RegisterHTMLHandler} from 'mathjax-full/js/handlers/html.js';
|
||||
import {AllPackages} from 'mathjax-full/js/input/tex/AllPackages.js';
|
||||
import { customAlphabet } from "nanoid";
|
||||
|
||||
type DataURL = string & { _brand: "DataURL" };
|
||||
type FileId = string & { _brand: "FileId" };
|
||||
const fileid = customAlphabet("1234567890abcdef", 40);
|
||||
|
||||
let adaptor: LiteAdaptor;
|
||||
let html: any;
|
||||
let preamble: string;
|
||||
|
||||
function svgToBase64(svg: string): string {
|
||||
return `data:image/svg+xml;base64,${btoa(
|
||||
decodeURIComponent(encodeURIComponent(svg.replaceAll(" ", " "))),
|
||||
)}`;
|
||||
}
|
||||
|
||||
async function getImageSize(src: string): Promise<{ height: number; width: number }> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = new Image();
|
||||
img.onload = () => resolve({ height: img.naturalHeight, width: img.naturalWidth });
|
||||
img.onerror = reject;
|
||||
img.src = src;
|
||||
});
|
||||
}
|
||||
|
||||
export async function tex2dataURL(
|
||||
tex: string,
|
||||
scale: number = 4,
|
||||
app?: any
|
||||
): Promise<{
|
||||
mimeType: string;
|
||||
fileId: FileId;
|
||||
dataURL: DataURL;
|
||||
created: number;
|
||||
size: { height: number; width: number };
|
||||
}> {
|
||||
let input: TeX<unknown, unknown, unknown>;
|
||||
let output: SVG<unknown, unknown, unknown>;
|
||||
|
||||
if(!adaptor) {
|
||||
if (app) {
|
||||
const file = app.vault.getAbstractFileByPath("preamble.sty");
|
||||
preamble = file ? await app.vault.read(file) : null;
|
||||
}
|
||||
adaptor = liteAdaptor();
|
||||
RegisterHTMLHandler(adaptor);
|
||||
input = new TeX({
|
||||
packages: AllPackages,
|
||||
...(preamble ? {
|
||||
inlineMath: [['$', '$']],
|
||||
displayMath: [['$$', '$$']]
|
||||
} : {}),
|
||||
});
|
||||
output = new SVG({ fontCache: "local" });
|
||||
html = mathjax.document("", { InputJax: input, OutputJax: output });
|
||||
}
|
||||
|
||||
try {
|
||||
const node = html.convert(
|
||||
preamble ? `${preamble}${tex}` : tex,
|
||||
{ display: true, scale }
|
||||
);
|
||||
const svg = new DOMParser().parseFromString(adaptor.innerHTML(node), "image/svg+xml").firstChild as SVGSVGElement;
|
||||
if (svg) {
|
||||
if(svg.width.baseVal.valueInSpecifiedUnits < 2) {
|
||||
svg.width.baseVal.valueAsString = `${(svg.width.baseVal.valueInSpecifiedUnits+1).toFixed(3)}ex`;
|
||||
}
|
||||
const img = svgToBase64(svg.outerHTML);
|
||||
svg.width.baseVal.valueAsString = (svg.width.baseVal.valueInSpecifiedUnits * 10).toFixed(3);
|
||||
svg.height.baseVal.valueAsString = (svg.height.baseVal.valueInSpecifiedUnits * 10).toFixed(3);
|
||||
const dataURL = svgToBase64(svg.outerHTML);
|
||||
return {
|
||||
mimeType: "image/svg+xml",
|
||||
fileId: fileid() as FileId,
|
||||
dataURL: dataURL as DataURL,
|
||||
created: Date.now(),
|
||||
size: await getImageSize(img),
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function clearMathJaxVariables(): void {
|
||||
adaptor = null;
|
||||
html = null;
|
||||
preamble = null;
|
||||
}
|
||||
23
MathjaxToSVG/package.json
Normal file
23
MathjaxToSVG/package.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "@zsviczian/mathjax-to-svg",
|
||||
"version": "1.0.0",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"build": "cross-env NODE_ENV=production rollup --config rollup.config.js",
|
||||
"dev": "cross-env NODE_ENV=development rollup --config rollup.config.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"mathjax-full": "^3.2.2",
|
||||
"nanoid": "^4.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-commonjs": "^26.0.1",
|
||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||
"@rollup/plugin-typescript": "^11.1.6",
|
||||
"cross-env": "^7.0.3",
|
||||
"obsidian": "1.5.7-1",
|
||||
"rollup": "^2.70.1",
|
||||
"typescript": "^5.2.2",
|
||||
"rollup-plugin-terser": "^7.0.2"
|
||||
}
|
||||
}
|
||||
35
MathjaxToSVG/rollup.config.js
Normal file
35
MathjaxToSVG/rollup.config.js
Normal file
@@ -0,0 +1,35 @@
|
||||
import { nodeResolve } from '@rollup/plugin-node-resolve';
|
||||
import commonjs from '@rollup/plugin-commonjs';
|
||||
import typescript from '@rollup/plugin-typescript';
|
||||
import { terser } from 'rollup-plugin-terser';
|
||||
|
||||
const isProd = (process.env.NODE_ENV === 'production');
|
||||
|
||||
export default {
|
||||
input: './index.ts',
|
||||
output: {
|
||||
dir: 'dist',
|
||||
format: 'iife',
|
||||
name: 'MathjaxToSVG', // Global variable name
|
||||
exports: 'named',
|
||||
sourcemap: !isProd,
|
||||
},
|
||||
plugins: [
|
||||
typescript({
|
||||
tsconfig: '../tsconfig.json',
|
||||
}),
|
||||
commonjs(),
|
||||
nodeResolve({
|
||||
browser: true,
|
||||
preferBuiltins: false
|
||||
}),
|
||||
isProd && terser({
|
||||
format: {
|
||||
comments: false,
|
||||
},
|
||||
compress: {
|
||||
passes: 2,
|
||||
}
|
||||
})
|
||||
].filter(Boolean)
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "obsidian-excalidraw-plugin",
|
||||
"name": "Excalidraw",
|
||||
"version": "2.6.8",
|
||||
"version": "2.7.0-beta-4",
|
||||
"minAppVersion": "1.1.6",
|
||||
"description": "An Obsidian plugin to edit and view Excalidraw drawings",
|
||||
"author": "Zsolt Viczian",
|
||||
|
||||
10
package.json
10
package.json
@@ -8,18 +8,22 @@
|
||||
"lib/**/*"
|
||||
],
|
||||
"scripts": {
|
||||
"dev": "cross-env NODE_ENV=development rollup --config rollup.config.js -w",
|
||||
"dev": "cross-env NODE_ENV=development rollup --config rollup.config.js",
|
||||
"build": "cross-env NODE_ENV=production rollup --config rollup.config.js",
|
||||
"lib": "cross-env NODE_ENV=lib rollup --config rollup.config.js",
|
||||
"code:fix": "eslint --max-warnings=0 --ext .ts,.tsx ./src --fix",
|
||||
"madge": "madge --circular ."
|
||||
"madge": "madge --circular .",
|
||||
"build:mathjax": "cd MathjaxToSVG && npm run build",
|
||||
"build:all": "npm run build:mathjax && npm run build",
|
||||
"dev:mathjax": "cd MathjaxToSVG && npm run dev",
|
||||
"dev:all": "npm run dev:mathjax && npm run dev"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@zsviczian/excalidraw": "0.17.6-18",
|
||||
"@zsviczian/excalidraw": "0.17.6-21",
|
||||
"chroma-js": "^2.4.2",
|
||||
"clsx": "^2.0.0",
|
||||
"@zsviczian/colormaster": "^1.2.2",
|
||||
|
||||
@@ -19,6 +19,8 @@ const isProd = (process.env.NODE_ENV === "production");
|
||||
const isLib = (process.env.NODE_ENV === "lib");
|
||||
console.log(`Running: ${process.env.NODE_ENV}; isProd: ${isProd}; isLib: ${isLib}`);
|
||||
|
||||
const mathjaxtosvg_pkg = isLib ? "" : fs.readFileSync("./MathjaxToSVG/dist/index.js", "utf8");
|
||||
|
||||
const excalidraw_pkg = isLib ? "" : isProd
|
||||
? fs.readFileSync("./node_modules/@zsviczian/excalidraw/dist/excalidraw.production.min.js", "utf8")
|
||||
: fs.readFileSync("./node_modules/@zsviczian/excalidraw/dist/excalidraw.development.js", "utf8");
|
||||
@@ -48,7 +50,9 @@ if (!isLib) {
|
||||
|
||||
const manifestStr = isLib ? "" : fs.readFileSync("manifest.json", "utf-8");
|
||||
const manifest = isLib ? {} : JSON.parse(manifestStr);
|
||||
if (!isLib) console.log(manifest.version);
|
||||
if (!isLib) {
|
||||
console.log(manifest.version);
|
||||
}
|
||||
|
||||
const packageString = isLib
|
||||
? ""
|
||||
@@ -56,11 +60,16 @@ const packageString = isLib
|
||||
'\nlet REACT_PACKAGES = `' +
|
||||
jsesc(react_pkg + reactdom_pkg, { quotes: 'backtick' }) +
|
||||
'`;\n' +
|
||||
'let EXCALIDRAW_PACKAGE = ""; const unpackExcalidraw = () => {EXCALIDRAW_PACKAGE = LZString.decompressFromBase64("' + LZString.compressToBase64(excalidraw_pkg) + '");};\n' +
|
||||
/* 'let EXCALIDRAW_PACKAGE = `' +
|
||||
jsesc(excalidraw_pkg, { quotes: 'backtick' }) +
|
||||
'`;\n' +*/
|
||||
'const unpackExcalidraw = () => LZString.decompressFromBase64("' + LZString.compressToBase64(excalidraw_pkg) + '");\n' +
|
||||
'let {react, reactDOM } = window.eval.call(window, `(function() {' + '${REACT_PACKAGES};' + 'return {react: React, reactDOM: ReactDOM};})();`);\n' +
|
||||
`let excalidrawLib = {};\n` +
|
||||
'let excalidrawLib = {};\n' +
|
||||
'const loadMathjaxToSVG = () => window.eval.call(window, `(function() {' +
|
||||
'${LZString.decompressFromBase64("' + LZString.compressToBase64(mathjaxtosvg_pkg) + '")}' +
|
||||
'return MathjaxToSVG;})();`);\n' +
|
||||
'const PLUGIN_VERSION="' + manifest.version + '";';
|
||||
|
||||
|
||||
const BASE_CONFIG = {
|
||||
input: 'src/main.ts',
|
||||
|
||||
@@ -676,7 +676,7 @@ export class EmbeddedFilesLoader {
|
||||
}
|
||||
if (!excalidrawData.getEquation(id).isLoaded) {
|
||||
const latex = equation.latex;
|
||||
const data = await tex2dataURL(latex);
|
||||
const data = await tex2dataURL(latex, 4, this.plugin.app);
|
||||
if (data) {
|
||||
const fileData = {
|
||||
mimeType: data.mimeType,
|
||||
@@ -701,7 +701,11 @@ export class EmbeddedFilesLoader {
|
||||
return;
|
||||
}
|
||||
const data = getMermaidText(element);
|
||||
const result = await mermaidToExcalidraw(data, {fontSize: 20}, true);
|
||||
const result = await mermaidToExcalidraw(
|
||||
data,
|
||||
{ themeVariables: { fontSize: "20" } },
|
||||
true
|
||||
);
|
||||
if(!result) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1489,7 +1489,12 @@ export class ExcalidrawAutomate {
|
||||
diagram: string,
|
||||
groupElements: boolean = true,
|
||||
): Promise<string[]|string> {
|
||||
const result = await mermaidToExcalidraw(diagram, {fontSize: this.style.fontSize});
|
||||
const result = await mermaidToExcalidraw(
|
||||
diagram, {
|
||||
themeVariables: {fontSize: `${this.style.fontSize}`},
|
||||
flowchart: {curve: this.style.roundness===null ? "linear" : "basis"},
|
||||
}
|
||||
);
|
||||
const ids:string[] = [];
|
||||
if(!result) return null;
|
||||
if(result?.error) return result.error;
|
||||
@@ -1600,7 +1605,7 @@ export class ExcalidrawAutomate {
|
||||
*/
|
||||
async addLaTex(topX: number, topY: number, tex: string): Promise<string> {
|
||||
const id = nanoid();
|
||||
const image = await tex2dataURL(tex);
|
||||
const image = await tex2dataURL(tex, 4, this.plugin.app);
|
||||
if (!image) {
|
||||
return null;
|
||||
}
|
||||
@@ -1643,7 +1648,7 @@ export class ExcalidrawAutomate {
|
||||
created: number;
|
||||
size: { height: number; width: number };
|
||||
}> {
|
||||
return await tex2dataURL(tex,scale);
|
||||
return await tex2dataURL(tex,scale, this.plugin.app);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
35
src/ExcalidrawLib.d.ts
vendored
35
src/ExcalidrawLib.d.ts
vendored
@@ -6,7 +6,38 @@ import { FontMetadata } from "@zsviczian/excalidraw/types/excalidraw/fonts/metad
|
||||
import { AppState, BinaryFiles, DataURL, GenerateDiagramToCode, Zoom } from "@zsviczian/excalidraw/types/excalidraw/types";
|
||||
import { Mutable } from "@zsviczian/excalidraw/types/excalidraw/utility-types";
|
||||
import { GlobalPoint } from "@zsviczian/excalidraw/types/math/types";
|
||||
import ExcalidrawPlugin from "./main";
|
||||
|
||||
interface MermaidConfig {
|
||||
/**
|
||||
* Whether to start the diagram automatically when the page loads.
|
||||
* @default false
|
||||
*/
|
||||
startOnLoad?: boolean;
|
||||
/**
|
||||
* The flowchart curve style.
|
||||
* @default "linear"
|
||||
*/
|
||||
flowchart?: {
|
||||
curve?: "linear" | "basis";
|
||||
};
|
||||
/**
|
||||
* Theme variables
|
||||
* @default { fontSize: "25px" }
|
||||
*/
|
||||
themeVariables?: {
|
||||
fontSize?: string;
|
||||
};
|
||||
/**
|
||||
* Maximum number of edges to be rendered.
|
||||
* @default 1000
|
||||
*/
|
||||
maxEdges?: number;
|
||||
/**
|
||||
* Maximum number of characters to be rendered.
|
||||
* @default 1000
|
||||
*/
|
||||
maxTextSize?: number;
|
||||
}
|
||||
|
||||
type EmbeddedLink =
|
||||
| ({
|
||||
@@ -159,7 +190,7 @@ declare namespace ExcalidrawLib {
|
||||
|
||||
function mermaidToExcalidraw(
|
||||
mermaidDefinition: string,
|
||||
opts: {fontSize: number},
|
||||
opts: MermaidConfig,
|
||||
forceSVG?: boolean,
|
||||
): Promise<{
|
||||
elements?: ExcalidrawElement[];
|
||||
|
||||
@@ -703,24 +703,24 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
}
|
||||
}
|
||||
|
||||
public async setEmbeddableIsEditingSelf() {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.setEmbeddableIsEditingSelf, "ExcalidrawView.setEmbeddableIsEditingSelf");
|
||||
this.clearEmbeddableIsEditingSelfTimer();
|
||||
public async setEmbeddableNodeIsEditing() {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.setEmbeddableNodeIsEditing, "ExcalidrawView.setEmbeddableNodeIsEditing");
|
||||
this.clearEmbeddableNodeIsEditingTimer();
|
||||
await this.forceSave(true);
|
||||
this.semaphores.embeddableIsEditingSelf = true;
|
||||
}
|
||||
|
||||
public clearEmbeddableIsEditingSelfTimer () {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.clearEmbeddableIsEditingSelfTimer, "ExcalidrawView.clearEmbeddableIsEditingSelfTimer");
|
||||
public clearEmbeddableNodeIsEditingTimer () {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.clearEmbeddableNodeIsEditingTimer, "ExcalidrawView.clearEmbeddableNodeIsEditingTimer");
|
||||
if(this.editingSelfResetTimer) {
|
||||
window.clearTimeout(this.editingSelfResetTimer);
|
||||
this.editingSelfResetTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
public clearEmbeddableIsEditingSelf() {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.clearEmbeddableIsEditingSelf, "ExcalidrawView.clearEmbeddableIsEditingSelf");
|
||||
this.clearEmbeddableIsEditingSelfTimer();
|
||||
public clearEmbeddableNodeIsEditing() {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.clearEmbeddableNodeIsEditing, "ExcalidrawView.clearEmbeddableNodeIsEditing");
|
||||
this.clearEmbeddableNodeIsEditingTimer();
|
||||
this.editingSelfResetTimer = window.setTimeout(()=>this.semaphores.embeddableIsEditingSelf = false,EMBEDDABLE_SEMAPHORE_TIMEOUT);
|
||||
}
|
||||
|
||||
@@ -1938,7 +1938,7 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
}
|
||||
|
||||
this.clearPreventReloadTimer();
|
||||
this.clearEmbeddableIsEditingSelfTimer();
|
||||
this.clearEmbeddableNodeIsEditingTimer();
|
||||
this.plugin.scriptEngine?.removeViewEAs(this);
|
||||
this.excalidrawAPI = null;
|
||||
if(this.draginfoDiv) {
|
||||
@@ -2048,7 +2048,7 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
if (this.semaphores.embeddableIsEditingSelf) {
|
||||
//console.log("reload - embeddable is editing")
|
||||
if(this.editingSelfResetTimer) {
|
||||
this.clearEmbeddableIsEditingSelfTimer();
|
||||
this.clearEmbeddableNodeIsEditingTimer();
|
||||
this.semaphores.embeddableIsEditingSelf = false;
|
||||
}
|
||||
if(loadOnModifyTrigger) {
|
||||
@@ -2657,9 +2657,7 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
this.clearDirty();
|
||||
const om = this.excalidrawData.getOpenMode();
|
||||
this.semaphores.preventReload = false;
|
||||
const penEnabled =
|
||||
this.plugin.settings.defaultPenMode === "always" ||
|
||||
(this.plugin.settings.defaultPenMode === "mobile" && DEVICE.isMobile);
|
||||
const penEnabled = this.plugin.isPenMode();
|
||||
const api = this.excalidrawAPI;
|
||||
if (api) {
|
||||
//isLoaded flags that a new file is being loaded, isLoaded will be true after loadDrawing completes
|
||||
@@ -3700,6 +3698,7 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
if (selectedElementWithLink?.id) {
|
||||
linktext = getLinkTextFromLink(selectedElementWithLink.text);
|
||||
if(!linktext) return;
|
||||
if(this.app.metadataCache.getFirstLinkpathDest(linktext.split("#")[0],this.file.path) === this.file) return;
|
||||
}
|
||||
} else {
|
||||
const {linkText, selectedElement} = this.getLinkTextForElement(selectedEl, selectedEl);
|
||||
@@ -3964,6 +3963,9 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
if(st.newElement?.type === "freedraw") {
|
||||
this.freedrawLastActiveTimestamp = Date.now();
|
||||
}
|
||||
if (st.newElement || st.editingTextElement || st.editingLinearElement) {
|
||||
this.plugin.wasPenModeActivePreviously = st.penMode;
|
||||
}
|
||||
this.viewModeEnabled = st.viewModeEnabled;
|
||||
if (this.semaphores.justLoaded) {
|
||||
const elcount = this.excalidrawData?.scene?.elements?.length ?? 0;
|
||||
|
||||
111
src/LaTeX.ts
111
src/LaTeX.ts
@@ -1,18 +1,30 @@
|
||||
// LaTeX.ts
|
||||
import { DataURL } from "@zsviczian/excalidraw/types/excalidraw/types";
|
||||
import {mathjax} from "mathjax-full/js/mathjax";
|
||||
import {TeX} from 'mathjax-full/js/input/tex.js';
|
||||
import {SVG} from 'mathjax-full/js/output/svg.js';
|
||||
import {LiteAdaptor, liteAdaptor} from 'mathjax-full/js/adaptors/liteAdaptor.js';
|
||||
import {RegisterHTMLHandler} from 'mathjax-full/js/handlers/html.js';
|
||||
import {AllPackages} from 'mathjax-full/js/input/tex/AllPackages.js';
|
||||
|
||||
import ExcalidrawView from "./ExcalidrawView";
|
||||
import { FileData, MimeType } from "./EmbeddedFileLoader";
|
||||
import { FileId } from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
import { getImageSize, svgToBase64 } from "./utils/Utils";
|
||||
import { fileid } from "./constants/constants";
|
||||
import { TFile } from "obsidian";
|
||||
import { MathDocument } from "mathjax-full/js/core/MathDocument";
|
||||
import { App } from "obsidian";
|
||||
|
||||
declare const loadMathjaxToSVG: Function;
|
||||
let mathjaxLoaded = false;
|
||||
let tex2dataURLExternal: Function;
|
||||
let clearVariables: Function;
|
||||
|
||||
let loadMathJaxPromise: Promise<void> | null = null;
|
||||
|
||||
const loadMathJax = async () => {
|
||||
if (!loadMathJaxPromise) {
|
||||
loadMathJaxPromise = (async () => {
|
||||
if (!mathjaxLoaded) {
|
||||
const module = await loadMathjaxToSVG();
|
||||
tex2dataURLExternal = module.tex2dataURL;
|
||||
clearVariables = module.clearMathJaxVariables;
|
||||
mathjaxLoaded = true;
|
||||
}
|
||||
})();
|
||||
}
|
||||
return loadMathJaxPromise;
|
||||
};
|
||||
|
||||
export const updateEquation = async (
|
||||
equation: string,
|
||||
@@ -20,13 +32,14 @@ export const updateEquation = async (
|
||||
view: ExcalidrawView,
|
||||
addFiles: Function,
|
||||
) => {
|
||||
const data = await tex2dataURL(equation);
|
||||
await loadMathJax();
|
||||
const data = await tex2dataURLExternal(equation, 4, app);
|
||||
if (data) {
|
||||
const files: FileData[] = [];
|
||||
files.push({
|
||||
mimeType: data.mimeType,
|
||||
mimeType: data.mimeType as MimeType,
|
||||
id: fileId as FileId,
|
||||
dataURL: data.dataURL,
|
||||
dataURL: data.dataURL as DataURL,
|
||||
created: data.created,
|
||||
size: data.size,
|
||||
hasSVGwithBitmap: false,
|
||||
@@ -36,27 +49,10 @@ export const updateEquation = async (
|
||||
}
|
||||
};
|
||||
|
||||
let adaptor: LiteAdaptor;
|
||||
let html: MathDocument<any, any, any>;
|
||||
let preamble: string;
|
||||
|
||||
export const clearMathJaxVariables = () => {
|
||||
adaptor = null;
|
||||
html = null;
|
||||
preamble = null;
|
||||
};
|
||||
|
||||
//https://github.com/xldenis/obsidian-latex/blob/master/main.ts
|
||||
const loadPreamble = async () => {
|
||||
const file = app.vault.getAbstractFileByPath("preamble.sty");
|
||||
preamble = file && file instanceof TFile
|
||||
? await app.vault.read(file)
|
||||
: null;
|
||||
};
|
||||
|
||||
export async function tex2dataURL(
|
||||
tex: string,
|
||||
scale: number = 4 // Default scale value, adjust as needed
|
||||
scale: number = 4,
|
||||
app: App,
|
||||
): Promise<{
|
||||
mimeType: MimeType;
|
||||
fileId: FileId;
|
||||
@@ -64,47 +60,12 @@ export async function tex2dataURL(
|
||||
created: number;
|
||||
size: { height: number; width: number };
|
||||
}> {
|
||||
let input: TeX<unknown, unknown, unknown>;
|
||||
let output: SVG<unknown, unknown, unknown>;
|
||||
await loadMathJax();
|
||||
return tex2dataURLExternal(tex, scale, app);
|
||||
}
|
||||
|
||||
if(!adaptor) {
|
||||
await loadPreamble();
|
||||
adaptor = liteAdaptor();
|
||||
RegisterHTMLHandler(adaptor);
|
||||
input = new TeX({
|
||||
packages: AllPackages,
|
||||
...Boolean(preamble) ? {
|
||||
inlineMath: [['$', '$']],
|
||||
displayMath: [['$$', '$$']]
|
||||
} : {},
|
||||
});
|
||||
output = new SVG({ fontCache: "local" });
|
||||
html = mathjax.document("", { InputJax: input, OutputJax: output });
|
||||
export const clearMathJaxVariables = () => {
|
||||
if (clearVariables) {
|
||||
clearVariables();
|
||||
}
|
||||
try {
|
||||
const node = html.convert(
|
||||
Boolean(preamble) ? `${preamble}${tex}` : tex,
|
||||
{ display: true, scale }
|
||||
);
|
||||
const svg = new DOMParser().parseFromString(adaptor.innerHTML(node), "image/svg+xml").firstChild as SVGSVGElement;
|
||||
if (svg) {
|
||||
if(svg.width.baseVal.valueInSpecifiedUnits < 2) {
|
||||
svg.width.baseVal.valueAsString = `${(svg.width.baseVal.valueInSpecifiedUnits+1).toFixed(3)}ex`;
|
||||
}
|
||||
const img = svgToBase64(svg.outerHTML);
|
||||
svg.width.baseVal.valueAsString = (svg.width.baseVal.valueInSpecifiedUnits * 10).toFixed(3);
|
||||
svg.height.baseVal.valueAsString = (svg.height.baseVal.valueInSpecifiedUnits * 10).toFixed(3);
|
||||
const dataURL = svgToBase64(svg.outerHTML);
|
||||
return {
|
||||
mimeType: "image/svg+xml",
|
||||
fileId: fileid() as FileId,
|
||||
dataURL: dataURL as DataURL,
|
||||
created: Date.now(),
|
||||
size: await getImageSize(img),
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
@@ -265,20 +265,20 @@ function RenderObsidianView(
|
||||
const color = element?.backgroundColor
|
||||
? (element.backgroundColor.toLowerCase() === "transparent"
|
||||
? "transparent"
|
||||
: ea.getCM(element.backgroundColor).alphaTo(opacity).stringHEX())
|
||||
: ea.getCM(element.backgroundColor).alphaTo(opacity).stringHEX({alpha: true}))
|
||||
: "transparent";
|
||||
|
||||
color === "transparent" ? canvasNode?.addClass("transparent") : canvasNode?.removeClass("transparent");
|
||||
canvasNode?.style.setProperty("--canvas-background", color);
|
||||
canvasNode?.style.setProperty("--background-primary", color);
|
||||
canvasNodeContainer?.style.setProperty("background-color", color);
|
||||
} else if (!(mdProps?.backgroundMatchElement ?? true )) {
|
||||
} else if (!(mdProps.backgroundMatchElement ?? true )) {
|
||||
const opacity = (mdProps.backgroundOpacity??100)/100;
|
||||
const color = mdProps.backgroundMatchCanvas
|
||||
? (canvasColor.toLowerCase() === "transparent"
|
||||
? "transparent"
|
||||
: ea.getCM(canvasColor).alphaTo(opacity).stringHEX())
|
||||
: ea.getCM(mdProps.backgroundColor).alphaTo((mdProps.backgroundOpacity??100)/100).stringHEX();
|
||||
: ea.getCM(canvasColor).alphaTo(opacity).stringHEX({alpha: true}))
|
||||
: ea.getCM(mdProps.backgroundColor).alphaTo((mdProps.backgroundOpacity??100)/100).stringHEX({alpha: true});
|
||||
|
||||
color === "transparent" ? canvasNode?.addClass("transparent") : canvasNode?.removeClass("transparent");
|
||||
canvasNode?.style.setProperty("--canvas-background", color);
|
||||
@@ -291,13 +291,13 @@ function RenderObsidianView(
|
||||
const color = element?.strokeColor
|
||||
? (element.strokeColor.toLowerCase() === "transparent"
|
||||
? "transparent"
|
||||
: ea.getCM(element.strokeColor).alphaTo(opacity).stringHEX())
|
||||
: ea.getCM(element.strokeColor).alphaTo(opacity).stringHEX({alpha: true}))
|
||||
: "transparent";
|
||||
canvasNode?.style.setProperty("--canvas-border", color);
|
||||
canvasNode?.style.setProperty("--canvas-color", color);
|
||||
//canvasNodeContainer?.style.setProperty("border-color", color);
|
||||
} else if(!(mdProps?.borderMatchElement ?? true)) {
|
||||
const color = ea.getCM(mdProps.borderColor).alphaTo((mdProps.borderOpacity??100)/100).stringHEX();
|
||||
const color = ea.getCM(mdProps.borderColor).alphaTo((mdProps.borderOpacity??100)/100).stringHEX({alpha: true});
|
||||
canvasNode?.style.setProperty("--canvas-border", color);
|
||||
canvasNode?.style.setProperty("--canvas-color", color);
|
||||
//canvasNodeContainer?.style.setProperty("border-color", color);
|
||||
@@ -315,8 +315,16 @@ function RenderObsidianView(
|
||||
const canvasNode = containerRef.current;
|
||||
if(!canvasNode.hasClass("canvas-node")) return;
|
||||
setColors(canvasNode, element, mdProps, canvasColor);
|
||||
console.log("Setting colors");
|
||||
}, [
|
||||
mdProps,
|
||||
mdProps?.useObsidianDefaults,
|
||||
mdProps?.backgroundMatchCanvas,
|
||||
mdProps?.backgroundMatchElement,
|
||||
mdProps?.backgroundColor,
|
||||
mdProps?.backgroundOpacity,
|
||||
mdProps?.borderMatchElement,
|
||||
mdProps?.borderColor,
|
||||
mdProps?.borderOpacity,
|
||||
elementRef.current,
|
||||
containerRef.current,
|
||||
canvasColor,
|
||||
@@ -395,7 +403,8 @@ function RenderObsidianView(
|
||||
|
||||
const previousIsActive = isActiveRef.current;
|
||||
isActiveRef.current = (activeEmbeddable?.element.id === element.id) && (activeEmbeddable?.state === "active");
|
||||
|
||||
|
||||
const node = leafRef.current?.node as ObsidianCanvasNode;
|
||||
if (previousIsActive === isActiveRef.current) {
|
||||
return;
|
||||
}
|
||||
@@ -414,15 +423,15 @@ function RenderObsidianView(
|
||||
isEditingRef.current = false;
|
||||
return;
|
||||
}
|
||||
} else if (leafRef.current?.node) {
|
||||
} else if (node) {
|
||||
//Handle canvas node
|
||||
if(view.plugin.settings.markdownNodeOneClickEditing && !containerRef.current?.hasClass("is-editing")) {
|
||||
if(isActiveRef.current && view.plugin.settings.markdownNodeOneClickEditing && !containerRef.current?.hasClass("is-editing")) { //!node.isEditing
|
||||
const newTheme = getTheme(view, themeRef.current);
|
||||
containerRef.current?.addClasses(["is-editing", "is-focused"]);
|
||||
view.canvasNodeFactory.startEditing(leafRef.current.node, newTheme);
|
||||
view.canvasNodeFactory.startEditing(node, newTheme);
|
||||
} else {
|
||||
containerRef.current?.removeClasses(["is-editing", "is-focused"]);
|
||||
view.canvasNodeFactory.stopEditing(leafRef.current.node);
|
||||
view.canvasNodeFactory.stopEditing(node);
|
||||
}
|
||||
}
|
||||
}, [
|
||||
|
||||
@@ -46,6 +46,16 @@ export class EmbeddalbeMDFileCustomDataSettingsComponent {
|
||||
);
|
||||
}
|
||||
contentEl.createEl("h4",{text: t("ES_BACKGROUND_HEAD")});
|
||||
const descDiv = contentEl.createDiv({ cls: "excalidraw-setting-desc" });
|
||||
descDiv.textContent = t("ES_BACKGROUND_DESC_INFO");
|
||||
|
||||
descDiv.addEventListener("click", () => {
|
||||
if (descDiv.textContent === t("ES_BACKGROUND_DESC_INFO")) {
|
||||
descDiv.textContent = t("ES_BACKGROUND_DESC_DETAIL");
|
||||
} else {
|
||||
descDiv.textContent = t("ES_BACKGROUND_DESC_INFO");
|
||||
}
|
||||
});
|
||||
|
||||
let bgSetting: Setting;
|
||||
let bgMatchElementToggle: ToggleComponent;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -910,6 +910,8 @@ FILENAME_HEAD: "Filename",
|
||||
ES_YOUTUBE_START_INVALID: "The YouTube Start Time is invalid. Please check the format and try again",
|
||||
ES_FILENAME_VISIBLE: "Filename Visible",
|
||||
ES_BACKGROUND_HEAD: "Embedded note background color",
|
||||
ES_BACKGROUND_DESC_INFO: "Click here for more info on colors",
|
||||
ES_BACKGROUND_DESC_DETAIL: "Background color affects only the preview mode of the markdown embeddable. When editing, it follows the Obsidian light/dark theme as set for the scene (via document property) or in plugin settings. The background color has two layers: the element background color (lower layer) and a color on top (upper layer). Selecting 'Match Element Background' means both layers follow the element color. Selecting 'Match Canvas' or a specific background color keeps the element background layer. Setting opacity (e.g., 50%) mixes the canvas or selected color with the element background color. To remove the element background layer, set the element color to transparent in Excalidraw's element properties editor. This makes only the upper layer effective.",
|
||||
ES_BACKGROUND_MATCH_ELEMENT: "Match Element Background Color",
|
||||
ES_BACKGROUND_MATCH_CANVAS: "Match Canvas Background Color",
|
||||
ES_BACKGROUND_COLOR: "Background Color",
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { FILE } from "dns";
|
||||
import {
|
||||
DEVICE,
|
||||
FRONTMATTER_KEYS,
|
||||
@@ -10,6 +11,8 @@ declare const PLUGIN_VERSION:string;
|
||||
|
||||
// 简体中文
|
||||
export default {
|
||||
// Sugester
|
||||
SELECT_FILE_TO_INSERT: "选择一个要插入的文件",
|
||||
// main.ts
|
||||
CONVERT_URL_TO_FILE: "从 URL 下载图像到本地",
|
||||
UNZIP_CURRENT_FILE: "解压当前 Excalidraw 文件",
|
||||
@@ -907,6 +910,8 @@ FILENAME_HEAD: "文件名",
|
||||
ES_YOUTUBE_START_INVALID: "YouTube 起始时间无效。请检查格式并重试",
|
||||
ES_FILENAME_VISIBLE: "显示文件名",
|
||||
ES_BACKGROUND_HEAD: "背景色",
|
||||
ES_BACKGROUND_DESC_INFO : "点击此处了解更多颜色信息" ,
|
||||
ES_BACKGROUND_DESC_DETAIL : "背景颜色仅影响 Markdown 嵌入预览模式。在编辑模式下,它会根据场景(通过文档属性设置)或插件设置,遵循 Obsidian 的浅色/深色主题。背景颜色有两层:元素背景颜色(下层)和上层颜色。选择“匹配元素背景”意味着两层都遵循元素颜色。选择“匹配画布”或特定背景颜色时,保留元素背景层。设置透明度(例如 50%)会将画布或选定的颜色与元素背景颜色混合。要移除元素背景层,可以在 Excalidraw 的元素属性编辑器中将元素颜色设置为透明,这样只有上层颜色生效。" ,
|
||||
ES_BACKGROUND_MATCH_ELEMENT: "匹配元素背景色",
|
||||
ES_BACKGROUND_MATCH_CANVAS: "匹配画布背景色",
|
||||
ES_BACKGROUND_COLOR: "背景色",
|
||||
|
||||
441
src/main.ts
441
src/main.ts
@@ -19,7 +19,6 @@ import {
|
||||
Workspace,
|
||||
Editor,
|
||||
MarkdownFileInfo,
|
||||
loadMermaid,
|
||||
} from "obsidian";
|
||||
import {
|
||||
BLANK_DRAWING,
|
||||
@@ -135,7 +134,6 @@ import { CustomMutationObserver, debug, log, DEBUGGING, setDebugging } from "./u
|
||||
import { carveOutImage, carveOutPDF, createImageCropperFile } from "./utils/CarveOut";
|
||||
import { ExcalidrawConfig } from "./utils/ExcalidrawConfig";
|
||||
import { EditorHandler } from "./CodeMirrorExtension/EditorHandler";
|
||||
import { clearMathJaxVariables } from "./LaTeX";
|
||||
import { showFrameSettings } from "./dialogs/FrameSettings";
|
||||
import { ExcalidrawLib } from "./ExcalidrawLib";
|
||||
import { Rank, SwordColors } from "./menu/ActionIcons";
|
||||
@@ -145,9 +143,10 @@ import { WeakArray } from "./utils/WeakArray";
|
||||
import { getCJKDataURLs } from "./utils/CJKLoader";
|
||||
import { ExcalidrawLoading, switchToExcalidraw } from "./dialogs/ExcalidrawLoading";
|
||||
import { insertImageToView } from "./utils/ExcalidrawViewUtils";
|
||||
import { clearMathJaxVariables } from "./LaTeX";
|
||||
|
||||
declare let EXCALIDRAW_PACKAGE:string;
|
||||
declare let REACT_PACKAGES:string;
|
||||
//declare let EXCALIDRAW_PACKAGE:string;
|
||||
declare const unpackExcalidraw: Function;
|
||||
declare let react:any;
|
||||
declare let reactDOM:any;
|
||||
@@ -156,6 +155,7 @@ declare const PLUGIN_VERSION:string;
|
||||
declare const INITIAL_TIMESTAMP: number;
|
||||
|
||||
export default class ExcalidrawPlugin extends Plugin {
|
||||
private EXCALIDRAW_PACKAGE: string;
|
||||
public eaInstances = new WeakArray<ExcalidrawAutomate>();
|
||||
public fourthFontLoaded: boolean = false;
|
||||
public excalidrawConfig: ExcalidrawConfig;
|
||||
@@ -205,6 +205,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
private startupAnalytics: string[] = [];
|
||||
private lastLogTimestamp: number;
|
||||
private settingsReady: boolean = false;
|
||||
public wasPenModeActivePreviously: boolean = false;
|
||||
|
||||
constructor(app: App, manifest: PluginManifest) {
|
||||
super(app, manifest);
|
||||
@@ -291,7 +292,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
//@ts-ignore
|
||||
const {react:r, reactDOM:rd, excalidrawLib:e} = win.eval.call(win,
|
||||
`(function() {
|
||||
${REACT_PACKAGES + EXCALIDRAW_PACKAGE};
|
||||
${REACT_PACKAGES + this.EXCALIDRAW_PACKAGE};
|
||||
return {react:React,reactDOM:ReactDOM,excalidrawLib:ExcalidrawLib};
|
||||
})()`);
|
||||
this.packageMap.set(win,{react:r, reactDOM:rd, excalidrawLib:e});
|
||||
@@ -336,6 +337,12 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
super.registerEvent(event);
|
||||
};
|
||||
}*/
|
||||
|
||||
public isPenMode() {
|
||||
return this.wasPenModeActivePreviously ||
|
||||
(this.settings.defaultPenMode === "always") ||
|
||||
(this.settings.defaultPenMode === "mobile" && DEVICE.isMobile);
|
||||
}
|
||||
|
||||
public getCJKFontSettings() {
|
||||
const assetsFoler = this.settings.fontAssetsPath;
|
||||
@@ -386,23 +393,8 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
this.addRibbonIcon(ICON_NAME, t("CREATE_NEW"), this.actionRibbonClick.bind(this));
|
||||
|
||||
try {
|
||||
this.loadSettings({reEnableAutosave:true}).then(async () => {
|
||||
const updateSettings = !this.settings.onceOffCompressFlagReset || !this.settings.onceOffGPTVersionReset;
|
||||
if(!this.settings.onceOffCompressFlagReset) {
|
||||
this.settings.compress = true;
|
||||
this.settings.onceOffCompressFlagReset = true;
|
||||
}
|
||||
if(!this.settings.onceOffGPTVersionReset) {
|
||||
if(this.settings.openAIDefaultVisionModel === "gpt-4-vision-preview") {
|
||||
this.settings.openAIDefaultVisionModel = "gpt-4o";
|
||||
}
|
||||
}
|
||||
if(updateSettings) {
|
||||
await this.saveSettings();
|
||||
}
|
||||
this.addSettingTab(new ExcalidrawSettingTab(this.app, this));
|
||||
this.settingsReady = true;
|
||||
});
|
||||
this.loadSettings({reEnableAutosave:true})
|
||||
.then(this.onloadCheckForOnceOffSettingsUpdates.bind(this));
|
||||
} catch (e) {
|
||||
new Notice("Error loading plugin settings", 6000);
|
||||
console.error("Error loading plugin settings", e);
|
||||
@@ -427,205 +419,218 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
}
|
||||
this.logStartupEvent("Markdown post processor added");
|
||||
|
||||
this.app.workspace.onLayoutReady(async () => {
|
||||
this.loadTimestamp = Date.now();
|
||||
this.lastLogTimestamp = this.loadTimestamp;
|
||||
this.logStartupEvent("\n----------------------------------\nWorkspace onLayoutReady event fired (these actions are outside the plugin initialization)");
|
||||
await this.awaitSettings();
|
||||
this.logStartupEvent("Settings awaited");
|
||||
try {
|
||||
unpackExcalidraw();
|
||||
excalidrawLib = window.eval.call(window,`(function() {${EXCALIDRAW_PACKAGE};return ExcalidrawLib;})()`);
|
||||
this.packageMap.set(window,{react, reactDOM, excalidrawLib});
|
||||
updateExcalidrawLib();
|
||||
} catch (e) {
|
||||
new Notice("Error loading the Excalidraw package", 6000);
|
||||
console.error("Error loading the Excalidraw package", e);
|
||||
}
|
||||
this.logStartupEvent("Excalidraw package unpacked");
|
||||
|
||||
try {
|
||||
initCompressionWorker();
|
||||
} catch (e) {
|
||||
new Notice("Error initializing compression worker", 6000);
|
||||
console.error("Error initializing compression worker", e);
|
||||
}
|
||||
this.logStartupEvent("Compression worker initialized");
|
||||
|
||||
try {
|
||||
this.excalidrawConfig = new ExcalidrawConfig(this);
|
||||
} catch (e) {
|
||||
new Notice("Error initializing Excalidraw config", 6000);
|
||||
console.error("Error initializing Excalidraw config", e);
|
||||
}
|
||||
this.logStartupEvent("Excalidraw config initialized");
|
||||
|
||||
try {
|
||||
await loadMermaid();
|
||||
} catch (e) {
|
||||
new Notice("Error loading Mermaid", 6000);
|
||||
console.error("Error loading Mermaid", e);
|
||||
}
|
||||
this.logStartupEvent("Mermaid loaded");
|
||||
|
||||
try {
|
||||
this.addThemeObserver();
|
||||
} catch (e) {
|
||||
new Notice("Error adding theme observer", 6000);
|
||||
console.error("Error adding theme observer", e);
|
||||
}
|
||||
this.logStartupEvent("Theme observer added");
|
||||
|
||||
try {
|
||||
//inspiration taken from kanban:
|
||||
//https://github.com/mgmeyers/obsidian-kanban/blob/44118e25661bff9ebfe54f71ae33805dc88ffa53/src/main.ts#L267
|
||||
this.registerMonkeyPatches();
|
||||
} catch (e) {
|
||||
new Notice("Error registering monkey patches", 6000);
|
||||
console.error("Error registering monkey patches", e);
|
||||
}
|
||||
this.logStartupEvent("Monkey patches registered");
|
||||
|
||||
try {
|
||||
this.stylesManager = new StylesManager(this);
|
||||
} catch (e) {
|
||||
new Notice("Error initializing styles manager", 6000);
|
||||
console.error("Error initializing styles manager", e);
|
||||
}
|
||||
this.logStartupEvent("Styles manager initialized");
|
||||
|
||||
try {
|
||||
this.scriptEngine = new ScriptEngine(this);
|
||||
} catch (e) {
|
||||
new Notice("Error initializing script engine", 6000);
|
||||
console.error("Error initializing script engine", e);
|
||||
}
|
||||
this.logStartupEvent("Script engine initialized");
|
||||
|
||||
try {
|
||||
await this.initializeFonts();
|
||||
} catch (e) {
|
||||
new Notice("Error initializing fonts", 6000);
|
||||
console.error("Error initializing fonts", e);
|
||||
}
|
||||
this.logStartupEvent("Fonts initialized");
|
||||
|
||||
try {
|
||||
imageCache.initializeDB(this);
|
||||
} catch (e) {
|
||||
new Notice("Error initializing image cache", 6000);
|
||||
console.error("Error initializing image cache", e);
|
||||
}
|
||||
this.logStartupEvent("Image cache initialized");
|
||||
|
||||
try {
|
||||
this.isReady = true;
|
||||
switchToExcalidraw(this.app);
|
||||
this.switchToExcalidarwAfterLoad();
|
||||
} catch (e) {
|
||||
new Notice("Error switching views to Excalidraw", 6000);
|
||||
console.error("Error switching views to Excalidraw", e);
|
||||
}
|
||||
this.logStartupEvent("Switched to Excalidraw views");
|
||||
|
||||
try {
|
||||
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") || !this.settings.previousRelease;
|
||||
|
||||
if (isVersionNewerThanOther(PLUGIN_VERSION, this.settings.previousRelease ?? "0.0.0")) {
|
||||
new ReleaseNotes(
|
||||
this.app,
|
||||
this,
|
||||
obsidianJustInstalled ? null : PLUGIN_VERSION,
|
||||
).open();
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
new Notice("Error opening release notes", 6000);
|
||||
console.error("Error opening release notes", e);
|
||||
}
|
||||
this.logStartupEvent("Release notes opened");
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
//initialization that can happen after Excalidraw views are initialized
|
||||
//---------------------------------------------------------------------
|
||||
try {
|
||||
this.registerEventListeners();
|
||||
} catch (e) {
|
||||
new Notice("Error registering event listeners", 6000);
|
||||
console.error("Error registering event listeners", e);
|
||||
}
|
||||
this.logStartupEvent("Event listeners registered");
|
||||
|
||||
try {
|
||||
this.runStartupScript();
|
||||
} catch (e) {
|
||||
new Notice("Error running startup script", 6000);
|
||||
console.error("Error running startup script", e);
|
||||
}
|
||||
this.logStartupEvent("Startup script run");
|
||||
|
||||
try {
|
||||
this.editorHandler = new EditorHandler(this);
|
||||
this.editorHandler.setup();
|
||||
} catch (e) {
|
||||
new Notice("Error setting up editor handler", 6000);
|
||||
console.error("Error setting up editor handler", e);
|
||||
}
|
||||
this.logStartupEvent("Editor handler initialized");
|
||||
|
||||
try {
|
||||
this.registerInstallCodeblockProcessor();
|
||||
} catch (e) {
|
||||
new Notice("Error registering script install-codeblock processor", 6000);
|
||||
console.error("Error registering script install-codeblock processor", e);
|
||||
}
|
||||
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) {
|
||||
new Notice("Error registering commands", 6000);
|
||||
console.error("Error registering commands", e);
|
||||
}
|
||||
this.logStartupEvent("Commands registered");
|
||||
|
||||
try {
|
||||
this.registerEditorSuggest(new FieldSuggester(this));
|
||||
} catch (e) {
|
||||
new Notice("Error registering editor suggester", 6000);
|
||||
console.error("Error registering editor suggester", e);
|
||||
}
|
||||
this.logStartupEvent("Editor suggester registered");
|
||||
|
||||
try {
|
||||
this.setPropertyTypes();
|
||||
} catch (e) {
|
||||
new Notice("Error setting up property types", 6000);
|
||||
console.error("Error setting up property types", e);
|
||||
}
|
||||
this.logStartupEvent("Property types set");
|
||||
|
||||
try {
|
||||
this.taskbone = new Taskbone(this);
|
||||
} catch (e) {
|
||||
new Notice("Error setting up taskbone", 6000);
|
||||
console.error("Error setting up taskbone", e);
|
||||
}
|
||||
this.logStartupEvent("Taskbone set up");
|
||||
});
|
||||
this.app.workspace.onLayoutReady(this.onloadOnLayoutReady.bind(this));
|
||||
this.logStartupEvent("Workspace ready event handler added");
|
||||
}
|
||||
|
||||
private async onloadCheckForOnceOffSettingsUpdates() {
|
||||
const updateSettings = !this.settings.onceOffCompressFlagReset || !this.settings.onceOffGPTVersionReset;
|
||||
if(!this.settings.onceOffCompressFlagReset) {
|
||||
this.settings.compress = true;
|
||||
this.settings.onceOffCompressFlagReset = true;
|
||||
}
|
||||
if(!this.settings.onceOffGPTVersionReset) {
|
||||
this.settings.onceOffGPTVersionReset = true;
|
||||
if(this.settings.openAIDefaultVisionModel === "gpt-4-vision-preview") {
|
||||
this.settings.openAIDefaultVisionModel = "gpt-4o";
|
||||
}
|
||||
}
|
||||
if(updateSettings) {
|
||||
await this.saveSettings();
|
||||
}
|
||||
this.addSettingTab(new ExcalidrawSettingTab(this.app, this));
|
||||
this.settingsReady = true;
|
||||
}
|
||||
|
||||
private async onloadOnLayoutReady() {
|
||||
this.loadTimestamp = Date.now();
|
||||
this.lastLogTimestamp = this.loadTimestamp;
|
||||
this.logStartupEvent("\n----------------------------------\nWorkspace onLayoutReady event fired (these actions are outside the plugin initialization)");
|
||||
await this.awaitSettings();
|
||||
this.logStartupEvent("Settings awaited");
|
||||
try {
|
||||
this.EXCALIDRAW_PACKAGE = unpackExcalidraw();
|
||||
excalidrawLib = window.eval.call(window,`(function() {${this.EXCALIDRAW_PACKAGE};return ExcalidrawLib;})()`);
|
||||
this.packageMap.set(window,{react, reactDOM, excalidrawLib});
|
||||
updateExcalidrawLib();
|
||||
} catch (e) {
|
||||
new Notice("Error loading the Excalidraw package", 6000);
|
||||
console.error("Error loading the Excalidraw package", e);
|
||||
}
|
||||
this.logStartupEvent("Excalidraw package unpacked");
|
||||
|
||||
try {
|
||||
initCompressionWorker();
|
||||
} catch (e) {
|
||||
new Notice("Error initializing compression worker", 6000);
|
||||
console.error("Error initializing compression worker", e);
|
||||
}
|
||||
this.logStartupEvent("Compression worker initialized");
|
||||
|
||||
try {
|
||||
this.excalidrawConfig = new ExcalidrawConfig(this);
|
||||
} catch (e) {
|
||||
new Notice("Error initializing Excalidraw config", 6000);
|
||||
console.error("Error initializing Excalidraw config", e);
|
||||
}
|
||||
this.logStartupEvent("Excalidraw config initialized");
|
||||
|
||||
try {
|
||||
this.addThemeObserver();
|
||||
} catch (e) {
|
||||
new Notice("Error adding theme observer", 6000);
|
||||
console.error("Error adding theme observer", e);
|
||||
}
|
||||
this.logStartupEvent("Theme observer added");
|
||||
|
||||
try {
|
||||
//inspiration taken from kanban:
|
||||
//https://github.com/mgmeyers/obsidian-kanban/blob/44118e25661bff9ebfe54f71ae33805dc88ffa53/src/main.ts#L267
|
||||
this.registerMonkeyPatches();
|
||||
} catch (e) {
|
||||
new Notice("Error registering monkey patches", 6000);
|
||||
console.error("Error registering monkey patches", e);
|
||||
}
|
||||
this.logStartupEvent("Monkey patches registered");
|
||||
|
||||
try {
|
||||
this.stylesManager = new StylesManager(this);
|
||||
} catch (e) {
|
||||
new Notice("Error initializing styles manager", 6000);
|
||||
console.error("Error initializing styles manager", e);
|
||||
}
|
||||
this.logStartupEvent("Styles manager initialized");
|
||||
|
||||
try {
|
||||
this.scriptEngine = new ScriptEngine(this);
|
||||
} catch (e) {
|
||||
new Notice("Error initializing script engine", 6000);
|
||||
console.error("Error initializing script engine", e);
|
||||
}
|
||||
this.logStartupEvent("Script engine initialized");
|
||||
|
||||
try {
|
||||
await this.initializeFonts();
|
||||
} catch (e) {
|
||||
new Notice("Error initializing fonts", 6000);
|
||||
console.error("Error initializing fonts", e);
|
||||
}
|
||||
this.logStartupEvent("Fonts initialized");
|
||||
|
||||
try {
|
||||
imageCache.initializeDB(this);
|
||||
} catch (e) {
|
||||
new Notice("Error initializing image cache", 6000);
|
||||
console.error("Error initializing image cache", e);
|
||||
}
|
||||
this.logStartupEvent("Image cache initialized");
|
||||
|
||||
try {
|
||||
this.isReady = true;
|
||||
switchToExcalidraw(this.app);
|
||||
this.switchToExcalidarwAfterLoad();
|
||||
} catch (e) {
|
||||
new Notice("Error switching views to Excalidraw", 6000);
|
||||
console.error("Error switching views to Excalidraw", e);
|
||||
}
|
||||
this.logStartupEvent("Switched to Excalidraw views");
|
||||
|
||||
try {
|
||||
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") || !this.settings.previousRelease;
|
||||
|
||||
if (isVersionNewerThanOther(PLUGIN_VERSION, this.settings.previousRelease ?? "0.0.0")) {
|
||||
new ReleaseNotes(
|
||||
this.app,
|
||||
this,
|
||||
obsidianJustInstalled ? null : PLUGIN_VERSION,
|
||||
).open();
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
new Notice("Error opening release notes", 6000);
|
||||
console.error("Error opening release notes", e);
|
||||
}
|
||||
this.logStartupEvent("Release notes opened");
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
//initialization that can happen after Excalidraw views are initialized
|
||||
//---------------------------------------------------------------------
|
||||
try {
|
||||
this.registerEventListeners();
|
||||
} catch (e) {
|
||||
new Notice("Error registering event listeners", 6000);
|
||||
console.error("Error registering event listeners", e);
|
||||
}
|
||||
this.logStartupEvent("Event listeners registered");
|
||||
|
||||
try {
|
||||
this.runStartupScript();
|
||||
} catch (e) {
|
||||
new Notice("Error running startup script", 6000);
|
||||
console.error("Error running startup script", e);
|
||||
}
|
||||
this.logStartupEvent("Startup script run");
|
||||
|
||||
try {
|
||||
this.editorHandler = new EditorHandler(this);
|
||||
this.editorHandler.setup();
|
||||
} catch (e) {
|
||||
new Notice("Error setting up editor handler", 6000);
|
||||
console.error("Error setting up editor handler", e);
|
||||
}
|
||||
this.logStartupEvent("Editor handler initialized");
|
||||
|
||||
try {
|
||||
this.registerInstallCodeblockProcessor();
|
||||
} catch (e) {
|
||||
new Notice("Error registering script install-codeblock processor", 6000);
|
||||
console.error("Error registering script install-codeblock processor", e);
|
||||
}
|
||||
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) {
|
||||
new Notice("Error registering commands", 6000);
|
||||
console.error("Error registering commands", e);
|
||||
}
|
||||
this.logStartupEvent("Commands registered");
|
||||
|
||||
try {
|
||||
this.registerEditorSuggest(new FieldSuggester(this));
|
||||
} catch (e) {
|
||||
new Notice("Error registering editor suggester", 6000);
|
||||
console.error("Error registering editor suggester", e);
|
||||
}
|
||||
this.logStartupEvent("Editor suggester registered");
|
||||
|
||||
try {
|
||||
this.setPropertyTypes();
|
||||
} catch (e) {
|
||||
new Notice("Error setting up property types", 6000);
|
||||
console.error("Error setting up property types", e);
|
||||
}
|
||||
this.logStartupEvent("Property types set");
|
||||
|
||||
try {
|
||||
this.taskbone = new Taskbone(this);
|
||||
} catch (e) {
|
||||
new Notice("Error setting up taskbone", 6000);
|
||||
console.error("Error setting up taskbone", e);
|
||||
}
|
||||
this.logStartupEvent("Taskbone set up");
|
||||
}
|
||||
|
||||
public async awaitSettings() {
|
||||
let counter = 0;
|
||||
while(!this.settingsReady && counter < 150) {
|
||||
@@ -3649,7 +3654,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
|
||||
this.settings = null;
|
||||
clearMathJaxVariables();
|
||||
EXCALIDRAW_PACKAGE = "";
|
||||
this.EXCALIDRAW_PACKAGE = "";
|
||||
REACT_PACKAGES = "";
|
||||
//pluginPackages = null;
|
||||
//PLUGIN_VERSION = null;
|
||||
|
||||
@@ -127,7 +127,7 @@ export class EmbeddableMenu {
|
||||
blockID = nanoid();
|
||||
const fileContents = await app.vault.cachedRead(file);
|
||||
if(!fileContents) return;
|
||||
await app.vault.modify(file, fileContents.slice(0, offset) + ` ^${blockID}` + fileContents.slice(offset));
|
||||
await this.view.app.vault.modify(file, fileContents.slice(0, offset) + ` ^${blockID}` + fileContents.slice(offset));
|
||||
await sleep(200); //wait for cache to update
|
||||
}
|
||||
this.updateElement(`#^${blockID}`, element, file);
|
||||
@@ -170,7 +170,6 @@ export class EmbeddableMenu {
|
||||
|
||||
renderButtons(appState: AppState) {
|
||||
const view = this.view;
|
||||
const app = view.app;
|
||||
const api = view?.excalidrawAPI as ExcalidrawImperativeAPI;
|
||||
if(!api) return null;
|
||||
if(!view.file) return null;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ExcalidrawAutomate, createPNG } from "../ExcalidrawAutomate";
|
||||
import { ExcalidrawAutomate } from "../ExcalidrawAutomate";
|
||||
import {Notice, requestUrl} from "obsidian"
|
||||
import ExcalidrawPlugin from "../main"
|
||||
import ExcalidrawView, { ExportSettings } from "../ExcalidrawView"
|
||||
|
||||
@@ -82,51 +82,74 @@ export class CanvasNodeFactory {
|
||||
return node;
|
||||
}
|
||||
|
||||
public async startEditing(node: ObsidianCanvasNode, theme: string) {
|
||||
if (!this.initialized || !node) return;
|
||||
if (node.file === this.view.file) {
|
||||
await this.view.setEmbeddableIsEditingSelf();
|
||||
private async waitForEditor(node: ObsidianCanvasNode): Promise<HTMLElement | null> {
|
||||
let counter = 0;
|
||||
while (!node.child.editor?.containerEl?.parentElement?.parentElement && counter++ < 100) {
|
||||
await new Promise(resolve => setTimeout(resolve, 25));
|
||||
}
|
||||
node.startEditing();
|
||||
|
||||
const obsidianTheme = isObsidianThemeDark() ? "theme-dark" : "theme-light";
|
||||
if (obsidianTheme === theme) return;
|
||||
|
||||
(async () => {
|
||||
let counter = 0;
|
||||
while (!node.child.editor?.containerEl?.parentElement?.parentElement && counter++ < 100) {
|
||||
await sleep(25);
|
||||
}
|
||||
if (!node.child.editor?.containerEl?.parentElement?.parentElement) return;
|
||||
node.child.editor.containerEl.parentElement.parentElement.classList.remove(obsidianTheme);
|
||||
node.child.editor.containerEl.parentElement.parentElement.classList.add(theme);
|
||||
|
||||
const nodeObserverFn: MutationCallback = (mutationsList) => {
|
||||
for (const mutation of mutationsList) {
|
||||
if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
|
||||
const targetElement = mutation.target as HTMLElement;
|
||||
if (targetElement.classList.contains(obsidianTheme)) {
|
||||
targetElement.classList.remove(obsidianTheme);
|
||||
targetElement.classList.add(theme);
|
||||
}
|
||||
return node.child.editor?.containerEl?.parentElement?.parentElement;
|
||||
}
|
||||
|
||||
private setupThemeObserver(editorEl: HTMLElement, obsidianTheme: string, theme: string) {
|
||||
const nodeObserverFn: MutationCallback = (mutationsList) => {
|
||||
for (const mutation of mutationsList) {
|
||||
if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
|
||||
const targetElement = mutation.target as HTMLElement;
|
||||
if (targetElement.classList.contains(obsidianTheme)) {
|
||||
targetElement.classList.remove(obsidianTheme);
|
||||
targetElement.classList.add(theme);
|
||||
}
|
||||
}
|
||||
};
|
||||
this.observer = DEBUGGING
|
||||
? new CustomMutationObserver(nodeObserverFn, "CanvasNodeFactory")
|
||||
: new MutationObserver(nodeObserverFn);
|
||||
|
||||
this.observer.observe(node.child.editor.containerEl.parentElement.parentElement, { attributes: true });
|
||||
})();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.observer?.disconnect();
|
||||
this.observer = DEBUGGING
|
||||
? new CustomMutationObserver(nodeObserverFn, "CanvasNodeFactory")
|
||||
: new MutationObserver(nodeObserverFn);
|
||||
|
||||
this.observer.observe(editorEl, { attributes: true });
|
||||
}
|
||||
|
||||
public async startEditing(node: ObsidianCanvasNode, theme: string) {
|
||||
if (!this.initialized || !node) return;
|
||||
|
||||
try {
|
||||
//if (node.file === this.view.file) {
|
||||
await this.view.setEmbeddableNodeIsEditing();
|
||||
//}
|
||||
node.startEditing();
|
||||
node.isEditing = true;
|
||||
|
||||
const obsidianTheme = isObsidianThemeDark() ? "theme-dark" : "theme-light";
|
||||
if (obsidianTheme === theme) return;
|
||||
|
||||
const editorEl = await this.waitForEditor(node);
|
||||
if (!editorEl) return;
|
||||
|
||||
editorEl.classList.remove(obsidianTheme);
|
||||
editorEl.classList.add(theme);
|
||||
|
||||
this.setupThemeObserver(editorEl, obsidianTheme, theme);
|
||||
} catch (error) {
|
||||
console.error('Error starting edit:', error);
|
||||
node.isEditing = false;
|
||||
}
|
||||
}
|
||||
|
||||
public stopEditing(node: ObsidianCanvasNode) {
|
||||
if(!this.initialized || !node) return;
|
||||
if(!node.child.editMode) return;
|
||||
if(node.file === this.view.file) {
|
||||
this.view.clearEmbeddableIsEditingSelf();
|
||||
if (!this.initialized || !node || !node.isEditing) return;
|
||||
|
||||
try {
|
||||
//if (node.file === this.view.file) {
|
||||
this.view.clearEmbeddableNodeIsEditing();
|
||||
//}
|
||||
node.child.showPreview();
|
||||
node.isEditing = false;
|
||||
this.observer?.disconnect();
|
||||
} catch (error) {
|
||||
console.error('Error stopping edit:', error);
|
||||
}
|
||||
node.child.showPreview();
|
||||
}
|
||||
|
||||
removeNode(node: ObsidianCanvasNode) {
|
||||
|
||||
14
styles.css
14
styles.css
@@ -651,3 +651,17 @@ textarea.excalidraw-wysiwyg, .excalidraw input {
|
||||
.excalidraw .ToolIcon_type_button {
|
||||
color: var(--text-primary-color);
|
||||
}
|
||||
|
||||
.excalidraw-setting-desc {
|
||||
padding: 10px;
|
||||
cursor: pointer;
|
||||
background-color: var(--background-secondary);
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
border-radius: 5px;
|
||||
transition: background-color 0.3s ease, color 0.3s ease;
|
||||
}
|
||||
|
||||
.excalidraw-setting-desc:hover {
|
||||
background-color: var(--background-modifier-hover);
|
||||
color: var(--text-accent);
|
||||
}
|
||||
Reference in New Issue
Block a user