mirror of
https://github.com/zsviczian/obsidian-excalidraw-plugin.git
synced 2025-08-06 05:46:28 +00:00
Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dfbd385de7 | ||
|
|
94fbac38bf | ||
|
|
73cf8e75d3 | ||
|
|
395cbc104c | ||
|
|
9a24db8379 | ||
|
|
934a5f4838 | ||
|
|
9022500087 | ||
|
|
795807b6cf | ||
|
|
9eff79733c | ||
|
|
c4e95d9207 | ||
|
|
32cd3a62b6 | ||
|
|
d05ccc0055 | ||
|
|
ff1d7b44b4 | ||
|
|
2b86ba2128 | ||
|
|
44a3b30e3b | ||
|
|
bb9389c7dd | ||
|
|
ba4bfe9de7 | ||
|
|
3a73b14ebb | ||
|
|
31f54db433 | ||
|
|
26812dd297 | ||
|
|
ae4f4b4f08 | ||
|
|
4ac0a4c565 | ||
|
|
69c9f824a0 | ||
|
|
6a2220c960 | ||
|
|
aa501c2843 | ||
|
|
efce44f0a7 | ||
|
|
491eb83d35 | ||
|
|
35cf0802d1 | ||
|
|
f768548f60 | ||
|
|
37789f9907 | ||
|
|
d5cf4ace21 |
12
.github/ISSUE_TEMPLATE/bug_report.md
vendored
12
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,12 +1,22 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help me improve Excalidraw
|
||||
about: When something is clearly broken. Everything else is a feature request.
|
||||
title: 'BUG: '
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
Help me help you. I am a one man show doing this plugin as a part time hobby. There is no point in flooding me with issues, if there are too many, and they are poorly documented, I will just ignore them. Sorry...
|
||||
|
||||
Before creating a bug report, please
|
||||
1. review recent release notes - maybe there is already an answer,
|
||||
2. search issues (including closed ones) to see if there is anything similar.
|
||||
|
||||
⚠️ I will have to close all recorded bugs that do not provide this background information. Sorry, I need to control my workload/time. ⚠️
|
||||
|
||||
--------
|
||||
|
||||
**Your environment**
|
||||
Please run `Command Palette/Show Debug info` in Obsidian and paste the result here.
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
The Obsidian-Excalidraw plugin integrates [Excalidraw](https://excalidraw.com/), a feature rich sketching tool, into Obsidian. You can store and edit Excalidraw files in your vault, you can embed drawings into your documents, and you can link to documents and other drawings to/and from Excalidraw. For a showcase of Excalidraw features, please read my blog post [here](https://www.zsolt.blog/2021/03/showcasing-excalidraw.html) and/or watch the videos below.
|
||||
|
||||
## Video Walkthrough
|
||||
|
||||
<a href="https://youtu.be/P_Q6avJGoWI" target="_blank"><img src="https://github.com/zsviczian/obsidian-excalidraw-plugin/assets/14358394/da34bb33-7610-45e6-b36f-cb7a02a9141b" width="300"/></a>
|
||||
<a href="https://youtu.be/o0exK-xFP3k" target="_blank"><img src="https://user-images.githubusercontent.com/14358394/156931370-aa4d88de-c4a8-46cc-aeb2-dc09aa0bea39.jpg" width="300"/></a>
|
||||
<a href="https://youtu.be/QKnQgSjJVuc" target="_blank"><img src="https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/thumbnail-getting-started.jpg" width="300"/></a>
|
||||
|
||||
|
||||
1
docs/API/ExcalidrawAutomate.d.ts
vendored
1
docs/API/ExcalidrawAutomate.d.ts
vendored
@@ -422,6 +422,7 @@ export declare class ExcalidrawAutomate {
|
||||
appState?: AppState;
|
||||
files?: BinaryFileData;
|
||||
commitToHistory?: boolean;
|
||||
storeAction?: "capture" | "none" | "update";
|
||||
}, restore?: boolean): void;
|
||||
/**
|
||||
* connect an object to the selected element in the view
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# [◀ Excalidraw Automate How To](../readme.md)
|
||||
## Generating a simple mindmap from a text outline
|
||||
This is a slightly more elaborate example. This will generate an a mindmap from a tabulated outline.
|
||||
This is a slightly more elaborate example. This will generate a mindmap from a tabulated outline.
|
||||
|
||||
### Output
|
||||

|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
|
||||

|
||||
|
||||
This script allows users to streamline their Obsidian-Excalidraw workflows by enabling the selection of elements based on similar properties. Users can precisely define which attributes such as stroke color, fill style, font family, and more, should match for selection. It's perfect for large canvases where manual selection would be cumbersome. Users can either run the script to find and select matching elements across the entire scene, or define a specific group of elements to apply the selection criteria within a defined timeframe. This script enhances control and efficiency in your Excalidraw experience.
|
||||
This script enables the selection of elements based on matching properties. Select the attributes (such as stroke color, fill style, font family, etc) that should match for selection. It's perfect for large scenes where manual selection of elements would be cumbersome. You can either run the script to select matching elements across the entire scene, or define a specific group of elements to apply the selection criteria to.
|
||||
|
||||
```js */
|
||||
|
||||
let config = window.ExcalidrawSelectConfig;
|
||||
config = config && (Date.now() - config.timestamp < 60000) ? config : null;
|
||||
config = Boolean(config) && (Date.now() - config.timestamp < 60000) ? config : null;
|
||||
|
||||
let elements = ea.getViewSelectedElements();
|
||||
if(!config && (elements.length !==1)) {
|
||||
@@ -27,7 +27,7 @@ const fragWithHTML = (html) => createFragment((frag) => (frag.createDiv().innerH
|
||||
// RUN
|
||||
//--------------------------
|
||||
const run = () => {
|
||||
selectedElements = ea.getViewElements().filter(el=>
|
||||
selectedElements = elements.filter(el=>
|
||||
((typeof config.angle === "undefined") || (el.angle === config.angle)) &&
|
||||
((typeof config.backgroundColor === "undefined") || (el.backgroundColor === config.backgroundColor)) &&
|
||||
((typeof config.fillStyle === "undefined") || (el.fillStyle === config.fillStyle)) &&
|
||||
@@ -61,7 +61,7 @@ const showInstructions = () => {
|
||||
instructionsModal.contentEl.createEl("p", {text: "Step 2: Select an action:"});
|
||||
instructionsModal.contentEl.createEl("ul", {}, el => {
|
||||
el.createEl("li", {text: "Click 'RUN' to find matching elements throughout the entire scene."});
|
||||
el.createEl("li", {text: "Click 'SELECT' to first choose a specific group of elements. Then run the 'Select Similar Elements' script once more on that group within 1 minute."});
|
||||
el.createEl("li", {text: "Click 'SELECT' to 1) first choose a specific group of elements in the scene, then 2) run the 'Select Similar Elements' once more within 1 minute to apply the filter criteria only to that group of elements."});
|
||||
});
|
||||
instructionsModal.contentEl.createEl("p", {text: "Note: If you choose 'SELECT', make sure to click the 'Select Similar Elements' script again within 1 minute to apply your selection criteria to the group of elements you chose."});
|
||||
};
|
||||
|
||||
@@ -21,7 +21,7 @@ The script will convert your drawing into a slideshow presentation.
|
||||
|
||||
```javascript
|
||||
*/
|
||||
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.9.23")) {
|
||||
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("2.1.7")) {
|
||||
new Notice("This script requires a newer version of Excalidraw. Please install the latest version.");
|
||||
return;
|
||||
}
|
||||
@@ -190,7 +190,7 @@ let preventFullscreenExit = true;
|
||||
const gotoFullscreen = async () => {
|
||||
if(isFullscreen) return;
|
||||
preventFullscreenExit = true;
|
||||
if(app.isMobile) {
|
||||
if(ea.DEVICE.isMobile) {
|
||||
ea.viewToggleFullScreen();
|
||||
} else {
|
||||
await contentEl.webkitRequestFullscreen();
|
||||
@@ -206,8 +206,8 @@ const gotoFullscreen = async () => {
|
||||
const exitFullscreen = async () => {
|
||||
if(!isFullscreen) return;
|
||||
preventFullscreenExit = true;
|
||||
if(!app.isMobile && ownerDocument?.fullscreenElement) await ownerDocument.exitFullscreen();
|
||||
if(app.isMobile) ea.viewToggleFullScreen();
|
||||
if(!ea.DEVICE.isMobile && ownerDocument?.fullscreenElement) await ownerDocument.exitFullscreen();
|
||||
if(ea.DEVICE.isMobile) ea.viewToggleFullScreen();
|
||||
if(toggleFullscreenButton) toggleFullscreenButton.innerHTML = SVG_MAXIMIZE;
|
||||
await waitForExcalidrawResize();
|
||||
resetControlPanelElPosition();
|
||||
@@ -649,7 +649,7 @@ const initializeEventListners = () => {
|
||||
controlPanelEl.removeEventListener('mouseenter', onMouseEnter, false);
|
||||
controlPanelEl.removeEventListener('mouseleave', onMouseLeave, false);
|
||||
controlPanelEl.parentElement?.removeChild(controlPanelEl);
|
||||
if(!app.isMobile) {
|
||||
if(!ea.DEVICE.isMobile) {
|
||||
contentEl.removeEventListener('webkitfullscreenchange', fullscreenListener);
|
||||
contentEl.removeEventListener('fullscreenchange', fullscreenListener);
|
||||
}
|
||||
@@ -664,7 +664,7 @@ const initializeEventListners = () => {
|
||||
return true;
|
||||
};
|
||||
|
||||
if(!app.isMobile) {
|
||||
if(!ea.DEVICE.isMobile) {
|
||||
contentEl.addEventListener('webkitfullscreenchange', fullscreenListener);
|
||||
contentEl.addEventListener('fullscreenchange', fullscreenListener);
|
||||
}
|
||||
@@ -727,7 +727,7 @@ const exitPresentation = async (openForEdit = false) => {
|
||||
//Resets pointer offsets. Ugly solution.
|
||||
//During testing offsets were wrong after presentation, but don't know why.
|
||||
//This should solve it even if they are wrong.
|
||||
hostView.refresh();
|
||||
hostView.refreshCanvasOffset();
|
||||
excalidrawAPI.setActiveTool({type: "selection"});
|
||||
})
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -508,7 +508,7 @@ https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea
|
||||
```excalidraw-script-install
|
||||
https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/Select%20Similar%20Elements.md
|
||||
```
|
||||
<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/Select%20Similar%20Elements.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script allows you to streamline your Obsidian-Excalidraw workflows by enabling the selection of elements based on similar properties. you can precisely define which attributes such as stroke color, fill style, font family, and more, should match for selection. It's perfect for large canvases where manual selection would be cumbersome. You can either run the script to find and select matching elements across the entire scene, or define a specific group of elements to apply the selection criteria within a defined timeframe. This script enhances control and efficiency in your Excalidraw experience.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-select-similar-elements.png'></td></tr></table>
|
||||
<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/Select%20Similar%20Elements.md'>File on GitHub</a></td></tr><tr valign='top'><td class="label">Description</td><td class="data">This script allows you to streamline your Obsidian-Excalidraw workflows by enabling the selection of elements based on similar properties. you can precisely define which attributes such as stroke color, fill style, font family, and more, should match for selection. It's perfect for large canvases where manual selection would be cumbersome. You can either run the script to find and select matching elements across the entire scene, or define a specific group of elements to apply the selection criteria within a defined timeframe.<br><img src='https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/images/scripts-select-similar-elements.png'></td></tr></table>
|
||||
|
||||
## Set background color of unclosed line object by adding a shadow clone
|
||||
```excalidraw-script-install
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "obsidian-excalidraw-plugin",
|
||||
"name": "Excalidraw",
|
||||
"version": "2.0.1-beta-2",
|
||||
"version": "2.1.8.2-beta-1",
|
||||
"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.1.4",
|
||||
"version": "2.2.5",
|
||||
"minAppVersion": "1.1.6",
|
||||
"description": "An Obsidian plugin to edit and view Excalidraw drawings",
|
||||
"author": "Zsolt Viczian",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "obsidian-excalidraw-plugin",
|
||||
"version": "2.0.14",
|
||||
"version": "2.2.5",
|
||||
"description": "This is an Obsidian.md plugin that lets you view and edit Excalidraw drawings",
|
||||
"main": "lib/index.js",
|
||||
"types": "lib/index.d.ts",
|
||||
@@ -19,7 +19,7 @@
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@zsviczian/excalidraw": "0.17.1-obsidian-20",
|
||||
"@zsviczian/excalidraw": "0.17.1-obsidian-27",
|
||||
"chroma-js": "^2.4.2",
|
||||
"clsx": "^2.0.0",
|
||||
"colormaster": "^1.2.1",
|
||||
@@ -35,6 +35,7 @@
|
||||
"js-yaml": "^4.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"dotenv": "^16.4.5",
|
||||
"@babel/core": "^7.22.9",
|
||||
"@babel/preset-env": "^7.22.10",
|
||||
"@babel/preset-react": "^7.22.5",
|
||||
@@ -57,7 +58,7 @@
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"eslint-plugin-prettier": "^5.0.0",
|
||||
"lz-string": "^1.5.0",
|
||||
"obsidian": "^1.4.0",
|
||||
"obsidian": "1.5.7-1",
|
||||
"prettier": "^3.0.1",
|
||||
"rollup": "^2.70.1",
|
||||
"rollup-plugin-copy": "^3.5.0",
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
import { nodeResolve } from '@rollup/plugin-node-resolve';
|
||||
import commonjs from '@rollup/plugin-commonjs';
|
||||
import { env } from "process";
|
||||
import babel from '@rollup/plugin-babel';
|
||||
import replace from "@rollup/plugin-replace";
|
||||
import { terser } from "rollup-plugin-terser";
|
||||
import copy from "rollup-plugin-copy";
|
||||
import typescript2 from "rollup-plugin-typescript2";
|
||||
import webWorker from "rollup-plugin-web-worker-loader";
|
||||
import fs from'fs';
|
||||
import fs from 'fs';
|
||||
import LZString from 'lz-string';
|
||||
import postprocess from 'rollup-plugin-postprocess';
|
||||
import cssnano from 'cssnano';
|
||||
|
||||
// Load environment variables
|
||||
import dotenv from 'dotenv';
|
||||
dotenv.config();
|
||||
|
||||
const DIST_FOLDER = 'dist';
|
||||
const isProd = (process.env.NODE_ENV === "production")
|
||||
const isProd = (process.env.NODE_ENV === "production");
|
||||
const isLib = (process.env.NODE_ENV === "lib");
|
||||
console.log(`Running: ${process.env.NODE_ENV}`);
|
||||
|
||||
@@ -28,13 +31,13 @@ const reactdom_pkg = isLib ? "" : isProd
|
||||
: fs.readFileSync("./node_modules/react-dom/umd/react-dom.development.js", "utf8");
|
||||
|
||||
const lzstring_pkg = isLib ? "" : fs.readFileSync("./node_modules/lz-string/libs/lz-string.min.js", "utf8");
|
||||
if(!isLib) {
|
||||
if (!isLib) {
|
||||
const excalidraw_styles = isProd
|
||||
? fs.readFileSync("./node_modules/@zsviczian/excalidraw/dist/styles.production.css", "utf8")
|
||||
: fs.readFileSync("./node_modules/@zsviczian/excalidraw/dist/styles.development.css", "utf8");
|
||||
const plugin_styles = fs.readFileSync("./styles.css", "utf8")
|
||||
const plugin_styles = fs.readFileSync("./styles.css", "utf8");
|
||||
const styles = plugin_styles + excalidraw_styles;
|
||||
cssnano()
|
||||
cssnano()
|
||||
.process(styles) // Process the CSS
|
||||
.then(result => {
|
||||
fs.writeFileSync(`./${DIST_FOLDER}/styles.css`, result.css);
|
||||
@@ -46,16 +49,16 @@ if(!isLib) {
|
||||
|
||||
const manifestStr = isLib ? "" : fs.readFileSync("manifest.json", "utf-8");
|
||||
const manifest = isLib ? {} : JSON.parse(manifestStr);
|
||||
!isLib && console.log(manifest.version);
|
||||
if (!isLib) console.log(manifest.version);
|
||||
|
||||
const packageString = isLib
|
||||
? ""
|
||||
const packageString = isLib
|
||||
? ""
|
||||
: ';' + lzstring_pkg +
|
||||
'\nconst EXCALIDRAW_PACKAGES = "' + LZString.compressToBase64(react_pkg + reactdom_pkg + excalidraw_pkg) + '";\n' +
|
||||
'const {react, reactDOM, excalidrawLib} = window.eval.call(window, `(function() {' +
|
||||
'${LZString.decompressFromBase64(EXCALIDRAW_PACKAGES)};' +
|
||||
'return {react:React, reactDOM:ReactDOM, excalidrawLib: ExcalidrawLib};})();`);\n' +
|
||||
'const PLUGIN_VERSION="'+manifest.version+'";';
|
||||
'return {react: React, reactDOM: ReactDOM, excalidrawLib: ExcalidrawLib};})();`);\n' +
|
||||
'const PLUGIN_VERSION="' + manifest.version + '";';
|
||||
|
||||
const BASE_CONFIG = {
|
||||
input: 'src/main.ts',
|
||||
@@ -76,33 +79,30 @@ const BASE_CONFIG = {
|
||||
'react',
|
||||
'react-dom'
|
||||
],
|
||||
}
|
||||
};
|
||||
|
||||
const getRollupPlugins = (tsconfig, ...plugins) =>
|
||||
[
|
||||
typescript2(tsconfig),
|
||||
nodeResolve({ browser: true }),
|
||||
commonjs(),
|
||||
webWorker({ inline: true, forceInline: true, targetPlatform: "browser" }),
|
||||
].concat(plugins);
|
||||
const getRollupPlugins = (tsconfig, ...plugins) => [
|
||||
typescript2(tsconfig),
|
||||
nodeResolve({ browser: true }),
|
||||
commonjs(),
|
||||
webWorker({ inline: true, forceInline: true, targetPlatform: "browser" }),
|
||||
].concat(plugins);
|
||||
|
||||
const BUILD_CONFIG = {
|
||||
...BASE_CONFIG,
|
||||
output: {
|
||||
dir: DIST_FOLDER,
|
||||
entryFileNames: 'main.js',
|
||||
//sourcemap: isProd?false:'inline',
|
||||
format: 'cjs',
|
||||
exports: 'default',
|
||||
},
|
||||
plugins: [
|
||||
typescript2({
|
||||
tsconfig: isProd ? "tsconfig.json" : "tsconfig.dev.json",
|
||||
//inlineSources: !isProd
|
||||
}),
|
||||
replace({
|
||||
preventAssignment: true,
|
||||
"process.env.NODE_ENV": JSON.stringify(env.NODE_ENV),
|
||||
"process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV),
|
||||
}),
|
||||
babel({
|
||||
presets: [['@babel/preset-env', {
|
||||
@@ -110,30 +110,27 @@ const BUILD_CONFIG = {
|
||||
esmodules: true,
|
||||
},
|
||||
}]],
|
||||
exclude: "node_modules/**"
|
||||
exclude: "node_modules/**",
|
||||
}),
|
||||
commonjs(),
|
||||
nodeResolve({ browser: true, preferBuiltins: false }),
|
||||
...isProd
|
||||
? [
|
||||
...(isProd ? [
|
||||
terser({
|
||||
toplevel: false,
|
||||
compress: {passes: 2}
|
||||
compress: { passes: 2 },
|
||||
}),
|
||||
//!postprocess - the version available on npmjs does not work, need this update:
|
||||
// npm install brettz9/rollup-plugin-postprocess#update --save-dev
|
||||
// https://github.com/developit/rollup-plugin-postprocess/issues/10
|
||||
postprocess([
|
||||
//[/,React=require\("react"\);/, packageString],
|
||||
[/React=require\("react"\),state=require\("@codemirror\/state"\),view=require\("@codemirror\/view"\)/,
|
||||
`state=require("@codemirror/state"),view=require("@codemirror/view")` + packageString]
|
||||
])
|
||||
]
|
||||
: [
|
||||
`state=require("@codemirror/state"),view=require("@codemirror/view")` + packageString],
|
||||
]),
|
||||
] : [
|
||||
postprocess([
|
||||
[/var React = require\('react'\);/, packageString],
|
||||
])
|
||||
],
|
||||
]),
|
||||
]),
|
||||
copy({
|
||||
targets: [
|
||||
{ src: 'manifest.json', dest: DIST_FOLDER },
|
||||
@@ -141,7 +138,7 @@ const BUILD_CONFIG = {
|
||||
verbose: true, // Optional: To display copied files in the console
|
||||
}),
|
||||
],
|
||||
}
|
||||
};
|
||||
|
||||
const LIB_CONFIG = {
|
||||
...BASE_CONFIG,
|
||||
@@ -153,16 +150,16 @@ const LIB_CONFIG = {
|
||||
name: "Excalidraw (Library)",
|
||||
},
|
||||
plugins: getRollupPlugins(
|
||||
{ tsconfig: "tsconfig-lib.json"},
|
||||
copy({ targets: [{ src: "src/*.d.ts", dest: "lib/typings" }] })
|
||||
),
|
||||
}
|
||||
{ tsconfig: "tsconfig-lib.json" },
|
||||
copy({ targets: [{ src: "src/*.d.ts", dest: "lib/typings" }] })
|
||||
),
|
||||
};
|
||||
|
||||
let config = [];
|
||||
if(process.env.NODE_ENV === "lib") {
|
||||
if (process.env.NODE_ENV === "lib") {
|
||||
config.push(LIB_CONFIG);
|
||||
} else {
|
||||
config.push(BUILD_CONFIG);
|
||||
}
|
||||
|
||||
export default config;
|
||||
export default config;
|
||||
|
||||
@@ -10,9 +10,10 @@ const o0 = Decoration.line({ attributes: {class: "ex-opacity-0"} });
|
||||
export const HideTextBetweenCommentsExtension = ViewPlugin.fromClass(
|
||||
class {
|
||||
view: EditorView;
|
||||
decorations: DecorationSet;
|
||||
decorations: DecorationSet;
|
||||
reExcalidrawData = /^%%(?:\r\n|\r|\n)# Excalidraw Data$/gm;
|
||||
reTextElements = /^%%(?:\r\n|\r|\n)# Text Elements$/gm;
|
||||
reDrawing = /^%%(?:\r\n|\r|\n)# Drawing$/gm;
|
||||
reDrawing = /^%%(?:\r\n|\r|\n)##? Drawing$/gm;
|
||||
linecount = 0;
|
||||
isExcalidraw = false;
|
||||
|
||||
@@ -32,11 +33,15 @@ export const HideTextBetweenCommentsExtension = ViewPlugin.fromClass(
|
||||
|
||||
const text = doc.toString();
|
||||
|
||||
let start = text.search(this.reTextElements);
|
||||
let start = text.search(this.reExcalidrawData);
|
||||
if(start == -1) {
|
||||
start = text.search(this.reTextElements);
|
||||
}
|
||||
if(start == -1) {
|
||||
start = text.search(this.reDrawing);
|
||||
if(start == -1) return Decoration.none;
|
||||
}
|
||||
if(start == -1) return Decoration.none;
|
||||
|
||||
const startLine = doc.lineAt(start).number;
|
||||
const endLine = doc.lines;
|
||||
let builder = new RangeSetBuilder<Decoration>()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//https://stackoverflow.com/questions/2068344/how-do-i-get-a-youtube-video-thumbnail-from-the-youtube-api
|
||||
//https://img.youtube.com/vi/uZz5MgzWXiM/maxresdefault.jpg
|
||||
|
||||
import { ExcalidrawElement, ExcalidrawImageElement, FileId } from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
import { ExcalidrawElement, FileId } from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
import { BinaryFileData, DataURL } from "@zsviczian/excalidraw/types/excalidraw/types";
|
||||
import { App, MarkdownRenderer, Notice, TFile } from "obsidian";
|
||||
import {
|
||||
@@ -23,7 +23,7 @@ import { ExportSettings } from "./ExcalidrawView";
|
||||
import { t } from "./lang/helpers";
|
||||
import { tex2dataURL } from "./LaTeX";
|
||||
import ExcalidrawPlugin from "./main";
|
||||
import { blobToBase64, getDataURLFromURL, getMimeType, getPDFDoc, getURLImageExtension, readLocalFileBinary } from "./utils/FileUtils";
|
||||
import { blobToBase64, getDataURLFromURL, getMimeType, getPDFDoc, getURLImageExtension, hasExcalidrawEmbeddedImagesTreeChanged, readLocalFileBinary } from "./utils/FileUtils";
|
||||
import {
|
||||
errorlog,
|
||||
getDataURL,
|
||||
@@ -38,10 +38,13 @@ import {
|
||||
LinkParts,
|
||||
svgToBase64,
|
||||
isMaskFile,
|
||||
embedFontsInSVG,
|
||||
} from "./utils/Utils";
|
||||
import { ValueOf } from "./types";
|
||||
import { getMermaidImageElements, getMermaidText, shouldRenderMermaid } from "./utils/MermaidUtils";
|
||||
import { mermaidToExcalidraw } from "src/constants/constants";
|
||||
import { ImageKey, imageCache } from "./utils/ImageCache";
|
||||
import { PreviewImageType } from "./utils/UtilTypes";
|
||||
|
||||
//An ugly workaround for the following situation.
|
||||
//File A is a markdown file that has an embedded Excalidraw file B
|
||||
@@ -164,7 +167,7 @@ export class EmbeddedFile {
|
||||
constructor(plugin: ExcalidrawPlugin, hostPath: string, imgPath: string, colorMapJSON?: string) {
|
||||
this.plugin = plugin;
|
||||
this.resetImage(hostPath, imgPath);
|
||||
if(this.file && (this.plugin.ea.isExcalidrawFile(this.file) || this.file.extension.toLowerCase() === "svg")) {
|
||||
if(this.file && (this.plugin.isExcalidrawFile(this.file) || this.file.extension.toLowerCase() === "svg")) {
|
||||
try {
|
||||
this.colorMap = colorMapJSON ? JSON.parse(colorMapJSON.toLocaleLowerCase()) : null;
|
||||
} catch (error) {
|
||||
@@ -229,7 +232,7 @@ export class EmbeddedFile {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return this.mtime != this.file.stat.mtime;
|
||||
return this.mtime !== this.file.stat.mtime;
|
||||
}
|
||||
|
||||
public setImage(
|
||||
@@ -358,22 +361,49 @@ export class EmbeddedFilesLoader {
|
||||
withTheme: !!forceTheme,
|
||||
isMask,
|
||||
};
|
||||
const svg = replaceSVGColors(
|
||||
await createSVG(
|
||||
file?.path,
|
||||
true,
|
||||
exportSettings,
|
||||
this,
|
||||
forceTheme,
|
||||
null,
|
||||
null,
|
||||
elements,
|
||||
this.plugin,
|
||||
depth+1,
|
||||
getExportPadding(this.plugin, file),
|
||||
),
|
||||
inFile instanceof EmbeddedFile ? inFile.colorMap : null
|
||||
) as SVGSVGElement;
|
||||
|
||||
const hasColorMap = Boolean(inFile instanceof EmbeddedFile ? inFile.colorMap : null);
|
||||
const shouldUseCache = !hasColorMap && this.plugin.settings.allowImageCacheInScene && file && imageCache.isReady();
|
||||
const cacheKey:ImageKey = {
|
||||
filepath: file.path,
|
||||
blockref: null,
|
||||
sectionref: null,
|
||||
isDark,
|
||||
previewImageType: PreviewImageType.SVG,
|
||||
scale: 1,
|
||||
isTransparent: !exportSettings.withBackground,
|
||||
hasBlockref: false,
|
||||
hasGroupref: false,
|
||||
hasTaskbone: false,
|
||||
hasArearef: false,
|
||||
hasFrameref: false,
|
||||
hasSectionref: false,
|
||||
linkpartReference: null,
|
||||
linkpartAlias: null,
|
||||
}
|
||||
|
||||
const maybeSVG = shouldUseCache
|
||||
? await imageCache.getImageFromCache(cacheKey)
|
||||
: undefined;
|
||||
|
||||
const svg = (maybeSVG && (maybeSVG instanceof SVGSVGElement))
|
||||
? maybeSVG
|
||||
: replaceSVGColors(
|
||||
await createSVG(
|
||||
file?.path,
|
||||
false, //false
|
||||
exportSettings,
|
||||
this,
|
||||
forceTheme,
|
||||
null,
|
||||
null,
|
||||
elements,
|
||||
this.plugin,
|
||||
depth+1,
|
||||
getExportPadding(this.plugin, file),
|
||||
),
|
||||
inFile instanceof EmbeddedFile ? inFile.colorMap : null
|
||||
) as SVGSVGElement;
|
||||
|
||||
//https://stackoverflow.com/questions/51154171/remove-css-filter-on-child-elements
|
||||
const imageList = svg.querySelectorAll(
|
||||
@@ -382,7 +412,8 @@ export class EmbeddedFilesLoader {
|
||||
if (imageList.length > 0) {
|
||||
hasSVGwithBitmap = true;
|
||||
}
|
||||
if (hasSVGwithBitmap && isDark) {
|
||||
|
||||
if (hasSVGwithBitmap && isDark && !Boolean(maybeSVG)) {
|
||||
imageList.forEach((i) => {
|
||||
const id = i.parentElement?.id;
|
||||
svg.querySelectorAll(`use[href='#${id}']`).forEach((u) => {
|
||||
@@ -393,7 +424,21 @@ export class EmbeddedFilesLoader {
|
||||
if (!hasSVGwithBitmap && svg.getAttribute("hasbitmap")) {
|
||||
hasSVGwithBitmap = true;
|
||||
}
|
||||
const dURL = svgToBase64(svg.outerHTML) as DataURL;
|
||||
if(shouldUseCache && !Boolean(maybeSVG)) {
|
||||
//cache SVG should have the width and height parameters and not the embedded font
|
||||
//see svgWithFont below
|
||||
imageCache.addImageToCache(cacheKey,"", svg);
|
||||
}
|
||||
const svgWithFont = embedFontsInSVG(svg, this.plugin);
|
||||
if(!svgWithFont.hasAttribute("width") && svgWithFont.hasAttribute("viewBox")){
|
||||
//2024.06.09
|
||||
//this addresses backward compatibility issues where the cache does not have the width and height attributes
|
||||
//this should be removed in the future
|
||||
const vb = svgWithFont.getAttr("viewBox").split(" ");
|
||||
Boolean(vb[2]) && svgWithFont.setAttribute("width", vb[2]);
|
||||
Boolean(vb[3]) && svgWithFont.setAttribute("height", vb[3]);
|
||||
}
|
||||
const dURL = svgToBase64(svgWithFont.outerHTML) as DataURL;
|
||||
return {dataURL: dURL as DataURL, hasSVGwithBitmap};
|
||||
};
|
||||
|
||||
@@ -526,7 +571,8 @@ export class EmbeddedFilesLoader {
|
||||
public async loadSceneFiles(
|
||||
excalidrawData: ExcalidrawData,
|
||||
addFiles: (files: FileData[], isDark: boolean, final?: boolean) => void,
|
||||
depth:number
|
||||
depth:number,
|
||||
isThemeChange:boolean = false,
|
||||
) {
|
||||
|
||||
if(depth > 7) {
|
||||
@@ -563,7 +609,8 @@ export class EmbeddedFilesLoader {
|
||||
}
|
||||
//files.push(fileData);
|
||||
}
|
||||
} /*else if (embeddedFile.isSVGwithBitmap) {
|
||||
} else if (embeddedFile.isSVGwithBitmap && (depth !== 0 || isThemeChange)) {
|
||||
//this will reload the image in light/dark mode when switching themes
|
||||
const fileData = {
|
||||
mimeType: embeddedFile.mimeType,
|
||||
id: entry.value[0],
|
||||
@@ -580,7 +627,7 @@ export class EmbeddedFilesLoader {
|
||||
catch(e) {
|
||||
errorlog({ where: "EmbeddedFileLoader.loadSceneFiles", error: e });
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
let equation;
|
||||
@@ -785,7 +832,7 @@ export class EmbeddedFilesLoader {
|
||||
? fileCache.frontmatter[FRONTMATTER_KEYS["md-css"].name] ?? ""
|
||||
: "";
|
||||
let frontmatterCSSisAfile = false;
|
||||
if (style && style != "") {
|
||||
if (style && style !== "") {
|
||||
const f = plugin.app.metadataCache.getFirstLinkpathDest(style, file.path);
|
||||
if (f) {
|
||||
style = await plugin.app.vault.read(f);
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
StrokeRoundness,
|
||||
RoundnessType,
|
||||
ExcalidrawFrameElement,
|
||||
ExcalidrawTextContainer,
|
||||
} from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
import { Editor, normalizePath, Notice, OpenViewState, RequestUrlResponse, TFile, TFolder, WorkspaceLeaf } from "obsidian";
|
||||
import * as obsidian_module from "obsidian";
|
||||
@@ -35,12 +36,10 @@ import {
|
||||
REG_LINKINDEX_INVALIDCHARS,
|
||||
THEME_FILTER,
|
||||
mermaidToExcalidraw,
|
||||
MD_TEXTELEMENTS,
|
||||
MD_DRAWING,
|
||||
refreshTextDimensions,
|
||||
} from "src/constants/constants";
|
||||
import { blobToBase64, checkAndCreateFolder, getDrawingFilename, getListOfTemplateFiles, getNewUniqueFilepath, } from "src/utils/FileUtils";
|
||||
import { blobToBase64, checkAndCreateFolder, getDrawingFilename, getExcalidrawEmbeddedFilesFiletree, getListOfTemplateFiles, getNewUniqueFilepath, hasExcalidrawEmbeddedImagesTreeChanged, } from "src/utils/FileUtils";
|
||||
import {
|
||||
arrayToMap,
|
||||
//debug,
|
||||
embedFontsInSVG,
|
||||
errorlog,
|
||||
@@ -51,15 +50,15 @@ import {
|
||||
getSVG,
|
||||
isMaskFile,
|
||||
isVersionNewerThanOther,
|
||||
log,
|
||||
scaleLoadedImage,
|
||||
wrapTextAtCharLength,
|
||||
arrayToMap,
|
||||
} from "src/utils/Utils";
|
||||
import { getAttachmentsFolderAndFilePath, getLeaf, getNewOrAdjacentLeaf, isObsidianThemeDark, mergeMarkdownFiles, openLeaf } from "src/utils/ObsidianUtils";
|
||||
import { AppState, BinaryFileData, DataURL, ExcalidrawImperativeAPI, Point } from "@zsviczian/excalidraw/types/excalidraw/types";
|
||||
import { EmbeddedFile, EmbeddedFilesLoader, FileData } from "src/EmbeddedFileLoader";
|
||||
import { tex2dataURL } from "src/LaTeX";
|
||||
import { GenericInputPrompt, NewFileActions, Prompt } from "src/dialogs/Prompt";
|
||||
import { GenericInputPrompt, NewFileActions } from "src/dialogs/Prompt";
|
||||
import { t } from "src/lang/helpers";
|
||||
import { ScriptEngine } from "src/Scripts";
|
||||
import { ConnectionPoint, DeviceType } from "src/types";
|
||||
@@ -81,7 +80,7 @@ import { TInput } from "colormaster/types";
|
||||
import {ConversionResult, svgToExcalidraw} from "src/svgToExcalidraw/parser"
|
||||
import { ROUNDNESS } from "src/constants/constants";
|
||||
import { ClipboardData } from "@zsviczian/excalidraw/types/excalidraw/clipboard";
|
||||
import { emulateKeysForLinkClick, KeyEvent, PaneTarget } from "src/utils/ModifierkeyHelper";
|
||||
import { emulateKeysForLinkClick, PaneTarget } from "src/utils/ModifierkeyHelper";
|
||||
import { Mutable } from "@zsviczian/excalidraw/types/excalidraw/utility-types";
|
||||
import PolyBool from "polybooljs";
|
||||
import { EmbeddableMDCustomProps } from "./dialogs/EmbeddableSettings";
|
||||
@@ -91,9 +90,9 @@ import {
|
||||
extractCodeBlocks as _extractCodeBlocks,
|
||||
} from "./utils/AIUtils";
|
||||
import { EXCALIDRAW_AUTOMATE_INFO, EXCALIDRAW_SCRIPTENGINE_INFO } from "./dialogs/SuggesterInfo";
|
||||
import { CropImage } from "./utils/CropImage";
|
||||
import { has } from "./svgToExcalidraw/attributes";
|
||||
import { getFrameBasedOnFrameNameOrId } from "./utils/ExcalidrawViewUtils";
|
||||
import { addBackOfTheNoteCard, getFrameBasedOnFrameNameOrId } from "./utils/ExcalidrawViewUtils";
|
||||
import { log } from "./utils/DebugHelper";
|
||||
import { auto } from "@popperjs/core";
|
||||
|
||||
extendPlugins([
|
||||
HarmonyPlugin,
|
||||
@@ -134,7 +133,7 @@ export class ExcalidrawAutomate {
|
||||
|
||||
public help(target: Function | string) {
|
||||
if (!target) {
|
||||
console.log("Usage: ea.help(ea.functionName) or ea.help('propertyName') or ea.help('utils.functionName') - notice property name and utils function name is in quotes");
|
||||
log("Usage: ea.help(ea.functionName) or ea.help('propertyName') or ea.help('utils.functionName') - notice property name and utils function name is in quotes");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -153,14 +152,14 @@ export class ExcalidrawAutomate {
|
||||
}
|
||||
|
||||
if(!funcInfo) {
|
||||
console.log("Usage: ea.help(ea.functionName) or ea.help('propertyName') or ea.help('utils.functionName') - notice property name and utils function name is in quotes");
|
||||
log("Usage: ea.help(ea.functionName) or ea.help('propertyName') or ea.help('utils.functionName') - notice property name and utils function name is in quotes");
|
||||
return;
|
||||
}
|
||||
|
||||
let isMissing = true;
|
||||
if (funcInfo.code) {
|
||||
isMissing = false;
|
||||
console.log(`Declaration: ${funcInfo.code}`);
|
||||
log(`Declaration: ${funcInfo.code}`);
|
||||
}
|
||||
if (funcInfo.desc) {
|
||||
isMissing = false;
|
||||
@@ -171,10 +170,10 @@ export class ExcalidrawAutomate {
|
||||
.replace(/<a onclick='window\.open\("(.*?)"\)'>(.*?)<\/a>/g, (_, href, text) => `%c\u200b${text}%c\u200b (link: ${href})`); // Zero-width non-joiner
|
||||
|
||||
const styles = Array.from({ length: (formattedDesc.match(/%c/g) || []).length }, (_, i) => i % 2 === 0 ? 'color: #007bff;' : '');
|
||||
console.log(`Description: ${formattedDesc}`, ...styles);
|
||||
log(`Description: ${formattedDesc}`, ...styles);
|
||||
}
|
||||
if (isMissing) {
|
||||
console.log("Description not available for this function.");
|
||||
log("Description not available for this function.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,6 +251,22 @@ export class ExcalidrawAutomate {
|
||||
return getListOfTemplateFiles(this.plugin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retruns the embedded images in the scene recursively. If excalidrawFile is not provided,
|
||||
* the function will use ea.targetView.file
|
||||
* @param excalidrawFile
|
||||
* @returns TFile[] of all nested images and Excalidraw drawings recursively
|
||||
*/
|
||||
public getEmbeddedImagesFiletree(excalidrawFile?: TFile): TFile[] {
|
||||
if(!excalidrawFile && this.targetView && this.targetView.file) {
|
||||
excalidrawFile = this.targetView.file;
|
||||
}
|
||||
if(!excalidrawFile) {
|
||||
return [];
|
||||
}
|
||||
return getExcalidrawEmbeddedFilesFiletree(excalidrawFile, this.plugin);
|
||||
}
|
||||
|
||||
public async getAttachmentFilepath(filename: string): Promise<string> {
|
||||
if (!this.targetView || !this.targetView?.file) {
|
||||
errorMessage("targetView not set", "getAttachmentFolderAndFilePath()");
|
||||
@@ -601,6 +616,7 @@ export class ExcalidrawAutomate {
|
||||
"excalidraw-export-dark"?: boolean;
|
||||
"excalidraw-export-padding"?: number;
|
||||
"excalidraw-export-pngscale"?: number;
|
||||
"excalidraw-export-embed-scene"?: boolean;
|
||||
"excalidraw-default-mode"?: "view" | "zen";
|
||||
"excalidraw-onload-script"?: string;
|
||||
"excalidraw-linkbutton-opacity"?: number;
|
||||
@@ -705,7 +721,7 @@ export class ExcalidrawAutomate {
|
||||
|
||||
const generateMD = ():string => {
|
||||
const textElements = this.getElements().filter(el => el.type === "text") as ExcalidrawTextElement[];
|
||||
let outString = `${MD_TEXTELEMENTS}\n`;
|
||||
let outString = `# Excalidraw Data\n## Text Elements\n`;
|
||||
textElements.forEach(te=> {
|
||||
outString += `${te.rawText ?? (te.originalText ?? te.text)} ^${te.id}\n\n`;
|
||||
});
|
||||
@@ -716,7 +732,7 @@ export class ExcalidrawAutomate {
|
||||
})
|
||||
|
||||
outString += Object.keys(this.imagesDict).length > 0
|
||||
? "\n# Embedded files\n"
|
||||
? `\n## Embedded Files\n`
|
||||
: "";
|
||||
|
||||
Object.keys(this.imagesDict).forEach((key: FileId)=> {
|
||||
@@ -1198,7 +1214,8 @@ export class ExcalidrawAutomate {
|
||||
topY: number,
|
||||
text: string,
|
||||
formatting?: {
|
||||
wrapAt?: number;
|
||||
autoResize?: boolean; //Default is true. Setting this to false will wrap the text in the text element without the need for the containser. If set to false, you must set a width value as well.
|
||||
wrapAt?: number; //wrapAt is ignored if autoResize is set to false (and width is provided)
|
||||
width?: number;
|
||||
height?: number;
|
||||
textAlign?: "left" | "center" | "right";
|
||||
@@ -1211,7 +1228,11 @@ export class ExcalidrawAutomate {
|
||||
): string {
|
||||
id = id ?? nanoid();
|
||||
const originalText = text;
|
||||
text = formatting?.wrapAt ? this.wrapText(text, formatting.wrapAt) : text;
|
||||
const autoresize = ((typeof formatting?.width === "undefined") || formatting?.box)
|
||||
? true
|
||||
: (formatting?.autoResize ?? true)
|
||||
text = (formatting?.wrapAt && autoresize) ? this.wrapText(text, formatting.wrapAt) : text;
|
||||
|
||||
const { w, h, baseline } = _measureText(
|
||||
text,
|
||||
this.style.fontSize,
|
||||
@@ -1222,9 +1243,9 @@ export class ExcalidrawAutomate {
|
||||
const height = formatting?.height ? formatting.height : h;
|
||||
|
||||
let boxId: string = null;
|
||||
const boxPadding = formatting?.boxPadding ?? 30;
|
||||
const strokeColor = this.style.strokeColor;
|
||||
this.style.strokeColor = formatting?.boxStrokeColor ?? strokeColor;
|
||||
const boxPadding = formatting?.boxPadding ?? 30;
|
||||
if (formatting?.box) {
|
||||
switch (formatting.box) {
|
||||
case "ellipse":
|
||||
@@ -1270,12 +1291,12 @@ export class ExcalidrawAutomate {
|
||||
? formatting.textAlign
|
||||
: this.style.textAlign ?? "left",
|
||||
verticalAlign: formatting?.textVerticalAlign ?? this.style.verticalAlign,
|
||||
baseline,
|
||||
...this.boxedElement(id, "text", topX, topY, width, height),
|
||||
containerId: isContainerBound ? boxId : null,
|
||||
originalText: isContainerBound ? originalText : text,
|
||||
rawText: isContainerBound ? originalText : text,
|
||||
lineHeight: getDefaultLineHeight(this.style.fontFamily),
|
||||
autoResize: formatting?.box ? true : (formatting?.autoResize ?? true),
|
||||
};
|
||||
if (boxId && formatting?.box === "blob") {
|
||||
this.addToGroup([id, boxId]);
|
||||
@@ -1287,6 +1308,25 @@ export class ExcalidrawAutomate {
|
||||
}
|
||||
box.boundElements.push({ type: "text", id });
|
||||
}
|
||||
const textElement = this.getElement(id) as Mutable<ExcalidrawTextElement>;
|
||||
const container = (boxId && formatting.box !== "blob") ? this.getElement(boxId) as Mutable<ExcalidrawTextContainer>: undefined;
|
||||
const dimensions = refreshTextDimensions(
|
||||
textElement,
|
||||
container,
|
||||
arrayToMap(this.getElements()),
|
||||
originalText,
|
||||
);
|
||||
if(dimensions) {
|
||||
textElement.width = dimensions.width;
|
||||
textElement.height = dimensions.height;
|
||||
textElement.x = dimensions.x;
|
||||
textElement.y = dimensions.y;
|
||||
textElement.text = dimensions.text;
|
||||
if(container) {
|
||||
container.width = dimensions.width + 2 * boxPadding;
|
||||
container.height = dimensions.height + 2 * boxPadding;
|
||||
}
|
||||
}
|
||||
return boxId ?? id;
|
||||
};
|
||||
|
||||
@@ -1820,12 +1860,30 @@ export class ExcalidrawAutomate {
|
||||
this.targetView.updateScene({
|
||||
elements: el.filter((e: ExcalidrawElement) => !elToDelete.includes(e)),
|
||||
appState: st,
|
||||
commitToHistory: true,
|
||||
storeAction: "capture",
|
||||
});
|
||||
//this.targetView.save();
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a back of the note card to the current active view
|
||||
* @param sectionTitle: string
|
||||
* @param activate:boolean = true; if true, the new Embedded Element will be activated after creation
|
||||
* @param sectionBody?: string;
|
||||
* @param embeddableCustomData?: EmbeddableMDCustomProps; formatting of the embeddable element
|
||||
* @returns embeddable element id
|
||||
*/
|
||||
async addBackOfTheCardNoteToView(sectionTitle: string, activate: boolean = false, sectionBody?: string, embeddableCustomData?: EmbeddableMDCustomProps): Promise<string> {
|
||||
//@ts-ignore
|
||||
if (!this.targetView || !this.targetView?._loaded) {
|
||||
errorMessage("targetView not set", "addBackOfTheCardNoteToView()");
|
||||
return null;
|
||||
}
|
||||
await this.targetView.forceSave(true);
|
||||
return addBackOfTheNoteCard(this.targetView, sectionTitle, activate, sectionBody, embeddableCustomData);
|
||||
}
|
||||
|
||||
/**
|
||||
* get the selected element in the view, if more are selected, get the first
|
||||
* @returns
|
||||
@@ -1938,7 +1996,7 @@ export class ExcalidrawAutomate {
|
||||
appState: {
|
||||
viewModeEnabled: !isFullscreen,
|
||||
},
|
||||
commitToHistory: false,
|
||||
storeAction: "none",
|
||||
});
|
||||
this.targetView.toolsPanelRef?.current?.setExcalidrawViewMode(!isFullscreen);
|
||||
}
|
||||
@@ -1973,6 +2031,7 @@ export class ExcalidrawAutomate {
|
||||
appState?: AppState,
|
||||
files?: BinaryFileData,
|
||||
commitToHistory?: boolean,
|
||||
storeAction?: "capture" | "none" | "update",
|
||||
},
|
||||
restore: boolean = false,
|
||||
):void {
|
||||
@@ -1981,7 +2040,16 @@ export class ExcalidrawAutomate {
|
||||
errorMessage("targetView not set", "viewToggleFullScreen()");
|
||||
return;
|
||||
}
|
||||
this.targetView.updateScene(scene,restore);
|
||||
if (!Boolean(scene.storeAction)) {
|
||||
scene.storeAction = scene.commitToHistory ? "capture" : "none";
|
||||
}
|
||||
|
||||
this.targetView.updateScene({
|
||||
elements: scene.elements,
|
||||
appState: scene.appState,
|
||||
files: scene.files,
|
||||
storeAction: scene.storeAction,
|
||||
},restore);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2542,7 +2610,7 @@ export class ExcalidrawAutomate {
|
||||
elements.splice(newZIndex, 0, elements.splice(oldZIndex, 1)[0]);
|
||||
this.targetView.updateScene({
|
||||
elements,
|
||||
commitToHistory: true,
|
||||
storeAction: "capture",
|
||||
});
|
||||
};
|
||||
|
||||
@@ -2628,7 +2696,7 @@ export class ExcalidrawAutomate {
|
||||
importSVG(svgString:string):boolean {
|
||||
const res:ConversionResult = svgToExcalidraw(svgString);
|
||||
if(res.hasErrors) {
|
||||
new Notice (`There were errors while parsing the given SVG:\n${[...res.errors].map((el) => el.innerHTML)}`);
|
||||
new Notice (`There were errors while parsing the given SVG:\n${res.errors}`);
|
||||
return false;
|
||||
}
|
||||
this.copyViewElementsToEAforEditing(res.content);
|
||||
@@ -2761,9 +2829,9 @@ async function getTemplate(
|
||||
textMode,
|
||||
);
|
||||
|
||||
let trimLocation = data.search(new RegExp(`^${MD_TEXTELEMENTS}$`,"m"));
|
||||
let trimLocation = data.search(/^##? Text Elements$/m);
|
||||
if (trimLocation == -1) {
|
||||
trimLocation = data.search(`${MD_DRAWING}\n`);
|
||||
trimLocation = data.search(/##? Drawing\n/);
|
||||
}
|
||||
|
||||
let scene = excalidrawData.scene;
|
||||
|
||||
@@ -11,16 +11,12 @@ import {
|
||||
fileid,
|
||||
DEVICE,
|
||||
EMBEDDABLE_THEME_FRONTMATTER_VALUES,
|
||||
getBoundTextMaxWidth,
|
||||
getDefaultLineHeight,
|
||||
getFontString,
|
||||
wrapText,
|
||||
ERROR_IFRAME_CONVERSION_CANCELED,
|
||||
JSON_parse,
|
||||
FRONTMATTER_KEYS,
|
||||
MD_TEXTELEMENTS,
|
||||
MD_DRAWING,
|
||||
MD_ELEMENTLINKS,
|
||||
refreshTextDimensions,
|
||||
getContainerElement,
|
||||
} from "./constants/constants";
|
||||
import { _measureText } from "./ExcalidrawAutomate";
|
||||
import ExcalidrawPlugin from "./main";
|
||||
@@ -28,11 +24,10 @@ import { TextMode } from "./ExcalidrawView";
|
||||
import {
|
||||
addAppendUpdateCustomData,
|
||||
compress,
|
||||
debug,
|
||||
decompress,
|
||||
//getBakPath,
|
||||
getBinaryFileFromDataURL,
|
||||
getContainerElement,
|
||||
_getContainerElement,
|
||||
getExportTheme,
|
||||
getLinkParts,
|
||||
hasExportTheme,
|
||||
@@ -40,17 +35,21 @@ import {
|
||||
LinkParts,
|
||||
updateFrontmatterInString,
|
||||
wrapTextAtCharLength,
|
||||
arrayToMap,
|
||||
} from "./utils/Utils";
|
||||
import { cleanBlockRef, cleanSectionHeading, getAttachmentsFolderAndFilePath, isObsidianThemeDark } from "./utils/ObsidianUtils";
|
||||
import {
|
||||
ExcalidrawElement,
|
||||
ExcalidrawImageElement,
|
||||
ExcalidrawTextElement,
|
||||
FileId,
|
||||
} from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
import { BinaryFiles, DataURL, SceneData } from "@zsviczian/excalidraw/types/excalidraw/types";
|
||||
import { EmbeddedFile, MimeType } from "./EmbeddedFileLoader";
|
||||
import { ConfirmationPrompt } from "./dialogs/Prompt";
|
||||
import { getMermaidImageElements, getMermaidText, shouldRenderMermaid } from "./utils/MermaidUtils";
|
||||
import { DEBUGGING, debug } from "./utils/DebugHelper";
|
||||
import { Mutable } from "@zsviczian/excalidraw/types/excalidraw/utility-types";
|
||||
|
||||
type SceneDataWithFiles = SceneData & { files: BinaryFiles };
|
||||
|
||||
@@ -117,12 +116,12 @@ export const REGEX_LINK = {
|
||||
};
|
||||
|
||||
//added \n at and of DRAWING_REG: https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/357
|
||||
const DRAWING_REG = /\n# Drawing\n[^`]*(```json\n)([\s\S]*?)```\n/gm; //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/182
|
||||
const DRAWING_REG_FALLBACK = /\n# Drawing\n(```json\n)?(.*)(```)?(%%)?/gm;
|
||||
const DRAWING_REG = /\n##? Drawing\n[^`]*(```json\n)([\s\S]*?)```\n/gm; //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/182
|
||||
const DRAWING_REG_FALLBACK = /\n##? Drawing\n(```json\n)?(.*)(```)?(%%)?/gm;
|
||||
export const DRAWING_COMPRESSED_REG =
|
||||
/(\n# Drawing\n[^`]*(?:```compressed\-json\n))([\s\S]*?)(```\n)/gm; //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/182
|
||||
/(\n##? Drawing\n[^`]*(?:```compressed\-json\n))([\s\S]*?)(```\n)/gm; //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/182
|
||||
const DRAWING_COMPRESSED_REG_FALLBACK =
|
||||
/(\n# Drawing\n(?:```compressed\-json\n)?)(.*)((```)?(%%)?)/gm;
|
||||
/(\n##? Drawing\n(?:```compressed\-json\n)?)(.*)((```)?(%%)?)/gm;
|
||||
export const REG_LINKINDEX_HYPERLINK = /^\w+:\/\//;
|
||||
|
||||
const isCompressedMD = (data: string): boolean => {
|
||||
@@ -204,10 +203,10 @@ export function getMarkdownDrawingSection(
|
||||
compressed: boolean,
|
||||
) {
|
||||
return compressed
|
||||
? `# Drawing\n\x60\x60\x60compressed-json\n${compress(
|
||||
? `## Drawing\n\x60\x60\x60compressed-json\n${compress(
|
||||
jsonString,
|
||||
)}\n\x60\x60\x60\n%%`
|
||||
: `# Drawing\n\x60\x60\x60json\n${jsonString}\n\x60\x60\x60\n%%`;
|
||||
: `## Drawing\n\x60\x60\x60json\n${jsonString}\n\x60\x60\x60\n%%`;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -240,32 +239,161 @@ const estimateMaxLineLen = (text: string, originalText: string): number => {
|
||||
const wrap = (text: string, lineLen: number) =>
|
||||
lineLen ? wrapTextAtCharLength(text, lineLen, false, 0) : text;
|
||||
|
||||
const RE_TEXTELEMENTS = new RegExp(`^(%%\n)?${MD_TEXTELEMENTS}(?:\n|$)`, "m");
|
||||
//WITHSECTION refers to back of the card note (see this.inputEl.onkeyup in SelectCard.ts)
|
||||
const RE_EXCALIDRAWDATA_WITHSECTION_OK = /^(#\n+)%%\n+# Excalidraw Data(?:\n|$)/m;
|
||||
const RE_EXCALIDRAWDATA_WITHSECTION_NOTOK = /#\n+%%\n+# Excalidraw Data(?:\n|$)/m;
|
||||
const RE_EXCALIDRAWDATA_NOSECTION_OK = /^(%%\n+)?# Excalidraw Data(?:\n|$)/m;
|
||||
|
||||
//WITHSECTION refers to back of the card note (see this.inputEl.onkeyup in SelectCard.ts)
|
||||
const RE_TEXTELEMENTS_WITHSECTION_OK = /^#\n+%%\n+##? Text Elements(?:\n|$)/m;
|
||||
const RE_TEXTELEMENTS_WITHSECTION_NOTOK = /#\n+%%\n+##? Text Elements(?:\n|$)/m;
|
||||
const RE_TEXTELEMENTS_NOSECTION_OK = /^(%%\n+)?##? Text Elements(?:\n|$)/m;
|
||||
|
||||
|
||||
//The issue is that when editing in markdown embeds the user can delete the last enter causing two sections
|
||||
//to collide. This is particularly problematic when the user is editing the lest section before # Text Elements
|
||||
const RE_TEXTELEMENTS_FALLBACK_1 = new RegExp(`(.*)%%\n${MD_TEXTELEMENTS}(?:\n|$)`, "m");
|
||||
const RE_TEXTELEMENTS_FALLBACK_2 = new RegExp(`(.*)${MD_TEXTELEMENTS}(?:\n|$)`, "m");
|
||||
//to collide. This is particularly problematic when the user is editing the last section before # Text Elements
|
||||
const RE_EXCALIDRAWDATA_FALLBACK_1 = /(.*)%%\n+# Excalidraw Data(?:\n|$)/m;
|
||||
const RE_EXCALIDRAWDATA_FALLBACK_2 = /(.*)# Excalidraw Data(?:\n|$)/m;
|
||||
|
||||
const RE_TEXTELEMENTS_FALLBACK_1 = /(.*)%%\n+##? Text Elements(?:\n|$)/m;
|
||||
const RE_TEXTELEMENTS_FALLBACK_2 = /(.*)##? Text Elements(?:\n|$)/m;
|
||||
|
||||
|
||||
const RE_DRAWING = new RegExp(`(%%\n)?${MD_DRAWING}\n`);
|
||||
const RE_DRAWING = /^(%%\n+)?##? Drawing\n/m;
|
||||
|
||||
export const getExcalidrawMarkdownHeaderSection = (data:string, keys?:[string,string][]):string => {
|
||||
let trimLocation = data.search(RE_TEXTELEMENTS);
|
||||
//The base case scenario is at the top, continued with fallbacks in order of likelihood and file structure
|
||||
//change history for sake of backward compatibility
|
||||
|
||||
/* Expected markdown structure:
|
||||
bla bla bla
|
||||
#
|
||||
%%
|
||||
# Excalidraw Data
|
||||
*/
|
||||
|
||||
//trimming the json because in legacy excalidraw files the JSON was a single string resulting in very slow regexp parsing
|
||||
const drawingTrimLocation = data.search(RE_DRAWING);
|
||||
if(drawingTrimLocation>0) {
|
||||
data = data.substring(0, drawingTrimLocation);
|
||||
}
|
||||
|
||||
const m1 = data.match(RE_EXCALIDRAWDATA_WITHSECTION_OK);
|
||||
let trimLocation = m1?.index ?? -1; //data.search(RE_EXCALIDRAWDATA_WITHSECTION_OK);
|
||||
let shouldFixTrailingHashtag = false;
|
||||
if(trimLocation > 0) {
|
||||
trimLocation += m1[1].length; //accounts for the "(#\n\s*)" which I want to leave there untouched
|
||||
}
|
||||
|
||||
/* Expected markdown structure (this happens when the user deletes the last empty line of the last back-of-the-card note):
|
||||
bla bla bla#
|
||||
%%
|
||||
# Excalidraw Data
|
||||
*/
|
||||
if(trimLocation === -1) {
|
||||
trimLocation = data.search(RE_EXCALIDRAWDATA_WITHSECTION_NOTOK);
|
||||
if(trimLocation > 0) {
|
||||
shouldFixTrailingHashtag = true;
|
||||
}
|
||||
}
|
||||
/* Expected markdown structure
|
||||
a)
|
||||
bla bla bla
|
||||
%%
|
||||
# Excalidraw Data
|
||||
b)
|
||||
bla bla bla
|
||||
# Excalidraw Data
|
||||
*/
|
||||
if(trimLocation === -1) {
|
||||
trimLocation = data.search(RE_EXCALIDRAWDATA_NOSECTION_OK);
|
||||
}
|
||||
/* Expected markdown structure:
|
||||
bla bla bla%%
|
||||
# Excalidraw Data
|
||||
*/
|
||||
if(trimLocation === -1) {
|
||||
const res = data.match(RE_EXCALIDRAWDATA_FALLBACK_1);
|
||||
if(res && Boolean(res[1])) {
|
||||
trimLocation = res.index + res[1].length;
|
||||
}
|
||||
}
|
||||
/* Expected markdown structure:
|
||||
bla bla bla# Excalidraw Data
|
||||
*/
|
||||
if(trimLocation === -1) {
|
||||
const res = data.match(RE_EXCALIDRAWDATA_FALLBACK_2);
|
||||
if(res && Boolean(res[1])) {
|
||||
trimLocation = res.index + res[1].length;
|
||||
}
|
||||
}
|
||||
/* Expected markdown structure:
|
||||
bla bla bla
|
||||
#
|
||||
%%
|
||||
# Text Elements
|
||||
*/
|
||||
if(trimLocation === -1) {
|
||||
trimLocation = data.search(RE_TEXTELEMENTS_WITHSECTION_OK);
|
||||
if(trimLocation > 0) {
|
||||
trimLocation += 2; //accounts for the "#\n" which I want to leave there untouched
|
||||
}
|
||||
}
|
||||
/* Expected markdown structure:
|
||||
bla bla bla#
|
||||
%%
|
||||
# Text Elements
|
||||
*/
|
||||
if(trimLocation === -1) {
|
||||
trimLocation = data.search(RE_TEXTELEMENTS_WITHSECTION_NOTOK);
|
||||
if(trimLocation > 0) {
|
||||
shouldFixTrailingHashtag = true;
|
||||
}
|
||||
}
|
||||
/* Expected markdown structure
|
||||
a)
|
||||
bla bla bla
|
||||
%%
|
||||
# Text Elements
|
||||
b)
|
||||
bla bla bla
|
||||
# Text Elements
|
||||
*/
|
||||
if(trimLocation === -1) {
|
||||
trimLocation = data.search(RE_TEXTELEMENTS_NOSECTION_OK);
|
||||
}
|
||||
/* Expected markdown structure:
|
||||
bla bla bla%%
|
||||
# Text Elements
|
||||
*/
|
||||
if(trimLocation === -1) {
|
||||
const res = data.match(RE_TEXTELEMENTS_FALLBACK_1);
|
||||
if(res && Boolean(res[1])) {
|
||||
trimLocation = res.index + res[1].length;
|
||||
}
|
||||
}
|
||||
/* Expected markdown structure:
|
||||
bla bla bla# Text Elements
|
||||
*/
|
||||
if(trimLocation === -1) {
|
||||
const res = data.match(RE_TEXTELEMENTS_FALLBACK_2);
|
||||
if(res && Boolean(res[1])) {
|
||||
trimLocation = res.index + res[1].length;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Expected markdown structure:
|
||||
a)
|
||||
bla bla bla
|
||||
# Drawing
|
||||
b)
|
||||
bla bla bla
|
||||
%%
|
||||
# Drawing
|
||||
*/
|
||||
if (trimLocation === -1) {
|
||||
trimLocation = data.search(RE_DRAWING);
|
||||
if (drawingTrimLocation > 0) {
|
||||
trimLocation = drawingTrimLocation;
|
||||
}
|
||||
}
|
||||
if (trimLocation === -1) {
|
||||
return data.endsWith("\n") ? data : (data + "\n");
|
||||
@@ -273,19 +401,21 @@ export const getExcalidrawMarkdownHeaderSection = (data:string, keys?:[string,st
|
||||
|
||||
let header = updateFrontmatterInString(data.substring(0, trimLocation),keys);
|
||||
//this should be removed at a later time. Left it here to remediate 1.4.9 mistake
|
||||
const REG_IMG = /(^---[\w\W]*?---\n)(!\[\[.*?]]\n(%%\n)?)/m; //(%%\n)? because of 1.4.8-beta... to be backward compatible with anyone who installed that version
|
||||
/*const REG_IMG = /(^---[\w\W]*?---\n)(!\[\[.*?]]\n(%%\n)?)/m; //(%%\n)? because of 1.4.8-beta... to be backward compatible with anyone who installed that version
|
||||
if (header.match(REG_IMG)) {
|
||||
header = header.replace(REG_IMG, "$1");
|
||||
}
|
||||
}*/
|
||||
//end of remove
|
||||
return header.endsWith("\n") ? header : (header + "\n");
|
||||
return shouldFixTrailingHashtag
|
||||
? header + "\n#\n"
|
||||
: header.endsWith("\n") ? header : (header + "\n");
|
||||
}
|
||||
|
||||
|
||||
export class ExcalidrawData {
|
||||
public textElements: Map<
|
||||
string,
|
||||
{ raw: string; parsed: string; wrapAt: number | null }
|
||||
{ raw: string; parsed: string}
|
||||
> = null;
|
||||
public elementLinks: Map<string, string> = null;
|
||||
public scene: any = null;
|
||||
@@ -392,6 +522,18 @@ export class ExcalidrawData {
|
||||
if (el.fontSize === null) {
|
||||
el.fontSize = 20;
|
||||
}
|
||||
|
||||
if (el.type === "text" && !el.hasOwnProperty("autoResize")) {
|
||||
el.autoResize = true;
|
||||
}
|
||||
|
||||
if (el.type === "text" && !el.hasOwnProperty("lineHeight")) {
|
||||
el.lineHeight = getDefaultLineHeight(el.fontFamily);
|
||||
}
|
||||
|
||||
if (el.type === "image" && !el.hasOwnProperty("roundness")) {
|
||||
el.roundness = null;
|
||||
}
|
||||
}
|
||||
|
||||
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/569
|
||||
@@ -486,10 +628,10 @@ export class ExcalidrawData {
|
||||
this.selectedElementIds = {};
|
||||
this.textElements = new Map<
|
||||
string,
|
||||
{ raw: string; parsed: string; wrapAt: number }
|
||||
{ raw: string; parsed: string}
|
||||
>();
|
||||
this.elementLinks = new Map<string, string>();
|
||||
if (this.file != file) {
|
||||
if (this.file !== file) {
|
||||
//this is a reload - files, equations and mermaids will take care of reloading when needed
|
||||
this.files.clear();
|
||||
this.equations.clear();
|
||||
@@ -580,28 +722,72 @@ export class ExcalidrawData {
|
||||
|
||||
data = data.substring(0, sceneJSONandPOS.pos);
|
||||
|
||||
//The Markdown # Text Elements take priority over the JSON text elements. Assuming the scenario in which the link was updated due to filename changes
|
||||
//The Markdown # Text Elements take priority over the JSON text elements. Assuming the scenario in which the
|
||||
//link was updated due to filename changes
|
||||
//The .excalidraw JSON is modified to reflect the MD in case of difference
|
||||
//Read the text elements into the textElements Map
|
||||
let position = data.search(RE_TEXTELEMENTS);
|
||||
let position = data.search(RE_EXCALIDRAWDATA_NOSECTION_OK);
|
||||
if (position === -1) {
|
||||
//resillience in case back of the note was saved right on top of text elements
|
||||
// # back of note section
|
||||
// ....# Excalidraw Data
|
||||
// ....
|
||||
// --------------
|
||||
// instead of
|
||||
// --------------
|
||||
// # back of note section
|
||||
// ....
|
||||
// # Excalidraw Data
|
||||
position = data.search(RE_EXCALIDRAWDATA_FALLBACK_2);
|
||||
}
|
||||
|
||||
if(position === -1) {
|
||||
// # back of note section
|
||||
// ....
|
||||
// # Text Elements
|
||||
position = data.search(RE_TEXTELEMENTS_NOSECTION_OK);
|
||||
}
|
||||
|
||||
if (position === -1) {
|
||||
//resillience in case back of the note was saved right on top of text elements
|
||||
// # back of note section
|
||||
// ....# Text Elements
|
||||
// ....
|
||||
// --------------
|
||||
// instead of
|
||||
// --------------
|
||||
// # back of note section
|
||||
// ....
|
||||
// # Text Elements
|
||||
position = data.search(RE_TEXTELEMENTS_FALLBACK_2);
|
||||
}
|
||||
if (position === -1) {
|
||||
await this.setTextMode(textMode, false);
|
||||
this.loaded = true;
|
||||
return true; //Text Elements header does not exist
|
||||
}
|
||||
const textElementsMatch = data.match(new RegExp(`^((%%\n)?${MD_TEXTELEMENTS}(?:\n|$))`, "m"))[0]
|
||||
position += textElementsMatch.length;
|
||||
|
||||
data = data.slice(position);
|
||||
const normalMatch = data.match(/^((%%\n*)?# Excalidraw Data\n## Text Elements(?:\n|$))/m)
|
||||
?? data.match(/^((%%\n*)?##? Text Elements(?:\n|$))/m);
|
||||
|
||||
const textElementsMatch = normalMatch
|
||||
? normalMatch[0]
|
||||
: data.match(/(.*##? Text Elements(?:\n|$))/m)[0];
|
||||
|
||||
data = data.slice(textElementsMatch.length);
|
||||
this.textElementCommentedOut = textElementsMatch.startsWith("%%\n");
|
||||
position = 0;
|
||||
let parts;
|
||||
|
||||
//load element links
|
||||
const elementLinkMap = new Map<string,string>();
|
||||
const elementLinksData = data.substring(
|
||||
data.indexOf(`${MD_ELEMENTLINKS}\n`) + `${MD_ELEMENTLINKS}\n`.length,
|
||||
);
|
||||
const indexOfNewElementLinks = data.indexOf("## Element Links\n");
|
||||
const lengthOfNewElementLinks = 17; //`## Element Links\n`.length
|
||||
const indexOfOldElementLinks = data.indexOf("# Element Links\n");
|
||||
const lengthOfOldElementLinks = 16; //`# Element Links\n`.length
|
||||
const elementLinksData = indexOfNewElementLinks>-1
|
||||
? data.substring(indexOfNewElementLinks + lengthOfNewElementLinks)
|
||||
: data.substring(indexOfOldElementLinks + lengthOfOldElementLinks);
|
||||
//Load Embedded files
|
||||
const RE_ELEMENT_LINKS = /^(.{8}):\s*(\[\[[^\]]*]])$/gm;
|
||||
const linksRes = elementLinksData.matchAll(RE_ELEMENT_LINKS);
|
||||
@@ -611,7 +797,7 @@ export class ExcalidrawData {
|
||||
|
||||
//iterating through all the text elements in .md
|
||||
//Text elements always contain the raw value
|
||||
const BLOCKREF_LEN: number = " ^12345678\n\n".length;
|
||||
const BLOCKREF_LEN: number = 12; // " ^12345678\n\n".length;
|
||||
const RE_TEXT_ELEMENT_LINK = /^%%\*\*\*>>>text element-link:(\[\[[^<*\]]*]])<<<\*\*\*%%/gm;
|
||||
let res = data.matchAll(/\s\^(.{8})[\n]+/g);
|
||||
while (!(parts = res.next()).done) {
|
||||
@@ -629,9 +815,8 @@ export class ExcalidrawData {
|
||||
}
|
||||
this.elementLinks.set(id, text);
|
||||
} else {
|
||||
const wrapAt = estimateMaxLineLen(textEl.text, textEl.originalText);
|
||||
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/566
|
||||
const elementLinkRes = text.matchAll(RE_TEXT_ELEMENT_LINK);
|
||||
const elementLinkRes = text.matchAll(RE_TEXT_ELEMENT_LINK);
|
||||
const elementLink = elementLinkRes.next();
|
||||
if(!elementLink.done) {
|
||||
text = text.replace(RE_TEXT_ELEMENT_LINK,"");
|
||||
@@ -646,7 +831,6 @@ export class ExcalidrawData {
|
||||
this.textElements.set(id, {
|
||||
raw: text,
|
||||
parsed: parseRes.parsed,
|
||||
wrapAt,
|
||||
});
|
||||
if (parseRes.link) {
|
||||
textEl.link = parseRes.link;
|
||||
@@ -672,13 +856,17 @@ export class ExcalidrawData {
|
||||
}
|
||||
}
|
||||
|
||||
const indexOfEmbeddedFiles = data.indexOf("# Embedded files\n");
|
||||
if(indexOfEmbeddedFiles>-1) {
|
||||
data = data.substring(
|
||||
indexOfEmbeddedFiles + "# Embedded files\n".length,
|
||||
);
|
||||
const indexOfNewEmbeddedFiles = data.indexOf("## Embedded Files\n");
|
||||
const embeddedFilesNewLength = 18; //"## Embedded Files\n".length
|
||||
const indexOfOldEmbeddedFiles = data.indexOf("# Embedded files\n");
|
||||
const embeddedFilesOldLength = 17; //"# Embedded files\n".length
|
||||
|
||||
if(indexOfNewEmbeddedFiles>-1 || indexOfOldEmbeddedFiles>-1) {
|
||||
data = indexOfNewEmbeddedFiles>-1
|
||||
? data.substring(indexOfNewEmbeddedFiles + embeddedFilesNewLength)
|
||||
: data.substring(indexOfOldEmbeddedFiles + embeddedFilesOldLength);
|
||||
//Load Embedded files
|
||||
const REG_FILEID_FILEPATH = /([\w\d]*):\s*\[\[([^\]]*)]]\s?(\{[^}]*})?\n/gm;
|
||||
const REG_FILEID_FILEPATH = /([\w\d]*):\s*\[\[([^\]]*)]]\s*(\{[^}]*})?\n/gm;
|
||||
res = data.matchAll(REG_FILEID_FILEPATH);
|
||||
while (!(parts = res.next()).done) {
|
||||
const embeddedFile = new EmbeddedFile(
|
||||
@@ -741,7 +929,7 @@ export class ExcalidrawData {
|
||||
this.file = file;
|
||||
this.textElements = new Map<
|
||||
string,
|
||||
{ raw: string; parsed: string; wrapAt: number }
|
||||
{ raw: string; parsed: string}
|
||||
>();
|
||||
this.elementLinks = new Map<string, string>();
|
||||
this.setShowLinkBrackets();
|
||||
@@ -772,34 +960,6 @@ export class ExcalidrawData {
|
||||
await this.updateSceneTextElements(forceupdate);
|
||||
}
|
||||
|
||||
//update a single text element in the scene if the newText is different
|
||||
public updateTextElement(
|
||||
sceneTextElement: any,
|
||||
newText: string,
|
||||
newOriginalText: string,
|
||||
forceUpdate: boolean = false,
|
||||
containerType?: string,
|
||||
) {
|
||||
if (forceUpdate || newText != sceneTextElement.text) {
|
||||
const measure = _measureText(
|
||||
newText,
|
||||
sceneTextElement.fontSize,
|
||||
sceneTextElement.fontFamily,
|
||||
sceneTextElement.lineHeight??getDefaultLineHeight(sceneTextElement.fontFamily),
|
||||
);
|
||||
sceneTextElement.text = newText;
|
||||
sceneTextElement.originalText = newOriginalText;
|
||||
|
||||
if (!sceneTextElement.containerId || containerType==="arrow") {
|
||||
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/376
|
||||
//I leave the setting of text width to excalidraw, when text is in a container
|
||||
//because text width is fixed to the container width
|
||||
sceneTextElement.width = measure.w;
|
||||
}
|
||||
sceneTextElement.height = measure.h;
|
||||
sceneTextElement.baseline = measure.baseline;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the TextElements in the Excalidraw scene based on textElements MAP in ExcalidrawData
|
||||
@@ -810,26 +970,27 @@ export class ExcalidrawData {
|
||||
private async updateSceneTextElements(forceupdate: boolean = false) {
|
||||
//update text in scene based on textElements Map
|
||||
//first get scene text elements
|
||||
const texts = this.scene.elements?.filter((el: any) => el.type === "text");
|
||||
const elementsMap = arrayToMap(this.scene.elements);
|
||||
const texts = this.scene.elements?.filter((el: any) => el.type === "text" && !el.isDeleted) as Mutable<ExcalidrawTextElement>[];
|
||||
for (const te of texts) {
|
||||
const container = getContainerElement(te,this.scene);
|
||||
const container = getContainerElement(te, elementsMap);
|
||||
const originalText =
|
||||
(await this.getText(te.id)) ?? te.originalText ?? te.text;
|
||||
const wrapAt = this.textElements.get(te.id)?.wrapAt;
|
||||
const {text, x, y, width, height} = refreshTextDimensions(
|
||||
te,
|
||||
container,
|
||||
elementsMap,
|
||||
originalText,
|
||||
)
|
||||
try { //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1062
|
||||
this.updateTextElement(
|
||||
te,
|
||||
wrapAt ? wrapText(
|
||||
originalText,
|
||||
getFontString({fontSize: te.fontSize, fontFamily: te.fontFamily}),
|
||||
getBoundTextMaxWidth(container as any)
|
||||
) : originalText,
|
||||
originalText,
|
||||
forceupdate,
|
||||
container?.type,
|
||||
); //(await this.getText(te.id))??te.text serves the case when the whole #Text Elements section is deleted by accident
|
||||
te.originalText = originalText;
|
||||
te.text = text;
|
||||
te.x = x;
|
||||
te.y = y;
|
||||
te.width = width;
|
||||
te.height = height;
|
||||
} catch(e) {
|
||||
debug({where: "ExcalidrawData.updateSceneTextElements", fn: this.updateSceneTextElements, textElement: te});
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.updateSceneTextElements, `ExcalidrawData.updateSceneTextElements, textElement:${te?.id}`, te, this.updateSceneTextElements);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -846,7 +1007,6 @@ export class ExcalidrawData {
|
||||
this.textElements.set(id, {
|
||||
raw: text.raw,
|
||||
parsed: (await this.parse(text.raw)).parsed,
|
||||
wrapAt: text.wrapAt,
|
||||
});
|
||||
}
|
||||
//console.log("parsed",this.textElements.get(id).parsed);
|
||||
@@ -923,21 +1083,18 @@ export class ExcalidrawData {
|
||||
this.textElements.set(id, {
|
||||
raw: text.raw,
|
||||
parsed: text.parsed,
|
||||
wrapAt: text.wrapAt,
|
||||
});
|
||||
this.textElements.delete(te.id); //delete the old ID from the Map
|
||||
}
|
||||
if (!this.textElements.has(id)) {
|
||||
const raw = te.rawText && te.rawText !== "" ? te.rawText : te.text; //this is for compatibility with drawings created before the rawText change on ExcalidrawTextElement
|
||||
const wrapAt = estimateMaxLineLen(te.text, te.originalText);
|
||||
this.textElements.set(id, { raw, parsed: null, wrapAt });
|
||||
this.parseasync(id, raw, wrapAt);
|
||||
this.textElements.set(id, { raw, parsed: null});
|
||||
this.parseasync(id, raw);
|
||||
}
|
||||
} else if (!this.textElements.has(te.id)) {
|
||||
const raw = te.rawText && te.rawText !== "" ? te.rawText : te.text; //this is for compatibility with drawings created before the rawText change on ExcalidrawTextElement
|
||||
const wrapAt = estimateMaxLineLen(te.text, te.originalText);
|
||||
this.textElements.set(id, { raw, parsed: null, wrapAt });
|
||||
this.parseasync(id, raw, wrapAt);
|
||||
this.textElements.set(id, { raw, parsed: null});
|
||||
this.parseasync(id, raw);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -985,22 +1142,19 @@ export class ExcalidrawData {
|
||||
? el[0].rawText
|
||||
: (el[0].originalText ?? el[0].text);
|
||||
if (text !== (el[0].originalText ?? el[0].text)) {
|
||||
const wrapAt = estimateMaxLineLen(el[0].text, el[0].originalText);
|
||||
this.textElements.set(key, {
|
||||
raw,
|
||||
parsed: (await this.parse(raw)).parsed,
|
||||
wrapAt,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async parseasync(key: string, raw: string, wrapAt: number) {
|
||||
private async parseasync(key: string, raw: string) {
|
||||
this.textElements.set(key, {
|
||||
raw,
|
||||
parsed: (await this.parse(raw)).parsed,
|
||||
wrapAt,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1191,7 +1345,10 @@ export class ExcalidrawData {
|
||||
disableCompression: boolean = false;
|
||||
generateMD(deletedElements: ExcalidrawElement[] = []): string {
|
||||
let outString = this.textElementCommentedOut ? "%%\n" : "";
|
||||
outString += `${MD_TEXTELEMENTS}\n`;
|
||||
outString += `# Excalidraw Data\n## Text Elements\n`;
|
||||
if (this.plugin.settings.addDummyTextElement) {
|
||||
outString += `\n^_dummy!_\n\n`;
|
||||
}
|
||||
const textElementLinks = new Map<string, string>();
|
||||
for (const key of this.textElements.keys()) {
|
||||
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/566
|
||||
@@ -1207,7 +1364,7 @@ export class ExcalidrawData {
|
||||
}
|
||||
|
||||
if (this.elementLinks.size > 0 || textElementLinks.size > 0) {
|
||||
outString += `${MD_ELEMENTLINKS}\n`;
|
||||
outString += `## Element Links\n`;
|
||||
for (const key of this.elementLinks.keys()) {
|
||||
outString += `${key}: ${this.elementLinks.get(key)}\n`;
|
||||
}
|
||||
@@ -1220,7 +1377,7 @@ export class ExcalidrawData {
|
||||
// deliberately not adding mermaids to here. It is enough to have the mermaidText in the image element's customData
|
||||
outString +=
|
||||
this.equations.size > 0 || this.files.size > 0
|
||||
? "# Embedded files\n"
|
||||
? "## Embedded Files\n"
|
||||
: "";
|
||||
if (this.equations.size > 0) {
|
||||
for (const key of this.equations.keys()) {
|
||||
@@ -1360,14 +1517,18 @@ export class ExcalidrawData {
|
||||
const processedIds = new Set<string>();
|
||||
fileIds.forEach((fileId,idx)=>{
|
||||
if(processedIds.has(fileId)) {
|
||||
const file = this.getFile(fileId);
|
||||
const embeddedFile = this.getFile(fileId);
|
||||
const equation = this.getEquation(fileId);
|
||||
const mermaid = this.getMermaid(fileId);
|
||||
|
||||
|
||||
|
||||
//images should have a single reference, but equations, and markdown embeds should have as many as instances of the file in the scene
|
||||
if(file && (file.isHyperLink || file.isLocalLink || (file.file && (file.file.extension !== "md" || this.plugin.isExcalidrawFile(file.file))))) {
|
||||
if (embeddedFile &&
|
||||
(embeddedFile.isHyperLink || embeddedFile.isLocalLink ||
|
||||
(embeddedFile.file &&
|
||||
(embeddedFile.file.extension !== "md" || this.plugin.isExcalidrawFile(embeddedFile.file))
|
||||
)
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if(mermaid) {
|
||||
@@ -1379,6 +1540,11 @@ export class ExcalidrawData {
|
||||
return;
|
||||
}
|
||||
|
||||
if(!embeddedFile && !equation && !mermaid) {
|
||||
//processing freshly pasted images from likely anotehr instance of excalidraw (e.g. Excalidraw.com, or another Obsidian instance)
|
||||
return;
|
||||
}
|
||||
|
||||
const newId = fileid();
|
||||
(scene
|
||||
.elements
|
||||
@@ -1387,8 +1553,8 @@ export class ExcalidrawData {
|
||||
.fileId = newId;
|
||||
dirty = true;
|
||||
processedIds.add(newId);
|
||||
if(file) {
|
||||
this.setFile(newId as FileId,new EmbeddedFile(this.plugin,this.file.path,file.linkParts.original));
|
||||
if(embeddedFile) {
|
||||
this.setFile(newId as FileId,new EmbeddedFile(this.plugin,this.file.path,embeddedFile.linkParts.original));
|
||||
}
|
||||
if(equation) {
|
||||
this.setEquation(newId as FileId, {latex:equation.latex, isLoaded:false});
|
||||
@@ -1458,12 +1624,12 @@ export class ExcalidrawData {
|
||||
* @param id
|
||||
* @returns
|
||||
*/
|
||||
public getParsedText(id: string): [parseResultWrapped: string, parseResultOriginal: string, link: string] {
|
||||
public getParsedText(id: string): string {
|
||||
const t = this.textElements.get(id);
|
||||
if (!t) {
|
||||
return [null, null, null];
|
||||
return null;
|
||||
}
|
||||
return [wrap(t.parsed, t.wrapAt), t.parsed, null];
|
||||
return t.parsed;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1480,24 +1646,23 @@ export class ExcalidrawData {
|
||||
* @param rawText
|
||||
* @param rawOriginalText
|
||||
* @param updateSceneCallback
|
||||
* @returns [parseResultWrapped: string, parseResultOriginal: string, link: string]
|
||||
* @returns [parseResultOriginal: string, link: string]
|
||||
*/
|
||||
public setTextElement(
|
||||
elementID: string,
|
||||
rawText: string,
|
||||
rawOriginalText: string,
|
||||
updateSceneCallback: Function,
|
||||
): [parseResultWrapped: string, parseResultOriginal: string, link: string] {
|
||||
const maxLineLen = estimateMaxLineLen(rawText, rawOriginalText);
|
||||
|
||||
): [parseResultOriginal: string, link: string] {
|
||||
//const maxLineLen = estimateMaxLineLen(rawText, rawOriginalText);
|
||||
const [parseResult, link] = this.quickParse(rawOriginalText); //will return the parsed result if raw text does not include transclusion
|
||||
if (parseResult) {
|
||||
//No transclusion
|
||||
this.textElements.set(elementID, {
|
||||
raw: rawOriginalText,
|
||||
parsed: parseResult,
|
||||
wrapAt: maxLineLen,
|
||||
});
|
||||
return [wrap(parseResult, maxLineLen), parseResult, link];
|
||||
return [parseResult, link];
|
||||
}
|
||||
//transclusion needs to be resolved asynchornously
|
||||
this.parse(rawOriginalText).then((parseRes) => {
|
||||
@@ -1505,35 +1670,28 @@ export class ExcalidrawData {
|
||||
this.textElements.set(elementID, {
|
||||
raw: rawOriginalText,
|
||||
parsed: parsedText,
|
||||
wrapAt: maxLineLen,
|
||||
});
|
||||
if (parsedText) {
|
||||
updateSceneCallback(wrap(parsedText, maxLineLen), parsedText);
|
||||
updateSceneCallback(parsedText);
|
||||
}
|
||||
});
|
||||
return [null, null, null];
|
||||
return [null, null];
|
||||
}
|
||||
|
||||
public async addTextElement(
|
||||
elementID: string,
|
||||
rawText: string,
|
||||
rawOriginalText: string,
|
||||
): Promise<[string, string, string]> {
|
||||
let wrapAt: number = estimateMaxLineLen(rawText, rawOriginalText);
|
||||
if (this.textElements.has(elementID)) {
|
||||
wrapAt = this.textElements.get(elementID).wrapAt;
|
||||
}
|
||||
): Promise<{parseResult: string, link:string}> {
|
||||
const parseResult = await this.parse(rawOriginalText);
|
||||
this.textElements.set(elementID, {
|
||||
raw: rawOriginalText,
|
||||
parsed: parseResult.parsed,
|
||||
wrapAt,
|
||||
});
|
||||
return [
|
||||
wrap(parseResult.parsed, wrapAt),
|
||||
parseResult.parsed,
|
||||
parseResult.link,
|
||||
];
|
||||
return {
|
||||
parseResult: parseResult.parsed,
|
||||
link: parseResult.link,
|
||||
};
|
||||
}
|
||||
|
||||
public deleteTextElement(id: string) {
|
||||
@@ -1547,7 +1705,8 @@ export class ExcalidrawData {
|
||||
: this.plugin.settings.defaultMode;
|
||||
if (
|
||||
fileCache?.frontmatter &&
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["default-mode"].name] != null
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["default-mode"].name] !== null &&
|
||||
(typeof fileCache.frontmatter[FRONTMATTER_KEYS["default-mode"].name] !== "undefined")
|
||||
) {
|
||||
mode = fileCache.frontmatter[FRONTMATTER_KEYS["default-mode"].name];
|
||||
}
|
||||
@@ -1567,7 +1726,8 @@ export class ExcalidrawData {
|
||||
let opacity = this.plugin.settings.linkOpacity;
|
||||
if (
|
||||
fileCache?.frontmatter &&
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["linkbutton-opacity"].name] != null
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["linkbutton-opacity"].name] !== null &&
|
||||
(typeof fileCache.frontmatter[FRONTMATTER_KEYS["linkbutton-opacity"].name] !== "undefined")
|
||||
) {
|
||||
opacity = fileCache.frontmatter[FRONTMATTER_KEYS["linkbutton-opacity"].name];
|
||||
}
|
||||
@@ -1578,7 +1738,8 @@ export class ExcalidrawData {
|
||||
const fileCache = this.app.metadataCache.getFileCache(this.file);
|
||||
if (
|
||||
fileCache?.frontmatter &&
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["onload-script"].name] != null
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["onload-script"].name] !== null &&
|
||||
(typeof fileCache.frontmatter[FRONTMATTER_KEYS["onload-script"].name] !== "undefined")
|
||||
) {
|
||||
return fileCache.frontmatter[FRONTMATTER_KEYS["onload-script"].name];
|
||||
}
|
||||
@@ -1590,13 +1751,13 @@ export class ExcalidrawData {
|
||||
const fileCache = this.app.metadataCache.getFileCache(this.file);
|
||||
if (
|
||||
fileCache?.frontmatter &&
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["link-prefix"].name] != null
|
||||
(typeof fileCache.frontmatter[FRONTMATTER_KEYS["link-prefix"].name] !== "undefined")
|
||||
) {
|
||||
this.linkPrefix = fileCache.frontmatter[FRONTMATTER_KEYS["link-prefix"].name];
|
||||
this.linkPrefix = fileCache.frontmatter[FRONTMATTER_KEYS["link-prefix"].name]??"";
|
||||
} else {
|
||||
this.linkPrefix = this.plugin.settings.linkPrefix;
|
||||
}
|
||||
return linkPrefix != this.linkPrefix;
|
||||
return linkPrefix !== this.linkPrefix;
|
||||
}
|
||||
|
||||
private setUrlPrefix(): boolean {
|
||||
@@ -1604,20 +1765,21 @@ export class ExcalidrawData {
|
||||
const fileCache = this.app.metadataCache.getFileCache(this.file);
|
||||
if (
|
||||
fileCache?.frontmatter &&
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["url-prefix"].name] != null
|
||||
(typeof fileCache.frontmatter[FRONTMATTER_KEYS["url-prefix"].name] !== "undefined")
|
||||
) {
|
||||
this.urlPrefix = fileCache.frontmatter[FRONTMATTER_KEYS["url-prefix"].name];
|
||||
this.urlPrefix = fileCache.frontmatter[FRONTMATTER_KEYS["url-prefix"].name]??"";
|
||||
} else {
|
||||
this.urlPrefix = this.plugin.settings.urlPrefix;
|
||||
}
|
||||
return urlPrefix != this.urlPrefix;
|
||||
return urlPrefix !== this.urlPrefix;
|
||||
}
|
||||
|
||||
private setAutoexportPreferences() {
|
||||
const fileCache = this.app.metadataCache.getFileCache(this.file);
|
||||
if (
|
||||
fileCache?.frontmatter &&
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["autoexport"].name] != null
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["autoexport"].name] !== null &&
|
||||
(typeof fileCache.frontmatter[FRONTMATTER_KEYS["autoexport"].name] !== "undefined")
|
||||
) {
|
||||
switch ((fileCache.frontmatter[FRONTMATTER_KEYS["autoexport"].name]).toLowerCase()) {
|
||||
case "none": this.autoexportPreference = AutoexportPreference.none; break;
|
||||
@@ -1636,16 +1798,28 @@ export class ExcalidrawData {
|
||||
const fileCache = this.app.metadataCache.getFileCache(this.file);
|
||||
if (
|
||||
fileCache?.frontmatter &&
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["iframe-theme"].name] != null
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["embeddable-theme"].name] !== null &&
|
||||
(typeof fileCache.frontmatter[FRONTMATTER_KEYS["embeddable-theme"].name] !== "undefined")
|
||||
) {
|
||||
this.embeddableTheme = fileCache.frontmatter[FRONTMATTER_KEYS["iframe-theme"].name].toLowerCase();
|
||||
this.embeddableTheme = fileCache.frontmatter[FRONTMATTER_KEYS["embeddable-theme"].name].toLowerCase();
|
||||
if (!EMBEDDABLE_THEME_FRONTMATTER_VALUES.includes(this.embeddableTheme)) {
|
||||
this.embeddableTheme = "default";
|
||||
}
|
||||
} else {
|
||||
this.embeddableTheme = this.plugin.settings.iframeMatchExcalidrawTheme ? "auto" : "default";
|
||||
if ( //backwards compatibility
|
||||
fileCache?.frontmatter &&
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["iframe-theme"].name] !== null &&
|
||||
(typeof fileCache.frontmatter[FRONTMATTER_KEYS["iframe-theme"].name] !== "undefined")
|
||||
) {
|
||||
this.embeddableTheme = fileCache.frontmatter[FRONTMATTER_KEYS["iframe-theme"].name].toLowerCase();
|
||||
if (!EMBEDDABLE_THEME_FRONTMATTER_VALUES.includes(this.embeddableTheme)) {
|
||||
this.embeddableTheme = "default";
|
||||
}
|
||||
} else {
|
||||
this.embeddableTheme = this.plugin.settings.iframeMatchExcalidrawTheme ? "auto" : "default";
|
||||
}
|
||||
}
|
||||
return embeddableTheme != this.embeddableTheme;
|
||||
return embeddableTheme !== this.embeddableTheme;
|
||||
}
|
||||
|
||||
private setShowLinkBrackets(): boolean {
|
||||
@@ -1653,14 +1827,15 @@ export class ExcalidrawData {
|
||||
const fileCache = this.app.metadataCache.getFileCache(this.file);
|
||||
if (
|
||||
fileCache?.frontmatter &&
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["link-brackets"].name] != null
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["link-brackets"].name] !== null &&
|
||||
(typeof fileCache.frontmatter[FRONTMATTER_KEYS["link-brackets"].name] !== "undefined")
|
||||
) {
|
||||
this.showLinkBrackets =
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["link-brackets"].name] != false;
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["link-brackets"].name] !== false;
|
||||
} else {
|
||||
this.showLinkBrackets = this.plugin.settings.showLinkBrackets;
|
||||
}
|
||||
return showLinkBrackets != this.showLinkBrackets;
|
||||
return showLinkBrackets !== this.showLinkBrackets;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1893,7 +2068,7 @@ export const getTransclusion = async (
|
||||
{ isCancelled: () => false },
|
||||
file,
|
||||
)
|
||||
).blocks.filter((block: any) => block.node.type != "comment");
|
||||
).blocks.filter((block: any) => block.node.type !== "comment");
|
||||
if (!blocks) {
|
||||
return { contents: linkParts.original.trim(), lineNum: 0 };
|
||||
}
|
||||
|
||||
20
src/ExcalidrawLib.d.ts
vendored
20
src/ExcalidrawLib.d.ts
vendored
@@ -1,7 +1,7 @@
|
||||
import { RestoredDataState } from "@zsviczian/excalidraw/types/excalidraw/data/restore";
|
||||
import { ImportedDataState } from "@zsviczian/excalidraw/types/excalidraw/data/types";
|
||||
import { BoundingBox } from "@zsviczian/excalidraw/types/excalidraw/element/bounds";
|
||||
import { ElementsMap, ExcalidrawBindableElement, ExcalidrawElement, ExcalidrawFrameElement, ExcalidrawTextElement, FontFamilyValues, FontString, NonDeleted, NonDeletedExcalidrawElement, Theme } from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
import { ElementsMap, ExcalidrawBindableElement, ExcalidrawElement, ExcalidrawFrameElement, ExcalidrawTextContainer, ExcalidrawTextElement, FontFamilyValues, FontString, NonDeleted, NonDeletedExcalidrawElement, Theme } from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
import { AppState, BinaryFiles, ExportOpts, Point, Zoom } from "@zsviczian/excalidraw/types/excalidraw/types";
|
||||
import { Mutable } from "@zsviczian/excalidraw/types/excalidraw/utility-types";
|
||||
|
||||
@@ -87,6 +87,24 @@ declare namespace ExcalidrawLib {
|
||||
elements: ExcalidrawElement[] | readonly NonDeleted<ExcalidrawElement>[],
|
||||
): BoundingBox;
|
||||
|
||||
function getContainerElement(
|
||||
element: ExcalidrawTextElement | null,
|
||||
elementsMap: ElementsMap,
|
||||
): ExcalidrawTextContainer | null;
|
||||
|
||||
function refreshTextDimensions(
|
||||
textElement: ExcalidrawTextElement,
|
||||
container: ExcalidrawTextContainer | null,
|
||||
elementsMap: ElementsMap,
|
||||
text: string,
|
||||
): {
|
||||
text: string,
|
||||
x: number,
|
||||
y: number,
|
||||
width: number,
|
||||
height: number,
|
||||
};
|
||||
|
||||
function getMaximumGroups(
|
||||
elements: ExcalidrawElement[],
|
||||
elementsMap: ElementsMap,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -26,7 +26,7 @@ import { getParentOfClass, isObsidianThemeDark, getFileCSSClasses } from "./util
|
||||
import { linkClickModifierType } from "./utils/ModifierkeyHelper";
|
||||
import { ImageKey, imageCache } from "./utils/ImageCache";
|
||||
import { FILENAMEPARTS, PreviewImageType } from "./utils/UtilTypes";
|
||||
import { CustomMutationObserver, isDebugMode } from "./utils/DebugHelper";
|
||||
import { CustomMutationObserver, DEBUGGING } from "./utils/DebugHelper";
|
||||
import { getExcalidrawFileForwardLinks } from "./utils/ExcalidrawViewUtils";
|
||||
import { linkPrompt } from "./dialogs/Prompt";
|
||||
|
||||
@@ -86,7 +86,7 @@ const _getPNG = async ({imgAttributes,filenameParts,theme,cacheReady,img,file,ex
|
||||
? 2
|
||||
: 1;
|
||||
|
||||
const cacheKey = {...filenameParts, isDark: theme==="dark", previewImageType: PreviewImageType.PNG, scale};
|
||||
const cacheKey = {...filenameParts, isDark: theme==="dark", previewImageType: PreviewImageType.PNG, scale, isTransparent: !exportSettings.withBackground};
|
||||
|
||||
if(cacheReady) {
|
||||
const src = await imageCache.getImageFromCache(cacheKey);
|
||||
@@ -163,7 +163,7 @@ const _getSVGIMG = async ({filenameParts,theme,cacheReady,img,file,exportSetting
|
||||
exportSettings: ExportSettings,
|
||||
loader: EmbeddedFilesLoader,
|
||||
}):Promise<HTMLImageElement> => {
|
||||
const cacheKey = {...filenameParts, isDark: theme==="dark", previewImageType: PreviewImageType.SVGIMG, scale:1};
|
||||
const cacheKey = {...filenameParts, isDark: theme==="dark", previewImageType: PreviewImageType.SVGIMG, scale:1, isTransparent: !exportSettings.withBackground};
|
||||
if(cacheReady) {
|
||||
const src = await imageCache.getImageFromCache(cacheKey);
|
||||
if(src && typeof src === "string") {
|
||||
@@ -220,13 +220,13 @@ const _getSVGNative = async ({filenameParts,theme,cacheReady,containerElement,fi
|
||||
exportSettings: ExportSettings,
|
||||
loader: EmbeddedFilesLoader,
|
||||
}):Promise<HTMLDivElement> => {
|
||||
const cacheKey = {...filenameParts, isDark: theme==="dark", previewImageType: PreviewImageType.SVG, scale:1};
|
||||
const cacheKey = {...filenameParts, isDark: theme==="dark", previewImageType: PreviewImageType.SVG, scale:1, isTransparent: !exportSettings.withBackground};
|
||||
let maybeSVG;
|
||||
if(cacheReady) {
|
||||
maybeSVG = await imageCache.getImageFromCache(cacheKey);
|
||||
}
|
||||
|
||||
let svg = maybeSVG && (maybeSVG instanceof SVGSVGElement)
|
||||
let svg = (maybeSVG && (maybeSVG instanceof SVGSVGElement))
|
||||
? maybeSVG
|
||||
: convertSVGStringToElement((await createSVG(
|
||||
filenameParts.hasGroupref || filenameParts.hasBlockref || filenameParts.hasSectionref || filenameParts.hasFrameref
|
||||
@@ -250,11 +250,12 @@ const _getSVGNative = async ({filenameParts,theme,cacheReady,containerElement,fi
|
||||
return null;
|
||||
}
|
||||
|
||||
//cache SVG should have the width and height parameters and not the embedded font
|
||||
cacheReady && imageCache.addImageToCache(cacheKey,"", svg);
|
||||
svg = embedFontsInSVG(svg, plugin, true);
|
||||
svg.removeAttribute("width");
|
||||
svg.removeAttribute("height");
|
||||
containerElement.append(svg);
|
||||
cacheReady && imageCache.addImageToCache(cacheKey,"", svg);
|
||||
return containerElement;
|
||||
}
|
||||
|
||||
@@ -614,7 +615,7 @@ const tmpObsidianWYSIWYG = async (
|
||||
) {
|
||||
internalEmbedDiv = internalEmbedDiv.parentElement;
|
||||
}
|
||||
|
||||
|
||||
if(
|
||||
internalEmbedDiv.hasClass("dataview") ||
|
||||
internalEmbedDiv.hasClass("cm-preview-code-block") ||
|
||||
@@ -623,6 +624,15 @@ const tmpObsidianWYSIWYG = async (
|
||||
return; //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/835
|
||||
}
|
||||
|
||||
|
||||
if(!plugin.settings.renderImageInHoverPreviewForMDNotes) {
|
||||
const isHoverPopover = internalEmbedDiv.parentElement?.hasClass("hover-popover");
|
||||
const shouldOpenMD = Boolean(ctx.frontmatter?.["excalidraw-open-md"]);
|
||||
if(isHoverPopover && shouldOpenMD) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const isPrinting = Boolean(internalEmbedDiv.hasClass("print"));
|
||||
|
||||
const attr: imgElementAttributes = {
|
||||
@@ -714,7 +724,7 @@ const tmpObsidianWYSIWYG = async (
|
||||
internalEmbedDiv.appendChild(imgDiv);
|
||||
}, 500);
|
||||
}
|
||||
const observer = isDebugMode
|
||||
const observer = DEBUGGING
|
||||
? new CustomMutationObserver(markdownObserverFn, "markdowPostProcessorObserverFn")
|
||||
: new MutationObserver(markdownObserverFn);
|
||||
observer.observe(internalEmbedDiv, {
|
||||
@@ -808,10 +818,10 @@ const legacyExcalidrawPopoverObserverFn: MutationCallback = async (m) => {
|
||||
if (!plugin.hover.linkText) {
|
||||
return;
|
||||
}
|
||||
if (m.length != 1) {
|
||||
if (m.length !== 1) {
|
||||
return;
|
||||
}
|
||||
if (m[0].addedNodes.length != 1) {
|
||||
if (m[0].addedNodes.length !== 1) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
@@ -848,7 +858,7 @@ const legacyExcalidrawPopoverObserverFn: MutationCallback = async (m) => {
|
||||
node.appendChild(div);
|
||||
};
|
||||
|
||||
export const legacyExcalidrawPopoverObserver = isDebugMode
|
||||
export const legacyExcalidrawPopoverObserver = DEBUGGING
|
||||
? new CustomMutationObserver(legacyExcalidrawPopoverObserverFn, "legacyExcalidrawPopoverObserverFn")
|
||||
: new MutationObserver(legacyExcalidrawPopoverObserverFn);
|
||||
|
||||
|
||||
@@ -9,13 +9,13 @@ export let EXCALIDRAW_PLUGIN: ExcalidrawPlugin = null;
|
||||
export const setExcalidrawPlugin = (plugin: ExcalidrawPlugin) => {
|
||||
EXCALIDRAW_PLUGIN = plugin;
|
||||
};
|
||||
export const MD_TEXTELEMENTS = "# Text Elements";
|
||||
export const MD_JSON_START = "```json\n";
|
||||
export const MD_JSON_END = "```";
|
||||
export const MD_DRAWING = "# Drawing";
|
||||
export const MD_ELEMENTLINKS = "# Element Links";
|
||||
export const MD_EMBEDFILES = "# Embed files";
|
||||
export const MD_EX_SECTIONS = [MD_TEXTELEMENTS, MD_DRAWING, MD_ELEMENTLINKS, MD_EMBEDFILES];
|
||||
const MD_EXCALIDRAW = "# Excalidraw Data";
|
||||
const MD_TEXTELEMENTS = "## Text Elements";
|
||||
const MD_ELEMENTLINKS = "## Element Links";
|
||||
const MD_EMBEDFILES = "## Embedded Files";
|
||||
const MD_DRAWING = "## Drawing";
|
||||
|
||||
export const MD_EX_SECTIONS = [MD_EXCALIDRAW, MD_TEXTELEMENTS, MD_ELEMENTLINKS, MD_EMBEDFILES, MD_DRAWING];
|
||||
|
||||
export const ERROR_IFRAME_CONVERSION_CANCELED = "iframe conversion canceled";
|
||||
|
||||
@@ -94,6 +94,8 @@ export const {
|
||||
mutateElement,
|
||||
restore,
|
||||
mermaidToExcalidraw,
|
||||
getContainerElement,
|
||||
refreshTextDimensions,
|
||||
} = excalidrawLib;
|
||||
|
||||
export function JSON_parse(x: string): any {
|
||||
@@ -176,7 +178,8 @@ export const FRONTMATTER_KEYS:{[key:string]: {name: string, type: string, depric
|
||||
"border-color": {name: "excalidraw-border-color", type: "text"},
|
||||
"md-css": {name: "excalidraw-css", type: "text"},
|
||||
"autoexport": {name: "excalidraw-autoexport", type: "text"},
|
||||
"iframe-theme": {name: "excalidraw-iframe-theme", type: "text"},
|
||||
"iframe-theme": {name: "excalidraw-iframe-theme", type: "text", depricated: true},
|
||||
"embeddable-theme": {name: "excalidraw-embeddable-theme", type: "text"},
|
||||
"open-as-markdown": {name: "excalidraw-open-md", type: "checkbox"},
|
||||
};
|
||||
|
||||
@@ -197,7 +200,7 @@ export const FRONTMATTER = [
|
||||
"tags: [excalidraw]",
|
||||
"",
|
||||
"---",
|
||||
"==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠==",
|
||||
"==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== You can decompress Drawing data with the command palette: 'Decompress current Excalidraw file'. For more info check in plugin settings under 'Saving'",
|
||||
"",
|
||||
"",
|
||||
].join("\n");
|
||||
|
||||
@@ -174,16 +174,24 @@ export class EmbeddableSettings extends Modal {
|
||||
const fnparts = splitFolderAndFilename(newPathWithExt);
|
||||
const newPath = getNewUniqueFilepath(
|
||||
this.app.vault,
|
||||
fnparts.folderpath,
|
||||
fnparts.filename,
|
||||
fnparts.folderpath,
|
||||
);
|
||||
await this.app.vault.rename(this.file,newPath);
|
||||
el.link = this.element.link.replace(
|
||||
/(\[\[)([^#\]]*)([^\]]*]])/,`$1${
|
||||
this.plugin.app.metadataCache.fileToLinktext(
|
||||
this.file,this.view.file.path,true)
|
||||
}$3`);
|
||||
dirty = true;
|
||||
if(this.app.vault.getAbstractFileByPath(newPath)) {
|
||||
new Notice("File rename failed. A file with this name already exists.\n"+newPath,10000);
|
||||
} else {
|
||||
try {
|
||||
await this.app.vault.rename(this.file,newPath);
|
||||
el.link = this.element.link.replace(
|
||||
/(\[\[)([^#\]]*)([^\]]*]])/,`$1${
|
||||
this.plugin.app.metadataCache.fileToLinktext(
|
||||
this.file,this.view.file.path,true)
|
||||
}$3`);
|
||||
dirty = true;
|
||||
} catch(e) {
|
||||
new Notice("File rename failed. "+e,10000);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if(this.isYouTube && this.youtubeStart !== getYouTubeStartAt(this.element.link)) {
|
||||
|
||||
@@ -6,7 +6,7 @@ If you'd like to learn more, please subscribe to my YouTube channel: [Visual PKM
|
||||
Thank you & Enjoy!
|
||||
|
||||
<div class="excalidraw-videoWrapper"><div>
|
||||
<iframe src="https://www.youtube.com/embed/o0exK-xFP3k" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
<iframe src="https://www.youtube.com/embed/P_Q6avJGoWI" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
</div></div>
|
||||
`;
|
||||
|
||||
@@ -17,6 +17,213 @@ I develop this plugin as a hobby, spending my free time doing this. If you find
|
||||
|
||||
<div class="ex-coffee-div"><a href="https://ko-fi.com/zsolt"><img src="https://cdn.ko-fi.com/cdn/kofi3.png?v=3" height=45></a></div>
|
||||
`,
|
||||
"2.2.5": `
|
||||
## Fixed
|
||||
- Cursor visibility in dark mode [#1812](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1812)
|
||||
- SVG to Excalidraw now...
|
||||
- converts elements inside the ${String.fromCharCode(96)}<switch>${String.fromCharCode(96)} tag, improving compatibility with SVGs from [The Noun Project](https://thenounproject.com/)
|
||||
- sets visibility for all elements, preventing invisible converted images.
|
||||
- Cached images sometimes lost their font face and natural size when nested in an Excalidraw scene. This issue occurred when drawings were embedded in a markdown note (native SVG) and nested in a drawing simultaneously. Depending on the update and render sequence, these drawings sometimes appeared incorrectly in the Excalidraw scene.
|
||||
`,
|
||||
"2.2.4":`
|
||||
<div style="text-align: center;">
|
||||
<a data-tooltip-position="top" aria-label="https://youtube.com/shorts/zF1p2yfk4f4" rel="noopener" class="external-link" href="https://youtube.com/shorts/zF1p2yfk4f4" target="_blank">
|
||||
<img src="https://private-user-images.githubusercontent.com/14358394/335857018-c4f5c4c7-9b8f-427f-aa6f-8c1b189610af.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MTczMjQxNDksIm5iZiI6MTcxNzMyMzg0OSwicGF0aCI6Ii8xNDM1ODM5NC8zMzU4NTcwMTgtYzRmNWM0YzctOWI4Zi00MjdmLWFhNmYtOGMxYjE4OTYxMGFmLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNDA2MDIlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQwNjAyVDEwMjQwOVomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTdhZGUwNDRkZmM2NmJjNTNiYjUwNjMxMmU2MGEyZTQwZGQwNmUyZmI5ZDFhNzMwMzg2OThjMjhmZmJkNzNiZDkmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JmFjdG9yX2lkPTAma2V5X2lkPTAmcmVwb19pZD0wIn0.wZfkXmBRcXwz0pN6q0EEvmwtxVAB9ymPk9a9upmGXYE" referrerpolicy="no-referrer" style="width: 150px; margin: 0 auto;">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
## New from Excalidraw.com
|
||||
- You can now set the text width even during creation. Simply drag with the text tool. Note, there's a minimum distance before the text enters the wrapped mode so there aren't false positives. [See example here](https://x.com/excalidraw/status/1795468201335075000)
|
||||
|
||||
## New
|
||||
- Updated zh-cn translation. Thank you @dmscode
|
||||
- New context menu and command palette action: "Move back-of-note card to File". This is only active when an eligible embeddable element is selected.
|
||||
|
||||
## Fixed
|
||||
- Setting different autosaveIntervals on Desktop and Mobile will no longer trigger unnecessary commits to settings each time you open Excalidraw on a different device. Thanks @jmhammond for the contribution! [#1805](https://github.com/zsviczian/obsidian-excalidraw-plugin/pull/1805), [#1652](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1652), [#888](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/888)
|
||||
|
||||
## Fixed in ExcalidrawAutomate
|
||||
- ${String.fromCharCode(96)}getCM(color)${String.fromCharCode(96)} was missing from ${String.fromCharCode(96)}ea.help()${String.fromCharCode(96)}. It is now added. getCM returns a ColorMaster object. ColorMaster is a powerful library should you want to create scripts to manipulate colors. Check out my [Scripting Colors](https://youtu.be/7gJDwNgQ6NU) video should you want to learn more. [#1806](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1806)
|
||||
`,
|
||||
"2.2.3":`
|
||||
## Fixed
|
||||
- Undo history was not properly initialized [#1791](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1785)
|
||||
- Excalidraw did not save edits when switching to markdown view mode with a hotkey or terminating the popout window
|
||||
- SVG export did not maintain the aspect ratio of manually distorted images [#1780](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1780)
|
||||
|
||||
## New
|
||||
- In pen mode, double tapping the screen will toggle the eraser tool when using freedraw tool, or one of the other tools in locked mode.
|
||||
- New setting under "Excalidraw appearance and behavior" to disable rendering of Excalidraw drawings in hover previews, in case the file has the ${String.fromCharCode(96)}excalidraw-open-md: true${String.fromCharCode(96)} frontmatter property [#1795](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1795)
|
||||
- Additional foolproofing of ${String.fromCharCode(96)}# Excalidraw Data${String.fromCharCode(96)}. The file is now more resilient to automated linting and other changes. There is also a new setting under "Compatibility Features" to add a dummy first text element to ${String.fromCharCode(96)}## Text Elements${String.fromCharCode(96)}. You can use this feature if your auto-linter adds empty lines after section headings.
|
||||
- Pasting markdown code blocks will create a back-of-the-note card with the code block. CTRL+SHIFT+V will paste the text as a normal text element. When copying code from Chat GPT the markdown code fence (triple backtick) is missing. In this case, you may use the new context menu action "Paste code block" to create a back of the note card with the code block.
|
||||
- Pasting long text will be wrapped in the text element.
|
||||
|
||||
## New in ExcalidrawAutomate
|
||||
- Updated viewUpdateScene: This now implements the [new Excalidraw API](https://github.com/excalidraw/excalidraw/pull/7898)
|
||||
${String.fromCharCode(96, 96, 96)}ts
|
||||
viewUpdateScene (
|
||||
scene: {
|
||||
elements?: ExcalidrawElement[],
|
||||
appState?: AppState,
|
||||
files?: BinaryFileData,
|
||||
commitToHistory?: boolean,
|
||||
storeAction?: "capture" | "none" | "update",
|
||||
},
|
||||
restore: boolean = false,
|
||||
):void ;
|
||||
${String.fromCharCode(96, 96, 96)}
|
||||
- Updated addText. The function now supports the new text-wrapping feature
|
||||
${String.fromCharCode(96, 96, 96)}ts
|
||||
addText(
|
||||
topX: number,
|
||||
topY: number,
|
||||
text: string,
|
||||
formatting?: {
|
||||
autoResize?: boolean; //Default is true. Setting this to false will wrap the text in the text element without the need for the container. If set to false, you must set a width value as well.
|
||||
wrapAt?: number; //wrapAt is ignored if autoResize is set to false (and width is provided)
|
||||
width?: number;
|
||||
height?: number;
|
||||
textAlign?: "left" | "center" | "right";
|
||||
box?: boolean | "box" | "blob" | "ellipse" | "diamond";
|
||||
boxPadding?: number;
|
||||
boxStrokeColor?: string;
|
||||
textVerticalAlign?: "top" | "middle" | "bottom";
|
||||
},
|
||||
id?: string,
|
||||
): string
|
||||
${String.fromCharCode(96, 96, 96)}
|
||||
`,
|
||||
"2.2.2":`
|
||||
## Fixed
|
||||
- ExcaliBrain stopped working with 2.2.0
|
||||
|
||||

|
||||
`,
|
||||
"2.2.1":`
|
||||
## Fixed
|
||||
- Text height becomes unreadable after 2.2.0 update [#1784](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1784)
|
||||
- Images are loaded with a rounded border when loading old Excalidraw files
|
||||
- Embedded Excalidraw images cache gets stuck with old version of the image
|
||||
- Extremely long loading times with legacy (3+ years old) Excalidraw files
|
||||
`,
|
||||
"2.2.0":`
|
||||
<div class="excalidraw-videoWrapper"><div>
|
||||
<iframe src="https://www.youtube.com/embed/dV0NEOwn5NM" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
</div></div>
|
||||
|
||||
⚠️⚠️⚠️ BREAKING CHANGE ⚠️⚠️⚠️
|
||||
Files you save with 2.2.0 are not backward compatible with earlier plugin versions!
|
||||
|
||||
## New from excalidraw.com
|
||||
- Wrapable text elements (without the need for transparent sticky notes!)
|
||||
|
||||
## New
|
||||
- File format. I nested all Excalidraw markup under ${String.fromCharCode(96)}# Excalidraw Data${String.fromCharCode(96)}. Here's the new structure.
|
||||
${String.fromCharCode(96,96,96)}markdown
|
||||
---
|
||||
excalidraw-plugin: parsed
|
||||
other-frontmatter-properties: values
|
||||
---
|
||||
back of the note bla bla bla
|
||||
|
||||
# Excalidraw Data
|
||||
## Text Element
|
||||
## Element Links
|
||||
## Embedded Files
|
||||
%%
|
||||
## Drawing
|
||||
%%
|
||||
${String.fromCharCode(96,96,96)}
|
||||
- When opening Excalidraw in Markdown ${String.fromCharCode(96)}# Excalidraw Data${String.fromCharCode(96)} will be folded.
|
||||
- New command palette action: ${String.fromCharCode(96)}Open the back-of-the-note of the selected Excalidraw image${String.fromCharCode(96)}. The action is only visible when selecting an embedded Excalidraw drawing in the Scene. On a desktop, the command will open the back of the selected card in a popout window, and on a mobile, in a new tab.
|
||||
|
||||
## Fixed
|
||||
- Drag and drop from Finder/Explorer (OS external). Images will retain their filenames. PDFs will be imported to the Vault. [#1779](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1779)
|
||||
`,
|
||||
"2.1.8":`
|
||||
## Fixed
|
||||
- Fixing issues that surfaced after upgrading to Obsidian 1.6.0
|
||||
- Fixed Excalidraw hover previews
|
||||
- Cursor for editing links, text elements, and frame names became virtually invisible if Obsidian was in dark mode and Excalidraw in light mode and vica versa.
|
||||
- Rendering Excalidraw drawings in Markdown views, right after Obsidian loaded did not work.
|
||||
- I implemented more graceful fail if you submitted a malformed SVG for SVG to Excalidraw conversation. [#1768](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1768)
|
||||
|
||||
## New
|
||||
- New setting under "Save" in plugin settings to NOT decompress JSON when switching to Markdown view mode. For details see description under "Save" settings. The benefit is smaller file size and fewer results in the Obsidian search. If you want to edit the JSON, you can always manually decompress JSON in markdown view mode with the command palette action "Excalidraw: Decompress JSON".
|
||||
`,
|
||||
"2.1.7:":`
|
||||
<div class="excalidraw-videoWrapper"><div>
|
||||
<iframe src="https://www.youtube.com/embed/P_Q6avJGoWI" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
</div></div>
|
||||
|
||||
## Updates from Excalidraw.com
|
||||
- Improved undo management.
|
||||
- Improved handle to scale images from the side.
|
||||
- Changed arrow binding behavior.
|
||||
- Many other minor fixes and improvements.
|
||||
|
||||
## New
|
||||
- Introduced image caching for nested (embedded) Excalidraw drawings on the scene. This enhancement should lead to improved scene loading times, especially when dealing with numerous embedded Excalidraw drawings.
|
||||
- Added new OCR Command Palette actions. Users can now re-run OCR and run OCR for selected elements.
|
||||
|
||||
## Fixed
|
||||
- Fixed an issue where cropping an embeddable PDF frame in the Excalidraw Scene caused distortion based on the embeddable element's aspect ratio. ([#1756](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1756))
|
||||
- Removed the listing of ${String.fromCharCode(96)}# Embedded files${String.fromCharCode(96)} section when adding a "Back of the note card". ([#1754](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1754))
|
||||
- Resolved the issue where closing the on-screen keyboard with the keyboard hide button of your phone, instead of tapping somewhere else on the Excalidraw scene, did not resize the scene correctly. ([#1729](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1729))
|
||||
- Fixed the problem where pasting a text element as text into markdown incorrectly pasted the text to the end of the MD note, with line breaks as rendered on screen in Excalidraw. Also addressed the issue where pasting an image element as an image resulted in it being pasted to the end of the document. ([#1749](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1749))
|
||||
- Corrected the color inversion of embedded images when changing the theme from light to dark, then back from dark to light, and again from light to dark on the third change.
|
||||
- Addressed the problem where cropping an image while unlocking and rotating it in the cropper did not reflect the rotation. Note that rotating the image in Cropper required switching to markdown view mode, changing the "locked": true property to false, then switching back to Excalidraw mode. This issue likely impacted only a very few power users. ([#1745](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1745))
|
||||
|
||||
## New in ExcalidrawAutomate
|
||||
${String.fromCharCode(96,96,96)}ts
|
||||
/**
|
||||
* Retruns the embedded images in the scene recursively. If excalidrawFile is not provided,
|
||||
* the function will use ea.targetView.file
|
||||
* @param excalidrawFile
|
||||
* @returns TFile[] of all nested images and Excalidraw drawings recursively
|
||||
*/
|
||||
public getEmbeddedImagesFiletree(excalidrawFile?: TFile): TFile[];
|
||||
${String.fromCharCode(96,96,96)}
|
||||
|
||||
`,
|
||||
"2.1.6":`
|
||||
## Two minor fixes
|
||||
- Scaling of LaTeX formulas when the formula is changed
|
||||
- If the back of the note card only contains a block embed ${String.fromCharCode(96)}![[embed]]${String.fromCharCode(96)} this got removed when saving the Excalidraw file. This issue has been present since November, 2021 (v1.4.9).
|
||||
`,
|
||||
"2.1.5":`
|
||||
## New
|
||||
- Save "Snap to objects" with the scene state. If this is the only change you make to the scene, force save it using CTRL+S (note, use CTRL on Mac as well).
|
||||
- Added "Copy markdown link" to the context menu.
|
||||
|
||||
## Fixed
|
||||
- Paste operation occasionally duplicated text elements.
|
||||
- Pasting multiple instances of the same image from excalidraw.com or another instance of Obsidian, or pasting an image from anywhere and making copies with ALT/OPT + drag immediately after pasting (before autosave triggered) led to broken images when reopening the drawing.
|
||||
- CTRL/CMD+Click on a Text Element with an element link did not work (previously, you had to click the top right link indicator). Now, you can click anywhere on the element.
|
||||
- Hover preview for elements with a link only worked when hovering over the element link. Now, you can hover anywhere. If there are multiple elements with links, the top-level element will take precedence.
|
||||
- Link navigation within drawing when the "Focus on Existing Tab" feature is enabled under "Links, transclusion and TODOs" in settings works again.
|
||||
- If a link points to a back-of-the-card section or block the drawing will automatically switch to markdown view mode and navigate to the block or section.
|
||||
- DynamicSytle, dark mode when canvas background is set to transparent.
|
||||
- Scale to maintain the aspect ratio of a markdown notes embedded as images.
|
||||
- You can now borrow interactive markdown embeds to tables, blockquotes, list elements and callouts - not just paragraphs.
|
||||
- Back of the drawing cards:
|
||||
- Leaving the Section Name empty when creating the first back of the card note resulted in an error.
|
||||
- If you add the markdown comment (${String.fromCharCode(96)}%%${String.fromCharCode(96)}) directly before ${String.fromCharCode(96)}# Text Elements${String.fromCharCode(96)}, a trailing ${String.fromCharCode(96)}#${String.fromCharCode(96)} will be added to your document, when adding a back of the card note. This is to hide the markdown comment from the card. The trailing (empty) ${String.fromCharCode(96)}#${String.fromCharCode(96)} will not be visible in reading mode, pdf exports, and when publishing with Obsidian Publish.
|
||||
Here's a sample markdown structure of your document:
|
||||
|
||||
${String.fromCharCode(96,96,96)}markdown
|
||||
---
|
||||
excalidraw-plugin: parsed
|
||||
---
|
||||
# Your back of the card section
|
||||
bla bla bla
|
||||
|
||||
#
|
||||
%%
|
||||
# Text Elements
|
||||
... the rest of the Excalidraw file
|
||||
${String.fromCharCode(96,96,96)}
|
||||
`,
|
||||
"2.1.4":`
|
||||
## Fixed
|
||||
- Fixed the **aspect ratio** of an Excalidraw embedded within another Excalidraw **not updating**. [#1707](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1707)
|
||||
@@ -33,7 +240,7 @@ I develop this plugin as a hobby, spending my free time doing this. If you find
|
||||
- **Enhanced annotation and cropping** of images in Markdown documents:
|
||||
- Newly embedded **links will now follow the style of the original link**. If the original format was a ${String.fromCharCode(96)}${String.fromCharCode(96)}, the annotated file will follow this format. For ${String.fromCharCode(96)}[[wiki links]]${String.fromCharCode(96)}, it will follow that style. Additionally, if an alias was specified like ${String.fromCharCode(96)}[[link|alias]]${String.fromCharCode(96)}, the annotated or cropped image will retain the alias.
|
||||
- Introduced a new setting under "Saving" titled **"Preserve image size when annotating"**. This setting is disabled by default. When enabled, the embed link replacing the annotated image will maintain the size of the original image.
|
||||
- Option to **automaticaly embed the scene in exported PNG and SVG image files**. Including the scene will allow users to open the picture on Excalidraw.com or in another Obsidian Vault as an editable Excalidraw file.New setting is under the Export category. The new frontmatter tag is: ${String.fromCharCode(96)}excalidraw-export-embed-scene: true/false${String.fromCharCode(96)}.
|
||||
- Option to **automatically embed the scene in exported PNG and SVG image files**. Including the scene will allow users to open the picture on Excalidraw.com or in another Obsidian Vault as an editable Excalidraw file.New setting is under the Export category. The new frontmatter tag is: ${String.fromCharCode(96)}excalidraw-export-embed-scene: true/false${String.fromCharCode(96)}.
|
||||
`,
|
||||
"2.1.3":`
|
||||
This is a republish of 2.1.2 with a minor change. Sorry about the frequent releases. I will hold back for a few weeks now.
|
||||
|
||||
@@ -748,8 +748,6 @@ export const linkPrompt = async (linkText:string, app: App, view?: ExcalidrawVie
|
||||
return [file, linkText, subpath];
|
||||
}
|
||||
|
||||
|
||||
|
||||
export const templatePromt = async (files: TFile[], app: App): Promise<TFile> => {
|
||||
if(files.length === 1) return files[0];
|
||||
return ((await linkPrompt(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { MarkdownRenderer, Modal, Notice, request } from "obsidian";
|
||||
import ExcalidrawPlugin from "../main";
|
||||
import { errorlog, escapeRegExp, log } from "../utils/Utils";
|
||||
import { errorlog, escapeRegExp } from "../utils/Utils";
|
||||
import { log } from "src/utils/DebugHelper";
|
||||
|
||||
const URL =
|
||||
"https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/index-new.md";
|
||||
|
||||
@@ -3,11 +3,8 @@ import { t } from "../lang/helpers";
|
||||
import ExcalidrawView from "src/ExcalidrawView";
|
||||
import { getEA } from "src";
|
||||
import { ExcalidrawAutomate } from "src/ExcalidrawAutomate";
|
||||
import { getExcalidrawMarkdownHeaderSection } from "src/ExcalidrawData";
|
||||
import { MD_EX_SECTIONS } from "src/constants/constants";
|
||||
import { ExcalidrawImageElement } from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
import { ExcalidrawImperativeAPI } from "@zsviczian/excalidraw/types/excalidraw/types";
|
||||
import { cleanSectionHeading } from "src/utils/ObsidianUtils";
|
||||
import { addBackOfTheNoteCard } from "src/utils/ExcalidrawViewUtils";
|
||||
|
||||
export class SelectCard extends FuzzySuggestModal<string> {
|
||||
|
||||
@@ -29,46 +26,12 @@ export class SelectCard extends FuzzySuggestModal<string> {
|
||||
if (e.key == "Enter") {
|
||||
if (this.containerEl.innerText.includes(t("EMPTY_SECTION_MESSAGE"))) {
|
||||
const item = this.inputEl.value;
|
||||
if(MD_EX_SECTIONS.includes(item)) {
|
||||
if(item === "" || MD_EX_SECTIONS.includes(item)) {
|
||||
new Notice(t("INVALID_SECTION_NAME"));
|
||||
this.close();
|
||||
return;
|
||||
}
|
||||
(async () => {
|
||||
const data = view.data;
|
||||
const header = getExcalidrawMarkdownHeaderSection(data);
|
||||
view.data = data.replace(header, header + `\n# ${item}\n\n`);
|
||||
await view.forceSave(true);
|
||||
let watchdog = 0;
|
||||
await sleep(200);
|
||||
let found:string;
|
||||
while (watchdog++ < 10 && !(found=(await this.app.metadataCache.blockCache
|
||||
.getForFile({ isCancelled: () => false },view.file))
|
||||
.blocks.filter((b: any) => b.display && b.node?.type === "heading")
|
||||
.filter((b: any) => !MD_EX_SECTIONS.includes(b.display))
|
||||
.map((b: any) => cleanSectionHeading(b.display))
|
||||
.find((b: any) => b === item))) {
|
||||
await sleep(200);
|
||||
}
|
||||
|
||||
const ea = getEA(this.view) as ExcalidrawAutomate;
|
||||
const id = ea.addEmbeddable(
|
||||
0,0,400,500,
|
||||
`[[${this.view.file.path}#${item}]]`
|
||||
);
|
||||
await ea.addElementsToView(true, false, true);
|
||||
|
||||
const api = view.excalidrawAPI as ExcalidrawImperativeAPI;
|
||||
const el = ea.getViewElements().find(el=>el.id === id);
|
||||
api.selectElements([el]);
|
||||
setTimeout(()=>{
|
||||
api.updateScene({appState: {activeEmbeddable: {element: el, state: "active"}}});
|
||||
if(found) view.getEmbeddableLeafElementById(el.id)?.editNode?.();
|
||||
});
|
||||
})();
|
||||
//create new section
|
||||
//`# ${this.inputEl.value}\n\n`;
|
||||
//Do not allow MD_EX_SECTIONS
|
||||
}
|
||||
addBackOfTheNoteCard(this.view, item);
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,9 @@ const hyperlink = (url: string, text: string) => {
|
||||
return `<a onclick='window.open("${url}")'>${text}</a>`;
|
||||
}
|
||||
|
||||
const EMBEDDABLE_MDCUSTOMPROPS = `type EmbeddableMDCustomProps = {<br>useObsidianDefaults: boolean;<br>backgroundMatchCanvas: boolean;<br>backgroundMatchElement: boolean;<br>backgroundColor: string;<br>backgroundOpacity: number;<br>borderMatchElement: boolean;<br>borderColor: string;<br>borderOpacity: number;<br>filenameVisible: boolean;<br>};<br>`;
|
||||
|
||||
|
||||
export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
|
||||
{
|
||||
field: "help",
|
||||
@@ -197,6 +200,7 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
|
||||
' "excalidraw-export-dark"?: boolean;\n' +
|
||||
' "excalidraw-export-padding"?: number;\n' +
|
||||
' "excalidraw-export-pngscale"?: number;\n' +
|
||||
' "excalidraw-export-embed-scene"?: boolean;\n' +
|
||||
' "excalidraw-default-mode"?: "view" | "zen";\n' +
|
||||
' "excalidraw-onload-script"?: string;\n' +
|
||||
' "excalidraw-linkbutton-opacity"?: number;\n' +
|
||||
@@ -261,8 +265,10 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
|
||||
},
|
||||
{
|
||||
field: "addText",
|
||||
code: 'addText(topX: number, topY: number, text: string, formatting?: {wrapAt?: number; width?: number; height?: number; textAlign?: "left" | "center" | "right"; textVerticalAlign: "top" | "middle" | "bottom"; box?: boolean | "box" | "blob" | "ellipse" | "diamond"; boxPadding?: number; boxStrokeColor?: string;}, id?: string,): string;',
|
||||
desc: "If box is !null, then text will be boxed\nThe function returns the id of the TextElement. If the text element is boxed i.e. it is a sticky note, then the id of the container object",
|
||||
code: 'addText(topX: number, topY: number, text: string, formatting?: {autoResize?: boolean; wrapAt?: number; width?: number; height?: number; textAlign?: "left" | "center" | "right"; textVerticalAlign: "top" | "middle" | "bottom"; box?: boolean | "box" | "blob" | "ellipse" | "diamond"; boxPadding?: number; boxStrokeColor?: string;}, id?: string,): string;',
|
||||
desc: "If box is !null, then text will be boxed\nThe function returns the id of the TextElement. If the text element is boxed i.e. it is a sticky note, then the id of the container object.\n"+
|
||||
"Default value for autoResize is true. Setting autoResize to false will wrap the text in the text element without the need for the container. If set to false, you must provide a width value as well.\n" +
|
||||
"wrapAt will be ignored if autoResize is set to false (and a width is also provided)",
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
@@ -285,8 +291,9 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
|
||||
},
|
||||
{
|
||||
field: "addEmbeddable",
|
||||
code: "addEmbeddable(topX: number, topY: number, width: number, height: number, url?: string, file?: TFile): string;",
|
||||
desc: "Adds an iframe/webview (depending on content and platform) to the drawing. If url is not null then the iframe/webview will be loaded from the url. The url maybe a markdown link to an note in the Vault or a weblink. If url is null then the iframe/webview will be loaded from the file. Both the url and the file may not be null.",
|
||||
code: "addEmbeddable(topX: number, topY: number, width: number, height: number, url?: string, file?: TFile, embeddableCustomData?: EmbeddableMDCustomProps): string;",
|
||||
desc: "Adds an iframe/webview (depending on content and platform) to the drawing. If url is not null then the iframe/webview will be loaded from the url. The url maybe a markdown link to an note in the Vault or a weblink. " +
|
||||
"If url is null then the iframe/webview will be loaded from the file. Both the url and the file may not be null.<br>" + EMBEDDABLE_MDCUSTOMPROPS,
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
@@ -364,6 +371,14 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
|
||||
desc: null,
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
field: "addBackOfTheCardNoteToView",
|
||||
code: "async addBackOfTheCardNoteToView(sectionTitle: string, activate: boolean = false, sectionBody?: string, embeddableCustomData?: EmbeddableMDCustomProps): Promise<string>",
|
||||
desc: "Adds a back of the note card to the current active view. If <b>body</b> is provided the note will be created with the body text, otherwise the note will be created with the title only.<br>Returns the id of the created element.<br>" +
|
||||
"If <b>activate</b> is true, the embedded note will be activated for editing.<br>" +
|
||||
"This is an async function, if you need the element ID of the created element, the function should be awaited.<br>" + EMBEDDABLE_MDCUSTOMPROPS,
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
field: "getViewSelectedElement",
|
||||
code: "getViewSelectedElement(): ExcalidrawElement;",
|
||||
@@ -496,6 +511,12 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
|
||||
desc: "Measures text size based on current style settings",
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
field: "getOriginalImageSize",
|
||||
code: "async getOriginalImageSize(imageElement: ExcalidrawImageElement): Promise<{width: number; height: number}>",
|
||||
desc: "Returns the size of the image element at 100% (i.e. the original size). This is an async function, you need to await the result.",
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
field: "verifyMinimumPluginVersion",
|
||||
code: "verifyMinimumPluginVersion(requiredVersion: string): boolean;",
|
||||
@@ -556,6 +577,14 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
|
||||
desc: "Converts a CSS color name to its HEX color equivalent. 'White' to #FFFFFF",
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
field: "getCM",
|
||||
code: "getCM(color:TInput): ColorMaster;",
|
||||
desc: `Returns a ${hyperlink("https://github.com/lbragile/ColorMaster", "ColorMaster")} object. ` +
|
||||
"The function also accepts css color names. Under the hood, before calling ColorMaster it uses " +
|
||||
"colorNameToHex to convert the color name to a HEX color.",
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
field: "obsidian",
|
||||
code: "obsidian",
|
||||
@@ -570,6 +599,13 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
|
||||
"If no template is set, it returns null.",
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
field: "getEmbeddedImagesFiletree",
|
||||
code: "getEmbeddedImagesFiletree(excalidrawFile?: TFile): TFile[]",
|
||||
desc: "Retruns the embedded images in the scene recursively. If excalidrawFile is not provided, " +
|
||||
"the function will use ea.targetView.file",
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
field: "getAttachmentFilepath",
|
||||
code: "async getAttachmentFilepath(filename: string): Promise<string>",
|
||||
@@ -678,8 +714,9 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
|
||||
},
|
||||
{
|
||||
field: "viewUpdateScene",
|
||||
code: "viewUpdateScene(scene:{elements?:ExcalidrawElement[],appState?: AppState,files?: BinaryFileData,commitToHistory?: boolean,},restore:boolean=false):void",
|
||||
desc: "Calls the ExcalidrawAPI updateScene function for the targetView. When restore=true, excalidraw will try to correct errors in the scene such as setting default values to missing element properties.",
|
||||
code: "viewUpdateScene(scene:{elements?:ExcalidrawElement[],appState?: AppState,files?: BinaryFileData,commitToHistory?: boolean,storeAction?: 'capture' | 'none' | 'update'},restore:boolean=false):void",
|
||||
desc: "Calls the ExcalidrawAPI updateScene function for the targetView. When restore=true, excalidraw will try to correct errors in the scene such as setting default values to missing element properties. " +
|
||||
`Note that commitToHistory has been deprecated in Excalidraw and is no longer used. You should use storeAction instead. See ${hyperlink("https://github.com/excalidraw/excalidraw/pull/7898", "ExcalidrawAPI")} documentation for more information.`,
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
@@ -821,6 +858,12 @@ export const FRONTMATTER_KEYS_INFO: SuggesterInfo[] = [
|
||||
desc: "If this key is present it will override the default excalidraw embed and export setting. This only affects export to PNG. Specify the export scale for the image. The typical range is between 0.5 and 5, but you can experiment with other values as well.",
|
||||
after: ": 1",
|
||||
},
|
||||
{
|
||||
field: "excalidraw-export-embed-scene",
|
||||
code: null,
|
||||
desc: "If this key is present it will override the default excalidraw embed and export setting.",
|
||||
after: ": false",
|
||||
},
|
||||
{
|
||||
field: "open-md",
|
||||
code: null,
|
||||
@@ -834,9 +877,9 @@ export const FRONTMATTER_KEYS_INFO: SuggesterInfo[] = [
|
||||
after: ": png",
|
||||
},
|
||||
{
|
||||
field: "iframe-theme",
|
||||
field: "embeddable-theme",
|
||||
code: null,
|
||||
desc: "Override iFrame theme plugin-settings for this file. 'match' will match the Excalidraw theme, 'default' will match the obsidian theme. Valid values are\ndark\nlight\nauto\ndefault",
|
||||
desc: "Override embeddable's theme plugin-settings for this file. 'auto' will match the Excalidraw theme, 'default' will match the Obsidian theme. Valid values are\ndark\nlight\nauto\ndefault",
|
||||
after: ": auto",
|
||||
},
|
||||
{
|
||||
|
||||
@@ -19,7 +19,7 @@ export class UniversalInsertFileModal extends Modal {
|
||||
private plugin: ExcalidrawPlugin,
|
||||
private view: ExcalidrawView,
|
||||
) {
|
||||
super(app);
|
||||
super(plugin.app);
|
||||
const appState = (view.excalidrawAPI as ExcalidrawImperativeAPI).getAppState();
|
||||
const containerRect = view.containerEl.getBoundingClientRect();
|
||||
const viewportWidth = window.innerWidth || document.documentElement.clientWidth;
|
||||
|
||||
@@ -9,6 +9,7 @@ export default {
|
||||
// main.ts
|
||||
CONVERT_URL_TO_FILE: "Save image from URL to local file",
|
||||
UNZIP_CURRENT_FILE: "Decompress current Excalidraw file",
|
||||
ZIP_CURRENT_FILE: "Compress current Excalidraw file",
|
||||
PUBLISH_SVG_CHECK: "Obsidian Publish: Find SVG and PNG exports that are out of date",
|
||||
EMBEDDABLE_PROPERTIES: "Embeddable Properties",
|
||||
EMBEDDABLE_RELATIVE_ZOOM: "Scale selected embeddable elements to 100% relative to the current canvas zoom",
|
||||
@@ -35,6 +36,7 @@ export default {
|
||||
TRANSCLUDE: "Embed a drawing",
|
||||
TRANSCLUDE_MOST_RECENT: "Embed the most recently edited drawing",
|
||||
TOGGLE_LEFTHANDED_MODE: "Toggle left-handed mode",
|
||||
FLIP_IMAGE: "Open the back-of-the-note of the selected excalidraw image",
|
||||
NEW_IN_NEW_PANE: "Create new drawing - IN AN ADJACENT WINDOW",
|
||||
NEW_IN_NEW_TAB: "Create new drawing - IN A NEW TAB",
|
||||
NEW_IN_ACTIVE_PANE: "Create new drawing - IN THE CURRENT ACTIVE WINDOW",
|
||||
@@ -48,6 +50,7 @@ export default {
|
||||
NEW_IN_POPOUT_WINDOW_EMBED: "Create new drawing - IN A POPOUT WINDOW - and embed into active document",
|
||||
TOGGLE_LOCK: "Toggle Text Element between edit RAW and PREVIEW",
|
||||
DELETE_FILE: "Delete selected image or Markdown file from Obsidian Vault",
|
||||
COPY_ELEMENT_LINK: "Copy markdown link for selected element(s)",
|
||||
INSERT_LINK_TO_ELEMENT:
|
||||
`Copy markdown link for selected element to clipboard. ${labelCTRL()}+CLICK to copy 'group=' link. ${labelSHIFT()}+CLICK to copy an 'area=' link. ${labelALT()}+CLICK to watch a help video.`,
|
||||
INSERT_LINK_TO_ELEMENT_GROUP:
|
||||
@@ -69,13 +72,19 @@ export default {
|
||||
INSERT_PDF: "Insert PDF file from vault",
|
||||
UNIVERSAL_ADD_FILE: "Insert ANY file",
|
||||
INSERT_CARD: "Add back-of-note card",
|
||||
CONVERT_CARD_TO_FILE: "Move back-of-note card to File",
|
||||
ERROR_TRY_AGAIN: "Please try again.",
|
||||
PASTE_CODEBLOCK: "Paste code block",
|
||||
INSERT_LATEX:
|
||||
`Insert LaTeX formula (e.g. \\binom{n}{k} = \\frac{n!}{k!(n-k)!}). ${labelALT()}+CLICK to watch a help video.`,
|
||||
ENTER_LATEX: "Enter a valid LaTeX expression",
|
||||
READ_RELEASE_NOTES: "Read latest release notes",
|
||||
RUN_OCR: "OCR: Grab text from freedraw scribble and pictures to clipboard",
|
||||
RUN_OCR: "OCR full drawing: Grab text from freedraw + images to clipboard and doc.props",
|
||||
RERUN_OCR: "OCR full drawing re-run: Grab text from freedraw + images to clipboard and doc.props",
|
||||
RUN_OCR_ELEMENTS: "OCR selected elements: Grab text from freedraw + images to clipboard",
|
||||
TRAY_MODE: "Toggle property-panel tray-mode",
|
||||
SEARCH: "Search for text in drawing",
|
||||
CROP_PAGE: "Crop and mask selected page",
|
||||
CROP_IMAGE: "Crop and mask image",
|
||||
ANNOTATE_IMAGE : "Annotate image in Excalidraw",
|
||||
INSERT_ACTIVE_PDF_PAGE_AS_IMAGE: "Insert active PDF page as image",
|
||||
@@ -214,6 +223,14 @@ export default {
|
||||
"once you switch back to Excalidraw view. " +
|
||||
"The setting only has effect 'point forward', meaning, existing drawings will not be affected by the setting " +
|
||||
"until you open them and save them.<br><b><u>Toggle ON:</u></b> Compress drawing JSON<br><b><u>Toggle OFF:</u></b> Leave drawing JSON uncompressed",
|
||||
DECOMPRESS_FOR_MD_NAME: "Decompress Excalidraw JSON in Markdown View",
|
||||
DECOMPRESS_FOR_MD_DESC:
|
||||
"By enabling this feature Excalidraw will automatically decompress the drawing JSON when you switch to Markdown view. " +
|
||||
"This will allow you to easily read and edit the JSON string. The drawing will be compressed again " +
|
||||
"once you switch back to Excalidraw view and save the drawing (CTRL+S).<br>" +
|
||||
"I recommend switching this feature off as it will result in smaller file sizes and avoiding unnecessary results in Obsidian search. " +
|
||||
"You can always use the 'Excalidraw: Decompress current Excalidraw file' command from the command palette "+
|
||||
"to manually decompress the drawing JSON when you need to read or edit it.",
|
||||
AUTOSAVE_INTERVAL_DESKTOP_NAME: "Interval for autosave on Desktop",
|
||||
AUTOSAVE_INTERVAL_DESKTOP_DESC:
|
||||
"The time interval between saves. Autosave will skip if there are no changes in the drawing. " +
|
||||
@@ -288,6 +305,9 @@ FILENAME_HEAD: "Filename",
|
||||
SHOW_PEN_MODE_FREEDRAW_CROSSHAIR_DESC:
|
||||
"Show crosshair in pen mode when using the freedraw tool. <b><u>Toggle ON:</u></b> SHOW <b><u>Toggle OFF:</u></b> HIDE<br>"+
|
||||
"The effect depends on the device. Crosshair is typically visible on drawing tablets, MS Surface, but not on iOS.",
|
||||
SHOW_DRAWING_OR_MD_IN_HOVER_PREVIEW_NAME: "Render image in hover preview for MD files",
|
||||
SHOW_DRAWING_OR_MD_IN_HOVER_PREVIEW_DESC:
|
||||
"This setting effects files that have the <b>excalidraw-open-md: true</b> frontmatter key.",
|
||||
SHOW_DRAWING_OR_MD_IN_READING_MODE_NAME: "Render image when in markdown reading mode",
|
||||
SHOW_DRAWING_OR_MD_IN_READING_MODE_DESC:
|
||||
"Must close the active excalidraw/markdown file and reopen it for this change to take effect.<br>When you are in markdown reading mode (aka. reading the back side of the drawing), should the Excalidraw drawing be rendered as an image? " +
|
||||
@@ -475,6 +495,10 @@ FILENAME_HEAD: "Filename",
|
||||
EMBED_IMAGE_CACHE_NAME: "Cache images for embedding in markdown",
|
||||
EMBED_IMAGE_CACHE_DESC: "Cache images for embedding in markdown. This will speed up the embedding process, but in case you compose images of several sub-component drawings, " +
|
||||
"the embedded image in Markdown won't update until you open the drawing and save it to trigger an update of the cache.",
|
||||
SCENE_IMAGE_CACHE_NAME: "Cache nested Excalidraws in Scene",
|
||||
SCENE_IMAGE_CACHE_DESC: "Cache nested Excalidraws in the Scene for faster scene rendering. This will speed up the rendering process, especially if you have deeply nested Excalidraws in your scene. " +
|
||||
"Excalidraw will try to intelligently identify if any children of a nested Excalidraw have changed and will update the cache accordingly. " +
|
||||
"You may want to turn this off, in case you are suspecting that the cache is not updating properly.",
|
||||
EMBED_IMAGE_CACHE_CLEAR: "Purge Cache",
|
||||
BACKUP_CACHE_CLEAR: "Purge Backups",
|
||||
BACKUP_CACHE_CLEAR_CONFIRMATION: "This action will delete all Excalidraw drawing backups. Backups are used as a safety measure in case your drawing file gets damaged. Each time you open Obsidian the plugin automatically deletes backups for files that no longer exist in your Vault. Are you sure you want to clear all backups?",
|
||||
@@ -562,6 +586,16 @@ FILENAME_HEAD: "Filename",
|
||||
"Double files will be exported both if auto-export SVG or PNG (or both) are enabled, as well as when clicking export on a single image.",
|
||||
COMPATIBILITY_HEAD: "Compatibility features",
|
||||
COMPATIBILITY_DESC: "You should only enable these features if you have a strong reason for wanting to work with excalidraw.com files instead of markdown files. Many of the plugin features are not supported on legacy files. Typical usecase would be if you use set your vault up on top of a Visual Studio Code project folder and you have .excalidraw drawings you want to access from Visual Studio Code as well. Another usecase might be using Excalidraw in Logseq and Obsidian in parallel.",
|
||||
DUMMY_TEXT_ELEMENT_LINT_SUPPORT_NAME: "Insert dummy first text element to support linting",
|
||||
DUMMY_TEXT_ELEMENT_LINT_SUPPORT_DESC: "Excalidraw is sensitive to the file structure below <code># Excalidraw Data</code>. Automatic linting of documents can create errors in Excalidraw Data. " +
|
||||
"While I've made some effort to make the data loading resilient to " +
|
||||
"lint changes, this solution is not foolproof.<br><mark>The best is to avoid liniting or otherwise automatically changing Excalidraw documents using different plugins.</mark><br>" +
|
||||
"Use this setting if for good reasons you have decided to ignore my recommendation and configured linting of Excalidraw files.<br> " +
|
||||
"The <code>## Text Elements</code> section is sensitive to empty lines. A common linting approach is to add an empty line after section headings. In case of Excalidraw this will break/change the first text element in your drawing. " +
|
||||
"To overcome this, you can enable this setting. When enabled, Excalidraw will add a dummy element to the beginning of <code>## Text Elements</code> that the linter can safely modify." ,
|
||||
DEBUGMODE_NAME: "Enable debug messages",
|
||||
DEBUGMODE_DESC: "I recommend restarting Obsidian after enabling/disabling this setting. This enable debug messages in the console. This is useful for troubleshooting issues. " +
|
||||
"If you are experiencing problems with the plugin, please enable this setting, reproduce the issue, and include the console log in the issue you raise on <a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/issues'>GitHub</a>",
|
||||
SLIDING_PANES_NAME: "Sliding panes plugin support",
|
||||
SLIDING_PANES_DESC:
|
||||
"Need to restart Obsidian for this change to take effect.<br>" +
|
||||
@@ -684,7 +718,7 @@ FILENAME_HEAD: "Filename",
|
||||
SELECT_SECTION_OR_TYPE_NEW:
|
||||
"Select existing section or type name of a new section then press Enter.",
|
||||
INVALID_SECTION_NAME: "Invalid section name.",
|
||||
EMPTY_SECTION_MESSAGE: "Hit enter to create a new Section",
|
||||
EMPTY_SECTION_MESSAGE: "Type the Section Name and hit enter to create a new Section",
|
||||
|
||||
//EmbeddedFileLoader.ts
|
||||
INFINITE_LOOP_WARNING:
|
||||
|
||||
@@ -9,6 +9,7 @@ export default {
|
||||
// main.ts
|
||||
CONVERT_URL_TO_FILE: "从 URL 下载图像到本地",
|
||||
UNZIP_CURRENT_FILE: "解压当前 Excalidraw 文件",
|
||||
ZIP_CURRENT_FILE: "压缩当前 Excalidraw 文件",
|
||||
PUBLISH_SVG_CHECK: "Obsidian Publish: 搜索过期的 SVG 和 PNG 导出文件",
|
||||
EMBEDDABLE_PROPERTIES: "Embeddable 元素设置",
|
||||
EMBEDDABLE_RELATIVE_ZOOM: "使元素的缩放等级等于当前画布的缩放等级",
|
||||
@@ -35,6 +36,7 @@ export default {
|
||||
TRANSCLUDE: "嵌入绘图(形如 ![[drawing]])到当前 Markdown 文档中",
|
||||
TRANSCLUDE_MOST_RECENT: "嵌入最近编辑过的绘图(形如 ![[drawing]])到当前 Markdown 文档中",
|
||||
TOGGLE_LEFTHANDED_MODE: "切换为左手模式",
|
||||
FLIP_IMAGE: "打开当前所选 excalidraw 图像的“背景笔记”",
|
||||
NEW_IN_NEW_PANE: "新建绘图 - 于新面板",
|
||||
NEW_IN_NEW_TAB: "新建绘图 - 于新页签",
|
||||
NEW_IN_ACTIVE_PANE: "新建绘图 - 于当前面板",
|
||||
@@ -48,6 +50,7 @@ export default {
|
||||
NEW_IN_POPOUT_WINDOW_EMBED: "新建绘图 - 于新窗口 - 并将其嵌入(形如 ![[drawing]])到当前 Markdown 文档中",
|
||||
TOGGLE_LOCK: "文本元素:原文模式(RAW)⟺ 预览模式(PREVIEW)",
|
||||
DELETE_FILE: "从库中删除所选图像(或以图像形式嵌入绘图中的 Markdown)的源文件",
|
||||
COPY_ELEMENT_LINK: "复制所选元素的 Markdown 链接",
|
||||
INSERT_LINK_TO_ELEMENT:
|
||||
`复制所选元素为内部链接(形如 [[file#^id]] )。\n按住 ${labelCTRL()} 可复制元素所在分组为内部链接(形如 [[file#^group=id]] )。\n按住 ${labelSHIFT()} 可复制所选元素所在区域为内部链接(形如 [[file#^area=id]] )。\n按住 ${labelALT()} 可观看视频演示。`,
|
||||
INSERT_LINK_TO_ELEMENT_GROUP:
|
||||
@@ -64,17 +67,25 @@ export default {
|
||||
INSERT_COMMAND: "插入 Obsidian 命令(以内部链接形式嵌入)到当前绘图中",
|
||||
INSERT_IMAGE: "插入图像或 Excalidraw 绘图(以图像形式嵌入)到当前绘图中",
|
||||
IMPORT_SVG: "从 SVG 文件导入图形元素到当前绘图中(暂不支持文本元素)",
|
||||
IMPORT_SVG_CONTEXTMENU: "转换 SVG 到线条 - 有限制",
|
||||
INSERT_MD: "插入 Markdown 文档(以图像形式嵌入)到当前绘图中",
|
||||
INSERT_PDF: "插入 PDF 文档(以图像形式嵌入)到当前绘图中",
|
||||
UNIVERSAL_ADD_FILE: "插入任意文件(以交互形式嵌入,或者以图像形式嵌入)到当前绘图中",
|
||||
INSERT_CARD: "插入“背景笔记”卡片",
|
||||
PASTE_CODEBLOCK: "粘贴代码块",
|
||||
INSERT_LATEX:
|
||||
`插入 LaTeX 公式到当前绘图`,
|
||||
ENTER_LATEX: "输入 LaTeX 表达式",
|
||||
READ_RELEASE_NOTES: "阅读本插件的更新说明",
|
||||
RUN_OCR: "OCR:识别涂鸦和图片里的文本并复制到剪贴板",
|
||||
RUN_OCR: "OCR 完整画布: 识别涂鸦和图片里的文本并复制到剪贴板和文档属性中",
|
||||
RERUN_OCR: "重新运行 OCR 完整画笔: 识别涂鸦和图片里的文本并复制到剪贴板和文档属性中",
|
||||
RUN_OCR_ELEMENTS: "OCR 选中的元素: 识别涂鸦和图片里的文本并复制到剪贴板",
|
||||
TRAY_MODE: "绘图工具属性页:面板模式 ⟺ 托盘模式",
|
||||
SEARCH: "搜索文本",
|
||||
CROP_IMAGE: "裁剪与蒙版",
|
||||
CROP_PAGE: "对所选页面裁剪并添加蒙版",
|
||||
CROP_IMAGE: "对图片裁剪并添加蒙版",
|
||||
ANNOTATE_IMAGE : "在 Excalidraw 中标注图像",
|
||||
INSERT_ACTIVE_PDF_PAGE_AS_IMAGE: "将当前激活的的 PDF 页面作为图片插入",
|
||||
RESET_IMG_TO_100: "重设图像元素的尺寸为 100%",
|
||||
TEMPORARY_DISABLE_AUTOSAVE: "临时禁用自动保存功能,直到本次 Obsidian 退出(小白慎用!)",
|
||||
TEMPORARY_ENABLE_AUTOSAVE: "启用自动保存功能",
|
||||
@@ -103,10 +114,14 @@ export default {
|
||||
BACKUP_RESTORED: "已恢复备份",
|
||||
CACHE_NOT_READY: "抱歉,加载绘图文件时出错。<br><br><mark>现在有耐心,将来更省心。</mark><br><br>该插件有备份机制,但您似乎刚刚打开 Obsidian,需要等待一分钟或更长的时间来读取缓存。缓存读取完毕时,您将会在右上角收到提示。<br><br>请点击 OK 并耐心等待缓存,或者选择点击取消后手动修复你的文件。<br>",
|
||||
OBSIDIAN_TOOLS_PANEL: "Obsidian 工具面板",
|
||||
ERROR_SAVING_IMAGE: "获取图像时发生未知错误",
|
||||
ERROR_SAVING_IMAGE: "获取图像时发生未知错误。可能是由于某种原因,图像不可用或拒绝了 Obsidian 的获取请求。",
|
||||
WARNING_PASTING_ELEMENT_AS_TEXT: "你不能将 Excalidraw 元素粘贴为文本元素!",
|
||||
USE_INSERT_FILE_MODAL: "使用“插入任意文件”功能来嵌入 Markdown 文档",
|
||||
CONVERT_TO_MARKDOWN: "转存为 Markdown 文档(并嵌入为 MD-Embeddable)",
|
||||
SELECT_TEXTELEMENT_ONLY: "只选择文本元素(非容器)",
|
||||
REMOVE_LINK: "移除文字元素链接",
|
||||
LASER_ON: "启用激光笔",
|
||||
LASER_OFF: "关闭激光笔",
|
||||
|
||||
//settings.ts
|
||||
RELEASE_NOTES_NAME: "显示更新说明",
|
||||
@@ -123,6 +138,23 @@ export default {
|
||||
FOLDER_NAME: "Excalidraw 文件夹",
|
||||
FOLDER_DESC:
|
||||
"新绘图的默认存储路径。若为空,将在库的根目录中创建新绘图。",
|
||||
CROP_PREFIX_NAME: "剪贴文件的前缀",
|
||||
CROP_PREFIX_DESC:
|
||||
"当剪贴图片进来时保存的文件名的前缀。 " +
|
||||
"留空则使用 'cropped_'",
|
||||
ANNOTATE_PREFIX_NAME: "标注文件的前缀",
|
||||
ANNOTATE_PREFIX_DESC:
|
||||
"在标注图像时创建新绘图的文件名的第一部分。" +
|
||||
"留空则使用'annotated_'",
|
||||
ANNOTATE_PRESERVE_SIZE_NAME: "在标注时保留图像尺寸",
|
||||
ANNOTATE_PRESERVE_SIZE_DESC:
|
||||
"当在 Markdown 中标注图像时,替换后的图像链接将包含原始图像的宽度。",
|
||||
CROP_FOLDER_NAME: "剪贴文件文件夹",
|
||||
CROP_FOLDER_DESC:
|
||||
"剪贴图像时创建新绘图的默认存储路径。如果留空,将按照 Vault 附件设置创建。",
|
||||
ANNOTATE_FOLDER_NAME: "图片标注文件文件夹",
|
||||
ANNOTATE_FOLDER_DESC:
|
||||
"创建图片标注是的默认存储路径. 如果留空, 将按照 Vault 附件设置创建。",
|
||||
FOLDER_EMBED_NAME:
|
||||
"将 Excalidraw 文件夹用于“新建绘图”系列命令",
|
||||
FOLDER_EMBED_DESC:
|
||||
@@ -131,11 +163,14 @@ export default {
|
||||
"<b>开启:</b>使用上面的 Excalidraw 文件夹。 <br><b>关闭:</b>使用 Obsidian 设置的新附件默认位置。",
|
||||
TEMPLATE_NAME: "Excalidraw 模板文件",
|
||||
TEMPLATE_DESC:
|
||||
"Excalidraw 模板文件的存储路径。<br>" +
|
||||
"如果您的模板在默认的 Excalidraw 文件夹中且文件名是 " +
|
||||
"Template.md,则此项应设为 Excalidraw/Template.md(也可省略 .md 扩展名,即 Excalidraw/Template)。<br>" +
|
||||
"Excalidraw 模板文件(文件夹)的存储路径。<br>" +
|
||||
"<b>模板文件:</b>比如.: 如果您的模板在默认的 Excalidraw 文件夹中且文件名是 " +
|
||||
"Template.md, 则此项应设为 Excalidraw/Template.md(也可省略 .md 扩展名,即 Excalidraw/Template)。" +
|
||||
"如果您在兼容模式下使用 Excalidraw,那么您的模板文件也必须是旧的 *.excalidraw 格式," +
|
||||
"例如 Excalidraw/Template.excalidraw。",
|
||||
"例如 Excalidraw/Template.excalidraw. <br><b>模板文件夹:</b> 你还可以将文件夹设置为模板。 " +
|
||||
"在这种情况下,创建新绘图时将提示您选择使用哪个模板。<br>" +
|
||||
"<b>专业提示:</b> 如果您正在使用 Obsidian Templater 插件,您可以 将Templater 代码添加到不同的" +
|
||||
"Excalidraw 模板中,以自动配置您的绘图",
|
||||
SCRIPT_FOLDER_NAME: "Excalidraw 自动化脚本的文件夹(大小写敏感!)",
|
||||
SCRIPT_FOLDER_DESC:
|
||||
"此文件夹用于存放 Excalidraw 自动化脚本。" +
|
||||
@@ -171,7 +206,7 @@ export default {
|
||||
AI_OPENAI_DEFAULT_API_URL_DESC:
|
||||
"默认的 OpenAI API URL。请填写有效的 OpenAI API URL。" +
|
||||
"Excalidraw 会通过该 URL 发送 API 请求给 OpenAI。我没有对此选项做任何错误处理,请谨慎修改。",
|
||||
AI_OPENAI_DEFAULT_IMAGE_API_URL_NAME: "OpenAI Image Generation API URL",
|
||||
AI_OPENAI_DEFAULT_IMAGE_API_URL_NAME: "OpenAI 图像生成 API URL",
|
||||
AI_OPENAI_DEFAULT_VISION_MODEL_PLACEHOLDER: "gpt-4-vision-preview",
|
||||
SAVING_HEAD: "保存",
|
||||
SAVING_DESC: "包括:压缩,自动保存的时间间隔,文件的命名格式和扩展名等的设置。",
|
||||
@@ -186,6 +221,14 @@ export default {
|
||||
"而当您切换回 Excalidraw 模式时,数据就会被再次编码。<br>" +
|
||||
"开启此项后,对于之前已存在但未压缩的绘图文件," +
|
||||
"需要重新打开并保存才能生效。",
|
||||
DECOMPRESS_FOR_MD_NAME: "在 Markdown 视图中解压缩 Excalidraw JSON",
|
||||
DECOMPRESS_FOR_MD_DESC:
|
||||
"通过启用此功能,Excalidraw 将在切换到 Markdown 视图时自动解压缩绘图 JSON。" +
|
||||
"这将使您能够轻松阅读和编辑 JSON 字符串。" +
|
||||
"一旦您切换回Excalidraw视图并保存绘图(CTRL+S),绘图将再次被压缩。<br>" +
|
||||
"我建议关闭此功能,因为这可以获得更小的文件尺寸,并避免在 Obsidian 搜索中出现不必要的结果。 " +
|
||||
"您始终可以使用命令面板中的“Excalidraw: 解压缩当前 Excalidraw 文件”命令"+
|
||||
"在需要阅读或编辑时手动解压缩绘图 JSON。",
|
||||
AUTOSAVE_INTERVAL_DESKTOP_NAME: "桌面端自动保存时间间隔",
|
||||
AUTOSAVE_INTERVAL_DESKTOP_DESC:
|
||||
"每隔多长时间自动保存一次(如果绘图文件没有发生改变,将不会保存)。" +
|
||||
@@ -256,6 +299,24 @@ FILENAME_HEAD: "文件名",
|
||||
DEFAULT_PEN_MODE_NAME: "触控笔模式(Pen mode)",
|
||||
DEFAULT_PEN_MODE_DESC:
|
||||
"打开绘图时,是否自动开启触控笔模式?",
|
||||
SHOW_PEN_MODE_FREEDRAW_CROSSHAIR_NAME: "在触控笔模式下显示十字准星(+)",
|
||||
SHOW_PEN_MODE_FREEDRAW_CROSSHAIR_DESC:
|
||||
"在触控笔模式下使用涂鸦功能会显示十字准星 <b><u>打开:</u></b> 显示 <b><u>关闭:</u></b> 隐藏<br>"+
|
||||
"效果取决于设备。十字准星通常在绘图板、MS Surface 上可见,但在iOS上不可见。",
|
||||
SHOW_DRAWING_OR_MD_IN_HOVER_PREVIEW_NAME: "在 Markdown 文件的悬停预览中渲染为图片",
|
||||
SHOW_DRAWING_OR_MD_IN_HOVER_PREVIEW_DESC:
|
||||
"这个设置影响 frontmatter 中具有 <b>excalidraw-open-md: true</b> 的文件.",
|
||||
SHOW_DRAWING_OR_MD_IN_READING_MODE_NAME: "在 Markdown 文件阅读模式下渲染为图片",
|
||||
SHOW_DRAWING_OR_MD_IN_READING_MODE_DESC:
|
||||
"必须关闭活动的 Excalidraw/Markdown 文件,然后重新打开才能使此更改生效。<br>当您处于 Markdown 阅读模式(即阅读 Excalidraw 的背景笔记)时 Excalidraw 绘图是否应该呈现为图像? " +
|
||||
"此设置不会影响您处于 Excalidraw 模式时的绘图显示,也不会影响将绘图嵌入到 Markdown 文档中或在渲染悬停预览时的显示。<br><ul>" +
|
||||
"<li>看下面的“嵌入和导出”中的<b>PDF导出</b>的其他相关设置。</li>" +
|
||||
"<li>请务必查看“其他功能”部分中的<b>淡化设置</b>。</li></ul>",
|
||||
SHOW_DRAWING_OR_MD_IN_EXPORTPDF_NAME: "在 Markdown 模式下导出为 PDF 时渲染为图片",
|
||||
SHOW_DRAWING_OR_MD_IN_EXPORTPDF_DESC:
|
||||
"必须关闭活动的 Excalidraw/Markdown 文件,然后重新打开才能使此更改生效。<br>当您处于 Markdown 阅读模式(即阅读 Excalidraw 的背景笔记)时将笔记导出为 PDF, Excalidraw 绘图是否应该呈现为图像? <br><ul>" +
|
||||
"<li>查看上面“外观和行为”下的 <b>Markdown 阅读模式</b>的其他相关设置。</li>" +
|
||||
"<li>请务必查看“其他功能”部分中的<b>淡化设置</b>。</li></ul>",
|
||||
THEME_HEAD: "主题和样式",
|
||||
ZOOM_HEAD: "缩放",
|
||||
DEFAULT_PINCHZOOM_NAME: "允许在触控笔模式下进行双指缩放",
|
||||
@@ -297,6 +358,18 @@ FILENAME_HEAD: "文件名",
|
||||
"以下选项在苹果和非苹果设备上区别很大,如果您在多个硬件平台上使用 Obsidian,需要分别进行设置。"+
|
||||
"选项里的 4 个开关依次代表 " +
|
||||
(DEVICE.isIOS || DEVICE.isMacOS ? "SHIFT, CMD, OPT, CONTROL." : "SHIFT, CTRL, ALT, META (Win 键)。"),
|
||||
LONG_PRESS_DESKTOP_NAME: "长按打开(电脑端)",
|
||||
LONG_PRESS_DESKTOP_DESC: "长按(以毫秒为单位)打开在 Markdown 文件中嵌入的 Excalidraw 绘图。",
|
||||
LONG_PRESS_MOBILE_NAME: "长按打开(移动端)",
|
||||
LONG_PRESS_MOBILE_DESC: "长按(以毫秒为单位)打开在 Markdown 文件中嵌入的 Excalidraw 绘图。",
|
||||
|
||||
FOCUS_ON_EXISTING_TAB_NAME: "聚焦于当前标签页",
|
||||
FOCUS_ON_EXISTING_TAB_DESC: "当打开一个链接时,如果该文件已经打开,Excalidraw 将会聚焦到现有的标签页上 " +
|
||||
"启用这个设置会在文件已经打开的情况下覆盖“重用相邻窗格”的设置。",
|
||||
SECOND_ORDER_LINKS_NAME: "显示二级链接",
|
||||
SECOND_ORDER_LINKS_DESC: "在 Excalidraw 中点击链接时显示链接。二级链接是指指向被点击链接的反向链接" +
|
||||
"当使用图标连接相似的笔记时,二级链接可以让你直接到达相关笔记,而不需要两次点击。" +
|
||||
"请观看 <a href='https://youtube.com/shorts/O_1ls9c6wBY?feature=share'>这个 YouTube Shorts 视频</a> 以了解更多信息。",
|
||||
ADJACENT_PANE_NAME: "在相邻面板中打开",
|
||||
ADJACENT_PANE_DESC:
|
||||
`按住 ${labelCTRL()}+${labelSHIFT()} 并点击绘图里的内部链接时,插件默认会在新面板中打开该链接。<br>` +
|
||||
@@ -340,7 +413,7 @@ FILENAME_HEAD: "文件名",
|
||||
`按住 ${labelCTRL()} 并点击含有 [[链接]] 或 [别名](链接) 的文本来打开链接`,
|
||||
LINK_CTRL_CLICK_DESC:
|
||||
"如果此功能影响到您使用某些原版 Excalidraw 功能,可将其关闭。" +
|
||||
"关闭后,您只能通过绘图面板标题栏中的链接按钮来打开链接。",
|
||||
"关闭后,您可以使用 ${labelCTRL()} + ${labelMETA()} 或者元素右上角的链接指示器来打开链接。",
|
||||
TRANSCLUSION_WRAP_NAME: "MD-Transclusion 的折行方式",
|
||||
TRANSCLUSION_WRAP_DESC:
|
||||
"中的 number 表示嵌入的文本溢出时,在第几个字符处进行折行。<br>" +
|
||||
@@ -420,6 +493,10 @@ FILENAME_HEAD: "文件名",
|
||||
EMBED_IMAGE_CACHE_NAME: "为嵌入到 Markdown 文档中的绘图创建预览图缓存",
|
||||
EMBED_IMAGE_CACHE_DESC: "可提高下次嵌入的速度。" +
|
||||
"但如果绘图中又嵌入了子绘图,当子绘图改变时,您需要打开子绘图并手动保存,才能够更新父绘图的预览图。",
|
||||
SCENE_IMAGE_CACHE_NAME: "缓存场景中嵌套的 Excalidraw",
|
||||
SCENE_IMAGE_CACHE_DESC: "缓存场景中嵌套的 Excalidraw 以加快场景渲染速度。这将加快渲染过程,特别是在您的场景中有深度嵌套的 Excalidraw 时。" +
|
||||
"Excalidraw 将智能地尝试识别嵌套 Excalidraw 的子元素是否发生变化,并更新缓存。 " +
|
||||
"如果您怀疑缓存未能正确更新,您可能需要关闭此功能。",
|
||||
EMBED_IMAGE_CACHE_CLEAR: "清除缓存",
|
||||
BACKUP_CACHE_CLEAR: "清除备份",
|
||||
BACKUP_CACHE_CLEAR_CONFIRMATION: "该操作将删除所有绘图文件的备份。备份是绘图文件损坏时的一种补救手段。每次您打开 Obsidian 时,本插件会自动清理无用的备份。您确定要现在删除所有备份吗?",
|
||||
@@ -449,17 +526,22 @@ FILENAME_HEAD: "文件名",
|
||||
"嵌入到 Markdown 文档中的绘图的预览图的默认宽度。该选项也适用于鼠标悬停时浮现的预览图。<br>" +
|
||||
"您可为某个要嵌入到 Markdown 文档中的绘图文件单独设置此项," +
|
||||
"方法是修改相应的内部链接格式为形如 <code>![[drawing.excalidraw|100]]</code> 或 <code>[[drawing.excalidraw|100x100]]</code>。",
|
||||
EMBED_HEIGHT_NAME: "预览图的默认高度",
|
||||
EMBED_HEIGHT_DESC:
|
||||
"嵌入到 Markdown 文档中的绘图的预览图得默认高度。该选项也适用于实时预览编辑和阅读模式,以及悬停预览。" +
|
||||
"您可以在使用 <code>![[drawing.excalidraw|100]]</code> 或者 <code>[[drawing.excalidraw|100x100]]</code>" +
|
||||
"格式在嵌入图像时指定自定义高度。",
|
||||
EMBED_TYPE_NAME: "“嵌入绘图到当前 Markdown 文档中”系列命令的源文件类型",
|
||||
EMBED_TYPE_DESC:
|
||||
"在命令面板中执行“嵌入绘图到当前 Markdown 文档中”系列命令时,要嵌入绘图文件本身,还是嵌入其 PNG 或 SVG 副本。<br>" +
|
||||
"如果您想选择 PNG 或 SVG 副本,需要先开启下方的“自动导出 PNG 副本”或“自动导出 SVG 副本”。<br>" +
|
||||
"如果您选择了 PNG 或 SVG 副本,当副本不存在时,该命令将会插入一条损坏的链接,您需要打开绘图文件并手动导出副本才能修复 —— " +
|
||||
"也就是说,该选项不会自动帮您生成 PNG/SVG 副本,而只会引用已有的 PNG/SVG 副本。",
|
||||
EMBED_MARKDOWN_COMMENT_NAME: "Embed link to drawing as comment",
|
||||
EMBED_MARKDOWN_COMMENT_NAME: "将链接作为注释嵌入",
|
||||
EMBED_MARKDOWN_COMMENT_DESC:
|
||||
"Embed the link to the original Excalidraw file as a markdown link under the image, e.g.:<code>%%[[drawing.excalidraw]]%%</code>.<br>" +
|
||||
"Instead of adding a markdown comment you may also select the embedded SVG or PNG line and use the command palette action: " +
|
||||
"'<code>Excalidraw: Open Excalidraw drawing</code>' to open the drawing.",
|
||||
"在图像下方以 Markdown 链接的形式嵌入原始 Excalidraw 文件的链接,例如:<code>%%[[drawing.excalidraw]]%%</code>。<br>" +
|
||||
"除了添加 Markdown 注释之外,您还可以选择嵌入的 SVG 或 PNG,并使用命令面板: " +
|
||||
"'<code>Excalidraw: 打开 Excalidraw 绘图</code>'来打开该绘图",
|
||||
EMBED_WIKILINK_NAME: "“嵌入绘图到当前 Markdown 文档中”系列命令产生的内部链接类型",
|
||||
EMBED_WIKILINK_DESC:
|
||||
"<b>开启:</b>将产生 <code>![[Wiki 链接]]</code>。<b>关闭:</b>将产生 <code></code>。",
|
||||
@@ -477,6 +559,10 @@ FILENAME_HEAD: "文件名",
|
||||
EXPORT_THEME_DESC:
|
||||
"导出与绘图的黑暗/明亮主题匹配的图像。" +
|
||||
"如果关闭,在黑暗主题下导出的图像将和明亮主题一样。",
|
||||
EXPORT_EMBED_SCENE_NAME: "在导出的图片中嵌入场景",
|
||||
EXPORT_EMBED_SCENE_DESC:
|
||||
"在导出的图像中嵌入 Excalidraw 场景。可以通过在文件级别添加 <code>excalidraw-export-embed-scene: true/false</code> frontmatter 元数据键来覆盖此设置。" +
|
||||
"此设置仅在您下次(重新)打开绘图时生效。",
|
||||
EXPORT_HEAD: "导出设置",
|
||||
EXPORT_SYNC_NAME:
|
||||
"保持 SVG/PNG 文件名与绘图文件同步",
|
||||
@@ -498,6 +584,16 @@ FILENAME_HEAD: "文件名",
|
||||
"该选项可作用于“自动导出 SVG 副本”、“自动导出 PNG 副本”,以及其他的手动的导出命令。",
|
||||
COMPATIBILITY_HEAD: "兼容性设置",
|
||||
COMPATIBILITY_DESC: "如果没有特殊原因(例如您想同时在 VSCode / Logseq 和 Obsidian 中使用 Excalidraw),建议您使用 markdown 格式的绘图文件,而不是旧的 excalidraw.com 格式,因为本插件的很多功能在旧格式中无法使用。",
|
||||
DUMMY_TEXT_ELEMENT_LINT_SUPPORT_NAME: "插入虚拟的第一个文本元素以支持代码格式化工具(Linting)",
|
||||
DUMMY_TEXT_ELEMENT_LINT_SUPPORT_DESC: "Excalidraw 对 <code># Excalidraw Data</code> 下的文件结构非常敏感。文档的自动代码格式化(linting)可能会在 Excalidraw 数据中造成错误。" +
|
||||
"虽然我已经努力使数据加载对自动代码格式化(linting)变更具有一定的抗性,但这种解决方案并非万无一失。<br>"+
|
||||
"<mark>最好的方法是避免使用不同的插件对 Excalidraw 文档进行自动更改。</mark><br>" +
|
||||
"如果出于某些合理的原因,您决定忽略我的建议并配置了 Excalidraw 文件的自动代码格式化,那么可以使用这个设置<br> " +
|
||||
"<code>## Text Elements</code> 部分对空行很敏感。一种常见的代码格式化是在章节标题后添加一个空行。但对于 Excalidraw 来说,这将破坏/改变您绘图中的第一个文本元素。" +
|
||||
"为了解决这个问题,您可以启用这个设置。启用后 Excalidraw 将在 <code>## Text Elements</code> 的开头添加一个虚拟元素,供自动代码格式化工具修改。" ,
|
||||
DEBUGMODE_NAME: "开启 debug 信息",
|
||||
DEBUGMODE_DESC: "我建议在启用/禁用此设置后重新启动 Obsidian。这将在控制台中启用调试消息。这对于排查问题很有帮助。" +
|
||||
"如果您在使用插件时遇到问题,请启用此设置,重现问题,并在 <a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/issues'>GitHub</a> 上提出的问题中包含控制台日志。",
|
||||
SLIDING_PANES_NAME: "Sliding panes 插件支持",
|
||||
SLIDING_PANES_DESC:
|
||||
"设置此项后需要重启 Obsidian 才能生效。<br>" +
|
||||
@@ -527,6 +623,11 @@ FILENAME_HEAD: "文件名",
|
||||
NONSTANDARD_HEAD: "非 Excalidraw.com 官方支持的特性",
|
||||
NONSTANDARD_DESC: `这些特性不受 Excalidraw.com 官方支持。如果以 Excalidraw.com 格式导出绘图,这些特性将会发生不可预知的变化。
|
||||
包括:自定义画笔工具的数量,自定义字体等。`,
|
||||
RENDER_TWEAK_HEAD: "渲染优化",
|
||||
MAX_IMAGE_ZOOM_IN_NAME: "最大图像放大倍数",
|
||||
MAX_IMAGE_ZOOM_IN_DESC: "为了节省内存,并且因为 Apple Safari (Obsidian on iOS) 有一些硬编码的限制,Excalidraw.com 在放大时会限制图像和大型对象的最大分辨率。您可以使用乘数来覆盖这个限制。" +
|
||||
"这意味着将乘以 Excalidraw 默认设置的限制,乘数越大,图像放大分辨率就越高,但也会消耗更多内存。" +
|
||||
"我建议尝试多个值来设置这个参数。当您放大一个较大的 PNG 图像时,如果图像突然从视图中消失,那就说明您已经达到了极限。默认值为 1。此设置对 iOS 无效。",
|
||||
CUSTOM_PEN_HEAD: "自定义画笔",
|
||||
CUSTOM_PEN_NAME: "自定义画笔工具的数量",
|
||||
CUSTOM_PEN_DESC: "在画布上的 Obsidian 菜单按钮旁边切换自定义画笔。长按画笔按钮可以修改其样式。",
|
||||
@@ -561,6 +662,11 @@ FILENAME_HEAD: "文件名",
|
||||
"开启此项,则可在 Obsidian 实时预览模式的编辑视图下,用形如 <code>![[绘图|宽度|样式]]</code> 的语法来嵌入绘图。<br>" +
|
||||
"该选项不会在已打开的文档中立刻生效 —— " +
|
||||
"你需要重新打开此文档来使其生效。",
|
||||
FADE_OUT_EXCALIDRAW_MARKUP_NAME: "淡化 Excalidraw 标记",
|
||||
FADE_OUT_EXCALIDRAW_MARKUP_DESC: "在 Markdown 视图模式下,在 Markdown 注释 %% " +
|
||||
"之后的部分会淡化。文本仍然存在,但视觉杂乱感会减少。请注意,您可以将 %% 放在 # Text Elements 行的上一行," +
|
||||
"这样,整个 Excalidraw Markdown 都会淡化,包括 # Text Elements。 副作用是您将无法在其他 Markdown 笔记中引用文本块,即 %% 注释部分之后的内容。这应该不是大问题。" +
|
||||
"如果您想编辑 Excalidraw Markdown 脚本,只需切换到 Markdown 视图模式并暂时删除 %% 注释。",
|
||||
CUSTOM_FONT_HEAD: "自定义字体",
|
||||
ENABLE_FOURTH_FONT_NAME: "为文本元素启用本地字体",
|
||||
ENABLE_FOURTH_FONT_DESC:
|
||||
@@ -605,6 +711,13 @@ FILENAME_HEAD: "文件名",
|
||||
PDF_PAGES_HEADER: "页码范围",
|
||||
PDF_PAGES_DESC: "示例:1, 3-5, 7, 9-11",
|
||||
|
||||
//SelectCard.ts
|
||||
TYPE_SECTION: "输入章节名称(标题)进行选择",
|
||||
SELECT_SECTION_OR_TYPE_NEW:
|
||||
"选择现有章节(标题)或输入新章节(标题)的名称,然后按 Enter。",
|
||||
INVALID_SECTION_NAME: "无效的章节名称(标题)",
|
||||
EMPTY_SECTION_MESSAGE: "输入章节(标题)名称以创建",
|
||||
|
||||
//EmbeddedFileLoader.ts
|
||||
INFINITE_LOOP_WARNING:
|
||||
"EXCALIDRAW 警告\n停止加载嵌入的图像,因为此文件中存在死循环:\n",
|
||||
@@ -677,7 +790,8 @@ FILENAME_HEAD: "文件名",
|
||||
PROMPT_BUTTON_INSERT_SPACE: "插入空格",
|
||||
PROMPT_BUTTON_INSERT_LINK: "插入内部链接",
|
||||
PROMPT_BUTTON_UPPERCASE: "大写",
|
||||
|
||||
PROMPT_SELECT_TEMPLATE: "选择一个模板",
|
||||
|
||||
//ModifierKeySettings
|
||||
WEB_BROWSER_DRAG_ACTION: "从浏览器拖进来时",
|
||||
LOCAL_FILE_DRAG_ACTION: "从本地文件系统拖进来时",
|
||||
|
||||
367
src/main.ts
367
src/main.ts
@@ -41,8 +41,9 @@ import {
|
||||
EXPORT_IMG_ICON,
|
||||
LOCALE,
|
||||
IMAGE_TYPES,
|
||||
MD_TEXTELEMENTS,
|
||||
setExcalidrawPlugin
|
||||
setExcalidrawPlugin,
|
||||
DEVICE,
|
||||
sceneCoordsToViewportCoords
|
||||
} from "./constants/constants";
|
||||
import {
|
||||
VIRGIL_FONT,
|
||||
@@ -95,7 +96,6 @@ import {
|
||||
import {
|
||||
getFontDataURL,
|
||||
errorlog,
|
||||
log,
|
||||
setLeftHandedMode,
|
||||
sleep,
|
||||
isVersionNewerThanOther,
|
||||
@@ -104,7 +104,7 @@ import {
|
||||
decompress,
|
||||
getImageSize,
|
||||
} from "./utils/Utils";
|
||||
import { extractSVGPNGFileName, getActivePDFPageNumberFromPDFView, getAttachmentsFolderAndFilePath, getNewOrAdjacentLeaf, getParentOfClass, isObsidianThemeDark, mergeMarkdownFiles, openLeaf } from "./utils/ObsidianUtils";
|
||||
import { editorInsertText, extractSVGPNGFileName, foldExcalidrawSection, getActivePDFPageNumberFromPDFView, getAttachmentsFolderAndFilePath, getNewOrAdjacentLeaf, getParentOfClass, isObsidianThemeDark, mergeMarkdownFiles, openLeaf } from "./utils/ObsidianUtils";
|
||||
import { ExcalidrawElement, ExcalidrawEmbeddableElement, ExcalidrawImageElement, ExcalidrawTextElement, FileId } from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
import { ScriptEngine } from "./Scripts";
|
||||
import {
|
||||
@@ -132,8 +132,8 @@ import { processLinkText } from "./utils/CustomEmbeddableUtils";
|
||||
import { getEA } from "src";
|
||||
import { ExcalidrawImperativeAPI } from "@zsviczian/excalidraw/types/excalidraw/types";
|
||||
import { Mutable } from "@zsviczian/excalidraw/types/excalidraw/utility-types";
|
||||
import { CustomMutationObserver, durationTreshold, isDebugMode } from "./utils/DebugHelper";
|
||||
import { carveOutImage, carveOutPDF, createImageCropperFile, CROPPED_PREFIX } from "./utils/CarveOut";
|
||||
import { CustomMutationObserver, debug, durationTreshold, log, DEBUGGING, setDebugging } from "./utils/DebugHelper";
|
||||
import { carveOutImage, carveOutPDF, createImageCropperFile } from "./utils/CarveOut";
|
||||
import { ExcalidrawConfig } from "./utils/ExcalidrawConfig";
|
||||
import { EditorHandler } from "./CodeMirrorExtension/EditorHandler";
|
||||
|
||||
@@ -185,6 +185,9 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
private stylesManager:StylesManager;
|
||||
private textMeasureDiv:HTMLDivElement = null;
|
||||
public editorHandler: EditorHandler;
|
||||
public activeLeafChangeEventHandler: (leaf: WorkspaceLeaf) => Promise<void>;
|
||||
//if set, the next time this file is opened it will be opened as markdown
|
||||
public forceToOpenInMarkdownFilepath: string = null;
|
||||
|
||||
constructor(app: App, manifest: PluginManifest) {
|
||||
super(app, manifest);
|
||||
@@ -210,6 +213,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
};
|
||||
|
||||
public getPackage(win:Window):Packages {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.getPackage, `ExcalidrawPlugin.getPackage`, win);
|
||||
if(win===window) {
|
||||
return {react, reactDOM, excalidrawLib};
|
||||
}
|
||||
@@ -228,35 +232,39 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
}
|
||||
|
||||
public registerEvent(event: any) {
|
||||
if(!isDebugMode) {
|
||||
if (process.env.NODE_ENV !== 'development') {
|
||||
super.registerEvent(event);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if(!DEBUGGING) {
|
||||
super.registerEvent(event);
|
||||
return;
|
||||
}
|
||||
const originalHandler = event.fn;
|
||||
|
||||
const originalHandler = event.fn;
|
||||
|
||||
// Wrap the original event handler
|
||||
const wrappedHandler = async (...args: any[]) => {
|
||||
const startTime = performance.now(); // Get start time
|
||||
|
||||
// Invoke the original event handler
|
||||
const result = await originalHandler(...args);
|
||||
|
||||
const endTime = performance.now(); // Get end time
|
||||
const executionTime = endTime - startTime;
|
||||
|
||||
if(executionTime > durationTreshold) {
|
||||
console.log(`Excalidraw Event '${event.name}' took ${executionTime}ms to execute`);
|
||||
// Wrap the original event handler
|
||||
const wrappedHandler = async (...args: any[]) => {
|
||||
const startTime = performance.now(); // Get start time
|
||||
|
||||
// Invoke the original event handler
|
||||
const result = await originalHandler(...args);
|
||||
|
||||
const endTime = performance.now(); // Get end time
|
||||
const executionTime = endTime - startTime;
|
||||
|
||||
if(executionTime > durationTreshold) {
|
||||
console.log(`Excalidraw Event '${event.name}' took ${executionTime}ms to execute`);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
return result;
|
||||
// Replace the original event handler with the wrapped one
|
||||
event.fn = wrappedHandler;
|
||||
|
||||
// Register the modified event
|
||||
super.registerEvent(event);
|
||||
};
|
||||
|
||||
// Replace the original event handler with the wrapped one
|
||||
event.fn = wrappedHandler;
|
||||
|
||||
// Register the modified event
|
||||
super.registerEvent(event);
|
||||
}
|
||||
|
||||
async onload() {
|
||||
@@ -265,6 +273,11 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
addIcon(EXPORT_IMG_ICON_NAME, EXPORT_IMG_ICON);
|
||||
|
||||
await this.loadSettings({reEnableAutosave:true});
|
||||
if(!this.settings.onceOffCompressFlagReset) {
|
||||
this.settings.compress = true;
|
||||
this.settings.onceOffCompressFlagReset = true;
|
||||
await this.saveSettings();
|
||||
}
|
||||
this.excalidrawConfig = new ExcalidrawConfig(this);
|
||||
await loadMermaid();
|
||||
this.editorHandler = new EditorHandler(this);
|
||||
@@ -319,6 +332,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
|
||||
const self = this;
|
||||
this.app.workspace.onLayoutReady(() => {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(self.onload,"ExcalidrawPlugin.onload > app.workspace.onLayoutReady");
|
||||
this.scriptEngine = new ScriptEngine(self);
|
||||
imageCache.initializeDB(self);
|
||||
});
|
||||
@@ -327,7 +341,9 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
|
||||
private setPropertyTypes() {
|
||||
const app = this.app;
|
||||
const self = this;
|
||||
this.app.workspace.onLayoutReady(() => {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(self.setPropertyTypes, `ExcalidrawPlugin.setPropertyTypes > app.workspace.onLayoutReady`);
|
||||
Object.keys(FRONTMATTER_KEYS).forEach((key) => {
|
||||
if(FRONTMATTER_KEYS[key].depricated === true) return;
|
||||
const {name, type} = FRONTMATTER_KEYS[key];
|
||||
@@ -337,7 +353,9 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
}
|
||||
|
||||
public initializeFonts() {
|
||||
const self = this;
|
||||
this.app.workspace.onLayoutReady(async () => {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(self.initializeFonts,`ExcalidrawPlugin.initializeFonts > app.workspace.onLayoutReady`);
|
||||
const font = await getFontDataURL(
|
||||
this.app,
|
||||
this.settings.experimantalFourthFont,
|
||||
@@ -379,9 +397,10 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
}
|
||||
|
||||
private getOpenObsidianDocuments(): Document[] {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.getOpenObsidianDocuments,`ExcalidrawPlugin.getOpenObsidianDocuments`);
|
||||
const visitedDocs = new Set<Document>();
|
||||
this.app.workspace.iterateAllLeaves((leaf)=>{
|
||||
const ownerDocument = this.app.isMobile?document:leaf.view.containerEl.ownerDocument;
|
||||
const ownerDocument = DEVICE.isMobile?document:leaf.view.containerEl.ownerDocument;
|
||||
if(!ownerDocument) return;
|
||||
if(visitedDocs.has(ownerDocument)) return;
|
||||
visitedDocs.add(ownerDocument);
|
||||
@@ -392,16 +411,17 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
private switchToExcalidarwAfterLoad() {
|
||||
const self = this;
|
||||
this.app.workspace.onLayoutReady(() => {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(self.switchToExcalidarwAfterLoad, `ExcalidrawPlugin.switchToExcalidarwAfterLoad > app.workspace.onLayoutReady`);
|
||||
let leaf: WorkspaceLeaf;
|
||||
for (leaf of this.app.workspace.getLeavesOfType("markdown")) {
|
||||
if (
|
||||
leaf.view instanceof MarkdownView &&
|
||||
self.isExcalidrawFile(leaf.view.file) &&
|
||||
fileShouldDefaultAsExcalidraw(leaf.view.file?.path, self.app)
|
||||
) {
|
||||
self.excalidrawFileModes[(leaf as any).id || leaf.view.file.path] =
|
||||
VIEW_TYPE_EXCALIDRAW;
|
||||
self.setExcalidrawView(leaf);
|
||||
if ( leaf.view instanceof MarkdownView && self.isExcalidrawFile(leaf.view.file)) {
|
||||
if (fileShouldDefaultAsExcalidraw(leaf.view.file?.path, self.app)) {
|
||||
self.excalidrawFileModes[(leaf as any).id || leaf.view.file.path] =
|
||||
VIEW_TYPE_EXCALIDRAW;
|
||||
self.setExcalidrawView(leaf);
|
||||
} else {
|
||||
foldExcalidrawSection(leaf.view);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -619,16 +639,19 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
* Displays a transcluded .excalidraw image in markdown preview mode
|
||||
*/
|
||||
private addMarkdownPostProcessor() {
|
||||
//Licat: Are you registering your post processors in onLayoutReady? You should register them in onload instead
|
||||
initializeMarkdownPostProcessor(this);
|
||||
this.registerMarkdownPostProcessor(markdownPostProcessor);
|
||||
|
||||
const self = this;
|
||||
this.app.workspace.onLayoutReady(() => {
|
||||
initializeMarkdownPostProcessor(self);
|
||||
self.registerMarkdownPostProcessor(markdownPostProcessor);
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(self.addMarkdownPostProcessor, `ExcalidrawPlugin.addMarkdownPostProcessor > app.workspace.onLayoutReady`);
|
||||
|
||||
// internal-link quick preview
|
||||
self.registerEvent(self.app.workspace.on("hover-link", hoverEvent));
|
||||
|
||||
//only add the legacy file observer if there are legacy files in the vault
|
||||
if(this.app.vault.getFiles().some(f=>f.extension === "excalidraw")) {
|
||||
if(self.app.vault.getFiles().some(f=>f.extension === "excalidraw")) {
|
||||
self.enableLegacyFilePopoverObserver();
|
||||
}
|
||||
});
|
||||
@@ -636,6 +659,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
|
||||
public enableLegacyFilePopoverObserver() {
|
||||
if(!this.legacyExcalidrawPopoverObserver) {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.enableLegacyFilePopoverObserver, `ExcalidrawPlugin.enableLegacyFilePopoverObserver > enabling`)
|
||||
//monitoring for div.popover.hover-popover.file-embed.is-loaded to be added to the DOM tree
|
||||
this.legacyExcalidrawPopoverObserver = legacyExcalidrawPopoverObserver;
|
||||
this.legacyExcalidrawPopoverObserver.observe(document.body, { childList: true, subtree: false });
|
||||
@@ -648,6 +672,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
if (!matchThemeTrigger) return;
|
||||
|
||||
const themeObserverFn:MutationCallback = async (mutations: MutationRecord[]) => {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(themeObserverFn, `ExcalidrawPlugin.addThemeObserver`, mutations);
|
||||
const { matchThemeTrigger } = this.settings;
|
||||
if (!matchThemeTrigger) return;
|
||||
|
||||
@@ -671,7 +696,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
});
|
||||
};
|
||||
|
||||
this.themeObserver = isDebugMode
|
||||
this.themeObserver = DEBUGGING
|
||||
? new CustomMutationObserver(themeObserverFn, "themeObserver")
|
||||
: new MutationObserver(themeObserverFn);
|
||||
|
||||
@@ -702,8 +727,9 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
* Display characters configured in settings, in front of the filename, if the markdown file is an excalidraw drawing
|
||||
*/
|
||||
private experimentalFileTypeDisplay() {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.experimentalFileTypeDisplay, `ExcalidrawPlugin.experimentalFileTypeDisplay`);
|
||||
const insertFiletype = (el: HTMLElement) => {
|
||||
if (el.childElementCount != 1) {
|
||||
if (el.childElementCount !== 1) {
|
||||
return;
|
||||
}
|
||||
const filename = el.getAttribute("data-path");
|
||||
@@ -726,6 +752,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
};
|
||||
|
||||
const fileExplorerObserverFn:MutationCallback = (mutationsList) => {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(fileExplorerObserverFn, `ExcalidrawPlugin.experimentalFileTypeDisplay > fileExplorerObserverFn`, mutationsList);
|
||||
const mutationsWithNodes = mutationsList.filter((mutation) => mutation.addedNodes.length > 0);
|
||||
mutationsWithNodes.forEach((mutationNode) => {
|
||||
mutationNode.addedNodes.forEach((node) => {
|
||||
@@ -737,12 +764,13 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
});
|
||||
};
|
||||
|
||||
this.fileExplorerObserver = isDebugMode
|
||||
this.fileExplorerObserver = DEBUGGING
|
||||
? new CustomMutationObserver(fileExplorerObserverFn, "fileExplorerObserver")
|
||||
: new MutationObserver(fileExplorerObserverFn);
|
||||
|
||||
const self = this;
|
||||
this.app.workspace.onLayoutReady(() => {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(self.experimentalFileTypeDisplay, `ExcalidrawPlugin.experimentalFileTypeDisplay > app.workspace.onLayoutReady`);
|
||||
document.querySelectorAll(".nav-file-title").forEach(insertFiletype); //apply filetype to files already displayed
|
||||
const container = document.querySelector(".nav-files-container");
|
||||
if (container) {
|
||||
@@ -867,9 +895,9 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
|
||||
(async () => {
|
||||
const data = await this.app.vault.read(activeFile);
|
||||
const parts = data.split("%%\n# Drawing\n```compressed-json\n");
|
||||
const parts = data.split("\n## Drawing\n```compressed-json\n");
|
||||
if(parts.length!==2) return;
|
||||
const header = parts[0] + "%%\n# Drawing\n```json\n";
|
||||
const header = parts[0] + "\n## Drawing\n```json\n";
|
||||
const compressed = parts[1].split("\n```\n%%");
|
||||
if(compressed.length!==2) return;
|
||||
const decompressed = decompress(compressed[0]);
|
||||
@@ -1044,7 +1072,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
if (checking) {
|
||||
return (
|
||||
Boolean(this.app.workspace.getActiveViewOfType(MarkdownView)) &&
|
||||
this.lastActiveExcalidrawFilePath != null
|
||||
this.lastActiveExcalidrawFilePath !== null
|
||||
);
|
||||
}
|
||||
const file = this.app.vault.getAbstractFileByPath(
|
||||
@@ -1087,7 +1115,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
name: t("NEW_IN_POPOUT_WINDOW"),
|
||||
checkCallback: (checking: boolean) => {
|
||||
if (checking) {
|
||||
return !app.isMobile
|
||||
return !DEVICE.isMobile;
|
||||
}
|
||||
this.createAndOpenDrawing(getDrawingFilename(this.settings), "popout-window");
|
||||
},
|
||||
@@ -1159,7 +1187,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
name: t("NEW_IN_POPOUT_WINDOW_EMBED"),
|
||||
checkCallback: (checking: boolean) => {
|
||||
if (checking) {
|
||||
return !app.isMobile && Boolean(this.app.workspace.getActiveViewOfType(MarkdownView));
|
||||
return !DEVICE.isMobile && Boolean(this.app.workspace.getActiveViewOfType(MarkdownView));
|
||||
}
|
||||
insertDrawingToDoc("popout-window");
|
||||
return true;
|
||||
@@ -1181,7 +1209,51 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
new Notice("Taskbone OCR is not enabled. Please go to plugins settings to enable it.",4000);
|
||||
return true;
|
||||
}
|
||||
this.taskbone.getTextForView(view, false);
|
||||
this.taskbone.getTextForView(view, {forceReScan: false});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
});
|
||||
|
||||
this.addCommand({
|
||||
id: "rerun-ocr",
|
||||
name: t("RERUN_OCR"),
|
||||
checkCallback: (checking: boolean) => {
|
||||
const view = this.app.workspace.getActiveViewOfType(ExcalidrawView);
|
||||
if (checking) {
|
||||
return (
|
||||
Boolean(view)
|
||||
);
|
||||
}
|
||||
if (view) {
|
||||
if(!this.settings.taskboneEnabled) {
|
||||
new Notice("Taskbone OCR is not enabled. Please go to plugins settings to enable it.",4000);
|
||||
return true;
|
||||
}
|
||||
this.taskbone.getTextForView(view, {forceReScan: true});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
});
|
||||
|
||||
this.addCommand({
|
||||
id: "run-ocr-selectedelements",
|
||||
name: t("RUN_OCR_ELEMENTS"),
|
||||
checkCallback: (checking: boolean) => {
|
||||
const view = this.app.workspace.getActiveViewOfType(ExcalidrawView);
|
||||
if (checking) {
|
||||
return (
|
||||
Boolean(view)
|
||||
);
|
||||
}
|
||||
if (view) {
|
||||
if(!this.settings.taskboneEnabled) {
|
||||
new Notice("Taskbone OCR is not enabled. Please go to plugins settings to enable it.",4000);
|
||||
return true;
|
||||
}
|
||||
this.taskbone.getTextForView(view, {forceReScan: false, selectedElementsOnly: true, addToFrontmatter: false});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -1530,6 +1602,39 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
},
|
||||
});
|
||||
|
||||
this.addCommand({
|
||||
id: "flip-image",
|
||||
name: t("FLIP_IMAGE"),
|
||||
checkCallback: (checking:boolean) => {
|
||||
const view = this.app.workspace.getActiveViewOfType(ExcalidrawView);
|
||||
if(!view) return false;
|
||||
if(!view.excalidrawAPI) return false;
|
||||
const els = view.getViewSelectedElements().filter(el=>el.type==="image");
|
||||
if(els.length !== 1) {
|
||||
return false;
|
||||
}
|
||||
const el = els[0] as ExcalidrawImageElement;
|
||||
let ef = view.excalidrawData.getFile(el.fileId);
|
||||
if(!ef) {
|
||||
return false;
|
||||
}
|
||||
if(!this.isExcalidrawFile(ef.file)) {
|
||||
return false;
|
||||
}
|
||||
if(checking) return true;
|
||||
this.forceToOpenInMarkdownFilepath = ef.file?.path;
|
||||
const appState = view.excalidrawAPI.getAppState();
|
||||
const {x:centerX,y:centerY} = sceneCoordsToViewportCoords({sceneX:el.x+el.width/2,sceneY:el.y+el.height/2},appState);
|
||||
const {width, height} = {width:600, height:600};
|
||||
const {x,y} = {
|
||||
x:centerX - width/2 + view.ownerWindow.screenX,
|
||||
y:centerY - height/2 + view.ownerWindow.screenY,
|
||||
}
|
||||
|
||||
this.openDrawing(ef.file, DEVICE.isMobile ? "new-tab":"popout-window", true, undefined, false, {x,y,width,height});
|
||||
}
|
||||
})
|
||||
|
||||
this.addCommand({
|
||||
id: "reset-image-to-100",
|
||||
name: t("RESET_IMG_TO_100"),
|
||||
@@ -1568,6 +1673,31 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
}
|
||||
})
|
||||
|
||||
this.addCommand({
|
||||
id: "convert-card-to-file",
|
||||
name: t("CONVERT_CARD_TO_FILE"),
|
||||
checkCallback: (checking:boolean) => {
|
||||
const view = this.app.workspace.getActiveViewOfType(ExcalidrawView);
|
||||
if(!view) return false;
|
||||
if(!view.excalidrawAPI) return false;
|
||||
const els = view.getViewSelectedElements().filter(el=>el.type==="embeddable");
|
||||
if(els.length !== 1) {
|
||||
if(checking) return false;
|
||||
new Notice("Select a single back-of-the-note card and try again");
|
||||
return false;
|
||||
}
|
||||
const embeddableData = view.getEmbeddableLeafElementById(els[0].id);
|
||||
const child = embeddableData?.node?.child;
|
||||
if(!child || (child.file !== view.file)) {
|
||||
if(checking) return false;
|
||||
new Notice("The selected embeddable is not a back-of-the-note card.");
|
||||
return false;
|
||||
}
|
||||
if(checking) return true;
|
||||
view.moveBackOfTheNoteCardToFile();
|
||||
}
|
||||
})
|
||||
|
||||
this.addCommand({
|
||||
id: "insert-active-pdfpage",
|
||||
name: t("INSERT_ACTIVE_PDF_PAGE_AS_IMAGE"),
|
||||
@@ -2226,12 +2356,14 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
|
||||
private registerMonkeyPatches() {
|
||||
const key = "https://github.com/zsviczian/obsidian-excalidraw-plugin/issues";
|
||||
|
||||
this.register(
|
||||
around(Workspace.prototype, {
|
||||
getActiveViewOfType(old) {
|
||||
return dedupe(key, old, function(...args) {
|
||||
return dedupe(key, old, function(...args) {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.registerMonkeyPatches, `ExcalidrawPlugin.MonkeyPatch >getActiveViewOfType`, key, old, ...args);
|
||||
const result = old && old.apply(this, args);
|
||||
const maybeEAView = app?.workspace?.activeLeaf?.view;
|
||||
const maybeEAView = self.app?.workspace?.activeLeaf?.view;
|
||||
if(!maybeEAView || !(maybeEAView instanceof ExcalidrawView)) return result;
|
||||
const error = new Error();
|
||||
const stackTrace = error.stack;
|
||||
@@ -2248,7 +2380,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
})
|
||||
);
|
||||
//@ts-ignore
|
||||
if(!app.plugins?.plugins?.["obsidian-hover-editor"]) {
|
||||
if(!this.app.plugins?.plugins?.["obsidian-hover-editor"]) {
|
||||
this.register( //stolen from hover editor
|
||||
around(WorkspaceLeaf.prototype, {
|
||||
getRoot(old) {
|
||||
@@ -2260,7 +2392,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
}));
|
||||
}
|
||||
this.registerEvent(
|
||||
app.workspace.on("editor-menu", (menu, editor, view) => {
|
||||
this.app.workspace.on("editor-menu", (menu, editor, view) => {
|
||||
if(!view || !(view instanceof MarkdownView)) return;
|
||||
const file = view.file;
|
||||
const leaf = view.leaf;
|
||||
@@ -2283,7 +2415,8 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
);
|
||||
|
||||
this.registerEvent(
|
||||
app.workspace.on("file-menu", (menu, file, source, leaf) => {
|
||||
this.app.workspace.on("file-menu", (menu, file, source, leaf) => {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.registerMonkeyPatches, `ExcalidrawPlugin.MonkeyPatch > file-menu`, file, source, leaf);
|
||||
if (!leaf) return;
|
||||
const view = leaf.view;
|
||||
if(!view || !(view instanceof MarkdownView)) return;
|
||||
@@ -2331,28 +2464,39 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
|
||||
setViewState(next) {
|
||||
return function (state: ViewState, ...rest: any[]) {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.registerMonkeyPatches, `ExcalidrawPlugin.MonkeyPatch > setViewState`, next);
|
||||
const markdownViewLoaded =
|
||||
self._loaded && // Don't force excalidraw mode during shutdown
|
||||
state.type === "markdown" && // If we have a markdown file
|
||||
state.state?.file;
|
||||
if (
|
||||
// Don't force excalidraw mode during shutdown
|
||||
self._loaded &&
|
||||
// If we have a markdown file
|
||||
state.type === "markdown" &&
|
||||
state.state?.file &&
|
||||
// And the current mode of the file is not set to markdown
|
||||
self.excalidrawFileModes[this.id || state.state.file] !==
|
||||
"markdown"
|
||||
markdownViewLoaded &&
|
||||
self.excalidrawFileModes[this.id || state.state.file] !== "markdown"
|
||||
) {
|
||||
if (fileShouldDefaultAsExcalidraw(state.state.file,this.app)) {
|
||||
const file = state.state.file;
|
||||
if ((self.forceToOpenInMarkdownFilepath !== file) && fileShouldDefaultAsExcalidraw(file,this.app)) {
|
||||
// If we have it, force the view type to excalidraw
|
||||
const newState = {
|
||||
...state,
|
||||
type: VIEW_TYPE_EXCALIDRAW,
|
||||
};
|
||||
|
||||
self.excalidrawFileModes[state.state.file] =
|
||||
self.excalidrawFileModes[file] =
|
||||
VIEW_TYPE_EXCALIDRAW;
|
||||
|
||||
return next.apply(this, [newState, ...rest]);
|
||||
}
|
||||
self.forceToOpenInMarkdownFilepath = null;
|
||||
}
|
||||
|
||||
if(markdownViewLoaded) {
|
||||
const leaf = this;
|
||||
setTimeout(async ()=> {
|
||||
if(!leaf || !leaf.view || !(leaf.view instanceof MarkdownView) ||
|
||||
!leaf.view.file || !self.isExcalidrawFile(leaf.view.file)
|
||||
) return;
|
||||
foldExcalidrawSection(leaf.view)
|
||||
},500);
|
||||
}
|
||||
|
||||
return next.apply(this, [state, ...rest]);
|
||||
@@ -2363,11 +2507,13 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
}
|
||||
|
||||
private runStartupScript() {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.runStartupScript, `ExcalidrawPlugin.runStartupScript`);
|
||||
if(!this.settings.startupScriptPath || this.settings.startupScriptPath === "") {
|
||||
return;
|
||||
}
|
||||
const self = this;
|
||||
this.app.workspace.onLayoutReady(async () => {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(self.runStartupScript, `ExcalidrawPlugin.runStartupScript > app.workspace.onLayoutReady, scriptPath:${self.settings?.startupScriptPath}`);
|
||||
const path = self.settings.startupScriptPath.endsWith(".md")
|
||||
? self.settings.startupScriptPath
|
||||
: `${self.settings.startupScriptPath}.md`;
|
||||
@@ -2390,6 +2536,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
private registerEventListeners() {
|
||||
const self: ExcalidrawPlugin = this;
|
||||
this.app.workspace.onLayoutReady(async () => {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(self.registerEventListeners,`ExcalidrawPlugin.registerEventListeners > app.workspace.onLayoutReady`);
|
||||
const onPasteHandler = (
|
||||
evt: ClipboardEvent,
|
||||
editor: Editor,
|
||||
@@ -2418,29 +2565,30 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
if(sourceFile && imageFile && imageFile instanceof TFile) {
|
||||
path = self.app.metadataCache.fileToLinktext(imageFile,sourceFile.path);
|
||||
}
|
||||
editor.insertText(getLink(self, {path}));
|
||||
editorInsertText(editor, getLink(self, {path}));
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (element.type === "text") {
|
||||
editor.insertText(element.text);
|
||||
editorInsertText(editor, element.rawText);
|
||||
return;
|
||||
}
|
||||
if (element.link) {
|
||||
editor.insertText(`${element.link}`);
|
||||
editorInsertText(editor, `${element.link}`);
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
};
|
||||
self.registerEvent(self.app.workspace.on('editor-paste', onPasteHandler));
|
||||
self.registerEvent(self.app.workspace.on("editor-paste", onPasteHandler));
|
||||
|
||||
//watch filename change to rename .svg, .png; to sync to .md; to update links
|
||||
const renameEventHandler = async (
|
||||
file: TAbstractFile,
|
||||
oldPath: string,
|
||||
) => {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(renameEventHandler,`ExcalidrawPlugin.renameEventHandler`, file, oldPath);
|
||||
if (!(file instanceof TFile)) {
|
||||
return;
|
||||
}
|
||||
@@ -2464,7 +2612,8 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
self.registerEvent(app.vault.on("rename", renameEventHandler));
|
||||
|
||||
const modifyEventHandler = async (file: TFile) => {
|
||||
const leaves = this.app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW);
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(modifyEventHandler,`ExcalidrawPlugin.modifyEventHandler`, file);
|
||||
const leaves = self.app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW);
|
||||
leaves.forEach(async (leaf: WorkspaceLeaf) => {
|
||||
const excalidrawView = leaf.view as ExcalidrawView;
|
||||
if (
|
||||
@@ -2489,7 +2638,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
if(file.extension==="md") {
|
||||
if(excalidrawView.semaphores.embeddableIsEditingSelf) return;
|
||||
const inData = new ExcalidrawData(self);
|
||||
const data = await this.app.vault.read(file);
|
||||
const data = await self.app.vault.read(file);
|
||||
await inData.loadData(data,file,getTextMode(data));
|
||||
excalidrawView.synchronizeWithData(inData);
|
||||
if(excalidrawView.semaphores.dirty) {
|
||||
@@ -2510,18 +2659,19 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
|
||||
//watch file delete and delete corresponding .svg and .png
|
||||
const deleteEventHandler = async (file: TFile) => {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(deleteEventHandler,`ExcalidrawPlugin.deleteEventHandler`, file);
|
||||
if (!(file instanceof TFile)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isExcalidarwFile = this.excalidrawFiles.has(file);
|
||||
this.updateFileCache(file, undefined, true);
|
||||
const isExcalidarwFile = self.excalidrawFiles.has(file);
|
||||
self.updateFileCache(file, undefined, true);
|
||||
if (!isExcalidarwFile) {
|
||||
return;
|
||||
}
|
||||
|
||||
//close excalidraw view where this file is open
|
||||
const leaves = this.app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW);
|
||||
const leaves = self.app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW);
|
||||
for (let i = 0; i < leaves.length; i++) {
|
||||
if ((leaves[i].view as ExcalidrawView).file.path == file.path) {
|
||||
await leaves[i].setViewState({
|
||||
@@ -2560,6 +2710,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
|
||||
//save Excalidraw leaf and update embeds when switching to another leaf
|
||||
const activeLeafChangeEventHandler = async (leaf: WorkspaceLeaf) => {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(activeLeafChangeEventHandler,`ExcalidrawPlugin.activeLeafChangeEventHandler`, leaf);
|
||||
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/723
|
||||
if(self.leafChangeTimeout) {
|
||||
clearTimeout(self.leafChangeTimeout);
|
||||
@@ -2580,14 +2731,14 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
|
||||
//!Temporary hack
|
||||
//https://discord.com/channels/686053708261228577/817515900349448202/1031101635784613968
|
||||
if (this.app.isMobile && newActiveviewEV && !previouslyActiveEV) {
|
||||
if (DEVICE.isMobile && newActiveviewEV && !previouslyActiveEV) {
|
||||
const navbar = document.querySelector("body>.app-container>.mobile-navbar");
|
||||
if(navbar && navbar instanceof HTMLDivElement) {
|
||||
navbar.style.position="relative";
|
||||
}
|
||||
}
|
||||
|
||||
if (this.app.isMobile && !newActiveviewEV && previouslyActiveEV) {
|
||||
if (DEVICE.isMobile && !newActiveviewEV && previouslyActiveEV) {
|
||||
const navbar = document.querySelector("body>.app-container>.mobile-navbar");
|
||||
if(navbar && navbar instanceof HTMLDivElement) {
|
||||
navbar.style.position="";
|
||||
@@ -2656,10 +2807,10 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
const scope = self.app.keymap.getRootScope();
|
||||
const handler_ctrlEnter = scope.register(["Mod"], "Enter", () => true);
|
||||
scope.keys.unshift(scope.keys.pop()); // Force our handler to the front of the list
|
||||
const handler_ctrlK = scope.register(["Mod"], "k", () => {return true});
|
||||
const handler_ctrlK = scope.register(["Mod"], "k", () => true);
|
||||
scope.keys.unshift(scope.keys.pop()); // Force our handler to the front of the list
|
||||
const handler_ctrlF = scope.register(["Mod"], "f", () => {
|
||||
const view = this.app.workspace.getActiveViewOfType(ExcalidrawView);
|
||||
const view = self.app.workspace.getActiveViewOfType(ExcalidrawView);
|
||||
if (view) {
|
||||
search(view);
|
||||
return true;
|
||||
@@ -2686,8 +2837,9 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
}
|
||||
}
|
||||
};
|
||||
self.activeLeafChangeEventHandler = activeLeafChangeEventHandler;
|
||||
self.registerEvent(
|
||||
app.workspace.on(
|
||||
self.app.workspace.on(
|
||||
"active-leaf-change",
|
||||
activeLeafChangeEventHandler,
|
||||
),
|
||||
@@ -2695,7 +2847,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
|
||||
self.addFileSaveTriggerEventHandlers();
|
||||
|
||||
const metaCache: MetadataCache = app.metadataCache;
|
||||
const metaCache: MetadataCache = self.app.metadataCache;
|
||||
//@ts-ignore
|
||||
metaCache.getCachedFiles().forEach((filename: string) => {
|
||||
const fm = metaCache.getCache(filename)?.frontmatter;
|
||||
@@ -2704,14 +2856,14 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
filename.match(/\.excalidraw$/)
|
||||
) {
|
||||
self.updateFileCache(
|
||||
app.vault.getAbstractFileByPath(filename) as TFile,
|
||||
self.app.vault.getAbstractFileByPath(filename) as TFile,
|
||||
fm,
|
||||
);
|
||||
}
|
||||
});
|
||||
this.registerEvent(
|
||||
self.registerEvent(
|
||||
metaCache.on("changed", (file, data, cache) =>
|
||||
this.updateFileCache(file, cache?.frontmatter),
|
||||
self.updateFileCache(file, cache?.frontmatter),
|
||||
),
|
||||
);
|
||||
});
|
||||
@@ -2739,6 +2891,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
});
|
||||
|
||||
const onFileMenuEventSaveActiveDrawing = () => {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(onFileMenuEventSaveActiveDrawing,`ExcalidrawPlugin.onFileMenuEventSaveActiveDrawing`);
|
||||
if (
|
||||
!this.activeExcalidrawView ||
|
||||
!this.activeExcalidrawView.semaphores.dirty
|
||||
@@ -2777,14 +2930,14 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
};
|
||||
|
||||
if (leftWorkspaceDrawer) {
|
||||
this.workspaceDrawerLeftObserver = isDebugMode
|
||||
this.workspaceDrawerLeftObserver = DEBUGGING
|
||||
? new CustomMutationObserver(action, "slidingDrawerLeftObserver")
|
||||
: new MutationObserver(action);
|
||||
this.workspaceDrawerLeftObserver.observe(leftWorkspaceDrawer, options);
|
||||
}
|
||||
|
||||
if (rightWorkspaceDrawer) {
|
||||
this.workspaceDrawerRightObserver = isDebugMode
|
||||
this.workspaceDrawerRightObserver = DEBUGGING
|
||||
? new CustomMutationObserver(action, "slidingDrawerRightObserver")
|
||||
: new MutationObserver(action);
|
||||
this.workspaceDrawerRightObserver.observe(
|
||||
@@ -2806,6 +2959,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
}
|
||||
//The user clicks settings, or "open another vault", or the command palette
|
||||
const modalContainerObserverFn: MutationCallback = async (m: MutationRecord[]) => {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(modalContainerObserverFn,`ExcalidrawPlugin.modalContainerObserverFn`, m);
|
||||
if (
|
||||
(m.length !== 1) ||
|
||||
(m[0].type !== "childList") ||
|
||||
@@ -2818,7 +2972,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
this.activeExcalidrawView.save();
|
||||
};
|
||||
|
||||
this.modalContainerObserver = isDebugMode
|
||||
this.modalContainerObserver = DEBUGGING
|
||||
? new CustomMutationObserver(modalContainerObserverFn, "modalContainerObserver")
|
||||
: new MutationObserver(modalContainerObserverFn);
|
||||
this.activeViewDoc = this.activeExcalidrawView.ownerDocument;
|
||||
@@ -2841,6 +2995,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
frontmatter?: FrontMatterCache,
|
||||
deleted: boolean = false,
|
||||
) {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.updateFileCache,`ExcalidrawPlugin.updateFileCache`, file, frontmatter, deleted);
|
||||
if (frontmatter && typeof frontmatter[FRONTMATTER_KEYS["plugin"].name] !== "undefined") {
|
||||
this.excalidrawFiles.add(file);
|
||||
return;
|
||||
@@ -2856,10 +3011,10 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
const excalidrawLeaves = this.app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW);
|
||||
excalidrawLeaves.forEach(async (leaf) => {
|
||||
const ev: ExcalidrawView = leaf.view as ExcalidrawView;
|
||||
console.log(ev.file.name, ev.semaphores.dirty);
|
||||
//console.log(ev.file.name, ev.semaphores.dirty);
|
||||
await this.setMarkdownView(leaf);
|
||||
//@ts-ignore
|
||||
console.log(leaf?.view?.file);
|
||||
//console.log(leaf?.view?.file);
|
||||
});
|
||||
|
||||
document.body.removeChild(this.textMeasureDiv);
|
||||
@@ -2977,6 +3132,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
reEnableAutosave?: boolean
|
||||
} = {applyLefthandedMode: true, reEnableAutosave: false}
|
||||
) {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.loadSettings,`ExcalidrawPlugin.loadSettings`, opts);
|
||||
if(typeof opts.applyLefthandedMode === "undefined") opts.applyLefthandedMode = true;
|
||||
if(typeof opts.reEnableAutosave === "undefined") opts.reEnableAutosave = false;
|
||||
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
|
||||
@@ -2991,16 +3147,16 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
}
|
||||
if(opts.applyLefthandedMode) setLeftHandedMode(this.settings.isLeftHanded);
|
||||
if(opts.reEnableAutosave) this.settings.autosave = true;
|
||||
this.settings.autosaveInterval = app.isMobile
|
||||
? this.settings.autosaveIntervalMobile
|
||||
: this.settings.autosaveIntervalDesktop;
|
||||
setDebugging(this.settings.isDebugMode);
|
||||
}
|
||||
|
||||
async saveSettings() {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.saveSettings,`ExcalidrawPlugin.saveSettings`);
|
||||
await this.saveData(this.settings);
|
||||
}
|
||||
|
||||
public getStencilLibrary(): {} {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.getStencilLibrary,`ExcalidrawPlugin.getStencilLibrary`);
|
||||
if (
|
||||
this.settings.library === "" ||
|
||||
this.settings.library === "deprecated"
|
||||
@@ -3011,14 +3167,16 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
}
|
||||
|
||||
public setStencilLibrary(library: {}) {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.setStencilLibrary,`ExcalidrawPlugin.setStencilLibrary`, library);
|
||||
this.settings.library = "deprecated";
|
||||
this.settings.library2 = library;
|
||||
}
|
||||
|
||||
public triggerEmbedUpdates(filepath?: string) {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.triggerEmbedUpdates,`ExcalidrawPlugin.triggerEmbedUpdates`, filepath);
|
||||
const visitedDocs = new Set<Document>();
|
||||
app.workspace.iterateAllLeaves((leaf)=>{
|
||||
const ownerDocument = app.isMobile?document:leaf.view.containerEl.ownerDocument;
|
||||
this.app.workspace.iterateAllLeaves((leaf)=>{
|
||||
const ownerDocument = DEVICE.isMobile?document:leaf.view.containerEl.ownerDocument;
|
||||
if(!ownerDocument) return;
|
||||
if(visitedDocs.has(ownerDocument)) return;
|
||||
visitedDocs.add(ownerDocument);
|
||||
@@ -3039,7 +3197,8 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
location: PaneTarget,
|
||||
active: boolean = false,
|
||||
subpath?: string,
|
||||
justCreated: boolean = false
|
||||
justCreated: boolean = false,
|
||||
popoutLocation?: {x?: number, y?: number, width?: number, height?: number},
|
||||
) {
|
||||
|
||||
const fnGetLeaf = ():WorkspaceLeaf => {
|
||||
@@ -3048,10 +3207,11 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
}
|
||||
let leaf: WorkspaceLeaf;
|
||||
if(location === "popout-window") {
|
||||
leaf = app.workspace.openPopoutLeaf();
|
||||
//@ts-ignore (the api does not include x,y)
|
||||
leaf = this.app.workspace.openPopoutLeaf(popoutLocation);
|
||||
}
|
||||
if(location === "new-tab") {
|
||||
leaf = app.workspace.getLeaf('tab');
|
||||
leaf = this.app.workspace.getLeaf('tab');
|
||||
}
|
||||
if(!leaf) {
|
||||
leaf = this.app.workspace.getLeaf(false);
|
||||
@@ -3132,7 +3292,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
const textElements = excalidrawData.elements?.filter(
|
||||
(el: any) => el.type == "text",
|
||||
);
|
||||
let outString = `${MD_TEXTELEMENTS}\n`;
|
||||
let outString = `# Excalidraw Data\n## Text Elements\n`;
|
||||
let id: string;
|
||||
for (const te of textElements) {
|
||||
id = te.id;
|
||||
@@ -3193,7 +3353,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
return file.path;
|
||||
}
|
||||
|
||||
public async setMarkdownView(leaf: WorkspaceLeaf) {
|
||||
public async setMarkdownView(leaf: WorkspaceLeaf, eState?: any) {
|
||||
const state = leaf.view.getState();
|
||||
|
||||
//Note v2.0.19: I have absolutely no idea why I thought this is necessary. Removing this.
|
||||
@@ -3209,11 +3369,18 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
state,
|
||||
popstate: true,
|
||||
} as ViewState,
|
||||
{ focus: true },
|
||||
eState ? eState : { focus: true },
|
||||
);
|
||||
|
||||
const mdView = leaf.view;
|
||||
if(mdView instanceof MarkdownView) {
|
||||
foldExcalidrawSection(mdView);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public async setExcalidrawView(leaf: WorkspaceLeaf) {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.setExcalidrawView,`ExcalidrawPlugin.setExcalidrawView`, leaf);
|
||||
await leaf.setViewState({
|
||||
type: VIEW_TYPE_EXCALIDRAW,
|
||||
state: leaf.view.getState(),
|
||||
@@ -3231,7 +3398,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
}
|
||||
|
||||
public async exportLibrary() {
|
||||
if (this.app.isMobile) {
|
||||
if (DEVICE.isMobile) {
|
||||
const prompt = new Prompt(
|
||||
this.app,
|
||||
"Please provide a filename",
|
||||
|
||||
@@ -39,8 +39,6 @@ export class EmbeddableMenu {
|
||||
const ea = getEA(view) as ExcalidrawAutomate;
|
||||
ea.copyViewElementsToEAforEditing([element]);
|
||||
ea.getElement(element.id).link = link;
|
||||
//mutateElement (element,{link});
|
||||
//view.setDirty(99);
|
||||
view.excalidrawData.elementLinks.set(element.id, link);
|
||||
ea.addElementsToView(false, true, true);
|
||||
}
|
||||
@@ -105,6 +103,7 @@ export class EmbeddableMenu {
|
||||
if(!file) return;
|
||||
const isMD = file.extension==="md";
|
||||
const isExcalidrawFile = view.plugin.isExcalidrawFile(file);
|
||||
const isPDF = file.extension==="pdf";
|
||||
const { x, y } = sceneCoordsToViewportCoords( { sceneX: element.x, sceneY: element.y }, appState);
|
||||
const top = `${y-2.5*ROOTELEMENTSIZE-appState.offsetTop}px`;
|
||||
const left = `${x-appState.offsetLeft}px`;
|
||||
@@ -172,7 +171,9 @@ export class EmbeddableMenu {
|
||||
view.updateScene({appState: {activeEmbeddable: null}});
|
||||
const paragraphs = (await app.metadataCache.blockCache
|
||||
.getForFile({ isCancelled: () => false },file))
|
||||
.blocks.filter((b: any) => b.display && b.node?.type === "paragraph");
|
||||
.blocks.filter((b: any) => b.display && b.node &&
|
||||
(b.node.type === "paragraph" || b.node.type === "blockquote" || b.node.type === "listItem" || b.node.type === "table" || b.node.type === "callout")
|
||||
);
|
||||
const values = ["entire-file"].concat(paragraphs);
|
||||
const display = [t("SHOW_ENTIRE_FILE")].concat(
|
||||
paragraphs.map((b: any) => `${b.node?.id ? `#^${b.node.id}: ` : ``}${b.display.trim()}`));
|
||||
@@ -225,6 +226,19 @@ export class EmbeddableMenu {
|
||||
icon={ICONS.Properties}
|
||||
view={view}
|
||||
/>
|
||||
{isPDF && (
|
||||
<ActionButton
|
||||
key={"Crop"}
|
||||
title={t("CROP_PAGE")}
|
||||
action={() => {
|
||||
if(!element) return;
|
||||
//@ts-ignore
|
||||
view.app.commands.executeCommandById("obsidian-excalidraw-plugin:crop-image");
|
||||
}}
|
||||
icon={ICONS.Crop}
|
||||
view={view}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -16,6 +16,7 @@ import { InsertPDFModal } from "src/dialogs/InsertPDFModal";
|
||||
import { ExportDialog } from "src/dialogs/ExportDialog";
|
||||
import { openExternalLink } from "src/utils/ExcalidrawViewUtils";
|
||||
import { UniversalInsertFileModal } from "src/dialogs/UniversalInsertFileModal";
|
||||
import { DEBUGGING, debug } from "src/utils/DebugHelper";
|
||||
|
||||
declare const PLUGIN_VERSION:string;
|
||||
const dark = '<svg style="stroke:#ced4da;#212529;color:#ced4da;fill:#ced4da" ';
|
||||
@@ -74,12 +75,14 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
|
||||
}
|
||||
|
||||
updateScriptIconMap(scriptIconMap: ScriptIconMap) {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.updateScriptIconMap,"ToolsPanel.updateScriptIconMap()");
|
||||
this.setState(() => {
|
||||
return { scriptIconMap };
|
||||
});
|
||||
}
|
||||
|
||||
setPreviewMode(isPreviewMode: boolean) {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.setPreviewMode,"ToolsPanel.setPreviewMode()");
|
||||
this.setState(() => {
|
||||
return {
|
||||
isPreviewMode,
|
||||
@@ -88,6 +91,7 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
|
||||
}
|
||||
|
||||
setFullscreen(isFullscreen: boolean) {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.setFullscreen,"ToolsPanel.setFullscreen()");
|
||||
this.setState(() => {
|
||||
return {
|
||||
isFullscreen,
|
||||
@@ -96,6 +100,7 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
|
||||
}
|
||||
|
||||
setDirty(isDirty: boolean) {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.setDirty,"ToolsPanel.setDirty()");
|
||||
this.setState(()=> {
|
||||
return {
|
||||
isDirty,
|
||||
@@ -104,6 +109,7 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
|
||||
}
|
||||
|
||||
setExcalidrawViewMode(isViewModeEnabled: boolean) {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.setExcalidrawViewMode,"ToolsPanel.setExcalidrawViewMode()");
|
||||
this.setState(() => {
|
||||
return {
|
||||
excalidrawViewMode: isViewModeEnabled,
|
||||
@@ -112,6 +118,7 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
|
||||
}
|
||||
|
||||
toggleVisibility(isMobileOrZen: boolean) {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.toggleVisibility,"ToolsPanel.toggleVisibility()");
|
||||
this.setTopCenter(isMobileOrZen);
|
||||
this.setState((prevState: PanelState) => {
|
||||
return {
|
||||
@@ -121,6 +128,7 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
|
||||
}
|
||||
|
||||
setTheme(theme: "dark" | "light") {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.setTheme,"ToolsPanel.setTheme()");
|
||||
this.setState((prevState: PanelState) => {
|
||||
return {
|
||||
theme,
|
||||
@@ -129,6 +137,7 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
|
||||
}
|
||||
|
||||
setTopCenter(isMobileOrZen: boolean) {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.setTopCenter,"ToolsPanel.setTopCenter()");
|
||||
this.setState(() => {
|
||||
return {
|
||||
left:
|
||||
@@ -144,6 +153,7 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
|
||||
}
|
||||
|
||||
updatePosition(deltaY: number = 0, deltaX: number = 0) {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.updatePosition,"ToolsPanel.updatePosition()");
|
||||
this.setState(() => {
|
||||
const {
|
||||
offsetTop,
|
||||
@@ -185,6 +195,7 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
|
||||
}
|
||||
|
||||
render() {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.render,"ToolsPanel.render()");
|
||||
return (
|
||||
<div
|
||||
ref={this.containerRef}
|
||||
@@ -381,7 +392,7 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
|
||||
new Notice("Taskbone OCR is not enabled. Please go to plugins settings to enable it.",4000);
|
||||
return;
|
||||
}
|
||||
this.props.view.plugin.taskbone.getTextForView(this.props.view, isWinCTRLorMacCMD(e));
|
||||
this.props.view.plugin.taskbone.getTextForView(this.props.view, {forceReScan: isWinCTRLorMacCMD(e)});
|
||||
}}
|
||||
icon={ICONS.ocr}
|
||||
view={this.props.view}
|
||||
@@ -579,7 +590,7 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
|
||||
title={t("CROP_IMAGE")}
|
||||
action={(e:React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||
// @ts-ignore
|
||||
this.props.view.app.commands.executeCommandById("obsidian-excalidraw-plugin:crop-image")
|
||||
this.props.view.app.commands.executeCommandById("obsidian-excalidraw-plugin:crop-image");
|
||||
}}
|
||||
icon={ICONS.Crop}
|
||||
view={this.props.view}
|
||||
@@ -596,6 +607,7 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
|
||||
}
|
||||
|
||||
private renderScriptButtons(isDownloaded: boolean) {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.renderScriptButtons,"ToolsPanel.renderScriptButtons()");
|
||||
if (Object.keys(this.state.scriptIconMap).length === 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { createPNG, ExcalidrawAutomate } from "../ExcalidrawAutomate";
|
||||
import { ExcalidrawAutomate, createPNG } from "../ExcalidrawAutomate";
|
||||
import {Notice, requestUrl} from "obsidian"
|
||||
import ExcalidrawPlugin from "../main"
|
||||
import {log} from "../utils/Utils"
|
||||
import ExcalidrawView, { ExportSettings } from "../ExcalidrawView"
|
||||
import FrontmatterEditor from "src/utils/Frontmatter";
|
||||
import { ExcalidrawElement, ExcalidrawImageElement } from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
import { ExcalidrawElement } from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
import { EmbeddedFilesLoader } from "src/EmbeddedFileLoader";
|
||||
import { blobToBase64 } from "src/utils/FileUtils";
|
||||
import { getEA } from "src";
|
||||
import { log } from "src/utils/DebugHelper";
|
||||
|
||||
const TASKBONE_URL = "https://api.taskbone.com/"; //"https://excalidraw-preview.onrender.com/";
|
||||
const TASKBONE_OCR_FN = "execute?id=60f394af-85f6-40bc-9613-5d26dc283cbb";
|
||||
@@ -39,23 +40,9 @@ export default class Taskbone {
|
||||
return apiKey;
|
||||
}
|
||||
|
||||
public async getTextForView(view: ExcalidrawView, forceReScan: boolean) {
|
||||
await view.forceSave(true);
|
||||
const viewElements = view.excalidrawAPI.getSceneElements().filter((el:ExcalidrawElement) =>
|
||||
el.type==="freedraw" ||
|
||||
( el.type==="image" &&
|
||||
!this.plugin.isExcalidrawFile(view.excalidrawData.getFile(el.fileId)?.file)
|
||||
));
|
||||
if(viewElements.length === 0) {
|
||||
new Notice ("Aborting OCR because there are no image or freedraw elements on the canvas.",4000);
|
||||
return;
|
||||
}
|
||||
const fe = new FrontmatterEditor(view.data);
|
||||
if(fe.hasKey("taskbone-ocr") && !forceReScan) {
|
||||
new Notice ("The drawing has already been processed, you will find the result in the frontmatter in markdown view mode. If you ran the command from the Obsidian Panel in Excalidraw then you can CTRL(CMD)+click the command to force the rescaning.",4000)
|
||||
return;
|
||||
}
|
||||
const bb = this.plugin.ea.getBoundingBox(viewElements);
|
||||
public async getTextForElements(elements: ExcalidrawElement[], ea: ExcalidrawAutomate): Promise<string> {
|
||||
ea.copyViewElementsToEAforEditing(elements, true);
|
||||
const bb = ea.getBoundingBox(elements);
|
||||
const size = (bb.width*bb.height);
|
||||
const minRatio = Math.sqrt(360000/size);
|
||||
const maxRatio = Math.sqrt(size/16000000);
|
||||
@@ -79,26 +66,52 @@ export default class Taskbone {
|
||||
};
|
||||
|
||||
const img =
|
||||
await createPNG(
|
||||
view.file.path + "#^taskbone",
|
||||
await ea.createPNG(
|
||||
null,
|
||||
scale,
|
||||
exportSettings,
|
||||
loader,
|
||||
"light",
|
||||
null,
|
||||
null,
|
||||
[],
|
||||
this.plugin,
|
||||
0
|
||||
);
|
||||
)
|
||||
return await this.getTextForImage(img);
|
||||
}
|
||||
|
||||
const text = await this.getTextForImage(img);
|
||||
public async getTextForView(view: ExcalidrawView, {
|
||||
forceReScan,
|
||||
selectedElementsOnly = false,
|
||||
addToFrontmatter = true,
|
||||
}: {
|
||||
forceReScan: boolean,
|
||||
selectedElementsOnly?: boolean,
|
||||
addToFrontmatter?: boolean,
|
||||
}) {
|
||||
await view.forceSave(true);
|
||||
const ea = getEA(view) as ExcalidrawAutomate;
|
||||
const viewElements = (selectedElementsOnly ? ea.getViewSelectedElements() : ea.getViewElements())
|
||||
.filter((el:ExcalidrawElement) =>
|
||||
el.type==="freedraw" ||
|
||||
( el.type==="image" &&
|
||||
!this.plugin.isExcalidrawFile(view.excalidrawData.getFile(el.fileId)?.file)
|
||||
));
|
||||
if(viewElements.length === 0) {
|
||||
new Notice ("Aborting OCR because there are no image or freedraw elements on the canvas.",4000);
|
||||
return;
|
||||
}
|
||||
const fe = new FrontmatterEditor(view.data);
|
||||
if(addToFrontmatter && fe.hasKey("taskbone-ocr") && !forceReScan) {
|
||||
new Notice ("The drawing has already been processed, you will find the result in the frontmatter in markdown view mode. If you ran the command from the Obsidian Panel in Excalidraw then you can CTRL(CMD)+click the command to force the rescaning.",4000)
|
||||
return;
|
||||
}
|
||||
|
||||
const text = await this.getTextForElements(viewElements, ea);
|
||||
if(text) {
|
||||
fe.setKey("taskbone-ocr",text);
|
||||
view.data = fe.data;
|
||||
view.save(false);
|
||||
if(addToFrontmatter) {
|
||||
fe.setKey("taskbone-ocr",text);
|
||||
view.data = fe.data;
|
||||
view.save(false);
|
||||
}
|
||||
window.navigator.clipboard.writeText(text);
|
||||
new Notice("I placed the recognized in the drawing's frontmatter and onto the system clipboard.");
|
||||
new Notice(`I placed the recognized text onto the system clipboard${addToFrontmatter?" and to document properties":""}.`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
TextComponent,
|
||||
TFile,
|
||||
} from "obsidian";
|
||||
import { GITHUB_RELEASES, VIEW_TYPE_EXCALIDRAW } from "./constants/constants";
|
||||
import { DEVICE, GITHUB_RELEASES, VIEW_TYPE_EXCALIDRAW } from "./constants/constants";
|
||||
import ExcalidrawView from "./ExcalidrawView";
|
||||
import { t } from "./lang/helpers";
|
||||
import type ExcalidrawPlugin from "./main";
|
||||
@@ -35,6 +35,7 @@ import { ModifierKeySet, ModifierSetType } from "./utils/ModifierkeyHelper";
|
||||
import { ModifierKeySettingsComponent } from "./dialogs/ModifierKeySettings";
|
||||
import { ANNOTATED_PREFIX, CROPPED_PREFIX } from "./utils/CarveOut";
|
||||
import { EDITOR_FADEOUT } from "./CodeMirrorExtension/EditorHandler";
|
||||
import { setDebugging } from "./utils/DebugHelper";
|
||||
|
||||
export interface ExcalidrawSettings {
|
||||
folder: string;
|
||||
@@ -44,8 +45,9 @@ export interface ExcalidrawSettings {
|
||||
templateFilePath: string;
|
||||
scriptFolderPath: string;
|
||||
compress: boolean;
|
||||
decompressForMDView: boolean;
|
||||
onceOffCompressFlagReset: boolean; //used to reset compress to true in 2.2.0
|
||||
autosave: boolean;
|
||||
autosaveInterval: number;
|
||||
autosaveIntervalDesktop: number;
|
||||
autosaveIntervalMobile: number;
|
||||
drawingFilenamePrefix: string;
|
||||
@@ -59,6 +61,7 @@ export interface ExcalidrawSettings {
|
||||
displaySVGInPreview: boolean; //No longer used since 1.9.13
|
||||
previewImageType: PreviewImageType; //Introduced with 1.9.13
|
||||
allowImageCache: boolean;
|
||||
allowImageCacheInScene: boolean;
|
||||
displayExportedImageIfAvailable: boolean;
|
||||
previewMatchObsidianTheme: boolean;
|
||||
width: string;
|
||||
@@ -73,6 +76,7 @@ export interface ExcalidrawSettings {
|
||||
defaultPenMode: "never" | "mobile" | "always";
|
||||
penModeCrosshairVisible: boolean;
|
||||
renderImageInMarkdownReadingMode: boolean,
|
||||
renderImageInHoverPreviewForMDNotes: boolean,
|
||||
renderImageInMarkdownToPDF: boolean,
|
||||
allowPinchZoom: boolean;
|
||||
allowWheelZoom: boolean;
|
||||
@@ -118,6 +122,7 @@ export interface ExcalidrawSettings {
|
||||
fadeOutExcalidrawMarkup: boolean;
|
||||
experimentalEnableFourthFont: boolean;
|
||||
experimantalFourthFont: string;
|
||||
addDummyTextElement: boolean;
|
||||
fieldSuggester: boolean;
|
||||
//loadCount: number; //version 1.2 migration counter
|
||||
drawingOpenCount: number;
|
||||
@@ -188,6 +193,7 @@ export interface ExcalidrawSettings {
|
||||
areaZoomLimit: number;
|
||||
longPressDesktop: number;
|
||||
longPressMobile: number;
|
||||
isDebugMode: boolean;
|
||||
}
|
||||
|
||||
declare const PLUGIN_VERSION:string;
|
||||
@@ -199,9 +205,10 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
|
||||
embedUseExcalidrawFolder: false,
|
||||
templateFilePath: "Excalidraw/Template.excalidraw",
|
||||
scriptFolderPath: "Excalidraw/Scripts",
|
||||
compress: false,
|
||||
compress: true,
|
||||
decompressForMDView: false,
|
||||
onceOffCompressFlagReset: false,
|
||||
autosave: true,
|
||||
autosaveInterval: 15000,
|
||||
autosaveIntervalDesktop: 15000,
|
||||
autosaveIntervalMobile: 10000,
|
||||
drawingFilenamePrefix: "Drawing ",
|
||||
@@ -215,6 +222,7 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
|
||||
displaySVGInPreview: undefined,
|
||||
previewImageType: undefined,
|
||||
allowImageCache: true,
|
||||
allowImageCacheInScene: true,
|
||||
displayExportedImageIfAvailable: false,
|
||||
previewMatchObsidianTheme: false,
|
||||
width: "400",
|
||||
@@ -229,6 +237,7 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
|
||||
defaultPenMode: "never",
|
||||
penModeCrosshairVisible: true,
|
||||
renderImageInMarkdownReadingMode: false,
|
||||
renderImageInHoverPreviewForMDNotes: false,
|
||||
renderImageInMarkdownToPDF: false,
|
||||
allowPinchZoom: false,
|
||||
allowWheelZoom: false,
|
||||
@@ -273,6 +282,7 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
|
||||
fadeOutExcalidrawMarkup: false,
|
||||
experimentalEnableFourthFont: false,
|
||||
experimantalFourthFont: "Virgil",
|
||||
addDummyTextElement: false,
|
||||
fieldSuggester: true,
|
||||
compatibilityMode: false,
|
||||
//loadCount: 0,
|
||||
@@ -436,6 +446,7 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
|
||||
areaZoomLimit: 1,
|
||||
longPressDesktop: 500,
|
||||
longPressMobile: 500,
|
||||
isDebugMode: false,
|
||||
};
|
||||
|
||||
export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
@@ -662,6 +673,18 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
}),
|
||||
);
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("DECOMPRESS_FOR_MD_NAME"))
|
||||
.setDesc(fragWithHTML(t("DECOMPRESS_FOR_MD_DESC")))
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings.decompressForMDView)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.decompressForMDView = value;
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("AUTOSAVE_INTERVAL_DESKTOP_NAME"))
|
||||
.setDesc(fragWithHTML(t("AUTOSAVE_INTERVAL_DESKTOP_DESC")))
|
||||
@@ -674,9 +697,6 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
.setValue(this.plugin.settings.autosaveIntervalDesktop.toString())
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.autosaveIntervalDesktop = parseInt(value);
|
||||
this.plugin.settings.autosaveInterval = app.isMobile
|
||||
? this.plugin.settings.autosaveIntervalMobile
|
||||
: this.plugin.settings.autosaveIntervalDesktop;
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
@@ -693,9 +713,6 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
.setValue(this.plugin.settings.autosaveIntervalMobile.toString())
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.autosaveIntervalMobile = parseInt(value);
|
||||
this.plugin.settings.autosaveInterval = app.isMobile
|
||||
? this.plugin.settings.autosaveIntervalMobile
|
||||
: this.plugin.settings.autosaveIntervalDesktop;
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
@@ -980,6 +997,18 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
}),
|
||||
);
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("SHOW_DRAWING_OR_MD_IN_HOVER_PREVIEW_NAME"))
|
||||
.setDesc(fragWithHTML(t("SHOW_DRAWING_OR_MD_IN_HOVER_PREVIEW_DESC")))
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings.renderImageInHoverPreviewForMDNotes)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.renderImageInHoverPreviewForMDNotes = value;
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("LEFTHANDED_MODE_NAME"))
|
||||
.setDesc(fragWithHTML(t("LEFTHANDED_MODE_DESC")))
|
||||
@@ -1702,6 +1731,19 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
this.applySettingsUpdate();
|
||||
})
|
||||
)
|
||||
new Setting(detailsEl)
|
||||
.setName(t("SCENE_IMAGE_CACHE_NAME"))
|
||||
.setDesc(fragWithHTML(t("SCENE_IMAGE_CACHE_DESC")))
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings.allowImageCacheInScene)
|
||||
.onChange((value) => {
|
||||
this.plugin.settings.allowImageCacheInScene = value;
|
||||
this.applySettingsUpdate();
|
||||
})
|
||||
)
|
||||
new Setting(detailsEl)
|
||||
.setName(t("EMBED_IMAGE_CACHE_CLEAR"))
|
||||
.addButton((button) =>
|
||||
button
|
||||
.setButtonText(t("EMBED_IMAGE_CACHE_CLEAR"))
|
||||
@@ -1709,6 +1751,8 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
imageCache.clearImageCache();
|
||||
})
|
||||
)
|
||||
new Setting(detailsEl)
|
||||
.setName(t("BACKUP_CACHE_CLEAR"))
|
||||
.addButton((button) =>
|
||||
button
|
||||
.setButtonText(t("BACKUP_CACHE_CLEAR"))
|
||||
@@ -2461,6 +2505,35 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
cls: "excalidraw-setting-h1",
|
||||
});
|
||||
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("DUMMY_TEXT_ELEMENT_LINT_SUPPORT_NAME"))
|
||||
.setDesc(fragWithHTML(t("DUMMY_TEXT_ELEMENT_LINT_SUPPORT_DESC")))
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings.addDummyTextElement)
|
||||
.onChange((value) => {
|
||||
this.plugin.settings.addDummyTextElement = value;
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
new Setting(detailsEl)
|
||||
.setName(t("DEBUGMODE_NAME"))
|
||||
.setDesc(fragWithHTML(t("DEBUGMODE_DESC")))
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings.isDebugMode)
|
||||
.onChange((value) => {
|
||||
this.plugin.settings.isDebugMode = value;
|
||||
setDebugging(value);
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("SLIDING_PANES_NAME"))
|
||||
.setDesc(fragWithHTML(t("SLIDING_PANES_DESC")))
|
||||
|
||||
@@ -4,37 +4,55 @@ import { createTreeWalker, walk } from "./walker";
|
||||
|
||||
export type ConversionResult = {
|
||||
hasErrors: boolean;
|
||||
errors: NodeListOf<Element> | null;
|
||||
errors: string;
|
||||
content: any; // Serialized Excalidraw JSON
|
||||
};
|
||||
|
||||
export const svgToExcalidraw = (svgString: string): ConversionResult => {
|
||||
const parser = new DOMParser();
|
||||
const svgDOM = parser.parseFromString(svgString, "image/svg+xml");
|
||||
try {
|
||||
const parser = new DOMParser();
|
||||
const svgDOM = parser.parseFromString(svgString, "image/svg+xml");
|
||||
|
||||
// was there a parsing error?
|
||||
const errorsElements = svgDOM.querySelectorAll("parsererror");
|
||||
const hasErrors = errorsElements.length > 0;
|
||||
let content = null;
|
||||
// was there a parsing error?
|
||||
const errorsElements = svgDOM.querySelectorAll("parsererror");
|
||||
const hasErrors = errorsElements.length > 0;
|
||||
let content = null;
|
||||
|
||||
if (hasErrors) {
|
||||
console.error(
|
||||
"There were errors while parsing the given SVG: ",
|
||||
[...errorsElements].map((el) => el.innerHTML),
|
||||
);
|
||||
} else {
|
||||
const tw = createTreeWalker(svgDOM);
|
||||
const scene = new ExcalidrawScene();
|
||||
const groups: Group[] = [];
|
||||
if (hasErrors) {
|
||||
console.error(
|
||||
"There were errors while parsing the given SVG: ",
|
||||
[...errorsElements].map((el) => el.innerHTML),
|
||||
);
|
||||
} else {
|
||||
const tw = createTreeWalker(svgDOM);
|
||||
const scene = new ExcalidrawScene();
|
||||
const groups: Group[] = [];
|
||||
|
||||
walk({ tw, scene, groups, root: svgDOM }, tw.nextNode());
|
||||
walk({ tw, scene, groups, root: svgDOM }, tw.nextNode());
|
||||
|
||||
content = scene.elements; //scene.toExJSON();
|
||||
const hasVisibleElements = Boolean(scene.elements.find((el)=>el.opacity !== 0));
|
||||
if (!hasVisibleElements) {
|
||||
scene.elements.forEach((el) => {
|
||||
el.opacity = 100;
|
||||
});
|
||||
}
|
||||
scene.elements.forEach((el) => {
|
||||
if(el.opacity <= 1) el.opacity = 100;
|
||||
});
|
||||
content = scene.elements; //scene.toExJSON();
|
||||
}
|
||||
|
||||
return {
|
||||
hasErrors,
|
||||
errors: hasErrors ? `${[...errorsElements].map((el) => el.innerHTML)}` : "",
|
||||
content,
|
||||
};
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return {
|
||||
hasErrors: true,
|
||||
errors: `${error}`,
|
||||
content:[],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
hasErrors,
|
||||
errors: hasErrors ? errorsElements : null,
|
||||
content,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -37,6 +37,7 @@ const SUPPORTED_TAGS = [
|
||||
"rect",
|
||||
"polyline",
|
||||
"polygon",
|
||||
"switch",
|
||||
];
|
||||
|
||||
const nodeValidator = (node: Element): number => {
|
||||
@@ -120,6 +121,18 @@ const walkers = {
|
||||
walk(args, args.tw.nextNode());
|
||||
},
|
||||
|
||||
switch: (args: WalkerArgs) => {
|
||||
const nextArgs = {
|
||||
...args,
|
||||
tw: createTreeWalker(args.tw.currentNode),
|
||||
groups: [...args.groups, new Group(args.tw.currentNode as Element)],
|
||||
};
|
||||
|
||||
walk(nextArgs, nextArgs.tw.nextNode());
|
||||
|
||||
walk(args, args.tw.nextSibling());
|
||||
},
|
||||
|
||||
g: (args: WalkerArgs) => {
|
||||
const nextArgs = {
|
||||
...args,
|
||||
|
||||
18
src/types.d.ts
vendored
18
src/types.d.ts
vendored
@@ -58,9 +58,23 @@ declare module "obsidian" {
|
||||
},
|
||||
basePath: string;
|
||||
}
|
||||
interface Editor {
|
||||
insertText(data: string): void;
|
||||
interface FoldPosition {
|
||||
from: number;
|
||||
to: number;
|
||||
}
|
||||
|
||||
interface FoldInfo {
|
||||
folds: FoldPosition[];
|
||||
lines: number;
|
||||
}
|
||||
|
||||
interface MarkdownSubView {
|
||||
applyFoldInfo(foldInfo: FoldInfo): void;
|
||||
getFoldInfo(): FoldInfo | null;
|
||||
}
|
||||
/*interface Editor {
|
||||
insertText(data: string): void;
|
||||
}*/
|
||||
interface MetadataCache {
|
||||
getBacklinksForFile(file: TFile): any;
|
||||
getLinks(): { [id: string]: Array<{ link: string; displayText: string; original: string; position: any }> };
|
||||
|
||||
@@ -11,7 +11,7 @@ container.appendChild(node.contentEl)
|
||||
import { TFile, WorkspaceLeaf, WorkspaceSplit } from "obsidian";
|
||||
import ExcalidrawView from "src/ExcalidrawView";
|
||||
import { getContainerForDocument, ConstructableWorkspaceSplit, isObsidianThemeDark } from "./ObsidianUtils";
|
||||
import { CustomMutationObserver, isDebugMode } from "./DebugHelper";
|
||||
import { CustomMutationObserver, DEBUGGING } from "./DebugHelper";
|
||||
|
||||
declare module "obsidian" {
|
||||
interface Workspace {
|
||||
@@ -110,7 +110,7 @@ export class CanvasNodeFactory {
|
||||
}
|
||||
}
|
||||
};
|
||||
const observer = isDebugMode
|
||||
const observer = DEBUGGING
|
||||
? new CustomMutationObserver(nodeObserverFn, "CanvasNodeFactory")
|
||||
: new MutationObserver(nodeObserverFn);
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ export const carveOutPDF = async (sourceEA: ExcalidrawAutomate, embeddableEl: Ex
|
||||
|
||||
const targetEA = getEA(sourceEA.targetView) as ExcalidrawAutomate;
|
||||
|
||||
const {height, width} = embeddableEl;
|
||||
let {height, width} = embeddableEl;
|
||||
|
||||
if(!height || !width || height === 0 || width === 0) return;
|
||||
|
||||
@@ -77,8 +77,6 @@ export const carveOutPDF = async (sourceEA: ExcalidrawAutomate, embeddableEl: Ex
|
||||
const newImage = targetEA.getElement(imageId) as Mutable<ExcalidrawImageElement>;
|
||||
newImage.x = 0;
|
||||
newImage.y = 0;
|
||||
newImage.width = width;
|
||||
newImage.height = height;
|
||||
const angle = embeddableEl.angle;
|
||||
|
||||
const fname = pdfFile.basename;
|
||||
|
||||
@@ -97,16 +97,12 @@ export class CropImage {
|
||||
withTheme: false,
|
||||
isMask: false,
|
||||
}
|
||||
const isRotated = this.imageEA.getElements().some(el=>el.type === "image" && el.angle !== 0);
|
||||
const images = Object.values(this.imageEA.imagesDict);
|
||||
if(images.length === 1) {
|
||||
if(!isRotated && (images.length === 1)) {
|
||||
return images[0].dataURL;
|
||||
}
|
||||
return await this.imageEA.createPNGBase64(null,1,exportSettings,null,null,0);
|
||||
const imageSVG = await this.imageEA.createSVG(null,false,exportSettings,null,null,0);
|
||||
const svgData = new XMLSerializer().serializeToString(imageSVG);
|
||||
return `data:image/svg+xml;base64,${btoa(unescape(encodeURIComponent(svgData)))}`;
|
||||
// const blob = new Blob([svgString], { type: 'image/svg+xml' });
|
||||
// return `data:image/svg+xml;base64,${await blobToBase64(blob)}`;
|
||||
}
|
||||
|
||||
private async buildSVG(): Promise<SVGSVGElement> {
|
||||
|
||||
@@ -1,6 +1,18 @@
|
||||
export const isDebugMode = false;
|
||||
export const durationTreshold = 0; //0.05; //ms
|
||||
|
||||
export function setDebugging(value: boolean) {
|
||||
DEBUGGING = (process.env.NODE_ENV === 'development')
|
||||
? value
|
||||
: false;
|
||||
}
|
||||
|
||||
export let DEBUGGING = false;
|
||||
|
||||
export const log = console.log.bind(window.console);
|
||||
export const debug = (fn: Function, fnName: string, ...messages: unknown[]) => {
|
||||
console.log(fnName,fn,...messages);
|
||||
};
|
||||
|
||||
export class CustomMutationObserver {
|
||||
private originalCallback: MutationCallback;
|
||||
private observer: MutationObserver | null;
|
||||
@@ -19,7 +31,7 @@ export class CustomMutationObserver {
|
||||
const endTime = performance.now(); // Get end time
|
||||
const executionTime = endTime - startTime;
|
||||
if (executionTime > durationTreshold) {
|
||||
console.log(`Excalidraw ${this.name} MutationObserver callback took ${executionTime}ms to execute`);
|
||||
console.log(`Excalidraw ${this.name} MutationObserver callback took ${executionTime}ms to execute`, observer);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -32,6 +32,10 @@ export const setDynamicStyle = (
|
||||
view?.excalidrawAPI?.getAppState?.()?.theme === "light" ||
|
||||
view?.excalidrawData?.scene?.appState?.theme === "light";
|
||||
|
||||
if (color==="transparent") {
|
||||
color = "#ffffff";
|
||||
}
|
||||
|
||||
const darker = "#101010";
|
||||
const lighter = "#f0f0f0";
|
||||
const step = 10;
|
||||
@@ -110,7 +114,8 @@ export const setDynamicStyle = (
|
||||
[`--h2-color`]: str(text),
|
||||
[`--h3-color`]: str(text),
|
||||
[`--h4-color`]: str(text),
|
||||
[`color`]: str(text),
|
||||
[`color`]: str(text),
|
||||
['--excalidraw-caret-color']: str(isLightTheme ? text : cmBG()),
|
||||
[`--select-highlight-color`]: str(gray1()),
|
||||
[`--color-gray-80`]: str(isDark?text.darkerBy(40):text.lighterBy(40)), //frame
|
||||
};
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
|
||||
import { MAX_IMAGE_SIZE, IMAGE_TYPES, ANIMATED_IMAGE_TYPES } from "src/constants/constants";
|
||||
import { MAX_IMAGE_SIZE, IMAGE_TYPES, ANIMATED_IMAGE_TYPES, MD_EX_SECTIONS } from "src/constants/constants";
|
||||
import { App, TFile } from "obsidian";
|
||||
import { ExcalidrawAutomate } from "src/ExcalidrawAutomate";
|
||||
import { REGEX_LINK, REG_LINKINDEX_HYPERLINK } from "src/ExcalidrawData";
|
||||
import { REGEX_LINK, REG_LINKINDEX_HYPERLINK, getExcalidrawMarkdownHeaderSection } from "src/ExcalidrawData";
|
||||
import ExcalidrawView from "src/ExcalidrawView";
|
||||
import { ExcalidrawElement, ExcalidrawFrameElement } from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
import { getLinkParts } from "./Utils";
|
||||
import { cleanSectionHeading } from "./ObsidianUtils";
|
||||
import { getEA } from "src";
|
||||
import { ExcalidrawImperativeAPI } from "@zsviczian/excalidraw/types/excalidraw/types";
|
||||
import { EmbeddableMDCustomProps } from "src/dialogs/EmbeddableSettings";
|
||||
|
||||
export const insertImageToView = async (
|
||||
ea: ExcalidrawAutomate,
|
||||
@@ -129,4 +133,53 @@ export const getFrameBasedOnFrameNameOrId = (frameName: string, elements: Excali
|
||||
.filter((item:any) => item.id === frameName || item.name === frameName)
|
||||
.map((item:any)=>item.el as ExcalidrawFrameElement);
|
||||
return frames.length === 1 ? frames[0] : null;
|
||||
}
|
||||
|
||||
export const addBackOfTheNoteCard = async (view: ExcalidrawView, title: string, activate: boolean = true, cardBody?: string, embeddableCustomData?: EmbeddableMDCustomProps):Promise<string> => {
|
||||
const data = view.data;
|
||||
const header = getExcalidrawMarkdownHeaderSection(data);
|
||||
const body = data.split(header)[1];
|
||||
const shouldAddHashtag = body && body.startsWith("%%");
|
||||
const hastag = header.match(/#\n+$/m);
|
||||
const shouldRemoveTrailingHashtag = Boolean(hastag);
|
||||
view.data = data.replace(
|
||||
header,
|
||||
(shouldRemoveTrailingHashtag
|
||||
? header.substring(0,header.length-hastag[0].length)
|
||||
: header) +
|
||||
`\n# ${title}\n\n${cardBody ? cardBody+"\n\n" : ""}${
|
||||
shouldAddHashtag || shouldRemoveTrailingHashtag ? "#\n" : ""}`);
|
||||
|
||||
await view.forceSave(true);
|
||||
let watchdog = 0;
|
||||
await sleep(200);
|
||||
let found:string;
|
||||
while (watchdog++ < 10 && !(found=(await view.app.metadataCache.blockCache
|
||||
.getForFile({ isCancelled: () => false },view.file))
|
||||
.blocks.filter((b: any) => b.display && b.node?.type === "heading")
|
||||
.filter((b: any) => !MD_EX_SECTIONS.includes(b.display))
|
||||
.map((b: any) => cleanSectionHeading(b.display))
|
||||
.find((b: any) => b === title))) {
|
||||
await sleep(200);
|
||||
}
|
||||
|
||||
const ea = getEA(view) as ExcalidrawAutomate;
|
||||
const id = ea.addEmbeddable(
|
||||
0,0,400,500,
|
||||
`[[${view.file.path}#${title}]]`,
|
||||
undefined,
|
||||
embeddableCustomData
|
||||
);
|
||||
await ea.addElementsToView(true, false, true);
|
||||
|
||||
const api = view.excalidrawAPI as ExcalidrawImperativeAPI;
|
||||
const el = ea.getViewElements().find(el=>el.id === id);
|
||||
api.selectElements([el]);
|
||||
if(activate) {
|
||||
setTimeout(()=>{
|
||||
api.updateScene({appState: {activeEmbeddable: {element: el, state: "active"}}});
|
||||
if(found) view.getEmbeddableLeafElementById(el.id)?.editNode?.();
|
||||
});
|
||||
}
|
||||
return el.id;
|
||||
}
|
||||
@@ -334,6 +334,7 @@ export const getPathWithoutExtension = (f:TFile): string => {
|
||||
const VAULT_BASE_URL = DEVICE.isDesktop
|
||||
? app.vault.adapter.url.pathToFileURL(app.vault.adapter.basePath).toString()
|
||||
: "";
|
||||
|
||||
export const getInternalLinkOrFileURLLink = (
|
||||
path: string, plugin:ExcalidrawPlugin, alias?: string, sourceFile?: TFile
|
||||
):{link: string, isInternal: boolean, file?: TFile, url?: string} => {
|
||||
@@ -345,7 +346,7 @@ export const getInternalLinkOrFileURLLink = (
|
||||
const vault = plugin.app.vault;
|
||||
const fileURLString = vault.adapter.url.pathToFileURL(path).toString();
|
||||
if (fileURLString.startsWith(VAULT_BASE_URL)) {
|
||||
const internalPath = normalizePath(fileURLString.substring(VAULT_BASE_URL.length));
|
||||
const internalPath = normalizePath(unescape(fileURLString.substring(VAULT_BASE_URL.length)));
|
||||
const file = vault.getAbstractFileByPath(internalPath);
|
||||
if(file && file instanceof TFile) {
|
||||
const link = plugin.app.metadataCache.fileToLinktext(
|
||||
@@ -440,3 +441,38 @@ export const fileShouldDefaultAsExcalidraw = (path:string, app:App):boolean => {
|
||||
cache.frontmatter[FRONTMATTER_KEYS["plugin"].name] &&
|
||||
!Boolean(cache.frontmatter[FRONTMATTER_KEYS["open-as-markdown"].name]);
|
||||
}
|
||||
|
||||
export const getExcalidrawEmbeddedFilesFiletree = (sourceFile: TFile, plugin: ExcalidrawPlugin):TFile[] => {
|
||||
if(!sourceFile || !plugin.isExcalidrawFile(sourceFile)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const fileList = new Set<TFile>();
|
||||
const app = plugin.app;
|
||||
|
||||
const addAttachedImages = (f:TFile) => Object
|
||||
.keys(app.metadataCache.resolvedLinks[f.path])
|
||||
.forEach(path => {
|
||||
const file = app.vault.getAbstractFileByPath(path);
|
||||
if (!file || !(file instanceof TFile)) return;
|
||||
const isExcalidraw = plugin.isExcalidrawFile(file);
|
||||
if (
|
||||
(file.extension === "md" && !isExcalidraw) ||
|
||||
fileList.has(file) //avoid infinite loops
|
||||
) {
|
||||
return;
|
||||
}
|
||||
fileList.add(file);
|
||||
if (isExcalidraw) {
|
||||
addAttachedImages(file);
|
||||
}
|
||||
});
|
||||
|
||||
addAttachedImages(sourceFile);
|
||||
return Array.from(fileList);
|
||||
}
|
||||
|
||||
export const hasExcalidrawEmbeddedImagesTreeChanged = (sourceFile: TFile, mtime:number, plugin: ExcalidrawPlugin):boolean => {
|
||||
const fileList = getExcalidrawEmbeddedFilesFiletree(sourceFile, plugin);
|
||||
return fileList.some(f=>f.stat.mtime > mtime);
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ export const getElementsAtPointer = (
|
||||
y <= pointer.y &&
|
||||
y + h >= pointer.y
|
||||
);
|
||||
});
|
||||
}).reverse();
|
||||
};
|
||||
|
||||
export const getTextElementAtPointer = (pointer: any, view: ExcalidrawView) => {
|
||||
|
||||
@@ -2,6 +2,7 @@ import { App, Notice, TFile } from "obsidian";
|
||||
import ExcalidrawPlugin from "src/main";
|
||||
import { convertSVGStringToElement } from "./Utils";
|
||||
import { FILENAMEPARTS, PreviewImageType } from "./UtilTypes";
|
||||
import { hasExcalidrawEmbeddedImagesTreeChanged } from "./FileUtils";
|
||||
|
||||
//@ts-ignore
|
||||
const DB_NAME = "Excalidraw " + app.appId;
|
||||
@@ -19,6 +20,7 @@ export type ImageKey = {
|
||||
isDark: boolean;
|
||||
previewImageType: PreviewImageType;
|
||||
scale: number;
|
||||
isTransparent: boolean;
|
||||
} & FILENAMEPARTS;
|
||||
|
||||
const getKey = (key: ImageKey): string =>
|
||||
@@ -29,7 +31,7 @@ const getKey = (key: ImageKey): string =>
|
||||
: key.previewImageType === PreviewImageType.PNG
|
||||
? 0
|
||||
: 2
|
||||
}#${key.scale}`; //key.isSVG ? 1 : 0
|
||||
}#${key.scale}${key.isTransparent?"#t":""}`; //key.isSVG ? 1 : 0
|
||||
|
||||
class ImageCache {
|
||||
private dbName: string;
|
||||
@@ -154,7 +156,7 @@ class ImageCache {
|
||||
const filepath = key.split("#")[0];
|
||||
const fileExists = files.some((f: TFile) => f.path === filepath);
|
||||
const file = fileExists ? files.find((f: TFile) => f.path === filepath) : null;
|
||||
if (isLegacyKey || !file || (file && file.stat.mtime > cursor.value.mtime) || (!cursor.value.blob && !cursor.value.svg)) {
|
||||
if (isLegacyKey || !file || (file && (file.stat.mtime > cursor.value.mtime)) || (!cursor.value.blob && !cursor.value.svg)) {
|
||||
deletePromises.push(
|
||||
new Promise<void>((innerResolve, innerReject) => {
|
||||
const deleteRequest = store.delete(cursor.primaryKey);
|
||||
@@ -291,7 +293,10 @@ class ImageCache {
|
||||
|
||||
const file = this.app.vault.getAbstractFileByPath(key_.filepath.split("#")[0]);
|
||||
if (!file || !(file instanceof TFile)) return undefined;
|
||||
if (cachedData && cachedData.mtime === file.stat.mtime) {
|
||||
if (cachedData && (cachedData.mtime >= file.stat.mtime)) {
|
||||
if(hasExcalidrawEmbeddedImagesTreeChanged(file, cachedData.mtime, this.plugin)) {
|
||||
return undefined;
|
||||
}
|
||||
if(cachedData.svg) {
|
||||
return convertSVGStringToElement(cachedData.svg);
|
||||
}
|
||||
@@ -316,6 +321,7 @@ class ImageCache {
|
||||
return this.getBackupData(filepath);
|
||||
}
|
||||
|
||||
//cache SVG should have the width and height parameters and not the embedded font
|
||||
public addImageToCache(key_: ImageKey, obsidianURL: string, image: Blob|SVGSVGElement): void {
|
||||
if (!this.isReady()) {
|
||||
return; // Database not initialized yet
|
||||
@@ -332,7 +338,7 @@ class ImageCache {
|
||||
} else {
|
||||
blob = image as Blob;
|
||||
}
|
||||
const data: FileCacheData = { mtime: file.stat.mtime, blob, svg};
|
||||
const data: FileCacheData = { mtime: Date.now(), blob, svg};
|
||||
const transaction = this.db.transaction(this.cacheStoreName, "readwrite");
|
||||
const store = transaction.objectStore(this.cacheStoreName);
|
||||
const key = getKey(key_);
|
||||
|
||||
@@ -37,7 +37,7 @@ export const modifierKeyTooltipMessages = ():ModifierKeyTooltipMessages => {
|
||||
// Add more messages for WebBrowserDragAction as needed
|
||||
},
|
||||
LocalFileDragAction: {
|
||||
"image-import": "Insert Image: import external or reuse existing if path in Vault",
|
||||
"image-import": "Import external file or reuse existing file if path is from the Vault",
|
||||
"image-url": `Insert Image: with local URI or internal-link if from Vault`,
|
||||
"link": "Insert Link: local URI or internal-link if from Vault",
|
||||
"embeddable": "Insert Interactive-Frame: local URI or internal-link if from Vault",
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import {
|
||||
App,
|
||||
Editor,
|
||||
FrontMatterCache,
|
||||
MarkdownView,
|
||||
normalizePath, OpenViewState, parseFrontMatterEntry, TFile, View, Workspace, WorkspaceLeaf, WorkspaceSplit
|
||||
} from "obsidian";
|
||||
import ExcalidrawPlugin from "../main";
|
||||
import { checkAndCreateFolder, splitFolderAndFilename } from "./FileUtils";
|
||||
import { linkClickModifierType, ModifierKeys } from "./ModifierkeyHelper";
|
||||
import { REG_BLOCK_REF_CLEAN, REG_SECTION_REF_CLEAN } from "src/constants/constants";
|
||||
import yaml from "js-yaml";
|
||||
import yaml, { Mark } from "js-yaml";
|
||||
|
||||
export const getParentOfClass = (element: Element, cssClass: string):HTMLElement | null => {
|
||||
let parent = element.parentElement;
|
||||
@@ -29,11 +31,11 @@ export const getLeaf = (
|
||||
const newTab = ():WorkspaceLeaf => {
|
||||
if(!plugin.settings.openInMainWorkspace) return app.workspace.getLeaf('tab');
|
||||
const [leafLoc, mainLeavesIds] = getLeafLoc(origo);
|
||||
if(leafLoc === 'main') return app.workspace.getLeaf('tab');
|
||||
if(leafLoc === 'main') return plugin.app.workspace.getLeaf('tab');
|
||||
return getNewOrAdjacentLeaf(plugin,origo);
|
||||
}
|
||||
const newTabGroup = ():WorkspaceLeaf => getNewOrAdjacentLeaf(plugin,origo);
|
||||
const newWindow = ():WorkspaceLeaf => app.workspace.openPopoutLeaf();
|
||||
const newWindow = ():WorkspaceLeaf => plugin.app.workspace.openPopoutLeaf();
|
||||
|
||||
switch(linkClickModifierType(ev)) {
|
||||
case "active-pane": return origo;
|
||||
@@ -285,7 +287,13 @@ export const openLeaf = ({
|
||||
leaf = l;
|
||||
}
|
||||
});
|
||||
if(leaf) return {leaf, promise: Promise.resolve()};
|
||||
if(leaf) {
|
||||
if(openState) {
|
||||
const promise = leaf.openFile(file, openState);
|
||||
return {leaf, promise};
|
||||
}
|
||||
return {leaf, promise: Promise.resolve()};
|
||||
}
|
||||
}
|
||||
leaf = fnGetLeaf();
|
||||
const promise = leaf.openFile(file, openState);
|
||||
@@ -336,4 +344,51 @@ export const mergeMarkdownFiles = (template: string, target: string): string =>
|
||||
const mergedMarkdown = `---\n${mergedFrontmatterYaml}---\n${targetContent}\n\n${templateContent.trim()}\n`;
|
||||
|
||||
return mergedMarkdown;
|
||||
};
|
||||
};
|
||||
|
||||
export const editorInsertText = (editor: Editor, text: string)=> {
|
||||
const cursor = editor.getCursor();
|
||||
const line = editor.getLine(cursor.line);
|
||||
const updatedLine = line.slice(0, cursor.ch) + text + line.slice(cursor.ch);
|
||||
editor.setLine(cursor.line, updatedLine);
|
||||
}
|
||||
|
||||
export const foldExcalidrawSection = (view: MarkdownView) => {
|
||||
if (!view || !(view instanceof MarkdownView)) return;
|
||||
|
||||
const foldStart = {
|
||||
ed: -1, // # Excalidraw Data
|
||||
te: -1, // ## Text Elements
|
||||
el: -1, // ## Element Links
|
||||
ef: -1, // ## Embedded Files
|
||||
d: -1, // ## Drawing
|
||||
};
|
||||
|
||||
const existingFolds = view.currentMode.getFoldInfo()?.folds ?? [];
|
||||
const lineCount = view.editor.lineCount();
|
||||
|
||||
for (let i = 0; i < lineCount; i++) {
|
||||
const line = view.editor.getLine(i);
|
||||
switch (line) {
|
||||
case "# Excalidraw Data": foldStart.ed = i; break;
|
||||
case "## Text Elements": foldStart.te = i; break;
|
||||
case "## Element Links": foldStart.el = i; break;
|
||||
case "## Embedded Files": foldStart.ef = i; break;
|
||||
case "## Drawing": foldStart.d = i; break;
|
||||
}
|
||||
if (line === "## Drawing") break;
|
||||
}
|
||||
|
||||
if (foldStart.ed > -1 && foldStart.d > -1) {
|
||||
const foldPositions = [
|
||||
...existingFolds,
|
||||
...(foldStart.te > -1 ? [{ from: foldStart.te, to: (foldStart.el > -1 ? foldStart.el : (foldStart.ef > -1 ? foldStart.ef : foldStart.d)) - 1 }] : []),
|
||||
...(foldStart.el > -1 ? [{ from: foldStart.el, to: (foldStart.ef > -1 ? foldStart.ef : foldStart.d) - 1 }] : []),
|
||||
...(foldStart.ef > -1 ? [{ from: foldStart.ef, to: foldStart.d - 1 }] : []),
|
||||
{ from: foldStart.d, to: lineCount - 1 },
|
||||
{ from: foldStart.ed, to: lineCount - 1 },
|
||||
];
|
||||
|
||||
view.currentMode.applyFoldInfo({ folds: foldPositions, lines: lineCount });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { WorkspaceWindow } from "obsidian";
|
||||
import ExcalidrawPlugin from "src/main";
|
||||
import { getAllWindowDocuments } from "./ObsidianUtils";
|
||||
import { DEBUGGING, debug } from "./DebugHelper";
|
||||
|
||||
const STYLE_VARIABLES = ["--background-modifier-cover","--background-primary-alt","--background-secondary","--background-secondary-alt","--background-modifier-border","--text-normal","--text-muted","--text-accent","--text-accent-hover","--text-faint","--text-highlight-bg","--text-highlight-bg-active","--text-selection","--interactive-normal","--interactive-hover","--interactive-accent","--interactive-accent-hover","--scrollbar-bg","--scrollbar-thumb-bg","--scrollbar-active-thumb-bg"];
|
||||
const EXCALIDRAW_CONTAINER_CLASS = "excalidraw__embeddable__outer";
|
||||
@@ -11,11 +12,11 @@ export class StylesManager {
|
||||
private styleDark: string;
|
||||
private plugin: ExcalidrawPlugin;
|
||||
|
||||
|
||||
|
||||
constructor(plugin: ExcalidrawPlugin) {
|
||||
this.plugin = plugin;
|
||||
const self = this;
|
||||
plugin.app.workspace.onLayoutReady(async () => {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(undefined, "StylesManager.constructor > app.workspace.onLayoutReady", self);
|
||||
await this.harvestStyles();
|
||||
getAllWindowDocuments(plugin.app).forEach(doc => {
|
||||
this.copyPropertiesToTheme(doc);
|
||||
|
||||
@@ -19,20 +19,20 @@ import {
|
||||
IMAGE_TYPES,
|
||||
FRONTMATTER_KEYS,
|
||||
EXCALIDRAW_PLUGIN,
|
||||
getCommonBoundingBox,
|
||||
DEVICE,
|
||||
getContainerElement,
|
||||
} from "../constants/constants";
|
||||
import ExcalidrawPlugin from "../main";
|
||||
import { ExcalidrawElement } from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
import { ExcalidrawElement, ExcalidrawTextElement } from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
import { ExportSettings } from "../ExcalidrawView";
|
||||
import { getDataURLFromURL, getIMGFilename, getMimeType, getURLImageExtension } from "./FileUtils";
|
||||
import { generateEmbeddableLink } from "./CustomEmbeddableUtils";
|
||||
import ExcalidrawScene from "src/svgToExcalidraw/elements/ExcalidrawScene";
|
||||
import { FILENAMEPARTS } from "./UtilTypes";
|
||||
import { Mutable } from "@zsviczian/excalidraw/types/excalidraw/utility-types";
|
||||
import { cleanBlockRef, cleanSectionHeading, getFileCSSClasses } from "./ObsidianUtils";
|
||||
import { updateElementLinksToObsidianLinks } from "src/ExcalidrawAutomate";
|
||||
import { CropImage } from "./CropImage";
|
||||
import { ExcalidrawData } from "src/ExcalidrawData";
|
||||
import { ExcalidrawGenericElement } from "lib/svgToExcalidraw/types";
|
||||
|
||||
|
||||
declare const PLUGIN_VERSION:string;
|
||||
@@ -157,6 +157,10 @@ const rotate = (
|
||||
export const rotatedDimensions = (
|
||||
element: ExcalidrawElement,
|
||||
): [number, number, number, number] => {
|
||||
const bb = getCommonBoundingBox([element]);
|
||||
return [bb.minX, bb.minY, bb.maxX - bb.minX, bb.maxY - bb.minY];
|
||||
|
||||
//removed with 2.1.5... will delete later
|
||||
if (element.angle === 0) {
|
||||
return [element.x, element.y, element.width, element.height];
|
||||
}
|
||||
@@ -290,7 +294,7 @@ export const getSVG = async (
|
||||
appState: {
|
||||
exportBackground: exportSettings.withBackground,
|
||||
exportWithDarkMode: exportSettings.withTheme
|
||||
? scene.appState?.theme != "light"
|
||||
? scene.appState?.theme !== "light"
|
||||
: false,
|
||||
...scene.appState,
|
||||
},
|
||||
@@ -342,7 +346,7 @@ export const getPNG = async (
|
||||
appState: {
|
||||
exportBackground: exportSettings.withBackground,
|
||||
exportWithDarkMode: exportSettings.withTheme
|
||||
? scene.appState?.theme != "light"
|
||||
? scene.appState?.theme !== "light"
|
||||
: false,
|
||||
...scene.appState,
|
||||
},
|
||||
@@ -389,13 +393,13 @@ export const embedFontsInSVG = (
|
||||
): SVGSVGElement => {
|
||||
//replace font references with base64 fonts)
|
||||
const includesVirgil = !localOnly &&
|
||||
svg.querySelector("text[font-family^='Virgil']") != null;
|
||||
svg.querySelector("text[font-family^='Virgil']") !== null;
|
||||
const includesCascadia = !localOnly &&
|
||||
svg.querySelector("text[font-family^='Cascadia']") != null;
|
||||
svg.querySelector("text[font-family^='Cascadia']") !== null;
|
||||
const includesAssistant = !localOnly &&
|
||||
svg.querySelector("text[font-family^='Assistant']") != null;
|
||||
svg.querySelector("text[font-family^='Assistant']") !== null;
|
||||
const includesLocalFont =
|
||||
svg.querySelector("text[font-family^='LocalFont']") != null;
|
||||
svg.querySelector("text[font-family^='LocalFont']") !== null;
|
||||
const defs = svg.querySelector("defs");
|
||||
if (defs && (includesCascadia || includesVirgil || includesLocalFont || includesAssistant)) {
|
||||
let style = defs.querySelector("style");
|
||||
@@ -447,10 +451,10 @@ export const scaleLoadedImage = (
|
||||
for (const f of files.filter((f:any)=>{
|
||||
if(!Boolean(EXCALIDRAW_PLUGIN)) return true; //this should never happen
|
||||
const ef = EXCALIDRAW_PLUGIN.filesMaster.get(f.id);
|
||||
if(!ef) return false;
|
||||
if(!ef) return true; //mermaid SVG or equation
|
||||
const file = EXCALIDRAW_PLUGIN.app.vault.getAbstractFileByPath(ef.path.replace(/#.*$/,"").replace(/\|.*$/,""));
|
||||
if(!file || (file instanceof TFolder)) return false;
|
||||
return EXCALIDRAW_PLUGIN.isExcalidrawFile(file as TFile)
|
||||
return (file as TFile).extension==="md" || EXCALIDRAW_PLUGIN.isExcalidrawFile(file as TFile)
|
||||
})) {
|
||||
const [w_image, h_image] = [f.size.width, f.size.height];
|
||||
const imageAspectRatio = f.size.width / f.size.height;
|
||||
@@ -464,7 +468,7 @@ export const scaleLoadedImage = (
|
||||
}
|
||||
if(f.shouldScale) {
|
||||
const elementAspectRatio = w_old / h_old;
|
||||
if (imageAspectRatio != elementAspectRatio) {
|
||||
if (imageAspectRatio !== elementAspectRatio) {
|
||||
dirty = true;
|
||||
const h_new = Math.sqrt((w_old * h_old * h_image) / w_image);
|
||||
const w_new = Math.sqrt((w_old * h_old * w_image) / h_image);
|
||||
@@ -503,7 +507,7 @@ export const setDocLeftHandedMode = (isLeftHanded: boolean, ownerDocument:Docume
|
||||
export const setLeftHandedMode = (isLeftHanded: boolean) => {
|
||||
const visitedDocs = new Set<Document>();
|
||||
app.workspace.iterateAllLeaves((leaf) => {
|
||||
const ownerDocument = app.isMobile?document:leaf.view.containerEl.ownerDocument;
|
||||
const ownerDocument = DEVICE.isMobile?document:leaf.view.containerEl.ownerDocument;
|
||||
if(!ownerDocument) return;
|
||||
if(visitedDocs.has(ownerDocument)) return;
|
||||
visitedDocs.add(ownerDocument);
|
||||
@@ -540,7 +544,7 @@ export const getLinkParts = (fname: string, file?: TFile): LinkParts => {
|
||||
};
|
||||
|
||||
export const compress = (data: string): string => {
|
||||
return LZString.compressToBase64(data).replace(/(.{64})/g, "$1\n\n");
|
||||
return LZString.compressToBase64(data).replace(/(.{256})/g, "$1\n\n");
|
||||
};
|
||||
|
||||
export const decompress = (data: string): string => {
|
||||
@@ -555,7 +559,8 @@ export const isMaskFile = (
|
||||
const fileCache = plugin.app.metadataCache.getFileCache(file);
|
||||
if (
|
||||
fileCache?.frontmatter &&
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["mask"].name] != null
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["mask"].name] !== null &&
|
||||
(typeof fileCache.frontmatter[FRONTMATTER_KEYS["mask"].name] !== "undefined")
|
||||
) {
|
||||
return Boolean(fileCache.frontmatter[FRONTMATTER_KEYS["mask"].name]);
|
||||
}
|
||||
@@ -571,7 +576,8 @@ export const hasExportTheme = (
|
||||
const fileCache = plugin.app.metadataCache.getFileCache(file);
|
||||
if (
|
||||
fileCache?.frontmatter &&
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["export-dark"].name] != null
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["export-dark"].name] !== null &&
|
||||
(typeof fileCache.frontmatter[FRONTMATTER_KEYS["export-dark"].name] !== "undefined")
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
@@ -588,7 +594,8 @@ export const getExportTheme = (
|
||||
const fileCache = plugin.app.metadataCache.getFileCache(file);
|
||||
if (
|
||||
fileCache?.frontmatter &&
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["export-dark"].name] != null
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["export-dark"].name] !== null &&
|
||||
(typeof fileCache.frontmatter[FRONTMATTER_KEYS["export-dark"].name] !== "undefined")
|
||||
) {
|
||||
return fileCache.frontmatter[FRONTMATTER_KEYS["export-dark"].name]
|
||||
? "dark"
|
||||
@@ -606,7 +613,8 @@ export const shouldEmbedScene = (
|
||||
const fileCache = plugin.app.metadataCache.getFileCache(file);
|
||||
if (
|
||||
fileCache?.frontmatter &&
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["export-embed-scene"].name] != null
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["export-embed-scene"].name] !== null &&
|
||||
(typeof fileCache.frontmatter[FRONTMATTER_KEYS["export-embed-scene"].name] !== "undefined")
|
||||
) {
|
||||
return fileCache.frontmatter[FRONTMATTER_KEYS["export-embed-scene"].name];
|
||||
}
|
||||
@@ -622,7 +630,8 @@ export const hasExportBackground = (
|
||||
const fileCache = plugin.app.metadataCache.getFileCache(file);
|
||||
if (
|
||||
fileCache?.frontmatter &&
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["export-transparent"].name] != null
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["export-transparent"].name] !== null &&
|
||||
(typeof fileCache.frontmatter[FRONTMATTER_KEYS["export-transparent"].name] !== "undefined")
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
@@ -638,7 +647,8 @@ export const getWithBackground = (
|
||||
const fileCache = plugin.app.metadataCache.getFileCache(file);
|
||||
if (
|
||||
fileCache?.frontmatter &&
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["export-transparent"].name] != null
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["export-transparent"].name] !== null &&
|
||||
(typeof fileCache.frontmatter[FRONTMATTER_KEYS["export-transparent"].name] !== "undefined")
|
||||
) {
|
||||
return !fileCache.frontmatter[FRONTMATTER_KEYS["export-transparent"].name];
|
||||
}
|
||||
@@ -654,7 +664,10 @@ export const getExportPadding = (
|
||||
const fileCache = plugin.app.metadataCache.getFileCache(file);
|
||||
if(!fileCache?.frontmatter) return plugin.settings.exportPaddingSVG;
|
||||
|
||||
if (fileCache.frontmatter[FRONTMATTER_KEYS["export-padding"].name] != null) {
|
||||
if (
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["export-padding"].name] !== null &&
|
||||
(typeof fileCache.frontmatter[FRONTMATTER_KEYS["export-padding"].name] !== "undefined")
|
||||
) {
|
||||
const val = parseInt(
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["export-padding"].name],
|
||||
);
|
||||
@@ -664,7 +677,10 @@ export const getExportPadding = (
|
||||
}
|
||||
|
||||
//deprecated. Retained for backward compatibility
|
||||
if (fileCache.frontmatter[FRONTMATTER_KEYS["export-svgpadding"].name] != null) {
|
||||
if (
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["export-svgpadding"].name] !== null &&
|
||||
(typeof fileCache.frontmatter[FRONTMATTER_KEYS["export-svgpadding"].name] !== "undefined")
|
||||
) {
|
||||
const val = parseInt(
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["export-svgpadding"].name],
|
||||
);
|
||||
@@ -682,7 +698,8 @@ export const getPNGScale = (plugin: ExcalidrawPlugin, file: TFile): number => {
|
||||
const fileCache = plugin.app.metadataCache.getFileCache(file);
|
||||
if (
|
||||
fileCache?.frontmatter &&
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["export-pngscale"].name] != null
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["export-pngscale"].name] !== null &&
|
||||
(typeof fileCache.frontmatter[FRONTMATTER_KEYS["export-pngscale"].name] !== "undefined")
|
||||
) {
|
||||
const val = parseFloat(
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["export-pngscale"].name],
|
||||
@@ -758,26 +775,41 @@ export const sleep = async (ms: number) => new Promise((resolve) => setTimeout(r
|
||||
export const awaitNextAnimationFrame = async () => new Promise(requestAnimationFrame);
|
||||
*/
|
||||
|
||||
export const log = console.log.bind(window.console);
|
||||
export const debug = console.log.bind(window.console);
|
||||
//export const debug = function(){};
|
||||
|
||||
|
||||
export const getContainerElement = (
|
||||
export const _getContainerElement = (
|
||||
element:
|
||||
| (ExcalidrawElement & { containerId: ExcalidrawElement["id"] | null })
|
||||
| null,
|
||||
scene: any,
|
||||
) => {
|
||||
if (!element) {
|
||||
if (!element || !scene?.elements || element.type !== "text") {
|
||||
return null;
|
||||
}
|
||||
if (element.containerId) {
|
||||
return scene.elements.find((el:ExcalidrawElement)=>el.id === element.containerId) ?? null;
|
||||
return getContainerElement(element as ExcalidrawTextElement, arrayToMap(scene.elements))
|
||||
//return scene.elements.find((el:ExcalidrawElement)=>el.id === element.containerId) ?? null;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Transforms array of objects containing `id` attribute,
|
||||
* or array of ids (strings), into a Map, keyd by `id`.
|
||||
*/
|
||||
export const arrayToMap = <T extends { id: string } | string>(
|
||||
items: readonly T[] | Map<string, T>,
|
||||
) => {
|
||||
if (items instanceof Map) {
|
||||
return items;
|
||||
}
|
||||
return items.reduce((acc: Map<string, T>, element) => {
|
||||
acc.set(typeof element === "string" ? element : element.id, element);
|
||||
return acc;
|
||||
}, new Map());
|
||||
};
|
||||
|
||||
export const updateFrontmatterInString = (data:string, keyValuePairs?: [string,string][]):string => {
|
||||
if(!data || !keyValuePairs) return data;
|
||||
for(const kvp of keyValuePairs) {
|
||||
@@ -862,20 +894,4 @@ export const addIframe = (containerEl: HTMLElement, link:string, startAt?: numbe
|
||||
sandbox: "allow-forms allow-presentation allow-same-origin allow-scripts allow-modals",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms array of objects containing `id` attribute,
|
||||
* or array of ids (strings), into a Map, keyd by `id`.
|
||||
*/
|
||||
export const arrayToMap = <T extends { id: string } | string>(
|
||||
items: readonly T[] | Map<string, T>,
|
||||
) => {
|
||||
if (items instanceof Map) {
|
||||
return items;
|
||||
}
|
||||
return items.reduce((acc: Map<string, T>, element) => {
|
||||
acc.set(typeof element === "string" ? element : element.id, element);
|
||||
return acc;
|
||||
}, new Map());
|
||||
};
|
||||
}
|
||||
15
styles.css
15
styles.css
@@ -579,4 +579,19 @@ img.excalidraw-cropped-pdfpage,
|
||||
|
||||
.ex-opacity-0 {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.popover .excalidraw-svg {
|
||||
width: 100%;
|
||||
max-width: inherit;
|
||||
height: 100%;
|
||||
max-height: inherit;
|
||||
}
|
||||
|
||||
root {
|
||||
--excalidraw-caret-color: initial;
|
||||
}
|
||||
|
||||
textarea.excalidraw-wysiwyg, .excalidraw input {
|
||||
caret-color: var(--excalidraw-caret-color);
|
||||
}
|
||||
Reference in New Issue
Block a user