mirror of
https://github.com/zsviczian/obsidian-excalidraw-plugin.git
synced 2025-08-06 05:46:28 +00:00
Compare commits
22 Commits
2.6.8-beta
...
2.7.0-beta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b8655cff5e | ||
|
|
be452fee6d | ||
|
|
90589dd075 | ||
|
|
9c5b48c037 | ||
|
|
4406709920 | ||
|
|
b7ba0f8909 | ||
|
|
c28911c739 | ||
|
|
28088754ad | ||
|
|
9e1d491981 | ||
|
|
ab5caa4877 | ||
|
|
44b580ae78 | ||
|
|
3859eddc80 | ||
|
|
6098e1b42e | ||
|
|
6ad8d2f620 | ||
|
|
5b3f3a56ad | ||
|
|
f746b4f4ac | ||
|
|
3e4a3ace56 | ||
|
|
c72f6add40 | ||
|
|
6cfb125a38 | ||
|
|
c91e57e341 | ||
|
|
0ddd75e5fe | ||
|
|
382d4ca827 |
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)
|
||||
};
|
||||
1200
ea-scripts/Image Occlusion.md
Normal file
1200
ea-scripts/Image Occlusion.md
Normal file
File diff suppressed because it is too large
Load Diff
20
ea-scripts/Image Occlusion.svg
Normal file
20
ea-scripts/Image Occlusion.svg
Normal file
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="100" height="100" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- Blue star background -->
|
||||
<path
|
||||
d="M50 5 L61 40 L98 40 L68 62 L79 95 L50 75 L21 95 L32 62 L2 40 L39 40 Z"
|
||||
fill="#4a9eff"
|
||||
stroke="#1e1e1e"
|
||||
stroke-width="2"
|
||||
/>
|
||||
<!-- White "A" text -->
|
||||
<text
|
||||
x="50"
|
||||
y="65"
|
||||
font-family="Arial"
|
||||
font-size="40"
|
||||
fill="white"
|
||||
text-anchor="middle"
|
||||
dominant-baseline="middle"
|
||||
>A</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 517 B |
@@ -130,6 +130,7 @@ I would love to include your contribution in the script library. If you have a s
|
||||
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Select%20Similar%20Elements.svg"/></div>|[[#Select Similar Elements]]|
|
||||
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Slideshow.svg"/></div>|[[#Slideshow]]|
|
||||
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Split%20Ellipse.svg"/></div>|[[#Split Ellipse]]|
|
||||
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Image%20Occlusion.svg"/></div>|[[#Image Occlusion]]|
|
||||
|
||||
## Collaboration and Export
|
||||
**Keywords**: Sharing, Teamwork, Exporting, Distribution, Cooperative, Publish
|
||||
@@ -154,6 +155,7 @@ I would love to include your contribution in the script library. If you have a s
|
||||
|----|-----|
|
||||
|<div><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Crop%20Vintage%20Mask.svg"/></div>|[[#Crop Vintage Mask]]|
|
||||
|
||||
|
||||
---
|
||||
|
||||
# Description and Installation
|
||||
@@ -267,6 +269,8 @@ https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Crop%20Vintage%20Mask.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">Adds a rounded mask to the image by adding a full cover black mask and a rounded rectangle white mask. The script is also useful for adding just a black mask. In this case, run the script, then delete the white mask and add your custom white mask.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-crop-vintage.jpg'></td></tr></table>
|
||||
|
||||
|
||||
|
||||
## Custom Zoom
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Custom%20Zoom.md
|
||||
@@ -395,6 +399,12 @@ https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/zsviczian'>@zsviczian</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/GPT-Draw-a-UI.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script was discontinued in favor of ExcaliAI. Draw a UI and let GPT create the code for you.<br><iframe width="400" height="225" src="https://www.youtube.com/embed/y3kHl_6Ll4w" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-draw-a-ui.jpg'></td></tr></table>
|
||||
|
||||
## Image Occlusion
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Image%20Occlusion.md
|
||||
```
|
||||
<table><tr valign='top'><td class="label">Author</td><td class="data"><a href='https://github.com/TrillStones'>@TrillStones</a></td></tr><tr valign='top'><td class="label">Source</td><td class="data"><a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Image%20Occlusion.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">An Excalidraw script for creating Anki image occlusion cards in Obsidian, similar to Anki's Image Occlusion Enhanced add-on but integrated into your Obsidian workflow.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-image-occlusion.png'></td></tr></table>
|
||||
|
||||
## Invert colors
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Invert%20colors.md
|
||||
|
||||
BIN
images/scripts-image-occlusion.png
Normal file
BIN
images/scripts-image-occlusion.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.0 MiB |
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "obsidian-excalidraw-plugin",
|
||||
"name": "Excalidraw",
|
||||
"version": "2.6.8-beta-1",
|
||||
"version": "2.7.0-beta-3",
|
||||
"minAppVersion": "1.1.6",
|
||||
"description": "An Obsidian plugin to edit and view Excalidraw drawings",
|
||||
"author": "Zsolt Viczian",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "obsidian-excalidraw-plugin",
|
||||
"name": "Excalidraw",
|
||||
"version": "2.6.7",
|
||||
"version": "2.6.8",
|
||||
"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-15",
|
||||
"@zsviczian/excalidraw": "0.17.6-20",
|
||||
"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',
|
||||
|
||||
@@ -6,13 +6,16 @@ import {
|
||||
EditorSuggestTriggerInfo,
|
||||
TFile,
|
||||
} from "obsidian";
|
||||
import { FRONTMATTER_KEYS_INFO } from "./SuggesterInfo";
|
||||
import { FRONTMATTER_KEYS_INFO } from "../../dialogs/SuggesterInfo";
|
||||
import {
|
||||
EXCALIDRAW_AUTOMATE_INFO,
|
||||
EXCALIDRAW_SCRIPTENGINE_INFO,
|
||||
} from "./SuggesterInfo";
|
||||
import type ExcalidrawPlugin from "../main";
|
||||
} from "../../dialogs/SuggesterInfo";
|
||||
import type ExcalidrawPlugin from "../../main";
|
||||
|
||||
/**
|
||||
* The field suggester recommends document properties in source mode, ea and utils function and attribute names.
|
||||
*/
|
||||
export class FieldSuggester extends EditorSuggest<string> {
|
||||
plugin: ExcalidrawPlugin;
|
||||
suggestType: "ea" | "excalidraw" | "utils";
|
||||
142
src/Components/Suggesters/FileSuggestionModal.ts
Normal file
142
src/Components/Suggesters/FileSuggestionModal.ts
Normal file
@@ -0,0 +1,142 @@
|
||||
import {
|
||||
FuzzyMatch,
|
||||
TFile,
|
||||
CachedMetadata,
|
||||
TextComponent,
|
||||
App,
|
||||
setIcon,
|
||||
} from "obsidian";
|
||||
import { SuggestionModal } from "./SuggestionModal";
|
||||
import { t } from "src/lang/helpers";
|
||||
import { LinkSuggestion } from "src/types/types";
|
||||
import ExcalidrawPlugin from "src/main";
|
||||
import { AUDIO_TYPES, CODE_TYPES, ICON_NAME, IMAGE_TYPES, VIDEO_TYPES } from "src/constants/constants";
|
||||
|
||||
export class FileSuggestionModal extends SuggestionModal<LinkSuggestion> {
|
||||
text: TextComponent;
|
||||
cache: CachedMetadata;
|
||||
filesAndAliases: LinkSuggestion[];
|
||||
file: TFile;
|
||||
constructor(app: App, input: TextComponent, items: TFile[], private plugin: ExcalidrawPlugin) {
|
||||
const filesAndAliases = [];
|
||||
for (const file of items) {
|
||||
const path = file.path;
|
||||
filesAndAliases.push({ file, path, alias: "" });
|
||||
const metadata = app.metadataCache.getFileCache(file); // Get metadata for the file
|
||||
const aliases = metadata?.frontmatter?.aliases || []; // Check for frontmatter aliases
|
||||
|
||||
for (const alias of aliases) {
|
||||
if(!alias) continue; // Skip empty aliases
|
||||
filesAndAliases.push({ file, path, alias });
|
||||
}
|
||||
}
|
||||
super(app, input.inputEl, filesAndAliases);
|
||||
this.limit = 20;
|
||||
this.filesAndAliases = filesAndAliases;
|
||||
this.text = input;
|
||||
this.suggestEl.style.maxWidth = "100%";
|
||||
this.suggestEl.style.width = `${input.inputEl.clientWidth}px`;
|
||||
this.inputEl.addEventListener("input", () => this.getFile());
|
||||
this.setPlaceholder(t("SELECT_FILE_TO_INSERT"));
|
||||
this.emptyStateText = t("NO_MATCH");
|
||||
}
|
||||
|
||||
getFile() {
|
||||
const v = this.inputEl.value;
|
||||
const file = this.app.vault.getAbstractFileByPath(v);
|
||||
if (file === this.file) {
|
||||
return;
|
||||
}
|
||||
if (!(file instanceof TFile)) {
|
||||
return;
|
||||
}
|
||||
this.file = file;
|
||||
|
||||
this.onInputChanged();
|
||||
}
|
||||
|
||||
getSelectedItem() {
|
||||
return this.file;
|
||||
}
|
||||
|
||||
getItemText(item: LinkSuggestion) {
|
||||
return `${item.file.path}${item.alias ? `|${item.alias}` : ""}`;
|
||||
}
|
||||
|
||||
onChooseItem(item: LinkSuggestion) {
|
||||
this.file = item.file;
|
||||
this.text.setValue(this.getItemText(item));
|
||||
this.text.onChanged();
|
||||
}
|
||||
|
||||
selectSuggestion({ item }: FuzzyMatch<LinkSuggestion>) {
|
||||
this.file = item.file;
|
||||
this.text.setValue(this.getItemText(item));
|
||||
this.onClose();
|
||||
this.text.onChanged();
|
||||
this.close();
|
||||
}
|
||||
|
||||
renderSuggestion(result: FuzzyMatch<LinkSuggestion>, itemEl: HTMLElement) {
|
||||
const { item, match: matches } = result || {};
|
||||
itemEl.addClass("mod-complex");
|
||||
const contentEl = itemEl.createDiv("suggestion-content");
|
||||
const auxEl = itemEl.createDiv("suggestion-aux");
|
||||
const titleEl = contentEl.createDiv("suggestion-title");
|
||||
const noteEl = contentEl.createDiv("suggestion-note");
|
||||
|
||||
//el.style.flexDirection = "column";
|
||||
//content.style.flexDirection = "initial";
|
||||
|
||||
if (!item) {
|
||||
titleEl.setText(this.emptyStateText);
|
||||
itemEl.addClass("is-selected");
|
||||
return;
|
||||
}
|
||||
|
||||
const path = item.file?.path ?? item.path;
|
||||
const pathLength = path.length - item.file.name.length;
|
||||
const matchElements = matches.matches.map((m) => {
|
||||
return createSpan("suggestion-highlight");
|
||||
});
|
||||
const itemText = this.getItemText(item);
|
||||
for (let i = pathLength; i < itemText.length; i++) {
|
||||
const match = matches.matches.find((m) => m[0] === i);
|
||||
if (match) {
|
||||
const element = matchElements[matches.matches.indexOf(match)];
|
||||
titleEl.appendChild(element);
|
||||
element.appendText(itemText.substring(match[0], match[1]));
|
||||
|
||||
i += match[1] - match[0] - 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
titleEl.appendText(itemText[i]);
|
||||
}
|
||||
noteEl.setText(path);
|
||||
|
||||
if(this.plugin.isExcalidrawFile(item.file)) {
|
||||
setIcon(auxEl, ICON_NAME);
|
||||
} else if (item.file.extension === "md") {
|
||||
setIcon(auxEl, "square-pen");
|
||||
} else if (IMAGE_TYPES.includes(item.file.extension)) {
|
||||
setIcon(auxEl, "image");
|
||||
} else if (VIDEO_TYPES.includes(item.file.extension)) {
|
||||
setIcon(auxEl, "monitor-play");
|
||||
} else if (AUDIO_TYPES.includes(item.file.extension)) {
|
||||
setIcon(auxEl, "file-audio");
|
||||
} else if (CODE_TYPES.includes(item.file.extension)) {
|
||||
setIcon(auxEl, "file-code");
|
||||
} else if (item.file.extension === "canvas") {
|
||||
setIcon(auxEl, "layout-dashboard");
|
||||
} else if (item.file.extension === "pdf") {
|
||||
setIcon(auxEl, "book-open-text");
|
||||
} else {
|
||||
auxEl.setText(item.file.extension);
|
||||
}
|
||||
}
|
||||
|
||||
getItems() {
|
||||
return this.filesAndAliases;
|
||||
}
|
||||
}
|
||||
87
src/Components/Suggesters/FolderSuggestionModal.ts
Normal file
87
src/Components/Suggesters/FolderSuggestionModal.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import {
|
||||
FuzzyMatch,
|
||||
CachedMetadata,
|
||||
TextComponent,
|
||||
App,
|
||||
TFolder,
|
||||
} from "obsidian";
|
||||
import { SuggestionModal } from "./SuggestionModal";
|
||||
|
||||
export class FolderSuggestionModal extends SuggestionModal<TFolder> {
|
||||
text: TextComponent;
|
||||
cache: CachedMetadata;
|
||||
folders: TFolder[];
|
||||
folder: TFolder;
|
||||
constructor(app: App, input: TextComponent, items: TFolder[]) {
|
||||
super(app, input.inputEl, items);
|
||||
this.folders = [...items];
|
||||
this.text = input;
|
||||
|
||||
this.inputEl.addEventListener("input", () => this.getFolder());
|
||||
}
|
||||
getFolder() {
|
||||
const v = this.inputEl.value;
|
||||
const folder = this.app.vault.getAbstractFileByPath(v);
|
||||
if (folder == this.folder) {
|
||||
return;
|
||||
}
|
||||
if (!(folder instanceof TFolder)) {
|
||||
return;
|
||||
}
|
||||
this.folder = folder;
|
||||
|
||||
this.onInputChanged();
|
||||
}
|
||||
getItemText(item: TFolder) {
|
||||
return item.path;
|
||||
}
|
||||
onChooseItem(item: TFolder) {
|
||||
this.text.setValue(item.path);
|
||||
this.folder = item;
|
||||
}
|
||||
selectSuggestion({ item }: FuzzyMatch<TFolder>) {
|
||||
const link = item.path;
|
||||
|
||||
this.text.setValue(link);
|
||||
this.onClose();
|
||||
|
||||
this.close();
|
||||
}
|
||||
renderSuggestion(result: FuzzyMatch<TFolder>, el: HTMLElement) {
|
||||
const { item, match: matches } = result || {};
|
||||
const content = el.createDiv({
|
||||
cls: "suggestion-content",
|
||||
});
|
||||
if (!item) {
|
||||
content.setText(this.emptyStateText);
|
||||
content.parentElement.addClass("is-selected");
|
||||
return;
|
||||
}
|
||||
|
||||
const pathLength = item.path.length - item.name.length;
|
||||
const matchElements = matches.matches.map((m) => {
|
||||
return createSpan("suggestion-highlight");
|
||||
});
|
||||
for (let i = pathLength; i < item.path.length; i++) {
|
||||
const match = matches.matches.find((m) => m[0] === i);
|
||||
if (match) {
|
||||
const element = matchElements[matches.matches.indexOf(match)];
|
||||
content.appendChild(element);
|
||||
element.appendText(item.path.substring(match[0], match[1]));
|
||||
|
||||
i += match[1] - match[0] - 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
content.appendText(item.path[i]);
|
||||
}
|
||||
el.createDiv({
|
||||
cls: "suggestion-note",
|
||||
text: item.path,
|
||||
});
|
||||
}
|
||||
|
||||
getItems() {
|
||||
return this.folders;
|
||||
}
|
||||
}
|
||||
163
src/Components/Suggesters/PathSuggestionModal.ts
Normal file
163
src/Components/Suggesters/PathSuggestionModal.ts
Normal file
@@ -0,0 +1,163 @@
|
||||
import {
|
||||
FuzzyMatch,
|
||||
TFile,
|
||||
BlockCache,
|
||||
HeadingCache,
|
||||
CachedMetadata,
|
||||
TextComponent,
|
||||
App,
|
||||
} from "obsidian";
|
||||
import { SuggestionModal } from "./SuggestionModal";
|
||||
|
||||
export class PathSuggestionModal extends SuggestionModal<
|
||||
TFile | BlockCache | HeadingCache
|
||||
> {
|
||||
file: TFile;
|
||||
files: TFile[];
|
||||
text: TextComponent;
|
||||
cache: CachedMetadata;
|
||||
constructor(app: App, input: TextComponent, items: TFile[]) {
|
||||
super(app, input.inputEl, items);
|
||||
this.files = [...items];
|
||||
this.text = input;
|
||||
//this.getFile();
|
||||
|
||||
this.inputEl.addEventListener("input", this.getFile.bind(this));
|
||||
}
|
||||
|
||||
getFile() {
|
||||
const v = this.inputEl.value;
|
||||
const file = this.app.metadataCache.getFirstLinkpathDest(
|
||||
v.split(/[\^#]/).shift() || "",
|
||||
"",
|
||||
);
|
||||
if (file == this.file) {
|
||||
return;
|
||||
}
|
||||
this.file = file;
|
||||
if (this.file) {
|
||||
this.cache = this.app.metadataCache.getFileCache(this.file);
|
||||
}
|
||||
this.onInputChanged();
|
||||
}
|
||||
getItemText(item: TFile | HeadingCache | BlockCache) {
|
||||
if (item instanceof TFile) {
|
||||
return item.path;
|
||||
}
|
||||
if (Object.prototype.hasOwnProperty.call(item, "heading")) {
|
||||
return (<HeadingCache>item).heading;
|
||||
}
|
||||
if (Object.prototype.hasOwnProperty.call(item, "id")) {
|
||||
return (<BlockCache>item).id;
|
||||
}
|
||||
}
|
||||
onChooseItem(item: TFile | HeadingCache | BlockCache) {
|
||||
if (item instanceof TFile) {
|
||||
this.text.setValue(item.basename);
|
||||
this.file = item;
|
||||
this.cache = this.app.metadataCache.getFileCache(this.file);
|
||||
} else if (Object.prototype.hasOwnProperty.call(item, "heading")) {
|
||||
this.text.setValue(
|
||||
`${this.file.basename}#${(<HeadingCache>item).heading}`,
|
||||
);
|
||||
} else if (Object.prototype.hasOwnProperty.call(item, "id")) {
|
||||
this.text.setValue(`${this.file.basename}^${(<BlockCache>item).id}`);
|
||||
}
|
||||
}
|
||||
selectSuggestion({ item }: FuzzyMatch<TFile | BlockCache | HeadingCache>) {
|
||||
let link: string;
|
||||
if (item instanceof TFile) {
|
||||
link = item.basename;
|
||||
} else if (Object.prototype.hasOwnProperty.call(item, "heading")) {
|
||||
link = `${this.file.basename}#${(<HeadingCache>item).heading}`;
|
||||
} else if (Object.prototype.hasOwnProperty.call(item, "id")) {
|
||||
link = `${this.file.basename}^${(<BlockCache>item).id}`;
|
||||
}
|
||||
|
||||
this.text.setValue(link);
|
||||
this.onClose();
|
||||
|
||||
this.close();
|
||||
}
|
||||
renderSuggestion(
|
||||
result: FuzzyMatch<TFile | BlockCache | HeadingCache>,
|
||||
el: HTMLElement,
|
||||
) {
|
||||
const { item, match: matches } = result || {};
|
||||
const content = el.createDiv({
|
||||
cls: "suggestion-content",
|
||||
});
|
||||
if (!item) {
|
||||
content.setText(this.emptyStateText);
|
||||
content.parentElement.addClass("is-selected");
|
||||
return;
|
||||
}
|
||||
|
||||
if (item instanceof TFile) {
|
||||
const pathLength = item.path.length - item.name.length;
|
||||
const matchElements = matches.matches.map((m) => {
|
||||
return createSpan("suggestion-highlight");
|
||||
});
|
||||
for (
|
||||
let i = pathLength;
|
||||
i < item.path.length - item.extension.length - 1;
|
||||
i++
|
||||
) {
|
||||
const match = matches.matches.find((m) => m[0] === i);
|
||||
if (match) {
|
||||
const element = matchElements[matches.matches.indexOf(match)];
|
||||
content.appendChild(element);
|
||||
element.appendText(item.path.substring(match[0], match[1]));
|
||||
|
||||
i += match[1] - match[0] - 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
content.appendText(item.path[i]);
|
||||
}
|
||||
el.createDiv({
|
||||
cls: "suggestion-note",
|
||||
text: item.path,
|
||||
});
|
||||
} else if (Object.prototype.hasOwnProperty.call(item, "heading")) {
|
||||
content.setText((<HeadingCache>item).heading);
|
||||
content.prepend(
|
||||
createSpan({
|
||||
cls: "suggestion-flair",
|
||||
text: `H${(<HeadingCache>item).level}`,
|
||||
}),
|
||||
);
|
||||
} else if (Object.prototype.hasOwnProperty.call(item, "id")) {
|
||||
content.setText((<BlockCache>item).id);
|
||||
}
|
||||
}
|
||||
get headings() {
|
||||
if (!this.file) {
|
||||
return [];
|
||||
}
|
||||
if (!this.cache) {
|
||||
this.cache = this.app.metadataCache.getFileCache(this.file);
|
||||
}
|
||||
return this.cache.headings || [];
|
||||
}
|
||||
get blocks() {
|
||||
if (!this.file) {
|
||||
return [];
|
||||
}
|
||||
if (!this.cache) {
|
||||
this.cache = this.app.metadataCache.getFileCache(this.file);
|
||||
}
|
||||
return Object.values(this.cache.blocks || {}) || [];
|
||||
}
|
||||
getItems() {
|
||||
const v = this.inputEl.value;
|
||||
if (/#/.test(v)) {
|
||||
this.modifyInput = (i) => i.split(/#/).pop();
|
||||
return this.headings;
|
||||
} else if (/\^/.test(v)) {
|
||||
this.modifyInput = (i) => i.split(/\^/).pop();
|
||||
return this.blocks;
|
||||
}
|
||||
return this.files;
|
||||
}
|
||||
}
|
||||
119
src/Components/Suggesters/Suggester.ts
Normal file
119
src/Components/Suggesters/Suggester.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
import {
|
||||
SuggestModal,
|
||||
Scope,
|
||||
} from "obsidian";
|
||||
|
||||
export class Suggester<T> {
|
||||
owner: SuggestModal<T>;
|
||||
items: T[];
|
||||
suggestions: HTMLDivElement[];
|
||||
selectedItem: number;
|
||||
containerEl: HTMLElement;
|
||||
constructor(owner: SuggestModal<T>, containerEl: HTMLElement, scope: Scope) {
|
||||
this.containerEl = containerEl;
|
||||
this.owner = owner;
|
||||
containerEl.on(
|
||||
"click",
|
||||
".suggestion-item",
|
||||
this.onSuggestionClick.bind(this),
|
||||
);
|
||||
containerEl.on(
|
||||
"mousemove",
|
||||
".suggestion-item",
|
||||
this.onSuggestionMouseover.bind(this),
|
||||
);
|
||||
|
||||
scope.register([], "ArrowUp", () => {
|
||||
this.setSelectedItem(this.selectedItem - 1, true);
|
||||
return false;
|
||||
});
|
||||
|
||||
scope.register([], "ArrowDown", () => {
|
||||
this.setSelectedItem(this.selectedItem + 1, true);
|
||||
return false;
|
||||
});
|
||||
|
||||
scope.register([], "Enter", (evt) => {
|
||||
this.useSelectedItem(evt);
|
||||
return false;
|
||||
});
|
||||
|
||||
scope.register([], "Tab", (evt) => {
|
||||
this.chooseSuggestion(evt);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
chooseSuggestion(evt: KeyboardEvent) {
|
||||
if (!this.items || !this.items.length) {
|
||||
return;
|
||||
}
|
||||
const currentValue = this.items[this.selectedItem];
|
||||
if (currentValue) {
|
||||
this.owner.onChooseSuggestion(currentValue, evt);
|
||||
}
|
||||
}
|
||||
onSuggestionClick(event: MouseEvent, el: HTMLDivElement): void {
|
||||
event.preventDefault();
|
||||
if (!this.suggestions || !this.suggestions.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const item = this.suggestions.indexOf(el);
|
||||
this.setSelectedItem(item, false);
|
||||
this.useSelectedItem(event);
|
||||
}
|
||||
|
||||
onSuggestionMouseover(event: MouseEvent, el: HTMLDivElement): void {
|
||||
if (!this.suggestions || !this.suggestions.length) {
|
||||
return;
|
||||
}
|
||||
const item = this.suggestions.indexOf(el);
|
||||
this.setSelectedItem(item, false);
|
||||
}
|
||||
empty() {
|
||||
this.containerEl.empty();
|
||||
}
|
||||
setSuggestions(items: T[]) {
|
||||
this.containerEl.empty();
|
||||
const els: HTMLDivElement[] = [];
|
||||
|
||||
items.forEach((item) => {
|
||||
const suggestionEl = this.containerEl.createDiv("suggestion-item");
|
||||
this.owner.renderSuggestion(item, suggestionEl);
|
||||
els.push(suggestionEl);
|
||||
});
|
||||
this.items = items;
|
||||
this.suggestions = els;
|
||||
this.setSelectedItem(0, false);
|
||||
}
|
||||
useSelectedItem(event: MouseEvent | KeyboardEvent) {
|
||||
if (!this.items || !this.items.length) {
|
||||
return;
|
||||
}
|
||||
const currentValue = this.items[this.selectedItem];
|
||||
if (currentValue) {
|
||||
this.owner.selectSuggestion(currentValue, event);
|
||||
}
|
||||
}
|
||||
wrap(value: number, size: number): number {
|
||||
return ((value % size) + size) % size;
|
||||
}
|
||||
setSelectedItem(index: number, scroll: boolean) {
|
||||
const nIndex = this.wrap(index, this.suggestions.length);
|
||||
const prev = this.suggestions[this.selectedItem];
|
||||
const next = this.suggestions[nIndex];
|
||||
|
||||
if (prev) {
|
||||
prev.removeClass("is-selected");
|
||||
}
|
||||
if (next) {
|
||||
next.addClass("is-selected");
|
||||
}
|
||||
|
||||
this.selectedItem = nIndex;
|
||||
|
||||
if (scroll) {
|
||||
next.scrollIntoView(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
128
src/Components/Suggesters/SuggestionModal.ts
Normal file
128
src/Components/Suggesters/SuggestionModal.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
import {
|
||||
FuzzyMatch,
|
||||
App,
|
||||
FuzzySuggestModal,
|
||||
Scope,
|
||||
} from "obsidian";
|
||||
import { createPopper, Instance as PopperInstance } from "@popperjs/core";
|
||||
import { Suggester } from "./Suggester";
|
||||
|
||||
export abstract class SuggestionModal<T> extends FuzzySuggestModal<T> {
|
||||
items: T[] = [];
|
||||
suggestions: HTMLDivElement[];
|
||||
popper: WeakRef<PopperInstance>;
|
||||
//@ts-ignore
|
||||
scope: Scope = new Scope(this.app.scope);
|
||||
suggester: Suggester<FuzzyMatch<T>>;
|
||||
suggestEl: HTMLDivElement;
|
||||
promptEl: HTMLDivElement;
|
||||
emptyStateText: string = "No match found";
|
||||
limit: number = 100;
|
||||
shouldNotOpen: boolean;
|
||||
constructor(app: App, inputEl: HTMLInputElement, items: T[]) {
|
||||
super(app);
|
||||
this.inputEl = inputEl;
|
||||
this.items = items;
|
||||
this.suggestEl = createDiv("suggestion-container");
|
||||
this.contentEl = this.suggestEl.createDiv("suggestion");
|
||||
this.suggester = new Suggester(this, this.contentEl, this.scope);
|
||||
this.scope.register([], "Escape", this.onEscape.bind(this));
|
||||
this.inputEl.addEventListener("input", this.onInputChanged.bind(this));
|
||||
this.inputEl.addEventListener("focus", this.onFocus.bind(this));
|
||||
this.inputEl.addEventListener("blur", this.close.bind(this));
|
||||
this.suggestEl.on(
|
||||
"mousedown",
|
||||
".suggestion-container",
|
||||
(event: MouseEvent) => {
|
||||
event.preventDefault();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
empty() {
|
||||
this.suggester.empty();
|
||||
}
|
||||
|
||||
onInputChanged(): void {
|
||||
if (this.shouldNotOpen) {
|
||||
return;
|
||||
}
|
||||
const inputStr = this.modifyInput(this.inputEl.value);
|
||||
const suggestions = this.getSuggestions(inputStr);
|
||||
if (suggestions.length > 0) {
|
||||
this.suggester.setSuggestions(suggestions.slice(0, this.limit));
|
||||
} else {
|
||||
this.onNoSuggestion();
|
||||
}
|
||||
this.open();
|
||||
}
|
||||
|
||||
onFocus(): void {
|
||||
this.shouldNotOpen = false;
|
||||
this.onInputChanged();
|
||||
}
|
||||
|
||||
modifyInput(input: string): string {
|
||||
return input;
|
||||
}
|
||||
|
||||
onNoSuggestion() {
|
||||
this.empty();
|
||||
this.renderSuggestion(null, this.contentEl.createDiv("suggestion-item"));
|
||||
}
|
||||
|
||||
open(): void {
|
||||
// TODO: Figure out a better way to do this. Idea from Periodic Notes plugin
|
||||
this.app.keymap.pushScope(this.scope);
|
||||
this.inputEl.ownerDocument.body.appendChild(this.suggestEl);
|
||||
this.popper = new WeakRef(createPopper(this.inputEl, this.suggestEl, {
|
||||
placement: "bottom-start",
|
||||
modifiers: [
|
||||
{
|
||||
name: "offset",
|
||||
options: {
|
||||
offset: [0, 10],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "flip",
|
||||
options: {
|
||||
fallbackPlacements: ["top"],
|
||||
},
|
||||
},
|
||||
],
|
||||
}));
|
||||
}
|
||||
|
||||
onEscape(): void {
|
||||
this.close();
|
||||
this.shouldNotOpen = true;
|
||||
}
|
||||
|
||||
close(): void {
|
||||
// TODO: Figure out a better way to do this. Idea from Periodic Notes plugin
|
||||
this.app.keymap.popScope(this.scope);
|
||||
this.suggester.setSuggestions([]);
|
||||
if (this.popper?.deref()) {
|
||||
this.popper.deref().destroy();
|
||||
}
|
||||
this.inputEl.removeEventListener("input", this.onInputChanged.bind(this));
|
||||
this.inputEl.removeEventListener("focus", this.onFocus.bind(this));
|
||||
this.inputEl.removeEventListener("blur", this.close.bind(this));
|
||||
this.suggestEl.detach();
|
||||
}
|
||||
|
||||
createPrompt(prompts: HTMLSpanElement[]) {
|
||||
if (!this.promptEl) {
|
||||
this.promptEl = this.suggestEl.createDiv("prompt-instructions");
|
||||
}
|
||||
const prompt = this.promptEl.createDiv("prompt-instruction");
|
||||
for (const p of prompts) {
|
||||
prompt.appendChild(p);
|
||||
}
|
||||
}
|
||||
|
||||
abstract onChooseItem(item: T, evt: MouseEvent | KeyboardEvent): void;
|
||||
abstract getItemText(arg: T): string;
|
||||
abstract getItems(): T[];
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -767,7 +771,7 @@ export class EmbeddedFilesLoader {
|
||||
}, 1200);
|
||||
|
||||
const iterator = loadIterator.bind(this)();
|
||||
const concurency = 5;
|
||||
const concurency = 3;
|
||||
await new PromisePool(iterator, concurency).all();
|
||||
|
||||
clearInterval(addFilesTimer);
|
||||
|
||||
@@ -17,7 +17,7 @@ import { MimeType } from "./EmbeddedFileLoader";
|
||||
import { Editor, normalizePath, Notice, OpenViewState, RequestUrlResponse, TFile, TFolder, WorkspaceLeaf } from "obsidian";
|
||||
import * as obsidian_module from "obsidian";
|
||||
import ExcalidrawView, { ExportSettings, TextMode, getTextMode } from "src/ExcalidrawView";
|
||||
import { ExcalidrawData, getMarkdownDrawingSection, REGEX_LINK } from "src/ExcalidrawData";
|
||||
import { ExcalidrawData, getExcalidrawMarkdownHeaderSection, getMarkdownDrawingSection, REGEX_LINK } from "src/ExcalidrawData";
|
||||
import {
|
||||
FRONTMATTER,
|
||||
nanoid,
|
||||
@@ -654,6 +654,13 @@ export class ExcalidrawAutomate {
|
||||
0
|
||||
)
|
||||
: null;
|
||||
if (template?.plaintext) {
|
||||
if(params.plaintext) {
|
||||
params.plaintext = params.plaintext + "\n\n" + template.plaintext;
|
||||
} else {
|
||||
params.plaintext = template.plaintext;
|
||||
}
|
||||
}
|
||||
let elements = template ? template.elements : [];
|
||||
elements = elements.concat(this.getElements());
|
||||
let frontmatter: string;
|
||||
@@ -679,7 +686,13 @@ export class ExcalidrawAutomate {
|
||||
: FRONTMATTER;
|
||||
}
|
||||
|
||||
frontmatter += params.plaintext ? params.plaintext + "\n\n" : "";
|
||||
frontmatter += params.plaintext
|
||||
? (params.plaintext.endsWith("\n\n")
|
||||
? params.plaintext
|
||||
: (params.plaintext.endsWith("\n")
|
||||
? params.plaintext + "\n"
|
||||
: params.plaintext + "\n\n"))
|
||||
: "";
|
||||
if(template?.frontmatter && params?.frontmatterKeys) {
|
||||
//the frontmatter tags supplyed to create take priority
|
||||
frontmatter = mergeMarkdownFiles(template.frontmatter,frontmatter);
|
||||
@@ -1476,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;
|
||||
@@ -2930,6 +2948,7 @@ async function getTemplate(
|
||||
frontmatter: string;
|
||||
files: any;
|
||||
hasSVGwithBitmap: boolean;
|
||||
plaintext: string; //markdown data above Excalidraw data and below YAML frontmatter
|
||||
}> {
|
||||
const app = plugin.app;
|
||||
const vault = app.vault;
|
||||
@@ -2955,6 +2974,7 @@ async function getTemplate(
|
||||
frontmatter: "",
|
||||
files: excalidrawData.scene.files,
|
||||
hasSVGwithBitmap,
|
||||
plaintext: "",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3027,7 +3047,7 @@ async function getTemplate(
|
||||
}
|
||||
|
||||
excalidrawData.destroy();
|
||||
const filehead = data.substring(0, trimLocation);
|
||||
const filehead = getExcalidrawMarkdownHeaderSection(data); // data.substring(0, trimLocation);
|
||||
let files:any = {};
|
||||
const sceneFilesSize = Object.values(scene.files).length;
|
||||
if (sceneFilesSize > 0) {
|
||||
@@ -3040,6 +3060,7 @@ async function getTemplate(
|
||||
}
|
||||
}
|
||||
|
||||
const frontmatter = filehead.match(/^---\n.*\n---\n/ms)?.[0] ?? filehead;
|
||||
return {
|
||||
elements: convertMarkdownLinksToObsidianURLs
|
||||
? updateElementLinksToObsidianLinks({
|
||||
@@ -3047,7 +3068,10 @@ async function getTemplate(
|
||||
hostFile: file,
|
||||
}) : groupElements,
|
||||
appState: scene.appState,
|
||||
frontmatter: filehead.match(/^---\n.*\n---\n/ms)?.[0] ?? filehead,
|
||||
frontmatter,
|
||||
plaintext: frontmatter !== filehead
|
||||
? (filehead.split(/^---\n.*\n---\n/ms)?.[1] ?? "")
|
||||
: "",
|
||||
files,
|
||||
hasSVGwithBitmap,
|
||||
};
|
||||
@@ -3058,6 +3082,7 @@ async function getTemplate(
|
||||
frontmatter: null,
|
||||
files: [],
|
||||
hasSVGwithBitmap,
|
||||
plaintext: "",
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
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[];
|
||||
|
||||
@@ -295,7 +295,7 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
private lastLoadedFile: TFile = null;
|
||||
//store key state for view mode link resolution
|
||||
private modifierKeyDown: ModifierKeys = {shiftKey:false, metaKey: false, ctrlKey: false, altKey: false}
|
||||
public currentPosition: {x:number,y:number} = { x: 0, y: 0 };
|
||||
public currentPosition: {x:number,y:number} = { x: 0, y: 0 }; //these are scene coord thus would be more apt to call them sceneX and sceneY, however due to scrits already using x and y, I will keep it as is
|
||||
//Obsidian 0.15.0
|
||||
private draginfoDiv: HTMLDivElement;
|
||||
public canvasNodeFactory: CanvasNodeFactory;
|
||||
@@ -3267,6 +3267,7 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
markdownlink: string,
|
||||
path: string,
|
||||
alias: string,
|
||||
originalLink?: string,
|
||||
) {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.addLink, "ExcalidrawView.addLink", markdownlink, path, alias);
|
||||
const api = this.excalidrawAPI as ExcalidrawImperativeAPI;
|
||||
@@ -3280,16 +3281,28 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
}
|
||||
const selectedElementId = Object.keys(api.getAppState().selectedElementIds)[0];
|
||||
const selectedElement = api.getSceneElements().find(el=>el.id === selectedElementId);
|
||||
if(!selectedElement || (selectedElement && selectedElement.link !== null)) {
|
||||
if(!selectedElement || (!Boolean(originalLink) && (selectedElement && selectedElement.link !== null) )) {
|
||||
if(selectedElement) new Notice("Selected element already has a link. Inserting link as text.");
|
||||
this.addText(markdownlink);
|
||||
return;
|
||||
}
|
||||
const ea = getEA(this) as ExcalidrawAutomate;
|
||||
ea.copyViewElementsToEAforEditing([selectedElement]);
|
||||
if(originalLink?.match(/\[\[(.*?)\]\]/)?.[1]) {
|
||||
markdownlink = originalLink.replace(/(\[\[.*?\]\])/,markdownlink);
|
||||
}
|
||||
ea.getElement(selectedElementId).link = markdownlink;
|
||||
await ea.addElementsToView(false, true);
|
||||
ea.destroy();
|
||||
if(Boolean(originalLink)) {
|
||||
this.updateScene({
|
||||
appState: {
|
||||
showHyperlinkPopup: {
|
||||
newValue : "info", oldValue : "editor"
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public async addText (
|
||||
@@ -3546,7 +3559,10 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
}
|
||||
|
||||
private clearHoverPreview() {
|
||||
if (this.hoverPopover) {
|
||||
//@ts-ignore
|
||||
const hoverContainerEl = this.hoverPopover?.containerEl;
|
||||
//don't auto hide hover-editor
|
||||
if (this.hoverPopover && !hoverContainerEl?.parentElement?.hasClass("hover-editor")) {
|
||||
this.hoverPreviewTarget = null;
|
||||
//@ts-ignore
|
||||
if(this.hoverPopover.embed?.editor) {
|
||||
@@ -4657,8 +4673,20 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
|
||||
//returns the raw text of the element which is the original text without parsing
|
||||
//in compatibility mode, returns the original text, and for backward compatibility the text if originalText is not available
|
||||
private onBeforeTextEdit (textElement: ExcalidrawTextElement) {
|
||||
private onBeforeTextEdit (textElement: ExcalidrawTextElement, isExistingElement: boolean): string {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.onBeforeTextEdit, "ExcalidrawView.onBeforeTextEdit", textElement);
|
||||
/*const api = this.excalidrawAPI as ExcalidrawImperativeAPI;
|
||||
const st = api.getAppState();
|
||||
setDynamicStyle(
|
||||
this.plugin.ea,
|
||||
this,
|
||||
st.viewBackgroundColor === "transparent" ? "white" : st.viewBackgroundColor,
|
||||
this.plugin.settings.dynamicStyling,
|
||||
api.getColorAtScenePoint({sceneX: this.currentPosition.x, sceneY: this.currentPosition.y})
|
||||
);*/
|
||||
if(!isExistingElement) {
|
||||
return;
|
||||
}
|
||||
window.clearTimeout(this.isEditingTextResetTimer);
|
||||
this.isEditingTextResetTimer = null;
|
||||
this.semaphores.isEditingText = true; //to prevent autoresize on mobile when keyboard pops up
|
||||
@@ -5079,6 +5107,15 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
new Notice("Image successfully converted to local file");
|
||||
}
|
||||
|
||||
private insertLinkAction(linkVal: string) {
|
||||
let link = linkVal.match(/\[\[(.*?)\]\]/)?.[1];
|
||||
if(!link) {
|
||||
link = linkVal.replaceAll("[","").replaceAll("]","");
|
||||
link = link.split("|")[0].trim();
|
||||
}
|
||||
this.plugin.insertLinkDialog.start(this.file.path, (markdownlink: string, path:string, alias:string) => this.addLink(markdownlink, path, alias, linkVal), link);
|
||||
}
|
||||
|
||||
private onContextMenu(elements: readonly ExcalidrawElement[], appState: AppState, onClose: (callback?: () => void) => void) {
|
||||
const React = this.packages.react;
|
||||
const contextMenuActions = [];
|
||||
@@ -5901,6 +5938,7 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
renderEmbeddable: this.renderEmbeddable.bind(this),
|
||||
renderMermaid: shouldRenderMermaid,
|
||||
showDeprecatedFonts: true,
|
||||
insertLinkAction: this.insertLinkAction.bind(this),
|
||||
},
|
||||
this.renderCustomActionsMenu(),
|
||||
this.renderWelcomeScreen(),
|
||||
|
||||
102
src/LaTeX.ts
102
src/LaTeX.ts
@@ -1,18 +1,22 @@
|
||||
// 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";
|
||||
|
||||
declare const loadMathjaxToSVG: Function;
|
||||
let mathjaxLoaded = false;
|
||||
let tex2dataURLExternal: Function;
|
||||
let clearVariables: Function;
|
||||
|
||||
const loadMathJax = async () => {
|
||||
if (!mathjaxLoaded) {
|
||||
const module = await loadMathjaxToSVG();
|
||||
tex2dataURLExternal = module.tex2dataURL;
|
||||
clearVariables = module.clearMathJaxVariables;
|
||||
mathjaxLoaded = true;
|
||||
}
|
||||
};
|
||||
|
||||
export const updateEquation = async (
|
||||
equation: string,
|
||||
@@ -20,13 +24,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 +41,9 @@ 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
|
||||
): Promise<{
|
||||
mimeType: MimeType;
|
||||
fileId: FileId;
|
||||
@@ -64,47 +51,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;
|
||||
}
|
||||
};
|
||||
@@ -195,6 +195,10 @@ export const ANIMATED_IMAGE_TYPES = ["gif", "webp", "apng", "svg"];
|
||||
export const EXPORT_TYPES = ["svg", "dark.svg", "light.svg", "png", "dark.png", "light.png"];
|
||||
export const MAX_IMAGE_SIZE = 500;
|
||||
|
||||
export const VIDEO_TYPES = ["mp4", "webm", "ogv", "mov", "mkv"];
|
||||
export const AUDIO_TYPES = ["mp3", "wav", "m4a", "3gp", "flac", "ogg", "oga", "opus"];
|
||||
export const CODE_TYPES = ["json", "css", "js"];
|
||||
|
||||
export const FRONTMATTER_KEYS:{[key:string]: {name: string, type: string, depricated?:boolean}} = {
|
||||
"plugin": {name: "excalidraw-plugin", type: "text"},
|
||||
"export-transparent": {name: "excalidraw-export-transparent", type: "checkbox"},
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,569 +0,0 @@
|
||||
import {
|
||||
FuzzyMatch,
|
||||
TFile,
|
||||
BlockCache,
|
||||
HeadingCache,
|
||||
CachedMetadata,
|
||||
TextComponent,
|
||||
App,
|
||||
TFolder,
|
||||
FuzzySuggestModal,
|
||||
SuggestModal,
|
||||
Scope,
|
||||
} from "obsidian";
|
||||
import { createPopper, Instance as PopperInstance } from "@popperjs/core";
|
||||
|
||||
class Suggester<T> {
|
||||
owner: SuggestModal<T>;
|
||||
items: T[];
|
||||
suggestions: HTMLDivElement[];
|
||||
selectedItem: number;
|
||||
containerEl: HTMLElement;
|
||||
constructor(owner: SuggestModal<T>, containerEl: HTMLElement, scope: Scope) {
|
||||
this.containerEl = containerEl;
|
||||
this.owner = owner;
|
||||
containerEl.on(
|
||||
"click",
|
||||
".suggestion-item",
|
||||
this.onSuggestionClick.bind(this),
|
||||
);
|
||||
containerEl.on(
|
||||
"mousemove",
|
||||
".suggestion-item",
|
||||
this.onSuggestionMouseover.bind(this),
|
||||
);
|
||||
|
||||
scope.register([], "ArrowUp", () => {
|
||||
this.setSelectedItem(this.selectedItem - 1, true);
|
||||
return false;
|
||||
});
|
||||
|
||||
scope.register([], "ArrowDown", () => {
|
||||
this.setSelectedItem(this.selectedItem + 1, true);
|
||||
return false;
|
||||
});
|
||||
|
||||
scope.register([], "Enter", (evt) => {
|
||||
this.useSelectedItem(evt);
|
||||
return false;
|
||||
});
|
||||
|
||||
scope.register([], "Tab", (evt) => {
|
||||
this.chooseSuggestion(evt);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
chooseSuggestion(evt: KeyboardEvent) {
|
||||
if (!this.items || !this.items.length) {
|
||||
return;
|
||||
}
|
||||
const currentValue = this.items[this.selectedItem];
|
||||
if (currentValue) {
|
||||
this.owner.onChooseSuggestion(currentValue, evt);
|
||||
}
|
||||
}
|
||||
onSuggestionClick(event: MouseEvent, el: HTMLDivElement): void {
|
||||
event.preventDefault();
|
||||
if (!this.suggestions || !this.suggestions.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const item = this.suggestions.indexOf(el);
|
||||
this.setSelectedItem(item, false);
|
||||
this.useSelectedItem(event);
|
||||
}
|
||||
|
||||
onSuggestionMouseover(event: MouseEvent, el: HTMLDivElement): void {
|
||||
if (!this.suggestions || !this.suggestions.length) {
|
||||
return;
|
||||
}
|
||||
const item = this.suggestions.indexOf(el);
|
||||
this.setSelectedItem(item, false);
|
||||
}
|
||||
empty() {
|
||||
this.containerEl.empty();
|
||||
}
|
||||
setSuggestions(items: T[]) {
|
||||
this.containerEl.empty();
|
||||
const els: HTMLDivElement[] = [];
|
||||
|
||||
items.forEach((item) => {
|
||||
const suggestionEl = this.containerEl.createDiv("suggestion-item");
|
||||
this.owner.renderSuggestion(item, suggestionEl);
|
||||
els.push(suggestionEl);
|
||||
});
|
||||
this.items = items;
|
||||
this.suggestions = els;
|
||||
this.setSelectedItem(0, false);
|
||||
}
|
||||
useSelectedItem(event: MouseEvent | KeyboardEvent) {
|
||||
if (!this.items || !this.items.length) {
|
||||
return;
|
||||
}
|
||||
const currentValue = this.items[this.selectedItem];
|
||||
if (currentValue) {
|
||||
this.owner.selectSuggestion(currentValue, event);
|
||||
}
|
||||
}
|
||||
wrap(value: number, size: number): number {
|
||||
return ((value % size) + size) % size;
|
||||
}
|
||||
setSelectedItem(index: number, scroll: boolean) {
|
||||
const nIndex = this.wrap(index, this.suggestions.length);
|
||||
const prev = this.suggestions[this.selectedItem];
|
||||
const next = this.suggestions[nIndex];
|
||||
|
||||
if (prev) {
|
||||
prev.removeClass("is-selected");
|
||||
}
|
||||
if (next) {
|
||||
next.addClass("is-selected");
|
||||
}
|
||||
|
||||
this.selectedItem = nIndex;
|
||||
|
||||
if (scroll) {
|
||||
next.scrollIntoView(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class SuggestionModal<T> extends FuzzySuggestModal<T> {
|
||||
items: T[] = [];
|
||||
suggestions: HTMLDivElement[];
|
||||
popper: WeakRef<PopperInstance>;
|
||||
//@ts-ignore
|
||||
scope: Scope = new Scope(this.app.scope);
|
||||
suggester: Suggester<FuzzyMatch<T>>;
|
||||
suggestEl: HTMLDivElement;
|
||||
promptEl: HTMLDivElement;
|
||||
emptyStateText: string = "No match found";
|
||||
limit: number = 100;
|
||||
shouldNotOpen: boolean;
|
||||
constructor(app: App, inputEl: HTMLInputElement, items: T[]) {
|
||||
super(app);
|
||||
this.inputEl = inputEl;
|
||||
this.items = items;
|
||||
|
||||
this.suggestEl = createDiv("suggestion-container");
|
||||
|
||||
this.contentEl = this.suggestEl.createDiv("suggestion");
|
||||
|
||||
this.suggester = new Suggester(this, this.contentEl, this.scope);
|
||||
|
||||
this.scope.register([], "Escape", this.onEscape.bind(this));
|
||||
|
||||
this.inputEl.addEventListener("input", this.onInputChanged.bind(this));
|
||||
this.inputEl.addEventListener("focus", this.onFocus.bind(this));
|
||||
this.inputEl.addEventListener("blur", this.close.bind(this));
|
||||
this.suggestEl.on(
|
||||
"mousedown",
|
||||
".suggestion-container",
|
||||
(event: MouseEvent) => {
|
||||
event.preventDefault();
|
||||
},
|
||||
);
|
||||
}
|
||||
empty() {
|
||||
this.suggester.empty();
|
||||
}
|
||||
onInputChanged(): void {
|
||||
if (this.shouldNotOpen) {
|
||||
return;
|
||||
}
|
||||
const inputStr = this.modifyInput(this.inputEl.value);
|
||||
const suggestions = this.getSuggestions(inputStr);
|
||||
if (suggestions.length > 0) {
|
||||
this.suggester.setSuggestions(suggestions.slice(0, this.limit));
|
||||
} else {
|
||||
this.onNoSuggestion();
|
||||
}
|
||||
this.open();
|
||||
}
|
||||
onFocus(): void {
|
||||
this.shouldNotOpen = false;
|
||||
this.onInputChanged();
|
||||
}
|
||||
modifyInput(input: string): string {
|
||||
return input;
|
||||
}
|
||||
onNoSuggestion() {
|
||||
this.empty();
|
||||
this.renderSuggestion(null, this.contentEl.createDiv("suggestion-item"));
|
||||
}
|
||||
open(): void {
|
||||
// TODO: Figure out a better way to do this. Idea from Periodic Notes plugin
|
||||
this.app.keymap.pushScope(this.scope);
|
||||
|
||||
this.inputEl.ownerDocument.body.appendChild(this.suggestEl);
|
||||
this.popper = new WeakRef(createPopper(this.inputEl, this.suggestEl, {
|
||||
placement: "bottom-start",
|
||||
modifiers: [
|
||||
{
|
||||
name: "offset",
|
||||
options: {
|
||||
offset: [0, 10],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "flip",
|
||||
options: {
|
||||
fallbackPlacements: ["top"],
|
||||
},
|
||||
},
|
||||
],
|
||||
}));
|
||||
}
|
||||
|
||||
onEscape(): void {
|
||||
this.close();
|
||||
this.shouldNotOpen = true;
|
||||
}
|
||||
close(): void {
|
||||
// TODO: Figure out a better way to do this. Idea from Periodic Notes plugin
|
||||
this.app.keymap.popScope(this.scope);
|
||||
|
||||
this.suggester.setSuggestions([]);
|
||||
if (this.popper?.deref()) {
|
||||
this.popper.deref().destroy();
|
||||
}
|
||||
|
||||
this.inputEl.removeEventListener("input", this.onInputChanged.bind(this));
|
||||
this.inputEl.removeEventListener("focus", this.onFocus.bind(this));
|
||||
this.inputEl.removeEventListener("blur", this.close.bind(this));
|
||||
|
||||
this.suggestEl.detach();
|
||||
}
|
||||
createPrompt(prompts: HTMLSpanElement[]) {
|
||||
if (!this.promptEl) {
|
||||
this.promptEl = this.suggestEl.createDiv("prompt-instructions");
|
||||
}
|
||||
const prompt = this.promptEl.createDiv("prompt-instruction");
|
||||
for (const p of prompts) {
|
||||
prompt.appendChild(p);
|
||||
}
|
||||
}
|
||||
abstract onChooseItem(item: T, evt: MouseEvent | KeyboardEvent): void;
|
||||
abstract getItemText(arg: T): string;
|
||||
abstract getItems(): T[];
|
||||
}
|
||||
|
||||
export class PathSuggestionModal extends SuggestionModal<
|
||||
TFile | BlockCache | HeadingCache
|
||||
> {
|
||||
file: TFile;
|
||||
files: TFile[];
|
||||
text: TextComponent;
|
||||
cache: CachedMetadata;
|
||||
constructor(app: App, input: TextComponent, items: TFile[]) {
|
||||
super(app, input.inputEl, items);
|
||||
this.files = [...items];
|
||||
this.text = input;
|
||||
//this.getFile();
|
||||
|
||||
this.inputEl.addEventListener("input", this.getFile.bind(this));
|
||||
}
|
||||
|
||||
getFile() {
|
||||
const v = this.inputEl.value;
|
||||
const file = this.app.metadataCache.getFirstLinkpathDest(
|
||||
v.split(/[\^#]/).shift() || "",
|
||||
"",
|
||||
);
|
||||
if (file == this.file) {
|
||||
return;
|
||||
}
|
||||
this.file = file;
|
||||
if (this.file) {
|
||||
this.cache = this.app.metadataCache.getFileCache(this.file);
|
||||
}
|
||||
this.onInputChanged();
|
||||
}
|
||||
getItemText(item: TFile | HeadingCache | BlockCache) {
|
||||
if (item instanceof TFile) {
|
||||
return item.path;
|
||||
}
|
||||
if (Object.prototype.hasOwnProperty.call(item, "heading")) {
|
||||
return (<HeadingCache>item).heading;
|
||||
}
|
||||
if (Object.prototype.hasOwnProperty.call(item, "id")) {
|
||||
return (<BlockCache>item).id;
|
||||
}
|
||||
}
|
||||
onChooseItem(item: TFile | HeadingCache | BlockCache) {
|
||||
if (item instanceof TFile) {
|
||||
this.text.setValue(item.basename);
|
||||
this.file = item;
|
||||
this.cache = this.app.metadataCache.getFileCache(this.file);
|
||||
} else if (Object.prototype.hasOwnProperty.call(item, "heading")) {
|
||||
this.text.setValue(
|
||||
`${this.file.basename}#${(<HeadingCache>item).heading}`,
|
||||
);
|
||||
} else if (Object.prototype.hasOwnProperty.call(item, "id")) {
|
||||
this.text.setValue(`${this.file.basename}^${(<BlockCache>item).id}`);
|
||||
}
|
||||
}
|
||||
selectSuggestion({ item }: FuzzyMatch<TFile | BlockCache | HeadingCache>) {
|
||||
let link: string;
|
||||
if (item instanceof TFile) {
|
||||
link = item.basename;
|
||||
} else if (Object.prototype.hasOwnProperty.call(item, "heading")) {
|
||||
link = `${this.file.basename}#${(<HeadingCache>item).heading}`;
|
||||
} else if (Object.prototype.hasOwnProperty.call(item, "id")) {
|
||||
link = `${this.file.basename}^${(<BlockCache>item).id}`;
|
||||
}
|
||||
|
||||
this.text.setValue(link);
|
||||
this.onClose();
|
||||
|
||||
this.close();
|
||||
}
|
||||
renderSuggestion(
|
||||
result: FuzzyMatch<TFile | BlockCache | HeadingCache>,
|
||||
el: HTMLElement,
|
||||
) {
|
||||
const { item, match: matches } = result || {};
|
||||
const content = el.createDiv({
|
||||
cls: "suggestion-content",
|
||||
});
|
||||
if (!item) {
|
||||
content.setText(this.emptyStateText);
|
||||
content.parentElement.addClass("is-selected");
|
||||
return;
|
||||
}
|
||||
|
||||
if (item instanceof TFile) {
|
||||
const pathLength = item.path.length - item.name.length;
|
||||
const matchElements = matches.matches.map((m) => {
|
||||
return createSpan("suggestion-highlight");
|
||||
});
|
||||
for (
|
||||
let i = pathLength;
|
||||
i < item.path.length - item.extension.length - 1;
|
||||
i++
|
||||
) {
|
||||
const match = matches.matches.find((m) => m[0] === i);
|
||||
if (match) {
|
||||
const element = matchElements[matches.matches.indexOf(match)];
|
||||
content.appendChild(element);
|
||||
element.appendText(item.path.substring(match[0], match[1]));
|
||||
|
||||
i += match[1] - match[0] - 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
content.appendText(item.path[i]);
|
||||
}
|
||||
el.createDiv({
|
||||
cls: "suggestion-note",
|
||||
text: item.path,
|
||||
});
|
||||
} else if (Object.prototype.hasOwnProperty.call(item, "heading")) {
|
||||
content.setText((<HeadingCache>item).heading);
|
||||
content.prepend(
|
||||
createSpan({
|
||||
cls: "suggestion-flair",
|
||||
text: `H${(<HeadingCache>item).level}`,
|
||||
}),
|
||||
);
|
||||
} else if (Object.prototype.hasOwnProperty.call(item, "id")) {
|
||||
content.setText((<BlockCache>item).id);
|
||||
}
|
||||
}
|
||||
get headings() {
|
||||
if (!this.file) {
|
||||
return [];
|
||||
}
|
||||
if (!this.cache) {
|
||||
this.cache = this.app.metadataCache.getFileCache(this.file);
|
||||
}
|
||||
return this.cache.headings || [];
|
||||
}
|
||||
get blocks() {
|
||||
if (!this.file) {
|
||||
return [];
|
||||
}
|
||||
if (!this.cache) {
|
||||
this.cache = this.app.metadataCache.getFileCache(this.file);
|
||||
}
|
||||
return Object.values(this.cache.blocks || {}) || [];
|
||||
}
|
||||
getItems() {
|
||||
const v = this.inputEl.value;
|
||||
if (/#/.test(v)) {
|
||||
this.modifyInput = (i) => i.split(/#/).pop();
|
||||
return this.headings;
|
||||
} else if (/\^/.test(v)) {
|
||||
this.modifyInput = (i) => i.split(/\^/).pop();
|
||||
return this.blocks;
|
||||
}
|
||||
return this.files;
|
||||
}
|
||||
}
|
||||
|
||||
export class FolderSuggestionModal extends SuggestionModal<TFolder> {
|
||||
text: TextComponent;
|
||||
cache: CachedMetadata;
|
||||
folders: TFolder[];
|
||||
folder: TFolder;
|
||||
constructor(app: App, input: TextComponent, items: TFolder[]) {
|
||||
super(app, input.inputEl, items);
|
||||
this.folders = [...items];
|
||||
this.text = input;
|
||||
|
||||
this.inputEl.addEventListener("input", () => this.getFolder());
|
||||
}
|
||||
getFolder() {
|
||||
const v = this.inputEl.value;
|
||||
const folder = this.app.vault.getAbstractFileByPath(v);
|
||||
if (folder == this.folder) {
|
||||
return;
|
||||
}
|
||||
if (!(folder instanceof TFolder)) {
|
||||
return;
|
||||
}
|
||||
this.folder = folder;
|
||||
|
||||
this.onInputChanged();
|
||||
}
|
||||
getItemText(item: TFolder) {
|
||||
return item.path;
|
||||
}
|
||||
onChooseItem(item: TFolder) {
|
||||
this.text.setValue(item.path);
|
||||
this.folder = item;
|
||||
}
|
||||
selectSuggestion({ item }: FuzzyMatch<TFolder>) {
|
||||
const link = item.path;
|
||||
|
||||
this.text.setValue(link);
|
||||
this.onClose();
|
||||
|
||||
this.close();
|
||||
}
|
||||
renderSuggestion(result: FuzzyMatch<TFolder>, el: HTMLElement) {
|
||||
const { item, match: matches } = result || {};
|
||||
const content = el.createDiv({
|
||||
cls: "suggestion-content",
|
||||
});
|
||||
if (!item) {
|
||||
content.setText(this.emptyStateText);
|
||||
content.parentElement.addClass("is-selected");
|
||||
return;
|
||||
}
|
||||
|
||||
const pathLength = item.path.length - item.name.length;
|
||||
const matchElements = matches.matches.map((m) => {
|
||||
return createSpan("suggestion-highlight");
|
||||
});
|
||||
for (let i = pathLength; i < item.path.length; i++) {
|
||||
const match = matches.matches.find((m) => m[0] === i);
|
||||
if (match) {
|
||||
const element = matchElements[matches.matches.indexOf(match)];
|
||||
content.appendChild(element);
|
||||
element.appendText(item.path.substring(match[0], match[1]));
|
||||
|
||||
i += match[1] - match[0] - 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
content.appendText(item.path[i]);
|
||||
}
|
||||
el.createDiv({
|
||||
cls: "suggestion-note",
|
||||
text: item.path,
|
||||
});
|
||||
}
|
||||
|
||||
getItems() {
|
||||
return this.folders;
|
||||
}
|
||||
}
|
||||
|
||||
export class FileSuggestionModal extends SuggestionModal<TFile> {
|
||||
text: TextComponent;
|
||||
cache: CachedMetadata;
|
||||
files: TFile[];
|
||||
file: TFile;
|
||||
constructor(app: App, input: TextComponent, items: TFile[]) {
|
||||
super(app, input.inputEl, items);
|
||||
this.limit = 20;
|
||||
this.files = [...items];
|
||||
this.text = input;
|
||||
this.inputEl.addEventListener("input", () => this.getFile());
|
||||
}
|
||||
|
||||
getFile() {
|
||||
const v = this.inputEl.value;
|
||||
const file = this.app.vault.getAbstractFileByPath(v);
|
||||
if (file === this.file) {
|
||||
return;
|
||||
}
|
||||
if (!(file instanceof TFile)) {
|
||||
return;
|
||||
}
|
||||
this.file = file;
|
||||
|
||||
this.onInputChanged();
|
||||
}
|
||||
|
||||
getSelectedItem() {
|
||||
return this.file;
|
||||
}
|
||||
|
||||
getItemText(item: TFile) {
|
||||
return item.path;
|
||||
}
|
||||
|
||||
onChooseItem(item: TFile) {
|
||||
this.file = item;
|
||||
this.text.setValue(item.path);
|
||||
this.text.onChanged();
|
||||
}
|
||||
|
||||
selectSuggestion({ item }: FuzzyMatch<TFile>) {
|
||||
this.file = item;
|
||||
this.text.setValue(item.path);
|
||||
this.onClose();
|
||||
this.text.onChanged();
|
||||
this.close();
|
||||
}
|
||||
|
||||
renderSuggestion(result: FuzzyMatch<TFile>, el: HTMLElement) {
|
||||
const { item, match: matches } = result || {};
|
||||
const content = el.createDiv({
|
||||
cls: "suggestion-content",
|
||||
});
|
||||
if (!item) {
|
||||
content.setText(this.emptyStateText);
|
||||
content.parentElement.addClass("is-selected");
|
||||
return;
|
||||
}
|
||||
|
||||
const pathLength = item.path.length - item.name.length;
|
||||
const matchElements = matches.matches.map((m) => {
|
||||
return createSpan("suggestion-highlight");
|
||||
});
|
||||
for (let i = pathLength; i < item.path.length; i++) {
|
||||
const match = matches.matches.find((m) => m[0] === i);
|
||||
if (match) {
|
||||
const element = matchElements[matches.matches.indexOf(match)];
|
||||
content.appendChild(element);
|
||||
element.appendText(item.path.substring(match[0], match[1]));
|
||||
|
||||
i += match[1] - match[0] - 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
content.appendText(item.path[i]);
|
||||
}
|
||||
el.createDiv({
|
||||
cls: "suggestion-note",
|
||||
text: item.path,
|
||||
});
|
||||
}
|
||||
|
||||
getItems() {
|
||||
return this.files;
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
import { App, FuzzySuggestModal, TFile } from "obsidian";
|
||||
import { REG_LINKINDEX_INVALIDCHARS } from "../constants/constants";
|
||||
import { FuzzyMatch, FuzzySuggestModal, setIcon } from "obsidian";
|
||||
import { AUDIO_TYPES, CODE_TYPES, ICON_NAME, IMAGE_TYPES, REG_LINKINDEX_INVALIDCHARS, VIDEO_TYPES } from "../constants/constants";
|
||||
import { t } from "../lang/helpers";
|
||||
import ExcalidrawPlugin from "src/main";
|
||||
import { getLink } from "src/utils/FileUtils";
|
||||
import { LinkSuggestion } from "src/types/types";
|
||||
|
||||
export class InsertLinkDialog extends FuzzySuggestModal<TFile> {
|
||||
|
||||
export class InsertLinkDialog extends FuzzySuggestModal<LinkSuggestion> {
|
||||
private addText: Function;
|
||||
private drawingPath: string;
|
||||
|
||||
@@ -28,7 +30,7 @@ export class InsertLinkDialog extends FuzzySuggestModal<TFile> {
|
||||
this.emptyStateText = t("NO_MATCH");
|
||||
}
|
||||
|
||||
getItems(): any[] {
|
||||
getItems(): LinkSuggestion[] {
|
||||
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/422
|
||||
return (
|
||||
this.app.metadataCache
|
||||
@@ -39,11 +41,11 @@ export class InsertLinkDialog extends FuzzySuggestModal<TFile> {
|
||||
);
|
||||
}
|
||||
|
||||
getItemText(item: any): string {
|
||||
getItemText(item: LinkSuggestion): string {
|
||||
return item.path + (item.alias ? `|${item.alias}` : "");
|
||||
}
|
||||
|
||||
onChooseItem(item: any): void {
|
||||
onChooseItem(item: LinkSuggestion): void {
|
||||
let filepath = item.path;
|
||||
if (item.file) {
|
||||
filepath = this.app.metadataCache.fileToLinktext(
|
||||
@@ -56,6 +58,65 @@ export class InsertLinkDialog extends FuzzySuggestModal<TFile> {
|
||||
this.addText(getLink(this.plugin,{embed: false, path: filepath, alias: item.alias}), filepath, item.alias);
|
||||
}
|
||||
|
||||
renderSuggestion(result: FuzzyMatch<LinkSuggestion>, itemEl: HTMLElement) {
|
||||
const { item, match: matches } = result || {};
|
||||
itemEl.addClass("mod-complex");
|
||||
const contentEl = itemEl.createDiv("suggestion-content");
|
||||
const auxEl = itemEl.createDiv("suggestion-aux");
|
||||
const titleEl = contentEl.createDiv("suggestion-title");
|
||||
const noteEl = contentEl.createDiv("suggestion-note");
|
||||
|
||||
if (!item) {
|
||||
titleEl.setText(this.emptyStateText);
|
||||
itemEl.addClass("is-selected");
|
||||
return;
|
||||
}
|
||||
|
||||
const path = item.file?.path ?? item.path;
|
||||
|
||||
const pathLength = path.length - (item.file?.name.length ?? 0);
|
||||
const matchElements = matches.matches.map((m) => {
|
||||
return createSpan("suggestion-highlight");
|
||||
});
|
||||
const itemText = this.getItemText(item);
|
||||
for (let i = pathLength; i < itemText.length; i++) {
|
||||
const match = matches.matches.find((m) => m[0] === i);
|
||||
if (match) {
|
||||
const element = matchElements[matches.matches.indexOf(match)];
|
||||
titleEl.appendChild(element);
|
||||
element.appendText(itemText.substring(match[0], match[1]));
|
||||
|
||||
i += match[1] - match[0] - 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
titleEl.appendText(itemText[i]);
|
||||
}
|
||||
noteEl.setText(path);
|
||||
|
||||
if(!item.file) {
|
||||
setIcon(auxEl, "ghost");
|
||||
} else if(this.plugin.isExcalidrawFile(item.file)) {
|
||||
setIcon(auxEl, ICON_NAME);
|
||||
} else if (item.file.extension === "md") {
|
||||
setIcon(auxEl, "square-pen");
|
||||
} else if (IMAGE_TYPES.includes(item.file.extension)) {
|
||||
setIcon(auxEl, "image");
|
||||
} else if (VIDEO_TYPES.includes(item.file.extension)) {
|
||||
setIcon(auxEl, "monitor-play");
|
||||
} else if (AUDIO_TYPES.includes(item.file.extension)) {
|
||||
setIcon(auxEl, "file-audio");
|
||||
} else if (CODE_TYPES.includes(item.file.extension)) {
|
||||
setIcon(auxEl, "file-code");
|
||||
} else if (item.file.extension === "canvas") {
|
||||
setIcon(auxEl, "layout-dashboard");
|
||||
} else if (item.file.extension === "pdf") {
|
||||
setIcon(auxEl, "book-open-text");
|
||||
} else {
|
||||
auxEl.setText(item.file.extension);
|
||||
}
|
||||
}
|
||||
|
||||
onClose(): void {
|
||||
window.setTimeout(()=>{
|
||||
this.addText = null
|
||||
@@ -63,9 +124,19 @@ export class InsertLinkDialog extends FuzzySuggestModal<TFile> {
|
||||
super.onClose();
|
||||
}
|
||||
|
||||
public start(drawingPath: string, addText: Function) {
|
||||
private inLink: string;
|
||||
onOpen(): void {
|
||||
super.onOpen();
|
||||
if(this.inLink) {
|
||||
this.inputEl.value = this.inLink;
|
||||
this.inputEl.dispatchEvent(new Event('input'));
|
||||
}
|
||||
}
|
||||
|
||||
public start(drawingPath: string, addText: Function, link?: string) {
|
||||
this.addText = addText;
|
||||
this.drawingPath = drawingPath;
|
||||
this.inLink = link;
|
||||
this.open();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import ExcalidrawView from "../ExcalidrawView";
|
||||
import ExcalidrawPlugin from "../main";
|
||||
import { getPDFDoc } from "src/utils/FileUtils";
|
||||
import { Modal, Setting, TextComponent } from "obsidian";
|
||||
import { FileSuggestionModal } from "./FolderSuggester";
|
||||
import { FileSuggestionModal } from "../Components/Suggesters/FileSuggestionModal";
|
||||
import { getEA } from "src";
|
||||
import { ExcalidrawAutomate } from "src/ExcalidrawAutomate";
|
||||
import { ExcalidrawImperativeAPI } from "@zsviczian/excalidraw/types/excalidraw/types";
|
||||
@@ -206,7 +206,11 @@ export class InsertPDFModal extends Modal {
|
||||
|
||||
const search = new TextComponent(ce);
|
||||
search.inputEl.style.width = "100%";
|
||||
const suggester = new FileSuggestionModal(this.app, search,app.vault.getFiles().filter((f: TFile) => f.extension.toLowerCase() === "pdf"));
|
||||
const suggester = new FileSuggestionModal(
|
||||
this.app,
|
||||
search,this.app.vault.getFiles().filter((f: TFile) => f.extension.toLowerCase() === "pdf"),
|
||||
this.plugin
|
||||
);
|
||||
search.onChange(async () => {
|
||||
const file = suggester.getSelectedItem();
|
||||
await setFile(file);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@ import { ButtonComponent, DropdownComponent, TFile, ToggleComponent } from "obsi
|
||||
import ExcalidrawView from "../ExcalidrawView";
|
||||
import ExcalidrawPlugin from "../main";
|
||||
import { Modal, Setting, TextComponent } from "obsidian";
|
||||
import { FileSuggestionModal } from "./FolderSuggester";
|
||||
import { FileSuggestionModal } from "../Components/Suggesters/FileSuggestionModal";
|
||||
import { IMAGE_TYPES, sceneCoordsToViewportCoords, viewportCoordsToSceneCoords, MAX_IMAGE_SIZE, ANIMATED_IMAGE_TYPES, MD_EX_SECTIONS } from "src/constants/constants";
|
||||
import { insertEmbeddableToView, insertImageToView } from "src/utils/ExcalidrawViewUtils";
|
||||
import { getEA } from "src";
|
||||
@@ -146,7 +146,9 @@ export class UniversalInsertFileModal extends Modal {
|
||||
const suggester = new FileSuggestionModal(
|
||||
this.app,
|
||||
search,
|
||||
this.app.vault.getFiles().filter((f: TFile) => sections?.length > 0 || f!==this.view.file));
|
||||
this.app.vault.getFiles().filter((f: TFile) => sections?.length > 0 || f!==this.view.file),
|
||||
this.plugin
|
||||
);
|
||||
search.onChange(() => {
|
||||
file = suggester.getSelectedItem();
|
||||
updateForm();
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { FILE } from "dns";
|
||||
import {
|
||||
DEVICE,
|
||||
FRONTMATTER_KEYS,
|
||||
@@ -10,6 +11,8 @@ declare const PLUGIN_VERSION:string;
|
||||
|
||||
// English
|
||||
export default {
|
||||
// Sugester
|
||||
SELECT_FILE_TO_INSERT: "Select a file to insert",
|
||||
// main.ts
|
||||
CONVERT_URL_TO_FILE: "Save image from URL to local file",
|
||||
UNZIP_CURRENT_FILE: "Decompress current Excalidraw file",
|
||||
@@ -907,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 文件",
|
||||
|
||||
436
src/main.ts
436
src/main.ts
@@ -19,7 +19,6 @@ import {
|
||||
Workspace,
|
||||
Editor,
|
||||
MarkdownFileInfo,
|
||||
loadMermaid,
|
||||
} from "obsidian";
|
||||
import {
|
||||
BLANK_DRAWING,
|
||||
@@ -113,7 +112,7 @@ import {
|
||||
legacyExcalidrawPopoverObserver,
|
||||
} from "./MarkdownPostProcessor";
|
||||
|
||||
import { FieldSuggester } from "./dialogs/FieldSuggester";
|
||||
import { FieldSuggester } from "./Components/Suggesters/FieldSuggester";
|
||||
import { ReleaseNotes } from "./dialogs/ReleaseNotes";
|
||||
import { Packages } from "./types/types";
|
||||
import { PreviewImageType } from "./utils/UtilTypes";
|
||||
@@ -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;
|
||||
@@ -291,7 +291,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});
|
||||
@@ -386,23 +386,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 +412,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 +3647,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;
|
||||
|
||||
7
src/types/types.d.ts
vendored
7
src/types/types.d.ts
vendored
@@ -1,3 +1,4 @@
|
||||
import { TFile } from "obsidian";
|
||||
import { ExcalidrawAutomate } from "../ExcalidrawAutomate";
|
||||
import { ExcalidrawLib } from "../ExcalidrawLib";
|
||||
|
||||
@@ -33,6 +34,12 @@ export type DeviceType = {
|
||||
|
||||
export type Point = [number, number];
|
||||
|
||||
export type LinkSuggestion = {
|
||||
file: TFile;
|
||||
path: string;
|
||||
alias?: string;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
ExcalidrawAutomate: ExcalidrawAutomate;
|
||||
|
||||
@@ -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.setEmbeddableIsEditingSelf();
|
||||
}
|
||||
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.clearEmbeddableIsEditingSelf();
|
||||
}
|
||||
node.child.showPreview();
|
||||
node.isEditing = false;
|
||||
this.observer?.disconnect();
|
||||
} catch (error) {
|
||||
console.error('Error stopping edit:', error);
|
||||
}
|
||||
node.child.showPreview();
|
||||
}
|
||||
|
||||
removeNode(node: ObsidianCanvasNode) {
|
||||
|
||||
@@ -13,6 +13,7 @@ export const setDynamicStyle = (
|
||||
view: ExcalidrawView, //the excalidraw view
|
||||
color: string,
|
||||
dynamicStyle: DynamicStyle,
|
||||
textBackgroundColor?: string,
|
||||
) => {
|
||||
if(dynamicStyle === "none") {
|
||||
view.excalidrawContainer?.removeAttribute("style");
|
||||
@@ -116,7 +117,9 @@ export const setDynamicStyle = (
|
||||
[`--h3-color`]: str(text),
|
||||
[`--h4-color`]: str(text),
|
||||
[`color`]: str(text),
|
||||
['--excalidraw-caret-color']: str(isLightTheme ? text : cmBG()),
|
||||
['--excalidraw-caret-color']: textBackgroundColor
|
||||
? str(isLightTheme ? invertColor(textBackgroundColor) : ea.getCM(textBackgroundColor))
|
||||
: str(isLightTheme ? text : cmBG()),
|
||||
[`--select-highlight-color`]: str(gray1()),
|
||||
[`--color-gray-90`]: str(isDark?text.darkerBy(5):text.lighterBy(5)), //search background
|
||||
[`--color-gray-80`]: str(isDark?text.darkerBy(10):text.lighterBy(10)), //frame
|
||||
|
||||
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