mirror of
https://github.com/zsviczian/obsidian-excalidraw-plugin.git
synced 2025-08-06 05:46:28 +00:00
Compare commits
23 Commits
custom-zoo
...
2.1.4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
131294464e | ||
|
|
b7652a41f8 | ||
|
|
91c5f85ec6 | ||
|
|
985983b31d | ||
|
|
9a27e38ce2 | ||
|
|
0017ed7c92 | ||
|
|
1c899746fd | ||
|
|
9ea09c0fdd | ||
|
|
cda674c289 | ||
|
|
ef7d7ccc91 | ||
|
|
21aa5eb2d6 | ||
|
|
e725fb9b65 | ||
|
|
fbd634bfce | ||
|
|
9a686f3827 | ||
|
|
999219a0c9 | ||
|
|
65fd370cc5 | ||
|
|
232f0c38fa | ||
|
|
fa03968508 | ||
|
|
78dace32d4 | ||
|
|
a21e7aa0c5 | ||
|
|
9b7d828209 | ||
|
|
a1071426c0 | ||
|
|
23baf21ef5 |
@@ -9,7 +9,7 @@ Select some elements in the scene. The script will take these elements and move
|
||||
|
||||
```javascript
|
||||
*/
|
||||
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.9.19")) {
|
||||
if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("2.0.25")) {
|
||||
new Notice("This script requires a newer version of Excalidraw. Please install the latest version.");
|
||||
return;
|
||||
}
|
||||
@@ -17,11 +17,11 @@ if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("1.9.19")) {
|
||||
// -------------------------------
|
||||
// Utility variables and functions
|
||||
// -------------------------------
|
||||
const excalidrawTemplate = app.metadataCache.getFirstLinkpathDest(ea.plugin.settings.templateFilePath,"");
|
||||
const excalidrawTemplates = ea.getListOfTemplateFiles();
|
||||
if(typeof window.ExcalidrawDeconstructElements === "undefined") {
|
||||
window.ExcalidrawDeconstructElements = {
|
||||
openDeconstructedImage: true,
|
||||
templatePath: excalidrawTemplate?.path??""
|
||||
templatePath: excalidrawTemplates?.[0].path??""
|
||||
};
|
||||
}
|
||||
|
||||
@@ -45,11 +45,21 @@ if(!settings["Templates"]) {
|
||||
await ea.setScriptSettings(settings);
|
||||
}
|
||||
|
||||
if(!settings["Default file name"]) {
|
||||
settings["Default file name"] = {
|
||||
value: "deconstructed",
|
||||
description: "The default filename to use when deconstructing elements."
|
||||
};
|
||||
await ea.setScriptSettings(settings);
|
||||
}
|
||||
|
||||
const DEFAULT_FILENAME = settings["Default file name"].value;
|
||||
|
||||
const templates = settings["Templates"]
|
||||
.value
|
||||
.split(",")
|
||||
.map(p=>app.metadataCache.getFirstLinkpathDest(p.trim(),""))
|
||||
.concat(excalidrawTemplate)
|
||||
.concat(excalidrawTemplates)
|
||||
.filter(f=>Boolean(f))
|
||||
.sort((a,b) => a.basename.localeCompare(b.basename));
|
||||
|
||||
@@ -144,7 +154,7 @@ const customControls = (container) => {
|
||||
const path = await utils.inputPrompt(
|
||||
"Filename for new file",
|
||||
"Filename",
|
||||
await ea.getAttachmentFilepath("deconstructed"),
|
||||
await ea.getAttachmentFilepath(DEFAULT_FILENAME),
|
||||
actionButtons,
|
||||
2,
|
||||
false,
|
||||
@@ -177,8 +187,14 @@ if(!f || !ea.isExcalidrawFile(f)) {
|
||||
new Notice("Something went wrong");
|
||||
return;
|
||||
}
|
||||
|
||||
let padding = parseFloat(app.metadataCache.getCache(f.path)?.frontmatter["excalidraw-export-padding"]);
|
||||
if(isNaN(padding)) {
|
||||
padding = ea.plugin.settings.exportPaddingSVG;
|
||||
}
|
||||
|
||||
ea.getElements().forEach(el=>el.isDeleted = true);
|
||||
await ea.addImage(bb.topX,bb.topY,f,false, shouldAnchor);
|
||||
await ea.addImage(bb.topX-padding,bb.topY-padding,f,false, shouldAnchor);
|
||||
await ea.addElementsToView(false, true, true);
|
||||
ea.getExcalidrawAPI().history.clear();
|
||||
if(!window.ExcalidrawDeconstructElements.openDeconstructedImage) {
|
||||
|
||||
@@ -1,12 +1,23 @@
|
||||
/*
|
||||
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/JwgtCrIVeEU" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
# About the slideshow script
|
||||
The script will convert your drawing into a slideshow presentation.
|
||||

|
||||
|
||||

|
||||
The script will convert your drawing into a slideshow presentation.
|
||||
If you select an arrow or line element, the script will use that as the presentation path.
|
||||
If you select nothing, but the file has a hidden presentation path, the script will use that for determining the slide sequence.
|
||||
If there are frames, the script will use the frames for the presentation. Frames are played in alphabetical order of their titles.
|
||||
## Presentation options
|
||||
- If you select an arrow or line element, the script will use that as the presentation path.
|
||||
- If you select nothing, but the file has a hidden presentation path, the script will use that for determining the slide sequence.
|
||||
- If there are frames, the script will use the frames for the presentation. Frames are played in alphabetical order of their titles.
|
||||
# Keyboard shortcuts and modifier keys
|
||||
**Forward**: Arrow Down, Arrow Right, or SPACE
|
||||
**Backward**: Arrow Up, Arrow Left
|
||||
**Finish presentation**: Backspace, ESC (I had issues with ESC not working in full screen presentation mode on Mac)
|
||||
|
||||
**Run presentation in a window**: Hold down the ALT/OPT modifier key when clicking the presentation script button
|
||||
**Continue presentation**: Hold down SHIFT when clicking the presentation script button. (The feature also works in combination with the ALT/OPT modifier to start the presentation in a window). The feature will only resume while you are within the same Obsidian session (i.e. if you restart Obsidian, slideshow will no longer remember where you were). I have two use cases in mind for this feature:
|
||||
1) When you are designing your presentation you may want to test how a slide looks. Using this feature you can get back to where you left off by starting the presentation with SHIFT.
|
||||
2) During presentation you may want to exit presentation mode to show something additional to your audience. You stop the presentation, show the additional thing you wanted, now you want to continue from where you left off. Hold down SHIFT when clicking the slideshow button.
|
||||
|
||||
```javascript
|
||||
*/
|
||||
@@ -20,7 +31,9 @@ const hostView = hostLeaf.view;
|
||||
const statusBarElement = document.querySelector("div.status-bar");
|
||||
const ctrlKey = ea.targetView.modifierKeyDown.ctrlKey || ea.targetView.modifierKeyDown.metaKey;
|
||||
const altKey = ea.targetView.modifierKeyDown.altKey || ctrlKey;
|
||||
|
||||
const shiftKey = ea.targetView.modifierKeyDown.shiftKey;
|
||||
const shouldStartWithLastSlide = shiftKey && window.ExcalidrawSlideshow &&
|
||||
(window.ExcalidrawSlideshow.script === utils.scriptFile.path) && (typeof window.ExcalidrawSlideshow.slide === "number")
|
||||
//-------------------------------
|
||||
//constants
|
||||
//-------------------------------
|
||||
@@ -28,7 +41,7 @@ const TRANSITION_STEP_COUNT = 100;
|
||||
const TRANSITION_DELAY = 1000; //maximum time for transition between slides in milliseconds
|
||||
const FRAME_SLEEP = 1; //milliseconds
|
||||
const EDIT_ZOOMOUT = 0.7; //70% of original slide zoom, set to a value between 1 and 0
|
||||
const FADE_LEVEL = 0.15; //opacity of the slideshow controls after fade delay (value between 0 and 1)
|
||||
const FADE_LEVEL = 0.1; //opacity of the slideshow controls after fade delay (value between 0 and 1)
|
||||
//using outerHTML because the SVG object returned by Obsidin is in the main workspace window
|
||||
//but excalidraw might be open in a popout window which has a different document object
|
||||
const SVG_COG = ea.obsidian.getIcon("lucide-settings").outerHTML;
|
||||
@@ -45,10 +58,11 @@ const SVG_LASER_OFF = ea.obsidian.getIcon("lucide-wand").outerHTML;
|
||||
//utility & convenience functions
|
||||
//-------------------------------
|
||||
let isLaserOn = false;
|
||||
let slide = 0;
|
||||
let slide = shouldStartWithLastSlide ? window.ExcalidrawSlideshow.slide : 0;
|
||||
let isFullscreen = false;
|
||||
const ownerDocument = ea.targetView.ownerDocument;
|
||||
const startFullscreen = !altKey;
|
||||
|
||||
//The plugin and Obsidian App run in the window object
|
||||
//When Excalidraw is open in a popout window, the Excalidraw component will run in the ownerWindow
|
||||
//and in this case ownerWindow !== window
|
||||
@@ -256,7 +270,7 @@ const getNavigationRect = ({ x1, y1, x2, y2 }) => {
|
||||
const { width, height } = excalidrawAPI.getAppState();
|
||||
const ratioX = width / Math.abs(x1 - x2);
|
||||
const ratioY = height / Math.abs(y1 - y2);
|
||||
let ratio = Math.min(Math.max(ratioX, ratioY), 10);
|
||||
let ratio = Math.min(Math.max(ratioX, ratioY), 30);
|
||||
|
||||
const scaledWidth = Math.abs(x1 - x2) * ratio;
|
||||
const scaledHeight = Math.abs(y1 - y2) * ratio;
|
||||
@@ -305,11 +319,11 @@ const scrollToNextRect = async ({left,top,right,bottom,nextZoom},steps = TRANSIT
|
||||
zoom:{value:zoom.value-zoomStep*i},
|
||||
}
|
||||
});
|
||||
const elapsed = Date.now()-startTimer;
|
||||
if(elapsed > TRANSITION_DELAY) {
|
||||
const ellapsed = Date.now()-startTimer;
|
||||
if(ellapsed > TRANSITION_DELAY) {
|
||||
i = i<steps ? steps : steps+1;
|
||||
} else {
|
||||
const timeProgress = elapsed / TRANSITION_DELAY;
|
||||
const timeProgress = ellapsed / TRANSITION_DELAY;
|
||||
i=Math.min(Math.round(steps*timeProgress),steps)
|
||||
await sleep(FRAME_SLEEP);
|
||||
}
|
||||
@@ -336,6 +350,9 @@ const navigate = async (dir) => {
|
||||
}
|
||||
if(selectSlideDropdown) selectSlideDropdown.value = slide+1;
|
||||
await scrollToNextRect(nextRect);
|
||||
if(window.ExcalidrawSlideshow && (typeof window.ExcalidrawSlideshow.slide === "number")) {
|
||||
window.ExcalidrawSlideshow.slide = slide;
|
||||
}
|
||||
}
|
||||
|
||||
const navigateToSlide = (slideNumber) => {
|
||||
@@ -532,9 +549,11 @@ const keydownListener = (e) => {
|
||||
if(hostLeaf.width === 0 && hostLeaf.height === 0) return;
|
||||
e.preventDefault();
|
||||
switch(e.key) {
|
||||
case "Backspace":
|
||||
case "Escape":
|
||||
exitPresentation();
|
||||
break;
|
||||
case "Space":
|
||||
case "ArrowRight":
|
||||
case "ArrowDown":
|
||||
navigate("fwd");
|
||||
@@ -719,6 +738,15 @@ const exitPresentation = async (openForEdit = false) => {
|
||||
const start = async () => {
|
||||
statusBarElement.style.display = "none";
|
||||
ea.setViewModeEnabled(true);
|
||||
const helpButton = ea.targetView.excalidrawContainer?.querySelector(".ToolIcon__icon.help-icon");
|
||||
if(helpButton) {
|
||||
helpButton.style.display = "none";
|
||||
}
|
||||
const zoomButton = ea.targetView.excalidrawContainer?.querySelector(".Stack.Stack_vertical.zoom-actions");
|
||||
if(zoomButton) {
|
||||
zoomButton.style.display = "none";
|
||||
}
|
||||
|
||||
createPresentationNavigationPanel();
|
||||
initializeEventListners();
|
||||
if(startFullscreen) {
|
||||
@@ -743,7 +771,8 @@ if(window.ExcalidrawSlideshow && (window.ExcalidrawSlideshow.script === utils.sc
|
||||
}
|
||||
window.ExcalidrawSlideshow = {
|
||||
script: utils.scriptFile.path,
|
||||
timestamp
|
||||
timestamp,
|
||||
slide: 0
|
||||
};
|
||||
window.ExcalidrawSlideshowStartTimer = window.setTimeout(start,500);
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "obsidian-excalidraw-plugin",
|
||||
"name": "Excalidraw",
|
||||
"version": "2.0.24",
|
||||
"version": "2.1.4",
|
||||
"minAppVersion": "1.1.6",
|
||||
"description": "An Obsidian plugin to edit and view Excalidraw drawings",
|
||||
"author": "Zsolt Viczian",
|
||||
|
||||
13
package.json
13
package.json
@@ -19,7 +19,7 @@
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@zsviczian/excalidraw": "0.17.1-obsidian-17",
|
||||
"@zsviczian/excalidraw": "0.17.1-obsidian-20",
|
||||
"chroma-js": "^2.4.2",
|
||||
"clsx": "^2.0.0",
|
||||
"colormaster": "^1.2.1",
|
||||
@@ -31,7 +31,8 @@
|
||||
"polybooljs": "^1.2.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"roughjs": "^4.5.2"
|
||||
"roughjs": "^4.5.2",
|
||||
"js-yaml": "^4.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.22.9",
|
||||
@@ -49,6 +50,7 @@
|
||||
"@types/node": "^20.10.5",
|
||||
"@types/react": "^18.2.45",
|
||||
"@types/react-dom": "^18.2.18",
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"@zerollup/ts-transform-paths": "^1.7.18",
|
||||
"cross-env": "^7.0.3",
|
||||
"cssnano": "^6.0.2",
|
||||
@@ -65,7 +67,12 @@
|
||||
"rollup-plugin-web-worker-loader": "^1.6.1",
|
||||
"tslib": "^2.6.1",
|
||||
"ttypescript": "^1.5.15",
|
||||
"typescript": "^5.2.2"
|
||||
"typescript": "^5.2.2",
|
||||
"@codemirror/commands": "^6.3.3",
|
||||
"@codemirror/language": "^6.10.0",
|
||||
"@codemirror/search": "^6.5.5",
|
||||
"@codemirror/state": "^6.4.0",
|
||||
"@codemirror/view": "^6.23.0"
|
||||
},
|
||||
"resolutions": {
|
||||
"@typescript-eslint/typescript-estree": "5.3.0"
|
||||
|
||||
@@ -26,6 +26,7 @@ const react_pkg = isLib ? "" : isProd
|
||||
const reactdom_pkg = isLib ? "" : isProd
|
||||
? fs.readFileSync("./node_modules/react-dom/umd/react-dom.production.min.js", "utf8")
|
||||
: 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) {
|
||||
const excalidraw_styles = isProd
|
||||
@@ -58,7 +59,23 @@ const packageString = isLib
|
||||
|
||||
const BASE_CONFIG = {
|
||||
input: 'src/main.ts',
|
||||
external: ['obsidian', '@zsviczian/excalidraw', 'react', 'react-dom'],
|
||||
external: [
|
||||
'@codemirror/autocomplete',
|
||||
'@codemirror/collab',
|
||||
'@codemirror/commands',
|
||||
'@codemirror/language',
|
||||
'@codemirror/lint',
|
||||
'@codemirror/search',
|
||||
'@codemirror/state',
|
||||
'@codemirror/view',
|
||||
'@lezer/common',
|
||||
'@lezer/highlight',
|
||||
'@lezer/lr',
|
||||
'obsidian',
|
||||
'@zsviczian/excalidraw',
|
||||
'react',
|
||||
'react-dom'
|
||||
],
|
||||
}
|
||||
|
||||
const getRollupPlugins = (tsconfig, ...plugins) =>
|
||||
@@ -107,7 +124,9 @@ const BUILD_CONFIG = {
|
||||
// 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"\);/, packageString],
|
||||
[/React=require\("react"\),state=require\("@codemirror\/state"\),view=require\("@codemirror\/view"\)/,
|
||||
`state=require("@codemirror/state"),view=require("@codemirror/view")` + packageString]
|
||||
])
|
||||
]
|
||||
: [
|
||||
|
||||
45
src/CodeMirrorExtension/EditorHandler.ts
Normal file
45
src/CodeMirrorExtension/EditorHandler.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { Extension } from "@codemirror/state";
|
||||
import ExcalidrawPlugin from "src/main";
|
||||
import { HideTextBetweenCommentsExtension } from "./Fadeout";
|
||||
export const EDITOR_FADEOUT = "fadeOutExcalidrawMarkup";
|
||||
|
||||
const editorExtensions: {[key:string]:Extension}= {
|
||||
[EDITOR_FADEOUT]: HideTextBetweenCommentsExtension,
|
||||
}
|
||||
|
||||
export class EditorHandler {
|
||||
private activeEditorExtensions: Extension[] = [];
|
||||
|
||||
constructor(private plugin: ExcalidrawPlugin) {}
|
||||
|
||||
setup(): void {
|
||||
this.plugin.registerEditorExtension(this.activeEditorExtensions);
|
||||
this.updateCMExtensionState(EDITOR_FADEOUT, this.plugin.settings.fadeOutExcalidrawMarkup);
|
||||
}
|
||||
|
||||
updateCMExtensionState(
|
||||
extensionIdentifier: string,
|
||||
extensionState: boolean,
|
||||
) {
|
||||
const extension = editorExtensions[extensionIdentifier];
|
||||
if(!extension) return;
|
||||
if (extensionState == true) {
|
||||
this.activeEditorExtensions.push(extension);
|
||||
// @ts-ignore
|
||||
this.activeEditorExtensions[this.activeEditorExtensions.length - 1].exID = extensionIdentifier;
|
||||
} else {
|
||||
for (let i = 0; i < this.activeEditorExtensions.length; i++) {
|
||||
const ext = this.activeEditorExtensions[i];
|
||||
// @ts-ignore
|
||||
if (ext.exID === extensionIdentifier) {
|
||||
this.activeEditorExtensions.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.plugin.app.workspace.updateOptions();
|
||||
}
|
||||
update(): void {
|
||||
this.plugin.app.workspace.updateOptions();
|
||||
}
|
||||
}
|
||||
61
src/CodeMirrorExtension/Fadeout.ts
Normal file
61
src/CodeMirrorExtension/Fadeout.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { RangeSetBuilder } from "@codemirror/state";
|
||||
import { Decoration, DecorationSet, EditorView, ViewPlugin, ViewUpdate } from "@codemirror/view";
|
||||
|
||||
const o30 = Decoration.line({ attributes: {class: "ex-opacity-30"} });
|
||||
const o15 = Decoration.line({ attributes: {class: "ex-opacity-15"} });
|
||||
const o8 = Decoration.line({ attributes: {class: "ex-opacity-8"} });
|
||||
const o5 = Decoration.line({ attributes: {class: "ex-opacity-5"} });
|
||||
const o0 = Decoration.line({ attributes: {class: "ex-opacity-0"} });
|
||||
|
||||
export const HideTextBetweenCommentsExtension = ViewPlugin.fromClass(
|
||||
class {
|
||||
view: EditorView;
|
||||
decorations: DecorationSet;
|
||||
reTextElements = /^%%(?:\r\n|\r|\n)# Text Elements$/gm;
|
||||
reDrawing = /^%%(?:\r\n|\r|\n)# Drawing$/gm;
|
||||
linecount = 0;
|
||||
isExcalidraw = false;
|
||||
|
||||
constructor(view: EditorView) {
|
||||
this.view = view;
|
||||
this.isExcalidraw = view.state.doc.toString().search(/^excalidraw-plugin: /m) > 0;
|
||||
if(!this.isExcalidraw) {
|
||||
this.decorations = Decoration.none;
|
||||
return;
|
||||
}
|
||||
this.decorations = this.updateDecorations(view);
|
||||
}
|
||||
|
||||
updateDecorations (view: EditorView) {
|
||||
const { state } = view;
|
||||
const { doc } = state;
|
||||
|
||||
const text = doc.toString();
|
||||
|
||||
let start = text.search(this.reTextElements);
|
||||
if(start == -1) {
|
||||
start = text.search(this.reDrawing);
|
||||
if(start == -1) return Decoration.none;
|
||||
}
|
||||
const startLine = doc.lineAt(start).number;
|
||||
const endLine = doc.lines;
|
||||
let builder = new RangeSetBuilder<Decoration>()
|
||||
for (let l = startLine; l <= endLine; l++) {
|
||||
const line = doc.line(l);
|
||||
const pos = l-startLine;
|
||||
builder.add(line.from, line.from,
|
||||
pos == 0 ? o30 : (pos == 1) ? o15 : (pos < 6) ? o8 : (pos < 12) ? o5 : o0);
|
||||
}
|
||||
return builder.finish()
|
||||
}
|
||||
|
||||
update(update: ViewUpdate) {
|
||||
if (this.isExcalidraw && update.docChanged) {
|
||||
this.decorations = this.updateDecorations(update.view)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
decorations: (x) => x.decorations,
|
||||
}
|
||||
);
|
||||
@@ -35,8 +35,10 @@ import {
|
||||
REG_LINKINDEX_INVALIDCHARS,
|
||||
THEME_FILTER,
|
||||
mermaidToExcalidraw,
|
||||
MD_TEXTELEMENTS,
|
||||
MD_DRAWING,
|
||||
} from "src/constants/constants";
|
||||
import { blobToBase64, checkAndCreateFolder, getDrawingFilename, getNewUniqueFilepath, } from "src/utils/FileUtils";
|
||||
import { blobToBase64, checkAndCreateFolder, getDrawingFilename, getListOfTemplateFiles, getNewUniqueFilepath, } from "src/utils/FileUtils";
|
||||
import {
|
||||
arrayToMap,
|
||||
//debug,
|
||||
@@ -53,7 +55,7 @@ import {
|
||||
scaleLoadedImage,
|
||||
wrapTextAtCharLength,
|
||||
} from "src/utils/Utils";
|
||||
import { getAttachmentsFolderAndFilePath, getLeaf, getNewOrAdjacentLeaf, isObsidianThemeDark, openLeaf } from "src/utils/ObsidianUtils";
|
||||
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";
|
||||
@@ -91,6 +93,7 @@ import {
|
||||
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";
|
||||
|
||||
extendPlugins([
|
||||
HarmonyPlugin,
|
||||
@@ -241,6 +244,14 @@ export class ExcalidrawAutomate {
|
||||
return getNewUniqueFilepath(app.vault, filename, folderpath);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns the Excalidraw Template files or null.
|
||||
*/
|
||||
public getListOfTemplateFiles(): TFile[] | null {
|
||||
return getListOfTemplateFiles(this.plugin);
|
||||
}
|
||||
|
||||
public async getAttachmentFilepath(filename: string): Promise<string> {
|
||||
if (!this.targetView || !this.targetView?.file) {
|
||||
errorMessage("targetView not set", "getAttachmentFolderAndFilePath()");
|
||||
@@ -595,6 +606,7 @@ export class ExcalidrawAutomate {
|
||||
"excalidraw-linkbutton-opacity"?: number;
|
||||
"excalidraw-autoexport"?: boolean;
|
||||
"excalidraw-mask"?: boolean;
|
||||
"excalidraw-open-md"?: boolean;
|
||||
"cssclasses"?: string;
|
||||
};
|
||||
plaintext?: string; //text to insert above the `# Text Elements` section
|
||||
@@ -634,6 +646,12 @@ export class ExcalidrawAutomate {
|
||||
: FRONTMATTER;
|
||||
}
|
||||
|
||||
frontmatter += params.plaintext ? params.plaintext + "\n\n" : "";
|
||||
if(template?.frontmatter && params?.frontmatterKeys) {
|
||||
//the frontmatter tags supplyed to create take priority
|
||||
frontmatter = mergeMarkdownFiles(template.frontmatter,frontmatter);
|
||||
}
|
||||
|
||||
const scene = {
|
||||
type: "excalidraw",
|
||||
version: 2,
|
||||
@@ -686,9 +704,8 @@ export class ExcalidrawAutomate {
|
||||
};
|
||||
|
||||
const generateMD = ():string => {
|
||||
let outString = params.plaintext ? params.plaintext + "\n\n" : "";
|
||||
const textElements = this.getElements().filter(el => el.type === "text") as ExcalidrawTextElement[];
|
||||
outString += "# Text Elements\n";
|
||||
let outString = `${MD_TEXTELEMENTS}\n`;
|
||||
textElements.forEach(te=> {
|
||||
outString += `${te.rawText ?? (te.originalText ?? te.text)} ^${te.id}\n\n`;
|
||||
});
|
||||
@@ -727,7 +744,7 @@ export class ExcalidrawAutomate {
|
||||
}
|
||||
}
|
||||
})
|
||||
return outString;
|
||||
return outString + "\n%%\n";
|
||||
}
|
||||
|
||||
const filename = params?.filename
|
||||
@@ -751,14 +768,6 @@ export class ExcalidrawAutomate {
|
||||
}
|
||||
};
|
||||
|
||||
/* getCropImageObject(): CropImage {
|
||||
const scene = this.targetView.getScene();
|
||||
return new CropImage(
|
||||
scene.elements,
|
||||
scene.files,
|
||||
);
|
||||
}*/
|
||||
|
||||
/**
|
||||
*
|
||||
* @param templatePath
|
||||
@@ -2752,9 +2761,9 @@ async function getTemplate(
|
||||
textMode,
|
||||
);
|
||||
|
||||
let trimLocation = data.search("# Text Elements\n");
|
||||
let trimLocation = data.search(new RegExp(`^${MD_TEXTELEMENTS}$`,"m"));
|
||||
if (trimLocation == -1) {
|
||||
trimLocation = data.search("# Drawing\n");
|
||||
trimLocation = data.search(`${MD_DRAWING}\n`);
|
||||
}
|
||||
|
||||
let scene = excalidrawData.scene;
|
||||
@@ -2790,9 +2799,10 @@ async function getTemplate(
|
||||
}
|
||||
}
|
||||
if(filenameParts.hasFrameref) {
|
||||
const el = scene.elements.filter((el: ExcalidrawElement)=>el.id===filenameParts.blockref)
|
||||
if(el.length === 1) {
|
||||
groupElements = plugin.ea.getElementsInFrame(el[0],scene.elements)
|
||||
const el = getFrameBasedOnFrameNameOrId(filenameParts.blockref,scene.elements);
|
||||
|
||||
if(el) {
|
||||
groupElements = plugin.ea.getElementsInFrame(el,scene.elements)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,9 @@ import {
|
||||
ERROR_IFRAME_CONVERSION_CANCELED,
|
||||
JSON_parse,
|
||||
FRONTMATTER_KEYS,
|
||||
MD_TEXTELEMENTS,
|
||||
MD_DRAWING,
|
||||
MD_ELEMENTLINKS,
|
||||
} from "./constants/constants";
|
||||
import { _measureText } from "./ExcalidrawAutomate";
|
||||
import ExcalidrawPlugin from "./main";
|
||||
@@ -201,10 +204,10 @@ export function getMarkdownDrawingSection(
|
||||
compressed: boolean,
|
||||
) {
|
||||
return compressed
|
||||
? `%%\n# Drawing\n\x60\x60\x60compressed-json\n${compress(
|
||||
? `# Drawing\n\x60\x60\x60compressed-json\n${compress(
|
||||
jsonString,
|
||||
)}\n\x60\x60\x60\n%%`
|
||||
: `%%\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%%`;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -237,13 +240,35 @@ const estimateMaxLineLen = (text: string, originalText: string): number => {
|
||||
const wrap = (text: string, lineLen: number) =>
|
||||
lineLen ? wrapTextAtCharLength(text, lineLen, false, 0) : text;
|
||||
|
||||
export const getExcalidrawMarkdownHeaderSection = (data:string, keys:[string,string][]):string => {
|
||||
let trimLocation = data.search(/(^%%\n)?# Text Elements\n/m);
|
||||
if (trimLocation == -1) {
|
||||
trimLocation = data.search(/(%%\n)?# Drawing\n/);
|
||||
const RE_TEXTELEMENTS = new RegExp(`^(%%\n)?${MD_TEXTELEMENTS}(?:\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");
|
||||
|
||||
|
||||
const RE_DRAWING = new RegExp(`(%%\n)?${MD_DRAWING}\n`);
|
||||
|
||||
export const getExcalidrawMarkdownHeaderSection = (data:string, keys?:[string,string][]):string => {
|
||||
let trimLocation = data.search(RE_TEXTELEMENTS);
|
||||
if(trimLocation === -1) {
|
||||
const res = data.match(RE_TEXTELEMENTS_FALLBACK_1);
|
||||
if(res && Boolean(res[1])) {
|
||||
trimLocation = res.index + res[1].length;
|
||||
}
|
||||
}
|
||||
if (trimLocation == -1) {
|
||||
return data;
|
||||
if(trimLocation === -1) {
|
||||
const res = data.match(RE_TEXTELEMENTS_FALLBACK_2);
|
||||
if(res && Boolean(res[1])) {
|
||||
trimLocation = res.index + res[1].length;
|
||||
}
|
||||
}
|
||||
if (trimLocation === -1) {
|
||||
trimLocation = data.search(RE_DRAWING);
|
||||
}
|
||||
if (trimLocation === -1) {
|
||||
return data.endsWith("\n") ? data : (data + "\n");
|
||||
}
|
||||
|
||||
let header = updateFrontmatterInString(data.substring(0, trimLocation),keys);
|
||||
@@ -253,7 +278,7 @@ export const getExcalidrawMarkdownHeaderSection = (data:string, keys:[string,str
|
||||
header = header.replace(REG_IMG, "$1");
|
||||
}
|
||||
//end of remove
|
||||
return header;
|
||||
return header.endsWith("\n") ? header : (header + "\n");
|
||||
}
|
||||
|
||||
|
||||
@@ -278,6 +303,7 @@ export class ExcalidrawData {
|
||||
private equations: Map<FileId, { latex: string; isLoaded: boolean }> = null; //fileId, path
|
||||
private mermaids: Map<FileId, { mermaid: string; isLoaded: boolean }> = null; //fileId, path
|
||||
private compatibilityMode: boolean = false;
|
||||
private textElementCommentedOut: boolean = false;
|
||||
selectedElementIds: {[key:string]:boolean} = {}; //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/609
|
||||
|
||||
constructor(
|
||||
@@ -557,22 +583,37 @@ export class ExcalidrawData {
|
||||
//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(/(^%%\n)?# Text Elements\n/m);
|
||||
let position = data.search(RE_TEXTELEMENTS);
|
||||
if (position === -1) {
|
||||
await this.setTextMode(textMode, false);
|
||||
this.loaded = true;
|
||||
return true; //Text Elements header does not exist
|
||||
}
|
||||
position += data.match(/((^%%\n)?# Text Elements\n)/m)[0].length;
|
||||
|
||||
data = data.substring(position);
|
||||
const textElementsMatch = data.match(new RegExp(`^((%%\n)?${MD_TEXTELEMENTS}(?:\n|$))`, "m"))[0]
|
||||
position += textElementsMatch.length;
|
||||
|
||||
data = data.slice(position);
|
||||
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,
|
||||
);
|
||||
//Load Embedded files
|
||||
const RE_ELEMENT_LINKS = /^(.{8}):\s*(\[\[[^\]]*]])$/gm;
|
||||
const linksRes = elementLinksData.matchAll(RE_ELEMENT_LINKS);
|
||||
while (!(parts = linksRes.next()).done) {
|
||||
elementLinkMap.set(parts.value[1], parts.value[2]);
|
||||
}
|
||||
|
||||
//iterating through all the text elements in .md
|
||||
//Text elements always contain the raw value
|
||||
const BLOCKREF_LEN: number = " ^12345678\n\n".length;
|
||||
const RE_TEXT_ELEMENT_LINK = /^%%\*\*\*>>>text element-link:(\[\[[^<*\]]*]])<<<\*\*\*%%/gm;
|
||||
let res = data.matchAll(/\s\^(.{8})[\n]+/g);
|
||||
let parts;
|
||||
while (!(parts = res.next()).done) {
|
||||
let text = data.substring(position, parts.value.index);
|
||||
const id: string = parts.value[1];
|
||||
@@ -580,6 +621,7 @@ export class ExcalidrawData {
|
||||
if (textEl) {
|
||||
if (textEl.type !== "text") {
|
||||
//markdown link attached to elements
|
||||
//legacy fileformat support as of 2.0.26
|
||||
if (textEl.link !== text) {
|
||||
textEl.link = text;
|
||||
textEl.version++;
|
||||
@@ -589,12 +631,16 @@ export class ExcalidrawData {
|
||||
} else {
|
||||
const wrapAt = estimateMaxLineLen(textEl.text, textEl.originalText);
|
||||
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/566
|
||||
const elementLinkRes = text.matchAll(/^%%\*\*\*>>>text element-link:(\[\[[^<*\]]*]])<<<\*\*\*%%/gm);
|
||||
const elementLinkRes = text.matchAll(RE_TEXT_ELEMENT_LINK);
|
||||
const elementLink = elementLinkRes.next();
|
||||
if(!elementLink.done) {
|
||||
text = text.replace(/^%%\*\*\*>>>text element-link:\[\[[^<*\]]*]]<<<\*\*\*%%/gm,"");
|
||||
text = text.replace(RE_TEXT_ELEMENT_LINK,"");
|
||||
textEl.link = elementLink.value[1];
|
||||
}
|
||||
}
|
||||
if(elementLinkMap.has(id)) {
|
||||
textEl.link = elementLinkMap.get(id);
|
||||
elementLinkMap.delete(id);
|
||||
}
|
||||
const parseRes = await this.parse(text);
|
||||
textEl.rawText = text;
|
||||
this.textElements.set(id, {
|
||||
@@ -614,54 +660,68 @@ export class ExcalidrawData {
|
||||
position = parts.value.index + BLOCKREF_LEN;
|
||||
}
|
||||
|
||||
data = data.substring(
|
||||
data.indexOf("# Embedded files\n") + "# Embedded files\n".length,
|
||||
);
|
||||
//Load Embedded files
|
||||
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(
|
||||
this.plugin,
|
||||
this.file.path,
|
||||
parts.value[2],
|
||||
parts.value[3],
|
||||
//In theory only non-text elements should be left in the elementLinkMap
|
||||
//new file format from 2.0.26
|
||||
for (const [id, link] of elementLinkMap) {
|
||||
const textEl = this.scene.elements.filter((el: any) => el.id === id)[0];
|
||||
if (textEl) {
|
||||
textEl.link = link;
|
||||
textEl.version++;
|
||||
textEl.versionNonce++;
|
||||
this.elementLinks.set(id, link);
|
||||
}
|
||||
}
|
||||
|
||||
const indexOfEmbeddedFiles = data.indexOf("# Embedded files\n");
|
||||
if(indexOfEmbeddedFiles>-1) {
|
||||
data = data.substring(
|
||||
indexOfEmbeddedFiles + "# Embedded files\n".length,
|
||||
);
|
||||
this.setFile(parts.value[1] as FileId, embeddedFile);
|
||||
}
|
||||
//Load Embedded files
|
||||
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(
|
||||
this.plugin,
|
||||
this.file.path,
|
||||
parts.value[2],
|
||||
parts.value[3],
|
||||
);
|
||||
this.setFile(parts.value[1] as FileId, embeddedFile);
|
||||
}
|
||||
|
||||
//Load links
|
||||
const REG_LINKID_FILEPATH = /([\w\d]*):\s*((?:https?|file|ftps?):\/\/[^\s]*)\n/gm;
|
||||
res = data.matchAll(REG_LINKID_FILEPATH);
|
||||
while (!(parts = res.next()).done) {
|
||||
const embeddedFile = new EmbeddedFile(
|
||||
this.plugin,
|
||||
null,
|
||||
parts.value[2],
|
||||
);
|
||||
this.setFile(parts.value[1] as FileId, embeddedFile);
|
||||
}
|
||||
//Load links
|
||||
const REG_LINKID_FILEPATH = /([\w\d]*):\s*((?:https?|file|ftps?):\/\/[^\s]*)\n/gm;
|
||||
res = data.matchAll(REG_LINKID_FILEPATH);
|
||||
while (!(parts = res.next()).done) {
|
||||
const embeddedFile = new EmbeddedFile(
|
||||
this.plugin,
|
||||
null,
|
||||
parts.value[2],
|
||||
);
|
||||
this.setFile(parts.value[1] as FileId, embeddedFile);
|
||||
}
|
||||
|
||||
//Load Equations
|
||||
const REG_FILEID_EQUATION = /([\w\d]*):\s*\$\$([\s\S]*?)(\$\$\s*\n)/gm;
|
||||
res = data.matchAll(REG_FILEID_EQUATION);
|
||||
while (!(parts = res.next()).done) {
|
||||
this.setEquation(parts.value[1] as FileId, {
|
||||
latex: parts.value[2],
|
||||
isLoaded: false,
|
||||
});
|
||||
}
|
||||
//Load Equations
|
||||
const REG_FILEID_EQUATION = /([\w\d]*):\s*\$\$([\s\S]*?)(\$\$\s*\n)/gm;
|
||||
res = data.matchAll(REG_FILEID_EQUATION);
|
||||
while (!(parts = res.next()).done) {
|
||||
this.setEquation(parts.value[1] as FileId, {
|
||||
latex: parts.value[2],
|
||||
isLoaded: false,
|
||||
});
|
||||
}
|
||||
|
||||
//Load Mermaids
|
||||
const mermaidElements = getMermaidImageElements(this.scene.elements);
|
||||
if(mermaidElements.length>0 && !shouldRenderMermaid()) {
|
||||
new Notice ("Mermaid images are only supported in Obsidian 1.4.14 and above. Please update Obsidian to see the mermaid images in this drawing. Obsidian mobile 1.4.14 currently only avaiable to Obsidian insiders", 5000);
|
||||
} else {
|
||||
mermaidElements.forEach(el =>
|
||||
this.setMermaid(el.fileId, {mermaid: getMermaidText(el), isLoaded: false})
|
||||
);
|
||||
//Load Mermaids
|
||||
const mermaidElements = getMermaidImageElements(this.scene.elements);
|
||||
if(mermaidElements.length>0 && !shouldRenderMermaid()) {
|
||||
new Notice ("Mermaid images are only supported in Obsidian 1.4.14 and above. Please update Obsidian to see the mermaid images in this drawing. Obsidian mobile 1.4.14 currently only avaiable to Obsidian insiders", 5000);
|
||||
} else {
|
||||
mermaidElements.forEach(el =>
|
||||
this.setMermaid(el.fileId, {mermaid: getMermaidText(el), isLoaded: false})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
//Check to see if there are text elements in the JSON that were missed from the # Text Elements section
|
||||
//e.g. if the entire text elements section was deleted.
|
||||
this.findNewTextElementsInScene();
|
||||
@@ -1130,27 +1190,37 @@ export class ExcalidrawData {
|
||||
*/
|
||||
disableCompression: boolean = false;
|
||||
generateMD(deletedElements: ExcalidrawElement[] = []): string {
|
||||
let outString = "# Text Elements\n";
|
||||
let outString = this.textElementCommentedOut ? "%%\n" : "";
|
||||
outString += `${MD_TEXTELEMENTS}\n`;
|
||||
const textElementLinks = new Map<string, string>();
|
||||
for (const key of this.textElements.keys()) {
|
||||
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/566
|
||||
const element = this.scene.elements.filter((el:any)=>el.id===key);
|
||||
let elementString = this.textElements.get(key).raw;
|
||||
if(element && element.length===1 && element[0].link && element[0].rawText === element[0].originalText) {
|
||||
if(element[0].link.match(/^\[\[[^\]]*]]$/g)) { //apply this only to markdown links
|
||||
elementString = `%%***>>>text element-link:${element[0].link}<<<***%%` + elementString;
|
||||
textElementLinks.set(key, element[0].link);
|
||||
//elementString = `%%***>>>text element-link:${element[0].link}<<<***%%` + elementString;
|
||||
}
|
||||
}
|
||||
outString += `${elementString} ^${key}\n\n`;
|
||||
}
|
||||
|
||||
for (const key of this.elementLinks.keys()) {
|
||||
outString += `${this.elementLinks.get(key)} ^${key}\n\n`;
|
||||
if (this.elementLinks.size > 0 || textElementLinks.size > 0) {
|
||||
outString += `${MD_ELEMENTLINKS}\n`;
|
||||
for (const key of this.elementLinks.keys()) {
|
||||
outString += `${key}: ${this.elementLinks.get(key)}\n`;
|
||||
}
|
||||
for (const key of textElementLinks.keys()) {
|
||||
outString += `${key}: ${textElementLinks.get(key)}\n`;
|
||||
}
|
||||
outString += "\n";
|
||||
}
|
||||
|
||||
// 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
|
||||
? "\n# Embedded files\n"
|
||||
? "# Embedded files\n"
|
||||
: "";
|
||||
if (this.equations.size > 0) {
|
||||
for (const key of this.equations.keys()) {
|
||||
@@ -1185,6 +1255,7 @@ export class ExcalidrawData {
|
||||
}, null, "\t");
|
||||
return (
|
||||
outString +
|
||||
(this.textElementCommentedOut ? "" : "%%\n") +
|
||||
getMarkdownDrawingSection(
|
||||
sceneJSONstring,
|
||||
this.disableCompression ? false : this.plugin.settings.compress,
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
//import Excalidraw from "@zsviczian/excalidraw";
|
||||
import {
|
||||
ExcalidrawElement,
|
||||
ExcalidrawGenericElement,
|
||||
ExcalidrawImageElement,
|
||||
ExcalidrawTextElement,
|
||||
FileId,
|
||||
@@ -51,6 +52,7 @@ import {
|
||||
MAX_IMAGE_SIZE,
|
||||
fileid,
|
||||
sceneCoordsToViewportCoords,
|
||||
MD_EX_SECTIONS,
|
||||
} from "./constants/constants";
|
||||
import ExcalidrawPlugin from "./main";
|
||||
import {
|
||||
@@ -100,8 +102,10 @@ import {
|
||||
isContainer,
|
||||
fragWithHTML,
|
||||
isMaskFile,
|
||||
shouldEmbedScene,
|
||||
getContainerElement,
|
||||
} from "./utils/Utils";
|
||||
import { getLeaf, getParentOfClass, obsidianPDFQuoteWithRef, openLeaf } from "./utils/ObsidianUtils";
|
||||
import { cleanSectionHeading, getLeaf, getParentOfClass, obsidianPDFQuoteWithRef, openLeaf } from "./utils/ObsidianUtils";
|
||||
import { splitFolderAndFilename } from "./utils/FileUtils";
|
||||
import { ConfirmationPrompt, GenericInputPrompt, NewFileActions, Prompt, linkPrompt } from "./dialogs/Prompt";
|
||||
import { ClipboardData } from "@zsviczian/excalidraw/types/excalidraw/clipboard";
|
||||
@@ -124,7 +128,7 @@ import { anyModifierKeysPressed, emulateKeysForLinkClick, webbrowserDragModifier
|
||||
import { setDynamicStyle } from "./utils/DynamicStyling";
|
||||
import { InsertPDFModal } from "./dialogs/InsertPDFModal";
|
||||
import { CustomEmbeddable, renderWebView } from "./customEmbeddable";
|
||||
import { getExcalidrawFileForwardLinks, getLinkTextFromLink, insertEmbeddableToView, insertImageToView, openExternalLink, openTagSearch } from "./utils/ExcalidrawViewUtils";
|
||||
import { getExcalidrawFileForwardLinks, getFrameBasedOnFrameNameOrId, getLinkTextFromLink, insertEmbeddableToView, insertImageToView, openExternalLink, openTagSearch } from "./utils/ExcalidrawViewUtils";
|
||||
import { imageCache } from "./utils/ImageCache";
|
||||
import { CanvasNodeFactory, ObsidianCanvasNode } from "./utils/CanvasNodeFactory";
|
||||
import { EmbeddableMenu } from "./menu/EmbeddableActionsMenu";
|
||||
@@ -135,6 +139,8 @@ import { nanoid } from "nanoid";
|
||||
import { CustomMutationObserver, isDebugMode } from "./utils/DebugHelper";
|
||||
import { extractCodeBlocks, postOpenAI } from "./utils/AIUtils";
|
||||
import { Mutable } from "@zsviczian/excalidraw/types/excalidraw/utility-types";
|
||||
import { SelectCard } from "./dialogs/SelectCard";
|
||||
import { link } from "fs";
|
||||
|
||||
declare const PLUGIN_VERSION:string;
|
||||
|
||||
@@ -263,6 +269,8 @@ export default class ExcalidrawView extends TextFileView {
|
||||
// private scrollYBeforeKeyboard: number = null;
|
||||
|
||||
public semaphores: {
|
||||
//flag to prevent overwriting the changes the user makes in an embeddable view editing the back side of the drawing
|
||||
embeddableIsEditingSelf: boolean;
|
||||
popoutUnload: boolean; //the unloaded Excalidraw view was the last leaf in the popout window
|
||||
viewunload: boolean;
|
||||
//first time initialization of the view
|
||||
@@ -297,6 +305,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
hoverSleep: boolean; //flag with timer to prevent hover preview from being triggered dozens of times
|
||||
wheelTimeout:NodeJS.Timeout; //used to avoid hover preview while zooming
|
||||
} = {
|
||||
embeddableIsEditingSelf: false,
|
||||
popoutUnload: false,
|
||||
viewunload: false,
|
||||
scriptsReady: false,
|
||||
@@ -428,6 +437,10 @@ export default class ExcalidrawView extends TextFileView {
|
||||
isMask: isMaskFile(this.plugin, this.file),
|
||||
};
|
||||
|
||||
if(typeof embedScene === "undefined") {
|
||||
embedScene = shouldEmbedScene(this.plugin, this.file);
|
||||
}
|
||||
|
||||
return await getSVG(
|
||||
{
|
||||
...scene,
|
||||
@@ -506,6 +519,11 @@ export default class ExcalidrawView extends TextFileView {
|
||||
withTheme: true,
|
||||
isMask: isMaskFile(this.plugin, this.file),
|
||||
};
|
||||
|
||||
if(typeof embedScene === "undefined") {
|
||||
embedScene = shouldEmbedScene(this.plugin, this.file);
|
||||
}
|
||||
|
||||
return await getPNG(
|
||||
{
|
||||
...scene,
|
||||
@@ -598,10 +616,48 @@ export default class ExcalidrawView extends TextFileView {
|
||||
}
|
||||
|
||||
private preventReloadResetTimer: NodeJS.Timeout = null;
|
||||
|
||||
public setPreventReload() {
|
||||
this.semaphores.preventReload = true;
|
||||
const self = this;
|
||||
this.preventReloadResetTimer = setTimeout(()=>self.semaphores.preventReload = false,2000);
|
||||
}
|
||||
|
||||
public clearPreventReloadTimer() {
|
||||
if(this.preventReloadResetTimer) {
|
||||
clearTimeout(this.preventReloadResetTimer);
|
||||
this.preventReloadResetTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
private editingSelfResetTimer: NodeJS.Timeout = null;
|
||||
public async setEmbeddableIsEditingSelf() {
|
||||
this.clearEmbeddableIsEditingSelfTimer();
|
||||
await this.forceSave(true);
|
||||
this.semaphores.embeddableIsEditingSelf = true;
|
||||
}
|
||||
|
||||
public clearEmbeddableIsEditingSelfTimer () {
|
||||
if(this.editingSelfResetTimer) {
|
||||
clearTimeout(this.editingSelfResetTimer);
|
||||
this.editingSelfResetTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
public clearEmbeddableIsEditingSelf() {
|
||||
const self = this;
|
||||
this.clearEmbeddableIsEditingSelfTimer();
|
||||
this.editingSelfResetTimer = setTimeout(()=>self.semaphores.embeddableIsEditingSelf = false,2000);
|
||||
}
|
||||
|
||||
async save(preventReload: boolean = true, forcesave: boolean = false) {
|
||||
if(!this.isLoaded) {
|
||||
return;
|
||||
}
|
||||
if (this.semaphores.embeddableIsEditingSelf) {
|
||||
return;
|
||||
}
|
||||
//console.log("saving - embeddable not editing")
|
||||
//debug({where:"save", preventReload, forcesave, semaphores:this.semaphores});
|
||||
if (this.semaphores.saving) {
|
||||
return;
|
||||
@@ -648,10 +704,8 @@ export default class ExcalidrawView extends TextFileView {
|
||||
//reload() is triggered indirectly when saving by the modifyEventHandler in main.ts
|
||||
//prevent reload is set here to override reload when not wanted: typically when the user is editing
|
||||
//and we do not want to interrupt the flow by reloading the drawing into the canvas.
|
||||
if(this.preventReloadResetTimer) {
|
||||
clearTimeout(this.preventReloadResetTimer);
|
||||
this.preventReloadResetTimer = null;
|
||||
}
|
||||
|
||||
this.clearPreventReloadTimer();
|
||||
|
||||
this.semaphores.preventReload = preventReload;
|
||||
await super.save();
|
||||
@@ -669,8 +723,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
//there were odd cases when preventReload semaphore did not get cleared and consequently a synchronized image
|
||||
//did not update the open drawing
|
||||
if(preventReload) {
|
||||
const self = this;
|
||||
this.preventReloadResetTimer = setTimeout(()=>self.semaphores.preventReload = false,2000);
|
||||
this.setPreventReload();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -729,13 +782,15 @@ export default class ExcalidrawView extends TextFileView {
|
||||
//deleted elements are only used if sync modifies files while Excalidraw is open
|
||||
//otherwise deleted elements are discarded when loading the scene
|
||||
if (!this.compatibilityMode) {
|
||||
|
||||
const keys:[string,string][] = this.exportDialog?.dirty && this.exportDialog?.saveSettings
|
||||
? [
|
||||
[FRONTMATTER_KEYS["export-padding"].name, this.exportDialog.padding.toString()],
|
||||
[FRONTMATTER_KEYS["export-pngscale"].name, this.exportDialog.scale.toString()],
|
||||
[FRONTMATTER_KEYS["export-dark"].name, this.exportDialog.theme === "dark" ? "true" : "false"],
|
||||
[FRONTMATTER_KEYS["export-transparent"].name, this.exportDialog.transparent ? "true" : "false"],
|
||||
[FRONTMATTER_KEYS["plugin"].name, this.textMode === TextMode.raw ? "raw" : "parsed"]
|
||||
[FRONTMATTER_KEYS["plugin"].name, this.textMode === TextMode.raw ? "raw" : "parsed"],
|
||||
[FRONTMATTER_KEYS["export-embed-scene"].name, this.exportDialog.embedScene ? "true" : "false"],
|
||||
]
|
||||
: [
|
||||
[FRONTMATTER_KEYS["plugin"].name, this.textMode === TextMode.raw ? "raw" : "parsed"]
|
||||
@@ -907,20 +962,38 @@ export default class ExcalidrawView extends TextFileView {
|
||||
let linkText: string = null;
|
||||
|
||||
if (selectedText?.id || selectedElementWithLink?.id) {
|
||||
const selectedTextElement: ExcalidrawTextElement = selectedText.id
|
||||
? this.excalidrawAPI.getSceneElements().find((el:ExcalidrawElement)=>el.id === selectedText.id)
|
||||
: null;
|
||||
|
||||
linkText =
|
||||
selectedElementWithLink?.text ??
|
||||
(this.textMode === TextMode.parsed
|
||||
? this.excalidrawData.getRawText(selectedText.id)
|
||||
: selectedText.text);
|
||||
|
||||
const partsArray = REGEX_LINK.getResList(linkText);
|
||||
if (!linkText || partsArray.length === 0) {
|
||||
//the container link takes precedence over the text link
|
||||
if(selectedTextElement?.containerId) {
|
||||
const container = getContainerElement(selectedTextElement, {elements: this.excalidrawAPI.getSceneElements()});
|
||||
if(container) {
|
||||
linkText = container.link;
|
||||
}
|
||||
}
|
||||
if(!linkText) {
|
||||
linkText = selectedTextElement?.link;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (!linkText) {
|
||||
return;
|
||||
return;
|
||||
}
|
||||
linkText = linkText.replaceAll("\n", ""); //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/187
|
||||
|
||||
const id = selectedText.id??selectedElementWithLink.id;
|
||||
const el = this.excalidrawAPI.getSceneElements().filter((el:ExcalidrawElement)=>el.id === id)[0];
|
||||
if(this.handleLinkHookCall(el,linkText,ev)) return;
|
||||
if(this.handleLinkHookCall(selectedTextElement,linkText,ev)) return;
|
||||
if(openExternalLink(linkText, this.app)) return;
|
||||
|
||||
const result = await linkPrompt(linkText, this.app, this);
|
||||
@@ -1411,9 +1484,11 @@ export default class ExcalidrawView extends TextFileView {
|
||||
this.plugin.settings.autosave &&
|
||||
!this.semaphores.forceSaving &&
|
||||
!this.semaphores.autosaving &&
|
||||
!this.semaphores.embeddableIsEditingSelf &&
|
||||
!editing &&
|
||||
st.draggingElement === null //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/630
|
||||
) {
|
||||
//console.log("autosave");
|
||||
this.autosaveTimer = null;
|
||||
if (this.excalidrawAPI) {
|
||||
this.semaphores.autosaving = true;
|
||||
@@ -1486,7 +1561,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
}
|
||||
|
||||
/**
|
||||
* reload is triggered by the modifyEventHandler in main.ts when ever an excalidraw drawing that is currently open
|
||||
* reload is triggered by the modifyEventHandler in main.ts whenever an excalidraw drawing that is currently open
|
||||
* in a workspace leaf is modified. There can be two reasons for the file change:
|
||||
* - The user saves the drawing in the active view (either force-save or autosave)
|
||||
* - The file is modified by some other process, typically as a result of background sync, or because the drawing is open
|
||||
@@ -1496,6 +1571,26 @@ export default class ExcalidrawView extends TextFileView {
|
||||
* @returns
|
||||
*/
|
||||
public async reload(fullreload: boolean = false, file?: TFile) {
|
||||
const loadOnModifyTrigger = file && file === this.file;
|
||||
|
||||
//once you've finished editing the embeddable, the first time the file
|
||||
//reloads will be because of the embeddable changed the file,
|
||||
//there is a 2000 ms time window allowed for this, but typically this will
|
||||
//happen within 100 ms. When this happens the timer is cleared and the
|
||||
//next time reload triggers the file will be reloaded as normal.
|
||||
if (this.semaphores.embeddableIsEditingSelf) {
|
||||
//console.log("reload - embeddable is editing")
|
||||
if(this.editingSelfResetTimer) {
|
||||
this.clearEmbeddableIsEditingSelfTimer();
|
||||
this.semaphores.embeddableIsEditingSelf = false;
|
||||
}
|
||||
if(loadOnModifyTrigger) {
|
||||
this.data = await this.app.vault.read(this.file);
|
||||
}
|
||||
return;
|
||||
}
|
||||
//console.log("reload - embeddable is not editing")
|
||||
|
||||
if (this.semaphores.preventReload) {
|
||||
this.semaphores.preventReload = false;
|
||||
return;
|
||||
@@ -1511,9 +1606,9 @@ export default class ExcalidrawView extends TextFileView {
|
||||
if (!this.file || !api) {
|
||||
return;
|
||||
}
|
||||
const loadOnModifyTrigger = file && file === this.file;
|
||||
|
||||
if (loadOnModifyTrigger) {
|
||||
this.data = await app.vault.read(file);
|
||||
this.data = await this.app.vault.read(file);
|
||||
this.preventAutozoom();
|
||||
}
|
||||
if (fullreload) {
|
||||
@@ -1536,7 +1631,14 @@ export default class ExcalidrawView extends TextFileView {
|
||||
const sceneElements = api.getSceneElements();
|
||||
|
||||
let elements = sceneElements.filter((el: ExcalidrawElement) => el.id === id);
|
||||
if(elements.length === 0) return;
|
||||
if(elements.length === 0) {
|
||||
const frame = getFrameBasedOnFrameNameOrId(id, sceneElements);
|
||||
if (frame) {
|
||||
elements = [frame];
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if(hasGroupref) {
|
||||
const groupElements = this.plugin.ea.getElementsInTheSameGroupWithElement(elements[0],sceneElements)
|
||||
if(groupElements.length>0) {
|
||||
@@ -1865,6 +1967,10 @@ export default class ExcalidrawView extends TextFileView {
|
||||
}
|
||||
|
||||
public async synchronizeWithData(inData: ExcalidrawData) {
|
||||
if(this.semaphores.embeddableIsEditingSelf) {
|
||||
return;
|
||||
}
|
||||
//console.log("synchronizeWithData - embeddable is not editing");
|
||||
//check if saving, wait until not
|
||||
let counter = 0;
|
||||
while(this.semaphores.saving && counter++<30) {
|
||||
@@ -3019,7 +3125,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
if (!(isWinCTRLorMacCMD(e)||isWinMETAorMacCTRL(e))) {
|
||||
return;
|
||||
}
|
||||
if (!this.plugin.settings.allowCtrlClick && !!isWinMETAorMacCTRL(e)) {
|
||||
if (!this.plugin.settings.allowCtrlClick && !isWinMETAorMacCTRL(e)) {
|
||||
return;
|
||||
}
|
||||
//added setTimeout when I changed onClick(e: MouseEvent) to onPointerDown() in 1.7.9.
|
||||
@@ -3957,6 +4063,16 @@ export default class ExcalidrawView extends TextFileView {
|
||||
return {imageEl: el, embeddedFile: imageFile};
|
||||
}
|
||||
|
||||
public async insertBackOfTheNoteCard() {
|
||||
const sections = (await this.app.metadataCache.blockCache
|
||||
.getForFile({ isCancelled: () => false },this.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));
|
||||
const selectCardDialog = new SelectCard(this.app,this,sections);
|
||||
selectCardDialog.start();
|
||||
}
|
||||
|
||||
public async convertImageElWithURLToLocalFile(data: {imageEl: ExcalidrawImageElement, embeddedFile: EmbeddedFile}) {
|
||||
const {imageEl, embeddedFile} = data;
|
||||
const imageDataURL = embeddedFile.getImage(false);
|
||||
@@ -4174,6 +4290,16 @@ export default class ExcalidrawView extends TextFileView {
|
||||
]);
|
||||
}
|
||||
|
||||
contextMenuActions.push([
|
||||
renderContextMenuAction(
|
||||
t("INSERT_CARD"),
|
||||
() => {
|
||||
this.insertBackOfTheNoteCard();
|
||||
},
|
||||
onClose
|
||||
),
|
||||
]);
|
||||
|
||||
contextMenuActions.push([
|
||||
renderContextMenuAction(
|
||||
t("UNIVERSAL_ADD_FILE"),
|
||||
@@ -4514,7 +4640,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
const delta = editingElViewY - scrollViewY;
|
||||
const isElementAboveKeyboard = height > (delta + appToolHeight*2)
|
||||
const excalidrawWrapper = this.excalidrawWrapperRef.current;
|
||||
console.log({isElementAboveKeyboard});
|
||||
//console.log({isElementAboveKeyboard});
|
||||
if(excalidrawWrapper && !isElementAboveKeyboard) {
|
||||
excalidrawWrapper.style.top = `${-(st.height - height)}px`;
|
||||
excalidrawWrapper.style.height = `${st.height}px`;
|
||||
@@ -5024,15 +5150,15 @@ export default class ExcalidrawView extends TextFileView {
|
||||
}
|
||||
}
|
||||
|
||||
public getEmbeddableLeafElementById(id: string): {leaf: WorkspaceLeaf; node?: ObsidianCanvasNode} | null {
|
||||
public getEmbeddableLeafElementById(id: string): {leaf: WorkspaceLeaf; node?: ObsidianCanvasNode; editNode?: Function} | null {
|
||||
const ref = this.embeddableLeafRefs.get(id);
|
||||
if(!ref) {
|
||||
return null;
|
||||
}
|
||||
return ref as {leaf: WorkspaceLeaf; node?: ObsidianCanvasNode};
|
||||
return ref as {leaf: WorkspaceLeaf; node?: ObsidianCanvasNode; editNode?: Function};
|
||||
}
|
||||
|
||||
getActiveEmbeddable = ():{leaf: WorkspaceLeaf; node?: ObsidianCanvasNode}|null => {
|
||||
getActiveEmbeddable = ():{leaf: WorkspaceLeaf; node?: ObsidianCanvasNode; editNode?: Function}|null => {
|
||||
if(!this.excalidrawAPI) return null;
|
||||
const api = this.excalidrawAPI as ExcalidrawImperativeAPI;
|
||||
const st = api.getAppState();
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
TFile,
|
||||
Vault,
|
||||
} from "obsidian";
|
||||
import { RERENDER_EVENT } from "./constants/constants";
|
||||
import { DEVICE, RERENDER_EVENT } from "./constants/constants";
|
||||
import { EmbeddedFilesLoader } from "./EmbeddedFileLoader";
|
||||
import { createPNG, createSVG } from "./ExcalidrawAutomate";
|
||||
import { ExportSettings } from "./ExcalidrawView";
|
||||
@@ -417,7 +417,10 @@ const createImgElement = async (
|
||||
});
|
||||
eventElement.addEventListener("pointerdown",(ev)=>{
|
||||
if(imgOrDiv?.parentElement?.hasClass("canvas-node-content")) return;
|
||||
timer = setTimeout(()=>clickEvent(ev),500);
|
||||
//@ts-ignore
|
||||
const PLUGIN = app.plugins.plugins["obsidian-excalidraw-plugin"] as ExcalidrawPlugin;
|
||||
const timeoutValue = DEVICE.isDesktop ? PLUGIN.settings.longPressDesktop : PLUGIN.settings.longPressMobile;
|
||||
timer = setTimeout(()=>clickEvent(ev),timeoutValue);
|
||||
pointerDownEvent = ev;
|
||||
});
|
||||
eventElement.addEventListener("pointerup",()=>{
|
||||
@@ -589,8 +592,18 @@ const tmpObsidianWYSIWYG = async (
|
||||
|
||||
//@ts-ignore
|
||||
const containerEl = ctx.containerEl;
|
||||
|
||||
if(!plugin.settings.renderImageInMarkdownReadingMode && containerEl.parentElement?.parentElement?.hasClass("markdown-reading-view")) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(!plugin.settings.renderImageInMarkdownToPDF && containerEl.parentElement?.hasClass("print")) {
|
||||
return;
|
||||
}
|
||||
|
||||
let internalEmbedDiv: HTMLElement = containerEl;
|
||||
while (
|
||||
!internalEmbedDiv.hasClass("print") &&
|
||||
!internalEmbedDiv.hasClass("dataview") &&
|
||||
!internalEmbedDiv.hasClass("cm-preview-code-block") &&
|
||||
!internalEmbedDiv.hasClass("cm-embed-block") &&
|
||||
@@ -610,18 +623,23 @@ const tmpObsidianWYSIWYG = async (
|
||||
return; //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/835
|
||||
}
|
||||
|
||||
const isPrinting = Boolean(internalEmbedDiv.hasClass("print"));
|
||||
|
||||
const attr: imgElementAttributes = {
|
||||
fname: ctx.sourcePath,
|
||||
fheight: getDefaultHeight(plugin),
|
||||
fwidth: getDefaultWidth(plugin),
|
||||
fheight: isPrinting ? "100%" : getDefaultHeight(plugin),
|
||||
fwidth: isPrinting ? "100%" : getDefaultWidth(plugin),
|
||||
style: ["excalidraw-svg"],
|
||||
};
|
||||
|
||||
attr.file = file;
|
||||
|
||||
const markdownEmbed = internalEmbedDiv.hasClass("markdown-embed");
|
||||
const markdownReadingView = internalEmbedDiv.hasClass("markdown-reading-view");
|
||||
const markdownReadingView = internalEmbedDiv.hasClass("markdown-reading-view") || isPrinting;
|
||||
if (!internalEmbedDiv.hasClass("internal-embed") && (markdownEmbed || markdownReadingView)) {
|
||||
if(isPrinting) {
|
||||
internalEmbedDiv = containerEl;
|
||||
}
|
||||
//We are processing the markdown preview of an actual Excalidraw file
|
||||
//the excalidraw file in markdown preview mode
|
||||
const isFrontmatterDiv = Boolean(el.querySelector(".frontmatter"));
|
||||
@@ -727,7 +745,8 @@ export const markdownPostProcessor = async (
|
||||
//transcluded text element or some other transcluded content inside the Excalidraw file
|
||||
//in reading mode these elements should be hidden
|
||||
const excalidrawFile = Boolean(ctx.frontmatter?.hasOwnProperty("excalidraw-plugin"));
|
||||
if (excalidrawFile) {
|
||||
const isPrinting = Boolean(document.body.querySelectorAll("body > .print"));
|
||||
if (excalidrawFile && !isPrinting) {
|
||||
el.style.display = "none";
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -172,6 +172,7 @@ export class ScriptEngine {
|
||||
(async()=>{
|
||||
const script = await app.vault.read(f);
|
||||
if(script) {
|
||||
//remove YAML frontmatter if present
|
||||
this.executeScript(view, script, scriptName,f);
|
||||
}
|
||||
})()
|
||||
@@ -212,6 +213,7 @@ export class ScriptEngine {
|
||||
if (!view || !script || !title) {
|
||||
return;
|
||||
}
|
||||
script = script.replace(/^---.*?---\n/gs, "");
|
||||
const ea = getEA(view);
|
||||
ea.activeScript = title;
|
||||
|
||||
|
||||
@@ -2,8 +2,20 @@ import { customAlphabet } from "nanoid";
|
||||
import { DeviceType } from "../types";
|
||||
import { ExcalidrawLib } from "../ExcalidrawLib";
|
||||
import { moment } from "obsidian";
|
||||
import ExcalidrawPlugin from "src/main";
|
||||
//This is only for backward compatibility because an early version of obsidian included an encoding to avoid fantom links from littering Obsidian graph view
|
||||
declare const PLUGIN_VERSION:string;
|
||||
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];
|
||||
|
||||
export const ERROR_IFRAME_CONVERSION_CANCELED = "iframe conversion canceled";
|
||||
|
||||
@@ -152,6 +164,7 @@ export const FRONTMATTER_KEYS:{[key:string]: {name: string, type: string, depric
|
||||
"export-svgpadding": {name: "excalidraw-export-svgpadding", type: "number", depricated: true},
|
||||
"export-padding": {name: "excalidraw-export-padding", type: "number"},
|
||||
"export-pngscale": {name: "excalidraw-export-pngscale", type: "number"},
|
||||
"export-embed-scene": {name: "excalidraw-export-embed-scene", type: "checkbox"},
|
||||
"link-prefix": {name: "excalidraw-link-prefix", type: "text"},
|
||||
"url-prefix": {name: "excalidraw-url-prefix", type: "text"},
|
||||
"link-brackets": {name: "excalidraw-link-brackets", type: "checkbox"},
|
||||
@@ -162,8 +175,9 @@ export const FRONTMATTER_KEYS:{[key:string]: {name: string, type: string, depric
|
||||
"font-color": {name: "excalidraw-font-color", type: "text"},
|
||||
"border-color": {name: "excalidraw-border-color", type: "text"},
|
||||
"md-css": {name: "excalidraw-css", type: "text"},
|
||||
"autoexport": {name: "excalidraw-autoexport", type: "checkbox"},
|
||||
"iframe-theme": {name: "excalidraw-iframe-theme", type: "text"},
|
||||
"autoexport": {name: "excalidraw-autoexport", type: "text"},
|
||||
"iframe-theme": {name: "excalidraw-iframe-theme", type: "text"},
|
||||
"open-as-markdown": {name: "excalidraw-open-md", type: "checkbox"},
|
||||
};
|
||||
|
||||
export const EMBEDDABLE_THEME_FRONTMATTER_VALUES = ["light", "dark", "auto", "dafault"];
|
||||
|
||||
@@ -89,7 +89,7 @@ function RenderObsidianView(
|
||||
const react = view.plugin.getPackage(view.ownerWindow).react;
|
||||
|
||||
//@ts-ignore
|
||||
const leafRef = react.useRef<{leaf: WorkspaceLeaf; node?: ObsidianCanvasNode} | null>(null);
|
||||
const leafRef = react.useRef<{leaf: WorkspaceLeaf; node?: ObsidianCanvasNode, editNode?: Function} | null>(null);
|
||||
const isEditingRef = react.useRef(false);
|
||||
const isActiveRef = react.useRef(false);
|
||||
const themeRef = react.useRef(theme);
|
||||
@@ -154,7 +154,7 @@ function RenderObsidianView(
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
//mount the workspace leaf or the canvas node depending on subpath
|
||||
//Mount the workspace leaf or the canvas node depending on subpath
|
||||
//--------------------------------------------------------------------------------
|
||||
react.useEffect(() => {
|
||||
if(!containerRef?.current) {
|
||||
@@ -176,7 +176,8 @@ function RenderObsidianView(
|
||||
rootSplit.containerEl.style.borderRadius = "var(--embeddable-radius)";
|
||||
leafRef.current = {
|
||||
leaf: app.workspace.createLeafInParent(rootSplit, 0),
|
||||
node: null
|
||||
node: null,
|
||||
editNode: null,
|
||||
};
|
||||
|
||||
const setKeepOnTop = () => {
|
||||
@@ -230,6 +231,9 @@ function RenderObsidianView(
|
||||
return () => {}; //cleanup on unmount
|
||||
}, [linkText, subpath, containerRef]);
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
//Set colors of the canvas node
|
||||
//--------------------------------------------------------------------------------
|
||||
const setColors = (canvasNode: HTMLDivElement, element: NonDeletedExcalidrawElement, mdProps: EmbeddableMDCustomProps, canvasColor: string) => {
|
||||
if(!mdProps) return;
|
||||
if (!leafRef.current?.hasOwnProperty("node")) return;
|
||||
@@ -289,6 +293,9 @@ function RenderObsidianView(
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
//Set colors of the canvas node
|
||||
//--------------------------------------------------------------------------------
|
||||
react.useEffect(() => {
|
||||
if(!containerRef.current) {
|
||||
return;
|
||||
@@ -304,6 +311,9 @@ function RenderObsidianView(
|
||||
canvasColor,
|
||||
])
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
//Switch to preview mode when the iframe is not active
|
||||
//--------------------------------------------------------------------------------
|
||||
react.useEffect(() => {
|
||||
if(isEditingRef.current) {
|
||||
if(leafRef.current?.node) {
|
||||
@@ -318,9 +328,9 @@ function RenderObsidianView(
|
||||
//--------------------------------------------------------------------------------
|
||||
//Switch to edit mode when markdown view is clicked
|
||||
//--------------------------------------------------------------------------------
|
||||
const handleClick = react.useCallback((event: React.PointerEvent<HTMLElement>) => {
|
||||
const handleClick = react.useCallback((event?: React.PointerEvent<HTMLElement>) => {
|
||||
if(isActiveRef.current) {
|
||||
event.stopPropagation();
|
||||
event?.stopPropagation();
|
||||
}
|
||||
|
||||
if (isActiveRef.current && !isEditingRef.current && leafRef.current?.leaf) {
|
||||
@@ -349,6 +359,22 @@ function RenderObsidianView(
|
||||
}
|
||||
}, [leafRef.current?.leaf, element.id, view, themeRef.current]);
|
||||
|
||||
if(leafRef.current) leafRef.current.editNode = handleClick;
|
||||
// Event listener for key press
|
||||
react.useEffect(() => {
|
||||
const handleKeyPress = (event: KeyboardEvent) => {
|
||||
if (event.key === "Enter") {
|
||||
handleClick(event); // Call handleClick function when Enter key is pressed
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener("keydown", handleKeyPress); // Add event listener for key press
|
||||
|
||||
return () => {
|
||||
document.removeEventListener("keydown", handleKeyPress); // Remove event listener when component unmounts
|
||||
};
|
||||
}, [handleClick]);
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
// Set isActiveRef and switch to preview mode when the iframe is not active
|
||||
//--------------------------------------------------------------------------------
|
||||
@@ -404,7 +430,6 @@ export const CustomEmbeddable: React.FC<{element: NonDeletedExcalidrawElement; v
|
||||
const containerRef: React.RefObject<HTMLDivElement> = react.useRef(null);
|
||||
const theme = getTheme(view, appState.theme);
|
||||
const mdProps: EmbeddableMDCustomProps = element.customData?.mdProps || null;
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
|
||||
@@ -49,7 +49,7 @@ export class EmbeddableSettings extends Modal {
|
||||
this.zoomValue = element.scale[0];
|
||||
this.isYouTube = isYouTube(this.element.link);
|
||||
this.notExcalidrawIsInternal = this.file && !this.view.plugin.isExcalidrawFile(this.file)
|
||||
this.isMDFile = this.file && this.file.extension === "md" && !this.view.plugin.isExcalidrawFile(this.file);
|
||||
this.isMDFile = this.file && this.file.extension === "md"; // && !this.view.plugin.isExcalidrawFile(this.file);
|
||||
this.isLocalURI = this.element.link.startsWith("file://");
|
||||
if(isYouTube) this.youtubeStart = getYouTubeStartAt(this.element.link);
|
||||
|
||||
@@ -212,7 +212,12 @@ export class EmbeddableSettings extends Modal {
|
||||
el.scale = [this.zoomValue,this.zoomValue];
|
||||
}
|
||||
if(dirty) {
|
||||
this.ea.addElementsToView();
|
||||
(async() => {
|
||||
await this.ea.addElementsToView();
|
||||
//@ts-ignore
|
||||
this.ea.viewUpdateScene({appState: {}});
|
||||
})();
|
||||
|
||||
}
|
||||
this.close();
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@ import { DEVICE } from "src/constants/constants";
|
||||
import { ExcalidrawAutomate } from "src/ExcalidrawAutomate";
|
||||
import ExcalidrawView from "src/ExcalidrawView";
|
||||
import ExcalidrawPlugin from "src/main";
|
||||
import { fragWithHTML, getExportPadding, getExportTheme, getPNGScale, getWithBackground } from "src/utils/Utils";
|
||||
import { fragWithHTML, getExportPadding, getExportTheme, getPNGScale, getWithBackground, shouldEmbedScene } from "src/utils/Utils";
|
||||
|
||||
export class ExportDialog extends Modal {
|
||||
private ea: ExcalidrawAutomate;
|
||||
@@ -40,7 +40,7 @@ export class ExportDialog extends Modal {
|
||||
this.scale = getPNGScale(this.plugin,this.file)
|
||||
this.theme = getExportTheme(this.plugin, this.file, (this.api).getAppState().theme)
|
||||
this.boundingBox = this.ea.getBoundingBox(this.ea.getViewElements());
|
||||
this.embedScene = false;
|
||||
this.embedScene = shouldEmbedScene(this.plugin, this.file);
|
||||
this.exportSelectedOnly = false;
|
||||
this.saveToVault = true;
|
||||
this.transparent = !getWithBackground(this.plugin, this.file);
|
||||
|
||||
@@ -17,6 +17,99 @@ 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.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)
|
||||
- Some plugins automatically add document properties to all files in the Vault. Users with this configuration were **unable to run Excalidraw scripts**. Excalidraw now removes document properties from the script before execution.
|
||||
- The very last markdown edit sometimes **wasn't saved when immediately switching from Markdown to Excalidraw View**. I now force a save before switching views.
|
||||
- The setting to disable/enable ${String.fromCharCode(96)}CTRL/CMD + CLICK on text with [[links]] or [](links) to open them${String.fromCharCode(96)} works again. [#1704](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1704)
|
||||
- **Annotation and cropping** of images in Markdown notes now also work **with Markdown links that have encoded characters** e.g.: ${String.fromCharCode(96)}${String.fromCharCode(96)}.
|
||||
- Solved compatibility issue of **Taskbone OCR on Android**.
|
||||
|
||||
## New
|
||||
- New settings:
|
||||
- Under "Appearance and Behavior": Option to **render Excalidraw file as an image in Markdown reading mode**. This setting is disabled by default. [#1706](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1706), [#1705](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1705)
|
||||
- Under "Embedding Excalidraw ... and Exporting"/"Export Settings": Option to **render Excalidraw file as an image when exporting to PDF** in Markdown mode. This option is disabled by default. When enabled, exporting an Excalidraw drawing in markdown view mode to PDF will render the image on the page.
|
||||
- **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)}.
|
||||
`,
|
||||
"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.
|
||||
`,
|
||||
"2.1.2":`
|
||||
## Quality of Life Improvements
|
||||
- The "Insert Any File" option that disappeared from the Command Palette is now restored. [#1690](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1690)
|
||||
- Improved two-finger pan speed.
|
||||
- Fixed text wrapping issue that caused text to jump around when editing text in a sticky note when the Obsidian zoom level was not set to 100%.
|
||||
- Mask Generation in [ExcaliAI](https://youtu.be/3G8hsV-V-gQ) Edit Image now works properly again. [#1684](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1684)
|
||||
- Fixed aspect ratio change for .jpg, .png, .bmp, .webp, .SVG (non-Excalidraw) images. Previously, if the image was distorted (i.e. you held SHIFT while resizing it), it would revert to the original aspect ratio upon saving the drawing. Resetting the aspect ratio is the desired behavior for nested Excalidraw drawings since you might have changed the source image and want it to still display with the correct aspect ratio, however for other image files, the behavior is not desired. [#1698](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1698)
|
||||
- The command palette action "Set selected image element size to 100% of original" now works even on freshly pasted images, not just after saving the drawing. ([#1695](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1698))
|
||||
- If a text element has an element link (CTRL/CMD+K), but the link was not reflected in the Element Text, then CTRL/CMD+clicking the text element did not navigate to the link, only clicking the link indicator did. Now you can also CTRL/CMD click anywhere on the text element and it will navigate. Note, however, that links in the text element text take precedence over element links.
|
||||
`,
|
||||
"2.1.1":`
|
||||
## Fixed
|
||||
- Printing a markdown page that has an Excalidraw drawing on the back side, resulted in an empty PDF. This is now resolved.
|
||||
|
||||
## New
|
||||
- Reduce the visual clutter by fading out the Excalidraw markup in markdown view mode. This feature needs to be enabled in plugin settings. You'll find the setting under ${String.fromCharCode(96)}Miscellaneous features${String.fromCharCode(96)}. Look for ${String.fromCharCode(96)}Fade out Excalidraw markup${String.fromCharCode(96)}. Depending on the location of the markdown comment ${String.fromCharCode(96)}%%${String.fromCharCode(96)}, if the comment starts before ${String.fromCharCode(96)}# Text Elements${String.fromCharCode(96)} then the fading will start from ${String.fromCharCode(96)}# Text Elements${String.fromCharCode(96)}, if the comment is before ${String.fromCharCode(96)}# Drawing${String.fromCharCode(96)} then the fading will only start with "drawing". If you delete the opening ${String.fromCharCode(96)}%%${String.fromCharCode(96)} the markup will be visible. Note, that if you place the comment before ${String.fromCharCode(96)}#Text Elements${String.fromCharCode(96)}, you will not be able to reference blocks in the ${String.fromCharCode(96)}# Text Elements${String.fromCharCode(96)} section, because Obsidian does not index blocks within comment blocks. Image references are not effective, they will work.
|
||||
|
||||
<img src="https://github.com/zsviczian/obsidian-excalidraw-plugin/assets/14358394/bb96cdb4-8c5f-4dc5-ad39-7fccee6d5cac" referrerpolicy="no-referrer" style="width: 150px; margin: 0 auto;">
|
||||
<img src="https://github.com/zsviczian/obsidian-excalidraw-plugin/assets/14358394/e627fdb7-6820-4d7d-97f9-a030016be9aa" referrerpolicy="no-referrer" style="width: 100%; margin: 0 auto;">
|
||||
`,
|
||||
"2.1.0":`
|
||||
Bumping the version to 2.1.0 due to minor file format changes that aren't backward compatible. Essentially, 2.0.26 is already not backward compatible, but I forgot to update the version number.
|
||||
|
||||
If you haven't watched the [walkthrough video](https://youtu.be/tHUcD4rWIuY) for 2.0.26, I recommend you do so.
|
||||
|
||||
## New
|
||||
- Settings under ${String.fromCharCode(96)}Excalidraw Appearance and Behavior${String.fromCharCode(96)}
|
||||
- Configure visibility of the crosshair cursor when using the pen tool. [#1673](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1673)
|
||||
- Set the time delay for long press to open drawings from markdown under "Link Click and Modifier Keys".
|
||||
|
||||
##
|
||||
`,
|
||||
"2.0.26":`
|
||||
<div class="excalidraw-videoWrapper"><div>
|
||||
<iframe src="https://www.youtube.com/embed/tHUcD4rWIuY" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
</div></div>
|
||||
|
||||
## New
|
||||
- Minor updates from [Excalidraw.com](https://excalidraw.com). The key change is text measurements that should result in consistent text sizing between desktop and mobile devices.
|
||||
- Now you can embed the markdown section of an Excalidraw note to your drawing. Simply select ${String.fromCharCode(96)}Insert ANY file${String.fromCharCode(96)}, choose the drawing, and select the relevant heading section to embed.
|
||||
- This also works with "back-of-the-drawing" markdown sections. Use the context menu ${String.fromCharCode(96)}Add back-of-note Card${String.fromCharCode(96)}. The action is also available on the Command Palette and in the Excalidraw-Obsidian Tools Panel.
|
||||
- Editing an embedded markdown note is now easier. Just press Enter when the element is selected. [#1650](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1650)
|
||||
- The crosshair cursor is now hidden when the freedraw tool is active and using a pen. [#1659](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1659)
|
||||
- ${String.fromCharCode(96)}Convert markdown note to Excalidraw Drawing${String.fromCharCode(96)} now converts an existing markdown note (not just empty notes) into a drawing. The original markdown note will be on the "back side of the drawing".
|
||||
- Introducing ${String.fromCharCode(96)}Annotate image in Excalidraw${String.fromCharCode(96)}, which works very similarly to ${String.fromCharCode(96)}Crop and mask image${String.fromCharCode(96)}. You can replace an image in a markdown note or on the Obsidian Canvas with an Excalidraw drawing containing that image. You will be able to annotate the image in Excalidraw.
|
||||
- Now you can reference frames in images embedded in markdown and canvas with frame names e.g. ${String.fromCharCode(96)}![[drawing#^frame=Frame 01]]${String.fromCharCode(96)}
|
||||
- Excalidraw file format change:
|
||||
- New frontmatter switch ${String.fromCharCode(96)}excalidraw-open-md${String.fromCharCode(96)}: If set to true, the file by default will open as a markdown file. You can switch to Excalidraw View Mode via the command palette action or by right-clicking the tab.
|
||||
- Easter Egg: If you add a comment in front of ${String.fromCharCode(96)}# Text Elements${String.fromCharCode(96)}, then the entire Excalidraw data: markdown and JSON will be commented out, thus invisible when exporting to the web. If you remove the comment from before ${String.fromCharCode(96)}# Text Elements${String.fromCharCode(96)}, then only the JSON will be commented out.
|
||||
|
||||
Before:
|
||||
${String.fromCharCode(96,96,96)}markdown
|
||||
#1657
|
||||
%%
|
||||
# Text Elements
|
||||
...
|
||||
# Drawing
|
||||
${String.fromCharCode(96,96,96)}
|
||||
|
||||
After:
|
||||
${String.fromCharCode(96,96,96)}markdown
|
||||
# Text Elements
|
||||
....
|
||||
%%
|
||||
# Drawing
|
||||
${String.fromCharCode(96,96,96)}
|
||||
`,
|
||||
"2.0.25":`
|
||||
# New - a small change that opens big opportunities
|
||||
- You can now set a folder as the Excalidraw Template in settings (See under Basic). If a folder is provided, Excalidraw will treat drawings in that folder as templates and will prompt you to select the template to use for new drawings.
|
||||
- I updated the <a href="https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/Deconstruct%20selected%20elements%20into%20new%20drawing.md">Deconstruct Selected Elements into new Drawing</a> script to accommodate the new template setting.
|
||||
`,
|
||||
"2.0.24":`
|
||||
Quality of Life Fixes!
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
TFile,
|
||||
Notice,
|
||||
TextAreaComponent,
|
||||
TFolder,
|
||||
} from "obsidian";
|
||||
import ExcalidrawView from "../ExcalidrawView";
|
||||
import ExcalidrawPlugin from "../main";
|
||||
@@ -701,24 +702,27 @@ export class ConfirmationPrompt extends Modal {
|
||||
}
|
||||
}
|
||||
|
||||
export const linkPrompt = async (linkText:string, app: App, view?: ExcalidrawView):Promise<[file:TFile, linkText:string, subpath: string]> => {
|
||||
export const linkPrompt = async (linkText:string, app: App, view?: ExcalidrawView, message: string = "Select link to open"):Promise<[file:TFile, linkText:string, subpath: string]> => {
|
||||
const partsArray = REGEX_LINK.getResList(linkText);
|
||||
let subpath: string = null;
|
||||
let file: TFile = null;
|
||||
let parts = partsArray[0];
|
||||
if (partsArray.length > 1) {
|
||||
parts = await ScriptEngine.suggester(
|
||||
app,
|
||||
partsArray.filter(p=>Boolean(p.value)).map(p => {
|
||||
const alias = REGEX_LINK.getAliasOrLink(p);
|
||||
return alias === "100%" ? REGEX_LINK.getLink(p) : alias;
|
||||
}),
|
||||
partsArray.filter(p=>Boolean(p.value)),
|
||||
"Select link to open"
|
||||
);
|
||||
if(!parts) return;
|
||||
}
|
||||
if(!parts) return;
|
||||
if (partsArray.length > 1) {
|
||||
parts = await ScriptEngine.suggester(
|
||||
app,
|
||||
partsArray.filter(p=>Boolean(p.value)).map(p => {
|
||||
const alias = REGEX_LINK.getAliasOrLink(p);
|
||||
return alias === "100%" ? REGEX_LINK.getLink(p) : alias;
|
||||
}),
|
||||
partsArray.filter(p=>Boolean(p.value)),
|
||||
message,
|
||||
);
|
||||
if(!parts) return;
|
||||
}
|
||||
|
||||
if(!parts) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!parts.value) {
|
||||
openTagSearch(linkText, app);
|
||||
@@ -742,4 +746,16 @@ export const linkPrompt = async (linkText:string, app: App, view?: ExcalidrawVie
|
||||
view ? view.file.path : "",
|
||||
);
|
||||
return [file, linkText, subpath];
|
||||
}
|
||||
|
||||
|
||||
|
||||
export const templatePromt = async (files: TFile[], app: App): Promise<TFile> => {
|
||||
if(files.length === 1) return files[0];
|
||||
return ((await linkPrompt(
|
||||
files.map(f=>`[[${f.path}|${f.name}]]`).join(" "),
|
||||
app,
|
||||
undefined,
|
||||
t("PROMPT_SELECT_TEMPLATE")
|
||||
))??[null, null, null])[0];
|
||||
}
|
||||
103
src/dialogs/SelectCard.ts
Normal file
103
src/dialogs/SelectCard.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import { App, FuzzySuggestModal, Notice, TFile } from "obsidian";
|
||||
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";
|
||||
|
||||
export class SelectCard extends FuzzySuggestModal<string> {
|
||||
|
||||
constructor(
|
||||
public app: App,
|
||||
private view: ExcalidrawView,
|
||||
private sections: string[]
|
||||
) {
|
||||
super(app);
|
||||
this.limit = 20;
|
||||
this.setInstructions([
|
||||
{
|
||||
command: t("TYPE_SECTION"),
|
||||
purpose: "",
|
||||
},
|
||||
]);
|
||||
|
||||
this.inputEl.onkeyup = (e) => {
|
||||
if (e.key == "Enter") {
|
||||
if (this.containerEl.innerText.includes(t("EMPTY_SECTION_MESSAGE"))) {
|
||||
const item = this.inputEl.value;
|
||||
if(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
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
getItems(): string[] {
|
||||
return this.sections;
|
||||
}
|
||||
|
||||
getItemText(item: string): string {
|
||||
return item;
|
||||
}
|
||||
|
||||
onChooseItem(item: string): void {
|
||||
const ea = getEA(this.view) as ExcalidrawAutomate;
|
||||
const id = ea.addEmbeddable(
|
||||
0,0,400,500,
|
||||
`[[${this.view.file.path}#${item}]]`
|
||||
);
|
||||
(async () => {
|
||||
await ea.addElementsToView(true, false, true);
|
||||
ea.selectElementsInView([id]);
|
||||
})();
|
||||
}
|
||||
|
||||
public start(): void {
|
||||
this.emptyStateText = t("EMPTY_SECTION_MESSAGE");
|
||||
this.setPlaceholder(t("SELECT_SECTION_OR_TYPE_NEW"));
|
||||
this.open();
|
||||
}
|
||||
}
|
||||
@@ -562,6 +562,14 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
|
||||
desc: `Access functions and objects available on the ${hyperlink("https://github.com/obsidianmd/obsidian-api/blob/master/obsidian.d.ts","Obsidian Module")}`,
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
field: "getListOfTemplateFiles",
|
||||
code: "getListOfTemplateFiles(): TFile[] | null",
|
||||
desc: "Returns a list of files in the template folder. " +
|
||||
"If the Excalidraw Template is set as a single file, it returns a single element in the list. " +
|
||||
"If no template is set, it returns null.",
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
field: "getAttachmentFilepath",
|
||||
code: "async getAttachmentFilepath(filename: string): Promise<string>",
|
||||
@@ -813,6 +821,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: "open-md",
|
||||
code: null,
|
||||
desc: "If this key is present the file will be opened as a markdown file in the editor",
|
||||
after: ": true",
|
||||
},
|
||||
{
|
||||
field: "autoexport",
|
||||
code: null,
|
||||
|
||||
@@ -3,7 +3,7 @@ import ExcalidrawView from "../ExcalidrawView";
|
||||
import ExcalidrawPlugin from "../main";
|
||||
import { Modal, Setting, TextComponent } from "obsidian";
|
||||
import { FileSuggestionModal } from "./FolderSuggester";
|
||||
import { IMAGE_TYPES, sceneCoordsToViewportCoords, viewportCoordsToSceneCoords, MAX_IMAGE_SIZE, ANIMATED_IMAGE_TYPES } from "src/constants/constants";
|
||||
import { IMAGE_TYPES, sceneCoordsToViewportCoords, viewportCoordsToSceneCoords, MAX_IMAGE_SIZE, ANIMATED_IMAGE_TYPES, MD_EX_SECTIONS } from "src/constants/constants";
|
||||
import { insertEmbeddableToView, insertImageToView } from "src/utils/ExcalidrawViewUtils";
|
||||
import { getEA } from "src";
|
||||
import { InsertPDFModal } from "./InsertPDFModal";
|
||||
@@ -78,6 +78,7 @@ export class UniversalInsertFileModal extends Modal {
|
||||
|
||||
const updateForm = async () => {
|
||||
const ea = this.plugin.ea;
|
||||
const isSelf = file === this.view.file;
|
||||
const isMarkdown = file && file.extension === "md" && !ea.isExcalidrawFile(file);
|
||||
const isImage = file && (IMAGE_TYPES.contains(file.extension) || ea.isExcalidrawFile(file));
|
||||
const isAnimatedImage = file && ANIMATED_IMAGE_TYPES.contains(file.extension);
|
||||
@@ -85,39 +86,43 @@ export class UniversalInsertFileModal extends Modal {
|
||||
const isPDF = file && file.extension === "pdf";
|
||||
const isExcalidraw = file && ea.isExcalidrawFile(file);
|
||||
|
||||
if (isMarkdown) {
|
||||
const sections = (file && file.extension === "md")
|
||||
? (await this.plugin.app.metadataCache.blockCache
|
||||
.getForFile({ isCancelled: () => false },file))
|
||||
.blocks.filter((b: any) => b.display && b.node?.type === "heading")
|
||||
.filter((b: any) => !isExcalidraw || !MD_EX_SECTIONS.includes(b.display))
|
||||
: null;
|
||||
|
||||
if (isMarkdown || (isExcalidraw && sections?.length > 0)) {
|
||||
sectionPickerSetting.settingEl.style.display = "";
|
||||
sectionPicker.selectEl.style.display = "block";
|
||||
while(sectionPicker.selectEl.options.length > 0) {
|
||||
sectionPicker.selectEl.remove(0);
|
||||
}
|
||||
sectionPicker.addOption("","");
|
||||
(await app.metadataCache.blockCache
|
||||
.getForFile({ isCancelled: () => false },file))
|
||||
.blocks.filter((b: any) => b.display && b.node?.type === "heading")
|
||||
.forEach((b: any) => {
|
||||
sectionPicker.addOption(
|
||||
`#${cleanSectionHeading(b.display)}`,
|
||||
b.display)
|
||||
});
|
||||
if(!isExcalidraw) sectionPicker.addOption("","");
|
||||
sections.forEach((b: any) => {
|
||||
sectionPicker.addOption(
|
||||
`#${cleanSectionHeading(b.display)}`,
|
||||
b.display)
|
||||
});
|
||||
} else {
|
||||
sectionPickerSetting.settingEl.style.display = "none";
|
||||
sectionPicker.selectEl.style.display = "none";
|
||||
}
|
||||
|
||||
if (isExcalidraw) {
|
||||
if (isExcalidraw && !isSelf) {
|
||||
sizeToggleSetting.settingEl.style.display = "";
|
||||
} else {
|
||||
sizeToggleSetting.settingEl.style.display = "none";
|
||||
}
|
||||
|
||||
if (isImage || (file?.extension === "md")) {
|
||||
if (!isSelf && (isImage || (file?.extension === "md"))) {
|
||||
actionImage.buttonEl.style.display = "block";
|
||||
} else {
|
||||
actionImage.buttonEl.style.display = "none";
|
||||
}
|
||||
|
||||
if (isIFrame || isAnimatedImage) {
|
||||
if (isIFrame || isAnimatedImage || (isExcalidraw && sections?.length > 0)) {
|
||||
actionIFrame.buttonEl.style.display = "block";
|
||||
} else {
|
||||
actionIFrame.buttonEl.style.display = "none";
|
||||
@@ -131,9 +136,17 @@ export class UniversalInsertFileModal extends Modal {
|
||||
|
||||
}
|
||||
|
||||
const sections = (await this.plugin.app.metadataCache.blockCache
|
||||
.getForFile({ isCancelled: () => false },this.view.file))
|
||||
.blocks.filter((b: any) => b.display && b.node?.type === "heading")
|
||||
.filter((b: any) => !MD_EX_SECTIONS.includes(b.display));
|
||||
|
||||
const search = new TextComponent(ce);
|
||||
search.inputEl.style.width = "100%";
|
||||
const suggester = new FileSuggestionModal(this.app, search,app.vault.getFiles().filter((f: TFile) => f!==this.view.file));
|
||||
const suggester = new FileSuggestionModal(
|
||||
this.app,
|
||||
search,
|
||||
this.app.vault.getFiles().filter((f: TFile) => sections?.length > 0 || f!==this.view.file));
|
||||
search.onChange(() => {
|
||||
file = suggester.getSelectedItem();
|
||||
updateForm();
|
||||
|
||||
@@ -23,7 +23,7 @@ export default {
|
||||
"Script is up to date - Click to reinstall",
|
||||
OPEN_AS_EXCALIDRAW: "Open as Excalidraw Drawing",
|
||||
TOGGLE_MODE: "Toggle between Excalidraw and Markdown mode",
|
||||
CONVERT_NOTE_TO_EXCALIDRAW: "Convert empty note to Excalidraw Drawing",
|
||||
CONVERT_NOTE_TO_EXCALIDRAW: "Convert markdown note to Excalidraw Drawing",
|
||||
CONVERT_EXCALIDRAW: "Convert *.excalidraw to *.md files",
|
||||
CREATE_NEW: "Create new drawing",
|
||||
CONVERT_FILE_KEEP_EXT: "*.excalidraw => *.excalidraw.md",
|
||||
@@ -68,6 +68,7 @@ export default {
|
||||
INSERT_MD: "Insert markdown file from vault",
|
||||
INSERT_PDF: "Insert PDF file from vault",
|
||||
UNIVERSAL_ADD_FILE: "Insert ANY file",
|
||||
INSERT_CARD: "Add back-of-note card",
|
||||
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",
|
||||
@@ -76,6 +77,7 @@ export default {
|
||||
TRAY_MODE: "Toggle property-panel tray-mode",
|
||||
SEARCH: "Search for text in drawing",
|
||||
CROP_IMAGE: "Crop and mask image",
|
||||
ANNOTATE_IMAGE : "Annotate image in Excalidraw",
|
||||
INSERT_ACTIVE_PDF_PAGE_AS_IMAGE: "Insert active PDF page as image",
|
||||
RESET_IMG_TO_100: "Set selected image element size to 100% of original",
|
||||
TEMPORARY_DISABLE_AUTOSAVE: "Disable autosave until next time Obsidian starts (only set this if you know what you are doing)",
|
||||
@@ -133,22 +135,35 @@ export default {
|
||||
CROP_PREFIX_DESC:
|
||||
"The first part of the filename for new drawings created when cropping an image. " +
|
||||
"If empty the default 'cropped_' will be used.",
|
||||
ANNOTATE_PREFIX_NAME: "Annotation file prefix",
|
||||
ANNOTATE_PREFIX_DESC:
|
||||
"The first part of the filename for new drawings created when annotating an image. " +
|
||||
"If empty the default 'annotated_' will be used.",
|
||||
ANNOTATE_PRESERVE_SIZE_NAME: "Preserve image size when annotating",
|
||||
ANNOTATE_PRESERVE_SIZE_DESC:
|
||||
"When annotating an image in markdown the replacment image link will include the width of the original image.",
|
||||
CROP_FOLDER_NAME: "Crop file folder",
|
||||
CROP_FOLDER_DESC:
|
||||
"Default location for new drawings created when cropping an image. If empty, drawings will be created following the Vault attachments settings.",
|
||||
ANNOTATE_FOLDER_NAME: "Image annotation file folder",
|
||||
ANNOTATE_FOLDER_DESC:
|
||||
"Default location for new drawings created when annotating an image. If empty, drawings will be created following the Vault attachments settings.",
|
||||
FOLDER_EMBED_NAME:
|
||||
"Use Excalidraw folder when embedding a drawing into the active document",
|
||||
FOLDER_EMBED_DESC:
|
||||
"Define which folder to place the newly inserted drawing into " +
|
||||
"when using the command palette action: 'Create a new drawing and embed into active document'.<br>" +
|
||||
"<b><u>Toggle ON:</u></b> Use Excalidraw folder<br><b><u>Toggle OFF:</u></b> Use the attachments folder defined in Obsidian settings.",
|
||||
TEMPLATE_NAME: "Excalidraw template file",
|
||||
TEMPLATE_NAME: "Excalidraw template file or folder",
|
||||
TEMPLATE_DESC:
|
||||
"Full filepath to the Excalidraw template. " +
|
||||
"E.g.: If your template is in the default Excalidraw folder and its name is " +
|
||||
"Full filepath or folderpath to the Excalidraw template.<br>" +
|
||||
"<b>Template File:</b>E.g.: If your template is in the default Excalidraw folder and its name is " +
|
||||
"Template.md, the setting would be: Excalidraw/Template.md (or just Excalidraw/Template - you may omit the .md file extension). " +
|
||||
"If you are using Excalidraw in compatibility mode, then your template must be a legacy Excalidraw file as well " +
|
||||
"such as Excalidraw/Template.excalidraw.",
|
||||
"such as Excalidraw/Template.excalidraw. <br><b>Template Folder:</b> You can also set a folder as your template. " +
|
||||
"In this case you will be prompted which tempalte to use when creating a new drawing.<br>" +
|
||||
"<b>Pro Tip:</b> If you are using the Obsidian Templater plugin, you can add Templater code to your different Excalidraw " +
|
||||
"templates to automate configuration of your drawings.",
|
||||
SCRIPT_FOLDER_NAME: "Excalidraw Automate script folder (CASE SeNSitiVE!)",
|
||||
SCRIPT_FOLDER_DESC:
|
||||
"The files you place in this folder will be treated as Excalidraw Automate scripts. " +
|
||||
@@ -269,6 +284,21 @@ FILENAME_HEAD: "Filename",
|
||||
DEFAULT_PEN_MODE_NAME: "Pen mode",
|
||||
DEFAULT_PEN_MODE_DESC:
|
||||
"Should pen mode be automatically enabled when opening Excalidraw?",
|
||||
SHOW_PEN_MODE_FREEDRAW_CROSSHAIR_NAME: "Show (+) crosshair in pen mode",
|
||||
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_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? " +
|
||||
"This setting will not affect the display of the drawing when you are in Excalidraw mode, when you embed the drawing into a markdown document or when rendering hover preview.<br><ul>" +
|
||||
"<li>See other related setting for <b>PDF Export</b> under 'Embedding and Exporting' further below.</li>" +
|
||||
"<li>Be sure to check out the <b>Fade Out setting</b> in the 'Miscellaneous fetures' section.</li></ul>",
|
||||
SHOW_DRAWING_OR_MD_IN_EXPORTPDF_NAME: "Render image when EXPORT TO PDF in markdown mode",
|
||||
SHOW_DRAWING_OR_MD_IN_EXPORTPDF_DESC:
|
||||
"Must close the active excalidraw/markdown file and reopen for this change to take effect.<br>When you are printing the markdown side of the note to PDF (aka. the back side of the drawing), should the Excalidraw drawing be rendered as an image?<br><ul>" +
|
||||
"<li>See other related setting for <b>Markdown Reading Mode</b> under 'Appearnace and Behavior' further above.</li>" +
|
||||
"<li>Be sure to check out the <b>Fade Out setting</b> in the 'Miscellaneous fetures' section.</li></ul>",
|
||||
THEME_HEAD: "Theme and styling",
|
||||
ZOOM_HEAD: "Zoom",
|
||||
DEFAULT_PINCHZOOM_NAME: "Allow pinch zoom in pen mode",
|
||||
@@ -310,6 +340,11 @@ FILENAME_HEAD: "Filename",
|
||||
"These settings are different for Apple and non-Apple. If you use Obsidian on multiple platforms, you'll need to make the settings separately. "+
|
||||
"The toggles follow the order of " +
|
||||
(DEVICE.isIOS || DEVICE.isMacOS ? "SHIFT, CMD, OPT, CONTROL." : "SHIFT, CTRL, ALT, META (Windows key)."),
|
||||
LONG_PRESS_DESKTOP_NAME: "Long press to open desktop",
|
||||
LONG_PRESS_DESKTOP_DESC: "Long press delay in milliseconds to open an Excalidraw Drawing embedded in a Markdown file. ",
|
||||
LONG_PRESS_MOBILE_NAME: "Long press to open mobile",
|
||||
LONG_PRESS_MOBILE_DESC: "Long press delay in milliseconds to open an Excalidraw Drawing embedded in a Markdown file. ",
|
||||
|
||||
FOCUS_ON_EXISTING_TAB_NAME: "Focus on Existing Tab",
|
||||
FOCUS_ON_EXISTING_TAB_DESC: "When opening a link, Excalidraw will focus on the existing tab if the file is already open. " +
|
||||
"Enabling this setting overrides 'Reuse Adjacent Pane' when the file is already open.",
|
||||
@@ -360,7 +395,7 @@ FILENAME_HEAD: "Filename",
|
||||
`${labelCTRL()}+CLICK on text with [[links]] or [](links) to open them`,
|
||||
LINK_CTRL_CLICK_DESC:
|
||||
"You can turn this feature off if it interferes with default Excalidraw features you want to use. If " +
|
||||
"this is turned off, only the link button in the title bar of the drawing pane will open links.",
|
||||
`this is turned off, you can either use ${labelCTRL()} + ${labelMETA()} or the link indicator in the top right of the element to open links.`,
|
||||
TRANSCLUSION_WRAP_NAME: "Overflow wrap behavior of transcluded text",
|
||||
TRANSCLUSION_WRAP_DESC:
|
||||
"Number specifies the character count where the text should be wrapped. " +
|
||||
@@ -502,6 +537,10 @@ FILENAME_HEAD: "Filename",
|
||||
EXPORT_THEME_DESC:
|
||||
"Export the image matching the dark/light theme of your drawing. If turned off, " +
|
||||
"drawings created in dark mode will appear as they would in light mode.",
|
||||
EXPORT_EMBED_SCENE_NAME: "Embed scene in exported image",
|
||||
EXPORT_EMBED_SCENE_DESC:
|
||||
"Embed Excalidraw scene in exported image. Can be overridden at a file level by adding the <code>excalidraw-export-embed-scene: true/false<code> frontmatter key. " +
|
||||
"The setting only takes effect the next time you (re)open drawings.",
|
||||
EXPORT_HEAD: "Auto-export Settings",
|
||||
EXPORT_SYNC_NAME:
|
||||
"Keep the .SVG and/or .PNG filenames in sync with the drawing file",
|
||||
@@ -591,6 +630,11 @@ FILENAME_HEAD: "Filename",
|
||||
"Turn this on to support image embedding styles such as ![[drawing|width|style]] in live preview editing mode. " +
|
||||
"The setting will not affect the currently open documents. You need close the open documents and re-open them for the change " +
|
||||
"to take effect.",
|
||||
FADE_OUT_EXCALIDRAW_MARKUP_NAME: "Fade out Excalidraw markup",
|
||||
FADE_OUT_EXCALIDRAW_MARKUP_DESC: "In Markdown view mode, the section after the markdown comment %% " +
|
||||
"fades out. The text is still there, but the visual clutter is reduced. Note, you can place the %% in the line right above # Text Elements, " +
|
||||
"in this case the entire drawing markdown will fade out including # Text Elements. The side effect is you won't be able to block reference text in other markdown notes, that is after the %% comment section. This is seldom an issue. " +
|
||||
"Should you want to edit the Excalidraw markdown script, simply switch to markdown view mode and temporarily remove the %% comment.",
|
||||
CUSTOM_FONT_HEAD: "Fourth font",
|
||||
ENABLE_FOURTH_FONT_NAME: "Enable fourth font option",
|
||||
ENABLE_FOURTH_FONT_DESC:
|
||||
@@ -635,6 +679,13 @@ FILENAME_HEAD: "Filename",
|
||||
PDF_PAGES_HEADER: "Pages to load?",
|
||||
PDF_PAGES_DESC: "Format: 1, 3-5, 7, 9-11",
|
||||
|
||||
//SelectCard.ts
|
||||
TYPE_SECTION: "Type section name to select.",
|
||||
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",
|
||||
|
||||
//EmbeddedFileLoader.ts
|
||||
INFINITE_LOOP_WARNING:
|
||||
"EXCALIDRAW WARNING\nAborted loading embedded images due to infinite loop in file:\n",
|
||||
@@ -707,6 +758,7 @@ FILENAME_HEAD: "Filename",
|
||||
PROMPT_BUTTON_INSERT_SPACE: "Insert space",
|
||||
PROMPT_BUTTON_INSERT_LINK: "Insert markdown link to file",
|
||||
PROMPT_BUTTON_UPPERCASE: "Uppercase",
|
||||
PROMPT_SELECT_TEMPLATE: "Select a template",
|
||||
|
||||
//ModifierKeySettings
|
||||
WEB_BROWSER_DRAG_ACTION: "Web Browser Drag Action",
|
||||
|
||||
313
src/main.ts
313
src/main.ts
@@ -41,6 +41,8 @@ import {
|
||||
EXPORT_IMG_ICON,
|
||||
LOCALE,
|
||||
IMAGE_TYPES,
|
||||
MD_TEXTELEMENTS,
|
||||
setExcalidrawPlugin
|
||||
} from "./constants/constants";
|
||||
import {
|
||||
VIRGIL_FONT,
|
||||
@@ -72,20 +74,23 @@ import {
|
||||
insertLaTeXToView,
|
||||
search,
|
||||
} from "./ExcalidrawAutomate";
|
||||
import { Prompt } from "./dialogs/Prompt";
|
||||
import { Prompt, templatePromt } from "./dialogs/Prompt";
|
||||
import { around, dedupe } from "monkey-around";
|
||||
import { t } from "./lang/helpers";
|
||||
import {
|
||||
checkAndCreateFolder,
|
||||
download,
|
||||
fileShouldDefaultAsExcalidraw,
|
||||
getAliasWithSize,
|
||||
getAnnotationFileNameAndFolder,
|
||||
getCropFileNameAndFolder,
|
||||
getDrawingFilename,
|
||||
getEmbedFilename,
|
||||
getIMGFilename,
|
||||
getLink,
|
||||
getListOfTemplateFiles,
|
||||
getNewUniqueFilepath,
|
||||
getURLImageExtension,
|
||||
splitFolderAndFilename,
|
||||
} from "./utils/FileUtils";
|
||||
import {
|
||||
getFontDataURL,
|
||||
@@ -97,8 +102,9 @@ import {
|
||||
getExportTheme,
|
||||
isCallerFromTemplaterPlugin,
|
||||
decompress,
|
||||
getImageSize,
|
||||
} from "./utils/Utils";
|
||||
import { extractSVGPNGFileName, getActivePDFPageNumberFromPDFView, getAttachmentsFolderAndFilePath, getNewOrAdjacentLeaf, getParentOfClass, isObsidianThemeDark, openLeaf } from "./utils/ObsidianUtils";
|
||||
import { extractSVGPNGFileName, 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 {
|
||||
@@ -129,6 +135,7 @@ 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 { ExcalidrawConfig } from "./utils/ExcalidrawConfig";
|
||||
import { EditorHandler } from "./CodeMirrorExtension/EditorHandler";
|
||||
|
||||
declare const EXCALIDRAW_PACKAGES:string;
|
||||
declare const react:any;
|
||||
@@ -177,6 +184,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
private removeEventLisnters:(()=>void)[] = [];
|
||||
private stylesManager:StylesManager;
|
||||
private textMeasureDiv:HTMLDivElement = null;
|
||||
public editorHandler: EditorHandler;
|
||||
|
||||
constructor(app: App, manifest: PluginManifest) {
|
||||
super(app, manifest);
|
||||
@@ -186,6 +194,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
>();
|
||||
this.equationsMaster = new Map<FileId, string>();
|
||||
this.mermaidsMaster = new Map<FileId, string>();
|
||||
setExcalidrawPlugin(this);
|
||||
}
|
||||
|
||||
get locale() {
|
||||
@@ -258,6 +267,8 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
await this.loadSettings({reEnableAutosave:true});
|
||||
this.excalidrawConfig = new ExcalidrawConfig(this);
|
||||
await loadMermaid();
|
||||
this.editorHandler = new EditorHandler(this);
|
||||
this.editorHandler.setup();
|
||||
|
||||
this.addSettingTab(new ExcalidrawSettingTab(this.app, this));
|
||||
this.ea = await initExcalidrawAutomate(this);
|
||||
@@ -382,10 +393,11 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
const self = this;
|
||||
this.app.workspace.onLayoutReady(() => {
|
||||
let leaf: WorkspaceLeaf;
|
||||
for (leaf of app.workspace.getLeavesOfType("markdown")) {
|
||||
for (leaf of this.app.workspace.getLeavesOfType("markdown")) {
|
||||
if (
|
||||
leaf.view instanceof MarkdownView &&
|
||||
self.isExcalidrawFile(leaf.view.file)
|
||||
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;
|
||||
@@ -1531,16 +1543,18 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
new Notice("Select a single image element and try again");
|
||||
return false;
|
||||
}
|
||||
const el = els[0] as ExcalidrawImageElement;
|
||||
const ef = view.excalidrawData.getFile(el.fileId);
|
||||
if(!ef) {
|
||||
if(checking) return false;
|
||||
new Notice("Select a single image element and try again");
|
||||
return false;
|
||||
}
|
||||
if(checking) return true;
|
||||
|
||||
|
||||
(async () => {
|
||||
const el = els[0] as ExcalidrawImageElement;
|
||||
let ef = view.excalidrawData.getFile(el.fileId);
|
||||
if(!ef) {
|
||||
await view.forceSave();
|
||||
let ef = view.excalidrawData.getFile(el.fileId);
|
||||
new Notice("Select a single image element and try again");
|
||||
return false;
|
||||
}
|
||||
|
||||
const ea = new ExcalidrawAutomate(this,view);
|
||||
const size = await ea.getOriginalImageSize(el);
|
||||
if(size) {
|
||||
@@ -1723,7 +1737,11 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
const line = editor.getLine(cursor.line);
|
||||
const parts = REGEX_LINK.getResList(line);
|
||||
if(parts.length === 0) return false;
|
||||
const imgpath = REGEX_LINK.getLink(parts[0]);
|
||||
let imgpath = REGEX_LINK.getLink(parts[0]);
|
||||
const isWikilink = REGEX_LINK.isWikiLink(parts[0]);
|
||||
let alias = REGEX_LINK.getAliasOrLink(parts[0]);
|
||||
if(alias === imgpath) alias = null;
|
||||
imgpath = decodeURI(imgpath);
|
||||
const imagePathParts = imgpath.split("#");
|
||||
const hasRef = imagePathParts.length === 2;
|
||||
const imageFile = this.app.metadataCache.getFirstLinkpathDest(
|
||||
@@ -1747,11 +1765,169 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
const pdfLink = isFile && ref
|
||||
? "\n" + getLink(this ,{
|
||||
embed: false,
|
||||
alias: `${imageFile.basename}, ${ref.replace("="," ")}`,
|
||||
alias: alias ?? `${imageFile.basename}, ${ref.replace("="," ")}`,
|
||||
path:`${imageFile.path}#${ref}`
|
||||
})
|
||||
}, isWikilink)
|
||||
: "";
|
||||
editor.setLine(cursor.line,lineparts[0] + getLink(this ,{embed: true, path:link}) + pdfLink + lineparts[1]);
|
||||
editor.setLine(cursor.line,lineparts[0] + getLink(this ,{embed: true, path:link, alias}, isWikilink) + pdfLink + lineparts[1]);
|
||||
}
|
||||
carveout(isFile, markdownView.file, imageFile, imagepath, replacer, ref);
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
this.addCommand({
|
||||
id: "annotate-image",
|
||||
name: t("ANNOTATE_IMAGE"),
|
||||
checkCallback: (checking:boolean) => {
|
||||
const markdownView = this.app.workspace.getActiveViewOfType(MarkdownView);
|
||||
const canvasView:any = this.app.workspace.activeLeaf?.view;
|
||||
const isCanvas = canvasView && canvasView.getViewType() === "canvas";
|
||||
if(!markdownView && !isCanvas) return false;
|
||||
|
||||
const carveout = async (isFile: boolean, sourceFile: TFile, imageFile: TFile, imageURL: string, replacer: Function, ref?: string) => {
|
||||
const ea = getEA() as ExcalidrawAutomate;
|
||||
const imageID = await ea.addImage(
|
||||
0, 0,
|
||||
isFile
|
||||
? ((isFile && imageFile.extension === "pdf" && ref) ? `${imageFile.path}#${ref}` : imageFile)
|
||||
: imageURL,
|
||||
false, false
|
||||
);
|
||||
if(!imageID) {
|
||||
new Notice(`Can't load image\n\n${imageURL}`);
|
||||
return;
|
||||
}
|
||||
const el = ea.getElement(imageID) as Mutable<ExcalidrawImageElement>;
|
||||
el.locked = true;
|
||||
const size = this.settings.annotatePreserveSize
|
||||
? await getImageSize(ea.imagesDict[el.fileId].dataURL)
|
||||
: null;
|
||||
let fnBase = "";
|
||||
let imageLink = "";
|
||||
if(isFile) {
|
||||
fnBase = imageFile.basename;
|
||||
imageLink = ref
|
||||
? `[[${imageFile.path}#${ref}]]`
|
||||
: `[[${imageFile.path}]]`;
|
||||
} else {
|
||||
imageLink = imageURL;
|
||||
const imagename = imageURL.match(/^.*\/([^?]*)\??.*$/)?.[1];
|
||||
fnBase = imagename.substring(0,imagename.lastIndexOf("."));
|
||||
}
|
||||
|
||||
let template:TFile;
|
||||
const templates = getListOfTemplateFiles(this);
|
||||
if(templates) {
|
||||
template = await templatePromt(templates, this.app);
|
||||
}
|
||||
|
||||
const {folderpath, filename} = await getAnnotationFileNameAndFolder(this,sourceFile.path,fnBase)
|
||||
const newPath = await ea.create ({
|
||||
templatePath: template?.path,
|
||||
filename,
|
||||
foldername: folderpath,
|
||||
onNewPane: true,
|
||||
frontmatterKeys: {
|
||||
...this.settings.matchTheme ? {"excalidraw-export-dark": isObsidianThemeDark()} : {},
|
||||
...(imageFile.extension === "pdf") ? {"cssclasses": "excalidraw-cropped-pdfpage"} : {},
|
||||
}
|
||||
});
|
||||
|
||||
//wait for file to be created/indexed by Obsidian
|
||||
let newFile = this.app.vault.getAbstractFileByPath(newPath);
|
||||
let counter = 0;
|
||||
while((!newFile || !this.isExcalidrawFile(newFile as TFile)) && counter < 50) {
|
||||
await sleep(100);
|
||||
newFile = this.app.vault.getAbstractFileByPath(newPath);
|
||||
counter++;
|
||||
}
|
||||
//console.log({counter, file});
|
||||
if(!newFile || !(newFile instanceof TFile)) {
|
||||
new Notice("File not found. NewExcalidraw Drawing is taking too long to create. Please try again.");
|
||||
return;
|
||||
}
|
||||
|
||||
if(!newFile) return;
|
||||
const link = this.app.metadataCache.fileToLinktext(newFile,sourceFile.path, true);
|
||||
replacer(link, newFile, size ? `${size.width}` : null);
|
||||
}
|
||||
|
||||
if(isCanvas) {
|
||||
const selectedNodes:any = [];
|
||||
canvasView.canvas.nodes.forEach((node:any) => {
|
||||
if(node.nodeEl.hasClass("is-focused")) selectedNodes.push(node);
|
||||
})
|
||||
if(selectedNodes.length !== 1) return false;
|
||||
const node = selectedNodes[0];
|
||||
let extension = "";
|
||||
let isExcalidraw = false;
|
||||
if(node.file) {
|
||||
extension = node.file.extension;
|
||||
isExcalidraw = this.isExcalidrawFile(node.file);
|
||||
}
|
||||
if(node.url) {
|
||||
extension = getURLImageExtension(node.url);
|
||||
}
|
||||
const page = extension === "pdf" ? getActivePDFPageNumberFromPDFView(node?.child) : undefined;
|
||||
if(!page && !IMAGE_TYPES.contains(extension) && !isExcalidraw) return false;
|
||||
if(checking) return true;
|
||||
|
||||
const replacer = (link:string, file: TFile) => {
|
||||
if(node.file) {
|
||||
(node.file.extension === "pdf")
|
||||
? node.canvas.createFileNode({pos:{x:node.x + node.width + 10,y: node.y}, file})
|
||||
: node.setFile(file);
|
||||
}
|
||||
if(node.url) {
|
||||
node.canvas.createFileNode({pos:{x:node.x + 20,y: node.y+20}, file});
|
||||
}
|
||||
}
|
||||
carveout(Boolean(node.file), canvasView.file, node.file, node.url, replacer, page ? `page=${page}` : undefined);
|
||||
}
|
||||
|
||||
if (markdownView) {
|
||||
const editor = markdownView.editor;
|
||||
const cursor = editor.getCursor();
|
||||
const line = editor.getLine(cursor.line);
|
||||
const parts = REGEX_LINK.getResList(line);
|
||||
if(parts.length === 0) return false;
|
||||
let imgpath = REGEX_LINK.getLink(parts[0]);
|
||||
const isWikilink = REGEX_LINK.isWikiLink(parts[0]);
|
||||
let alias = REGEX_LINK.getAliasOrLink(parts[0]);
|
||||
if(alias === imgpath) alias = null;
|
||||
imgpath = decodeURI(imgpath);
|
||||
const imagePathParts = imgpath.split("#");
|
||||
const hasRef = imagePathParts.length === 2;
|
||||
const imageFile = this.app.metadataCache.getFirstLinkpathDest(
|
||||
hasRef ? imagePathParts[0] : imgpath,
|
||||
markdownView.file.path
|
||||
);
|
||||
const isFile = (imageFile && imageFile instanceof TFile);
|
||||
const isExcalidraw = isFile ? this.isExcalidrawFile(imageFile) : false;
|
||||
let imagepath = isFile ? imageFile.path : "";
|
||||
let extension = isFile ? imageFile.extension : "";
|
||||
if(imgpath.match(/^https?|file/)) {
|
||||
imagepath = imgpath;
|
||||
extension = getURLImageExtension(imgpath);
|
||||
}
|
||||
if(imagepath === "") return false;
|
||||
if(extension !== "pdf" && !IMAGE_TYPES.contains(extension) && !isExcalidraw) return false;
|
||||
if(checking) return true;
|
||||
const ref = imagePathParts[1];
|
||||
const replacer = (link:string, _:TFile, size:string) => {
|
||||
const lineparts = line.split(parts[0].value[0])
|
||||
const pdfLink = isFile && ref
|
||||
? "\n" + getLink(this ,{
|
||||
embed: false,
|
||||
alias: getAliasWithSize(alias ?? `${imageFile.basename}, ${ref.replace("="," ")}`,size),
|
||||
path:`${imageFile.path}#${ref}`
|
||||
}, isWikilink)
|
||||
: "";
|
||||
editor.setLine(
|
||||
cursor.line,
|
||||
lineparts[0] + getLink(this ,{embed: true, path:link, alias: getAliasWithSize(alias,size)}, isWikilink) + pdfLink + lineparts[1]
|
||||
);
|
||||
}
|
||||
carveout(isFile, markdownView.file, imageFile, imagepath, replacer, ref);
|
||||
}
|
||||
@@ -1876,6 +2052,22 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
},
|
||||
});
|
||||
|
||||
this.addCommand({
|
||||
id: "universal-card",
|
||||
name: t("INSERT_CARD"),
|
||||
checkCallback: (checking: boolean) => {
|
||||
if (checking) {
|
||||
return Boolean(this.app.workspace.getActiveViewOfType(ExcalidrawView))
|
||||
}
|
||||
const view = this.app.workspace.getActiveViewOfType(ExcalidrawView);
|
||||
if (view) {
|
||||
view.insertBackOfTheNoteCard();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
});
|
||||
|
||||
this.addCommand({
|
||||
id: "insert-LaTeX-symbol",
|
||||
name: t("INSERT_LATEX"),
|
||||
@@ -1920,10 +2112,13 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
|
||||
const markdownView = this.app.workspace.getActiveViewOfType(MarkdownView)
|
||||
if (markdownView && fileIsExcalidraw) {
|
||||
const activeLeaf = markdownView.leaf;
|
||||
this.excalidrawFileModes[(activeLeaf as any).id || activeFile.path] =
|
||||
VIEW_TYPE_EXCALIDRAW;
|
||||
this.setExcalidrawView(activeLeaf);
|
||||
(async()=>{
|
||||
await markdownView.save();
|
||||
const activeLeaf = markdownView.leaf;
|
||||
this.excalidrawFileModes[(activeLeaf as any).id || activeFile.path] =
|
||||
VIEW_TYPE_EXCALIDRAW;
|
||||
this.setExcalidrawView(activeLeaf);
|
||||
})()
|
||||
return;
|
||||
}
|
||||
},
|
||||
@@ -1940,21 +2135,25 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
return false;
|
||||
}
|
||||
|
||||
const isFileEmpty = activeFile.stat.size === 0;
|
||||
|
||||
if (checking) {
|
||||
return isFileEmpty;
|
||||
if(this.isExcalidrawFile(activeFile)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isFileEmpty) {
|
||||
(async () => {
|
||||
await this.app.vault.modify(
|
||||
activeFile,
|
||||
await this.getBlankDrawing(),
|
||||
);
|
||||
this.setExcalidrawView(activeView.leaf);
|
||||
})();
|
||||
if(checking) {
|
||||
return true;
|
||||
}
|
||||
|
||||
(async () => {
|
||||
await activeView.save();
|
||||
const template = await this.getBlankDrawing();
|
||||
const target = await this.app.vault.read(activeFile);
|
||||
const mergedTarget = mergeMarkdownFiles(template, target);
|
||||
await this.app.vault.modify(
|
||||
activeFile,
|
||||
mergedTarget,
|
||||
);
|
||||
this.setExcalidrawView(activeView.leaf);
|
||||
})();
|
||||
},
|
||||
});
|
||||
|
||||
@@ -2073,7 +2272,8 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
.setTitle(t("OPEN_AS_EXCALIDRAW"))
|
||||
.setIcon(ICON_NAME)
|
||||
.setSection("excalidraw")
|
||||
.onClick(() => {
|
||||
.onClick(async () => {
|
||||
await view.save();
|
||||
//@ts-ignore
|
||||
this.excalidrawFileModes[leaf.id || file.path] = VIEW_TYPE_EXCALIDRAW;
|
||||
this.setExcalidrawView(leaf);
|
||||
@@ -2084,7 +2284,9 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
|
||||
this.registerEvent(
|
||||
app.workspace.on("file-menu", (menu, file, source, leaf) => {
|
||||
if (!leaf || !(leaf.view instanceof MarkdownView)) return;
|
||||
if (!leaf) return;
|
||||
const view = leaf.view;
|
||||
if(!view || !(view instanceof MarkdownView)) return;
|
||||
if (!(file instanceof TFile)) return;
|
||||
const cache = this.app.metadataCache.getFileCache(file);
|
||||
if (!cache?.frontmatter || !cache.frontmatter[FRONTMATTER_KEYS["plugin"].name]) return;
|
||||
@@ -2094,7 +2296,8 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
.setTitle(t("OPEN_AS_EXCALIDRAW"))
|
||||
.setIcon(ICON_NAME)
|
||||
.setSection("pane")
|
||||
.onClick(() => {
|
||||
.onClick(async () => {
|
||||
await view.save();
|
||||
//@ts-ignore
|
||||
this.excalidrawFileModes[leaf.id || file.path] = VIEW_TYPE_EXCALIDRAW;
|
||||
this.setExcalidrawView(leaf);
|
||||
@@ -2138,10 +2341,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
self.excalidrawFileModes[this.id || state.state.file] !==
|
||||
"markdown"
|
||||
) {
|
||||
// Then check for the excalidraw frontMatterKey
|
||||
const cache = app.metadataCache.getCache(state.state.file);
|
||||
|
||||
if (cache?.frontmatter && cache.frontmatter[FRONTMATTER_KEYS["plugin"].name]) {
|
||||
if (fileShouldDefaultAsExcalidraw(state.state.file,this.app)) {
|
||||
// If we have it, force the view type to excalidraw
|
||||
const newState = {
|
||||
...state,
|
||||
@@ -2287,8 +2487,9 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
return;
|
||||
}
|
||||
if(file.extension==="md") {
|
||||
if(excalidrawView.semaphores.embeddableIsEditingSelf) return;
|
||||
const inData = new ExcalidrawData(self);
|
||||
const data = await app.vault.read(file);
|
||||
const data = await this.app.vault.read(file);
|
||||
await inData.loadData(data,file,getTextMode(data));
|
||||
excalidrawView.synchronizeWithData(inData);
|
||||
if(excalidrawView.semaphores.dirty) {
|
||||
@@ -2886,20 +3087,20 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
}
|
||||
|
||||
public async getBlankDrawing(): Promise<string> {
|
||||
const template = this.app.metadataCache.getFirstLinkpathDest(
|
||||
normalizePath(this.settings.templateFilePath),
|
||||
"",
|
||||
);
|
||||
if (template && template instanceof TFile) {
|
||||
if (
|
||||
(template.extension == "md" && !this.settings.compatibilityMode) ||
|
||||
(template.extension == "excalidraw" && this.settings.compatibilityMode)
|
||||
) {
|
||||
const data = await this.app.vault.read(template);
|
||||
if (data) {
|
||||
return this.settings.matchTheme
|
||||
? changeThemeOfExcalidrawMD(data)
|
||||
: data;
|
||||
const templates = getListOfTemplateFiles(this);
|
||||
if(templates) {
|
||||
const template = await templatePromt(templates, this.app);
|
||||
if (template && template instanceof TFile) {
|
||||
if (
|
||||
(template.extension == "md" && !this.settings.compatibilityMode) ||
|
||||
(template.extension == "excalidraw" && this.settings.compatibilityMode)
|
||||
) {
|
||||
const data = await this.app.vault.read(template);
|
||||
if (data) {
|
||||
return this.settings.matchTheme
|
||||
? changeThemeOfExcalidrawMD(data)
|
||||
: data;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2931,7 +3132,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
const textElements = excalidrawData.elements?.filter(
|
||||
(el: any) => el.type == "text",
|
||||
);
|
||||
let outString = "# Text Elements\n";
|
||||
let outString = `${MD_TEXTELEMENTS}\n`;
|
||||
let id: string;
|
||||
for (const te of textElements) {
|
||||
id = te.id;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Copy, Crop, Globe, RotateCcw, Scan, Settings } from "lucide-react";
|
||||
import { Copy, Crop, Globe, RotateCcw, Scan, Settings, TextSelect } from "lucide-react";
|
||||
import * as React from "react";
|
||||
import { PenStyle } from "src/PenTypes";
|
||||
|
||||
@@ -26,6 +26,7 @@ export const ICONS = {
|
||||
</g>
|
||||
</svg>
|
||||
),
|
||||
BackOfNote: (<TextSelect />),
|
||||
Reload: (<RotateCcw />),
|
||||
Copy: (<Copy /> ),
|
||||
Globe: (<Globe />),
|
||||
|
||||
@@ -7,12 +7,14 @@ import { ActionButton } from "./ActionButton";
|
||||
import { ICONS } from "./ActionIcons";
|
||||
import { t } from "src/lang/helpers";
|
||||
import { ScriptEngine } from "src/Scripts";
|
||||
import { ROOTELEMENTSIZE, mutateElement, nanoid, sceneCoordsToViewportCoords } from "src/constants/constants";
|
||||
import { MD_EX_SECTIONS, ROOTELEMENTSIZE, mutateElement, nanoid, sceneCoordsToViewportCoords } from "src/constants/constants";
|
||||
import { REGEX_LINK, REG_LINKINDEX_HYPERLINK } from "src/ExcalidrawData";
|
||||
import { processLinkText, useDefaultExcalidrawFrame } from "src/utils/CustomEmbeddableUtils";
|
||||
import { cleanSectionHeading } from "src/utils/ObsidianUtils";
|
||||
import { EmbeddableSettings } from "src/dialogs/EmbeddableSettings";
|
||||
import { openExternalLink } from "src/utils/ExcalidrawViewUtils";
|
||||
import { getEA } from "src";
|
||||
import { ExcalidrawAutomate } from "src/ExcalidrawAutomate";
|
||||
|
||||
export class EmbeddableMenu {
|
||||
|
||||
@@ -34,10 +36,13 @@ export class EmbeddableMenu {
|
||||
file.extension === "md",
|
||||
)
|
||||
const link = `[[${path}${subpath}]]`;
|
||||
mutateElement (element,{link});
|
||||
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);
|
||||
view.setDirty(99);
|
||||
view.updateScene({appState: {activeEmbeddable: null}});
|
||||
ea.addElementsToView(false, true, true);
|
||||
}
|
||||
|
||||
private menuFadeTimeout: number = 0;
|
||||
@@ -99,6 +104,7 @@ export class EmbeddableMenu {
|
||||
const { subpath, file } = processLinkText(link, view);
|
||||
if(!file) return;
|
||||
const isMD = file.extension==="md";
|
||||
const isExcalidrawFile = view.plugin.isExcalidrawFile(file);
|
||||
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`;
|
||||
@@ -128,15 +134,23 @@ export class EmbeddableMenu {
|
||||
key={"MarkdownSection"}
|
||||
title={t("NARROW_TO_HEADING")}
|
||||
action={async () => {
|
||||
view.updateScene({appState: {activeEmbeddable: null}});
|
||||
const sections = (await app.metadataCache.blockCache
|
||||
.getForFile({ isCancelled: () => false },file))
|
||||
.blocks.filter((b: any) => b.display && b.node?.type === "heading");
|
||||
const values = [""].concat(
|
||||
sections.map((b: any) => `#${cleanSectionHeading(b.display)}`)
|
||||
);
|
||||
const display = [t("SHOW_ENTIRE_FILE")].concat(
|
||||
sections.map((b: any) => b.display)
|
||||
);
|
||||
.blocks.filter((b: any) => b.display && b.node?.type === "heading")
|
||||
.filter((b: any) => !isExcalidrawFile || !MD_EX_SECTIONS.includes(b.display));
|
||||
let values, display;
|
||||
if(isExcalidrawFile) {
|
||||
values = sections.map((b: any) => `#${cleanSectionHeading(b.display)}`);
|
||||
display = sections.map((b: any) => b.display);
|
||||
} else {
|
||||
values = [""].concat(
|
||||
sections.map((b: any) => `#${cleanSectionHeading(b.display)}`)
|
||||
);
|
||||
display = [t("SHOW_ENTIRE_FILE")].concat(
|
||||
sections.map((b: any) => b.display)
|
||||
);
|
||||
}
|
||||
const newSubpath = await ScriptEngine.suggester(
|
||||
app, display, values, "Select section from document"
|
||||
);
|
||||
@@ -149,12 +163,13 @@ export class EmbeddableMenu {
|
||||
view={view}
|
||||
/>
|
||||
)}
|
||||
{isMD && (
|
||||
{isMD && !isExcalidrawFile && (
|
||||
<ActionButton
|
||||
key={"MarkdownBlock"}
|
||||
title={t("NARROW_TO_BLOCK")}
|
||||
action={async () => {
|
||||
if(!file) return;
|
||||
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");
|
||||
|
||||
@@ -15,6 +15,7 @@ import { isWinALTorMacOPT, isWinCTRLorMacCMD, isSHIFT } from "src/utils/Modifier
|
||||
import { InsertPDFModal } from "src/dialogs/InsertPDFModal";
|
||||
import { ExportDialog } from "src/dialogs/ExportDialog";
|
||||
import { openExternalLink } from "src/utils/ExcalidrawViewUtils";
|
||||
import { UniversalInsertFileModal } from "src/dialogs/UniversalInsertFileModal";
|
||||
|
||||
declare const PLUGIN_VERSION:string;
|
||||
const dark = '<svg style="stroke:#ced4da;#212529;color:#ced4da;fill:#ced4da" ';
|
||||
@@ -461,11 +462,37 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
|
||||
icon={ICONS.switchToMarkdown}
|
||||
view={this.props.view}
|
||||
/>
|
||||
<ActionButton
|
||||
key={"link-to-element"}
|
||||
title={t("INSERT_LINK_TO_ELEMENT")}
|
||||
action={(e:React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||
if(isWinALTorMacOPT(e)) {
|
||||
openExternalLink("https://youtu.be/yZQoJg2RCKI", this.props.view.app);
|
||||
return;
|
||||
}
|
||||
this.props.view.copyLinkToSelectedElementToClipboard(
|
||||
isWinCTRLorMacCMD(e) ? "group=" : (isSHIFT(e) ? "area=" : "")
|
||||
);
|
||||
}}
|
||||
icon={ICONS.copyElementLink}
|
||||
view={this.props.view}
|
||||
/>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>Insert actions</legend>
|
||||
<div className="buttonList buttonListIcon">
|
||||
<ActionButton
|
||||
key={"anyfile"}
|
||||
title={t("UNIVERSAL_ADD_FILE")}
|
||||
action={() => {
|
||||
this.props.centerPointer();
|
||||
const insertFileModal = new UniversalInsertFileModal(this.props.view.plugin, this.props.view);
|
||||
insertFileModal.open();
|
||||
}}
|
||||
icon={ICONS["add-file"]}
|
||||
view={this.props.view}
|
||||
/>
|
||||
<ActionButton
|
||||
key={"image"}
|
||||
title={t("INSERT_IMAGE")}
|
||||
@@ -501,6 +528,16 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
|
||||
icon={ICONS.insertMD}
|
||||
view={this.props.view}
|
||||
/>
|
||||
<ActionButton
|
||||
key={"insertBackOfNote"}
|
||||
title={t("INSERT_CARD")}
|
||||
action={() => {
|
||||
this.props.centerPointer();
|
||||
this.props.view.insertBackOfTheNoteCard();
|
||||
}}
|
||||
icon={ICONS.BackOfNote}
|
||||
view={this.props.view}
|
||||
/>
|
||||
<ActionButton
|
||||
key={"latex"}
|
||||
title={t("INSERT_LATEX")}
|
||||
@@ -528,21 +565,6 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
|
||||
icon={ICONS.insertLink}
|
||||
view={this.props.view}
|
||||
/>
|
||||
<ActionButton
|
||||
key={"link-to-element"}
|
||||
title={t("INSERT_LINK_TO_ELEMENT")}
|
||||
action={(e:React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||
if(isWinALTorMacOPT(e)) {
|
||||
openExternalLink("https://youtu.be/yZQoJg2RCKI", this.props.view.app);
|
||||
return;
|
||||
}
|
||||
this.props.view.copyLinkToSelectedElementToClipboard(
|
||||
isWinCTRLorMacCMD(e) ? "group=" : (isSHIFT(e) ? "area=" : "")
|
||||
);
|
||||
}}
|
||||
icon={ICONS.copyElementLink}
|
||||
view={this.props.view}
|
||||
/>
|
||||
<ActionButton
|
||||
key={"import-svg"}
|
||||
title={t("IMPORT_SVG")}
|
||||
|
||||
@@ -114,7 +114,18 @@ export default class Taskbone {
|
||||
}]
|
||||
};
|
||||
|
||||
const apiResponse = await requestUrl ({
|
||||
const apiResponse = await fetch(url,{
|
||||
method: "post",
|
||||
//@ts-ignore
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify(input),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
authorization: `Bearer ${this.apiKey}`
|
||||
}});
|
||||
const content = await apiResponse?.json();
|
||||
|
||||
/*const apiResponse = await requestUrl ({
|
||||
url: url,
|
||||
method: "post",
|
||||
contentType: "application/json",
|
||||
@@ -124,7 +135,7 @@ export default class Taskbone {
|
||||
},
|
||||
throw: false
|
||||
});
|
||||
const content = apiResponse?.json;
|
||||
const content = apiResponse?.json;*/
|
||||
|
||||
if(!content || apiResponse.status !== 200) {
|
||||
new Notice("Something went wrong while processing your request. Please check developer console for more information");
|
||||
|
||||
238
src/settings.ts
238
src/settings.ts
@@ -33,11 +33,13 @@ import { EmbeddalbeMDFileCustomDataSettingsComponent } from "./dialogs/Embeddabl
|
||||
import { startupScript } from "./constants/starutpscript";
|
||||
import { ModifierKeySet, ModifierSetType } from "./utils/ModifierkeyHelper";
|
||||
import { ModifierKeySettingsComponent } from "./dialogs/ModifierKeySettings";
|
||||
import { CROPPED_PREFIX } from "./utils/CarveOut";
|
||||
import { ANNOTATED_PREFIX, CROPPED_PREFIX } from "./utils/CarveOut";
|
||||
import { EDITOR_FADEOUT } from "./CodeMirrorExtension/EditorHandler";
|
||||
|
||||
export interface ExcalidrawSettings {
|
||||
folder: string;
|
||||
cropFolder: string;
|
||||
annotateFolder: string;
|
||||
embedUseExcalidrawFolder: boolean;
|
||||
templateFilePath: string;
|
||||
scriptFolderPath: string;
|
||||
@@ -52,6 +54,8 @@ export interface ExcalidrawSettings {
|
||||
drawingFilenameDateTime: string;
|
||||
useExcalidrawExtension: boolean;
|
||||
cropPrefix: string;
|
||||
annotatePrefix: string;
|
||||
annotatePreserveSize: boolean;
|
||||
displaySVGInPreview: boolean; //No longer used since 1.9.13
|
||||
previewImageType: PreviewImageType; //Introduced with 1.9.13
|
||||
allowImageCache: boolean;
|
||||
@@ -67,6 +71,9 @@ export interface ExcalidrawSettings {
|
||||
matchThemeTrigger: boolean;
|
||||
defaultMode: string;
|
||||
defaultPenMode: "never" | "mobile" | "always";
|
||||
penModeCrosshairVisible: boolean;
|
||||
renderImageInMarkdownReadingMode: boolean,
|
||||
renderImageInMarkdownToPDF: boolean,
|
||||
allowPinchZoom: boolean;
|
||||
allowWheelZoom: boolean;
|
||||
zoomToFitOnOpen: boolean;
|
||||
@@ -94,6 +101,7 @@ export interface ExcalidrawSettings {
|
||||
exportWithTheme: boolean;
|
||||
exportWithBackground: boolean;
|
||||
exportPaddingSVG: number;
|
||||
exportEmbedScene: boolean;
|
||||
keepInSync: boolean;
|
||||
autoexportSVG: boolean;
|
||||
autoexportPNG: boolean;
|
||||
@@ -107,6 +115,7 @@ export interface ExcalidrawSettings {
|
||||
experimentalFileType: boolean;
|
||||
experimentalFileTag: string;
|
||||
experimentalLivePreview: boolean;
|
||||
fadeOutExcalidrawMarkup: boolean;
|
||||
experimentalEnableFourthFont: boolean;
|
||||
experimantalFourthFont: string;
|
||||
fieldSuggester: boolean;
|
||||
@@ -177,6 +186,8 @@ export interface ExcalidrawSettings {
|
||||
},
|
||||
slidingPanesSupport: boolean;
|
||||
areaZoomLimit: number;
|
||||
longPressDesktop: number;
|
||||
longPressMobile: number;
|
||||
}
|
||||
|
||||
declare const PLUGIN_VERSION:string;
|
||||
@@ -184,6 +195,7 @@ declare const PLUGIN_VERSION:string;
|
||||
export const DEFAULT_SETTINGS: ExcalidrawSettings = {
|
||||
folder: "Excalidraw",
|
||||
cropFolder: "",
|
||||
annotateFolder: "",
|
||||
embedUseExcalidrawFolder: false,
|
||||
templateFilePath: "Excalidraw/Template.excalidraw",
|
||||
scriptFolderPath: "Excalidraw/Scripts",
|
||||
@@ -198,6 +210,8 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
|
||||
drawingFilenameDateTime: "YYYY-MM-DD HH.mm.ss",
|
||||
useExcalidrawExtension: true,
|
||||
cropPrefix: CROPPED_PREFIX,
|
||||
annotatePrefix: ANNOTATED_PREFIX,
|
||||
annotatePreserveSize: false,
|
||||
displaySVGInPreview: undefined,
|
||||
previewImageType: undefined,
|
||||
allowImageCache: true,
|
||||
@@ -213,6 +227,9 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
|
||||
matchThemeTrigger: false,
|
||||
defaultMode: "normal",
|
||||
defaultPenMode: "never",
|
||||
penModeCrosshairVisible: true,
|
||||
renderImageInMarkdownReadingMode: false,
|
||||
renderImageInMarkdownToPDF: false,
|
||||
allowPinchZoom: false,
|
||||
allowWheelZoom: false,
|
||||
zoomToFitOnOpen: true,
|
||||
@@ -240,6 +257,7 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
|
||||
exportWithTheme: true,
|
||||
exportWithBackground: true,
|
||||
exportPaddingSVG: 10, //since 1.6.17, not only SVG but also PNG
|
||||
exportEmbedScene: false,
|
||||
keepInSync: false,
|
||||
autoexportSVG: false,
|
||||
autoexportPNG: false,
|
||||
@@ -252,6 +270,7 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
|
||||
experimentalFileType: false,
|
||||
experimentalFileTag: "✏️",
|
||||
experimentalLivePreview: true,
|
||||
fadeOutExcalidrawMarkup: false,
|
||||
experimentalEnableFourthFont: false,
|
||||
experimantalFourthFont: "Virgil",
|
||||
fieldSuggester: true,
|
||||
@@ -415,6 +434,8 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
|
||||
},
|
||||
slidingPanesSupport: false,
|
||||
areaZoomLimit: 1,
|
||||
longPressDesktop: 500,
|
||||
longPressMobile: 500,
|
||||
};
|
||||
|
||||
export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
@@ -576,6 +597,19 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
}),
|
||||
);
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("ANNOTATE_FOLDER_NAME"))
|
||||
.setDesc(fragWithHTML(t("ANNOTATE_FOLDER_DESC")))
|
||||
.addText((text) =>
|
||||
text
|
||||
.setPlaceholder("e.g.: Excalidraw/Annotations")
|
||||
.setValue(this.plugin.settings.annotateFolder)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.annotateFolder = value;
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("TEMPLATE_NAME"))
|
||||
.setDesc(fragWithHTML(t("TEMPLATE_DESC")))
|
||||
@@ -772,7 +806,6 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("CROP_PREFIX_NAME"))
|
||||
.setDesc(fragWithHTML(t("CROP_PREFIX_DESC")))
|
||||
@@ -789,6 +822,36 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("ANNOTATE_PREFIX_NAME"))
|
||||
.setDesc(fragWithHTML(t("ANNOTATE_PREFIX_DESC")))
|
||||
.addText((text) =>
|
||||
text
|
||||
.setPlaceholder("e.g.: Annotated_ ")
|
||||
.setValue(this.plugin.settings.annotatePrefix)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.annotatePrefix = value.replaceAll(
|
||||
/[<>:"/\\|?*]/g,
|
||||
"_",
|
||||
);
|
||||
text.setValue(this.plugin.settings.annotatePrefix);
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("ANNOTATE_PRESERVE_SIZE_NAME"))
|
||||
.setDesc(fragWithHTML(t("ANNOTATE_PRESERVE_SIZE_DESC")))
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings.annotatePreserveSize)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.annotatePreserveSize = value;
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
|
||||
//------------------------------------------------
|
||||
// AI Settings
|
||||
//------------------------------------------------
|
||||
@@ -893,6 +956,30 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
}),
|
||||
);
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("SHOW_PEN_MODE_FREEDRAW_CROSSHAIR_NAME"))
|
||||
.setDesc(fragWithHTML(t("SHOW_PEN_MODE_FREEDRAW_CROSSHAIR_DESC")))
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings.penModeCrosshairVisible)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.penModeCrosshairVisible = value;
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("SHOW_DRAWING_OR_MD_IN_READING_MODE_NAME"))
|
||||
.setDesc(fragWithHTML(t("SHOW_DRAWING_OR_MD_IN_READING_MODE_DESC")))
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings.renderImageInMarkdownReadingMode)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.renderImageInMarkdownReadingMode = value;
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("LEFTHANDED_MODE_NAME"))
|
||||
.setDesc(fragWithHTML(t("LEFTHANDED_MODE_DESC")))
|
||||
@@ -908,43 +995,43 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
addIframe(detailsEl, "H8Njp7ZXYag",999);
|
||||
addIframe(detailsEl, "H8Njp7ZXYag",999);
|
||||
|
||||
detailsEl = displayDetailsEl.createEl("details");
|
||||
detailsEl.createEl("summary", {
|
||||
text: t("THEME_HEAD"),
|
||||
cls: "excalidraw-setting-h3",
|
||||
});
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("DYNAMICSTYLE_NAME"))
|
||||
.setDesc(fragWithHTML(t("DYNAMICSTYLE_DESC")))
|
||||
.addDropdown((dropdown) =>
|
||||
dropdown
|
||||
.addOption("none","Dynamic Styling OFF")
|
||||
.addOption("colorful","Match color")
|
||||
.addOption("gray","Gray, match tone")
|
||||
.setValue(this.plugin.settings.dynamicStyling)
|
||||
.onChange(async (value) => {
|
||||
this.requestUpdateDynamicStyling = true;
|
||||
this.plugin.settings.dynamicStyling = value as DynamicStyle;
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
addIframe(detailsEl, "fypDth_-8q0");
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("IFRAME_MATCH_THEME_NAME"))
|
||||
.setDesc(fragWithHTML(t("IFRAME_MATCH_THEME_DESC")))
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings.iframeMatchExcalidrawTheme)
|
||||
detailsEl = displayDetailsEl.createEl("details");
|
||||
detailsEl.createEl("summary", {
|
||||
text: t("THEME_HEAD"),
|
||||
cls: "excalidraw-setting-h3",
|
||||
});
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("DYNAMICSTYLE_NAME"))
|
||||
.setDesc(fragWithHTML(t("DYNAMICSTYLE_DESC")))
|
||||
.addDropdown((dropdown) =>
|
||||
dropdown
|
||||
.addOption("none","Dynamic Styling OFF")
|
||||
.addOption("colorful","Match color")
|
||||
.addOption("gray","Gray, match tone")
|
||||
.setValue(this.plugin.settings.dynamicStyling)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.iframeMatchExcalidrawTheme = value;
|
||||
this.applySettingsUpdate(true);
|
||||
this.requestUpdateDynamicStyling = true;
|
||||
this.plugin.settings.dynamicStyling = value as DynamicStyle;
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
addIframe(detailsEl, "ICpoyMv6KSs");
|
||||
addIframe(detailsEl, "fypDth_-8q0");
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("IFRAME_MATCH_THEME_NAME"))
|
||||
.setDesc(fragWithHTML(t("IFRAME_MATCH_THEME_DESC")))
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings.iframeMatchExcalidrawTheme)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.iframeMatchExcalidrawTheme = value;
|
||||
this.applySettingsUpdate(true);
|
||||
}),
|
||||
);
|
||||
addIframe(detailsEl, "ICpoyMv6KSs");
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("MATCH_THEME_NAME"))
|
||||
@@ -1151,6 +1238,48 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
});
|
||||
detailsEl.createDiv({ text: t("DRAG_MODIFIER_DESC"), cls: "setting-item-description" });
|
||||
|
||||
let longPressDesktop: HTMLDivElement;
|
||||
new Setting(detailsEl)
|
||||
.setName(t("LONG_PRESS_DESKTOP_NAME"))
|
||||
.setDesc(fragWithHTML(t("LONG_PRESS_DESKTOP_DESC")))
|
||||
.addSlider((slider) =>
|
||||
slider
|
||||
.setLimits(300, 3000, 100)
|
||||
.setValue(this.plugin.settings.longPressDesktop)
|
||||
.onChange(async (value) => {
|
||||
longPressDesktop.innerText = ` ${value.toString()}`;
|
||||
this.plugin.settings.longPressDesktop = value;
|
||||
this.applySettingsUpdate(true);
|
||||
}),
|
||||
)
|
||||
.settingEl.createDiv("", (el) => {
|
||||
longPressDesktop = el;
|
||||
el.style.minWidth = "2.3em";
|
||||
el.style.textAlign = "right";
|
||||
el.innerText = ` ${this.plugin.settings.longPressDesktop.toString()}`;
|
||||
});
|
||||
|
||||
let longPressMobile: HTMLDivElement;
|
||||
new Setting(detailsEl)
|
||||
.setName(t("LONG_PRESS_MOBILE_NAME"))
|
||||
.setDesc(fragWithHTML(t("LONG_PRESS_MOBILE_DESC")))
|
||||
.addSlider((slider) =>
|
||||
slider
|
||||
.setLimits(300, 3000, 100)
|
||||
.setValue(this.plugin.settings.longPressMobile)
|
||||
.onChange(async (value) => {
|
||||
longPressMobile.innerText = ` ${value.toString()}`;
|
||||
this.plugin.settings.longPressMobile = value;
|
||||
this.applySettingsUpdate(true);
|
||||
}),
|
||||
)
|
||||
.settingEl.createDiv("", (el) => {
|
||||
longPressMobile = el;
|
||||
el.style.minWidth = "2.3em";
|
||||
el.style.textAlign = "right";
|
||||
el.innerText = ` ${this.plugin.settings.longPressMobile.toString()}`;
|
||||
});
|
||||
|
||||
new ModifierKeySettingsComponent(
|
||||
detailsEl,
|
||||
this.plugin.settings.modifierKeyConfig,
|
||||
@@ -1612,12 +1741,36 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
cls: "excalidraw-setting-h3",
|
||||
});
|
||||
addIframe(detailsEl, "wTtaXmRJ7wg",171);
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("SHOW_DRAWING_OR_MD_IN_EXPORTPDF_NAME"))
|
||||
.setDesc(fragWithHTML(t("SHOW_DRAWING_OR_MD_IN_EXPORTPDF_DESC")))
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings.renderImageInMarkdownToPDF)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.renderImageInMarkdownToPDF = value;
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("EXPORT_EMBED_SCENE_NAME"))
|
||||
.setDesc(fragWithHTML(t("EXPORT_EMBED_SCENE_DESC")))
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings.exportEmbedScene)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.exportEmbedScene = value;
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
|
||||
detailsEl = exportDetailsEl.createEl("details");
|
||||
detailsEl.createEl("summary", {
|
||||
text: t("EMBED_SIZING"),
|
||||
cls: "excalidraw-setting-h4",
|
||||
});
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("EMBED_WIDTH_NAME"))
|
||||
.setDesc(fragWithHTML(t("EMBED_WIDTH_DESC")))
|
||||
@@ -2167,6 +2320,19 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
}),
|
||||
);
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("FADE_OUT_EXCALIDRAW_MARKUP_NAME"))
|
||||
.setDesc(fragWithHTML(t("FADE_OUT_EXCALIDRAW_MARKUP_DESC")))
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings.fadeOutExcalidrawMarkup)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.fadeOutExcalidrawMarkup = value;
|
||||
this.plugin.editorHandler.updateCMExtensionState(EDITOR_FADEOUT, value)
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
|
||||
detailsEl = experimentalDetailsEl.createEl("details");
|
||||
detailsEl.createEl("summary", {
|
||||
text: t("TASKBONE_HEAD"),
|
||||
|
||||
@@ -80,8 +80,11 @@ export class CanvasNodeFactory {
|
||||
return node;
|
||||
}
|
||||
|
||||
public startEditing(node: ObsidianCanvasNode, theme: string) {
|
||||
public async startEditing(node: ObsidianCanvasNode, theme: string) {
|
||||
if (!this.initialized || !node) return;
|
||||
if (node.file === this.view.file) {
|
||||
await this.view.setEmbeddableIsEditingSelf();
|
||||
}
|
||||
node.startEditing();
|
||||
|
||||
const obsidianTheme = isObsidianThemeDark() ? "theme-dark" : "theme-light";
|
||||
@@ -118,6 +121,9 @@ export class CanvasNodeFactory {
|
||||
public stopEditing(node: ObsidianCanvasNode) {
|
||||
if(!this.initialized || !node) return;
|
||||
if(!node.child.editMode) return;
|
||||
if(node.file === this.view.file) {
|
||||
this.view.clearEmbeddableIsEditingSelf();
|
||||
}
|
||||
node.child.showPreview();
|
||||
}
|
||||
|
||||
|
||||
@@ -2,12 +2,11 @@ import { ExcalidrawEmbeddableElement, ExcalidrawFrameElement, ExcalidrawImageEle
|
||||
import { Mutable } from "@zsviczian/excalidraw/types/excalidraw/utility-types";
|
||||
import { getEA } from "src";
|
||||
import { ExcalidrawAutomate } from "src/ExcalidrawAutomate";
|
||||
import { getCropFileNameAndFolder, splitFolderAndFilename } from "./FileUtils";
|
||||
import { getCropFileNameAndFolder, getListOfTemplateFiles, splitFolderAndFilename } from "./FileUtils";
|
||||
import { Notice, TFile } from "obsidian";
|
||||
import ExcalidrawView from "src/ExcalidrawView";
|
||||
import { ExcalidrawImperativeAPI } from "@zsviczian/excalidraw/types/excalidraw/types";
|
||||
|
||||
export const CROPPED_PREFIX = "cropped_";
|
||||
export const ANNOTATED_PREFIX = "annotated_";
|
||||
|
||||
export const carveOutImage = async (sourceEA: ExcalidrawAutomate, viewImageEl: ExcalidrawImageElement) => {
|
||||
if(!viewImageEl?.fileId) return;
|
||||
@@ -133,7 +132,8 @@ export const createImageCropperFile = async (targetEA: ExcalidrawAutomate, image
|
||||
targetEA.canvas.theme = "light";
|
||||
targetEA.canvas.viewBackgroundColor = isPDF ? "#5d5d5d" : "#3d3d3d";
|
||||
|
||||
const templateFile = app.vault.getAbstractFileByPath(targetEA.plugin.settings.templateFilePath);
|
||||
const templates = getListOfTemplateFiles(targetEA.plugin);
|
||||
const templateFile = templates && templates.length > 0 ? templates[0] : null;
|
||||
if(templateFile && templateFile instanceof TFile) {
|
||||
const {appState} = await targetEA.getSceneFromFile(templateFile);
|
||||
if(appState) {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { App, TFile } from "obsidian";
|
||||
import { ExcalidrawAutomate } from "src/ExcalidrawAutomate";
|
||||
import { REGEX_LINK, REG_LINKINDEX_HYPERLINK } from "src/ExcalidrawData";
|
||||
import ExcalidrawView from "src/ExcalidrawView";
|
||||
import { ExcalidrawElement } from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
import { ExcalidrawElement, ExcalidrawFrameElement } from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
import { getLinkParts } from "./Utils";
|
||||
|
||||
export const insertImageToView = async (
|
||||
@@ -118,4 +118,15 @@ export const getExcalidrawFileForwardLinks = (app: App, excalidrawFile: TFile, s
|
||||
secondOrderLinks = [...linkset].join(" ");
|
||||
}
|
||||
return secondOrderLinks;
|
||||
}
|
||||
|
||||
export const getFrameBasedOnFrameNameOrId = (frameName: string, elements: ExcalidrawElement[]): ExcalidrawFrameElement | null => {
|
||||
const frames = elements
|
||||
.filter((el: ExcalidrawElement)=>el.type==="frame")
|
||||
.map((el: ExcalidrawFrameElement, idx: number)=>{
|
||||
return {el: el, id: el.id, name: el.name ?? `Frame ${String(idx+1).padStart(2,"0")}`};
|
||||
})
|
||||
.filter((item:any) => item.id === frameName || item.name === frameName)
|
||||
.map((item:any)=>item.el as ExcalidrawFrameElement);
|
||||
return frames.length === 1 ? frames[0] : null;
|
||||
}
|
||||
@@ -1,12 +1,11 @@
|
||||
import { DataURL } from "@zsviczian/excalidraw/types/excalidraw/types";
|
||||
import { loadPdfJs, normalizePath, Notice, requestUrl, RequestUrlResponse, TAbstractFile, TFile, TFolder, Vault } from "obsidian";
|
||||
import { DEVICE, URLFETCHTIMEOUT } from "src/constants/constants";
|
||||
import { App, loadPdfJs, normalizePath, Notice, requestUrl, RequestUrlResponse, TAbstractFile, TFile, TFolder, Vault } from "obsidian";
|
||||
import { DEVICE, FRONTMATTER_KEYS, URLFETCHTIMEOUT } from "src/constants/constants";
|
||||
import { IMAGE_MIME_TYPES, MimeType } from "src/EmbeddedFileLoader";
|
||||
import { ExcalidrawSettings } from "src/settings";
|
||||
import { errorlog, getDataURL } from "./Utils";
|
||||
import ExcalidrawPlugin from "src/main";
|
||||
import ExcalidrawView from "src/ExcalidrawView";
|
||||
import { CROPPED_PREFIX } from "./CarveOut";
|
||||
import { ANNOTATED_PREFIX, CROPPED_PREFIX } from "./CarveOut";
|
||||
import { getAttachmentsFolderAndFilePath } from "./ObsidianUtils";
|
||||
|
||||
/**
|
||||
@@ -368,13 +367,24 @@ export const getInternalLinkOrFileURLLink = (
|
||||
*/
|
||||
export const getLink = (
|
||||
plugin: ExcalidrawPlugin,
|
||||
{ embed = true, path, alias }: { embed?: boolean; path: string; alias?: string }
|
||||
{ embed = true, path, alias }: { embed?: boolean; path: string; alias?: string },
|
||||
wikilinkOverride?: boolean
|
||||
):string => {
|
||||
return plugin.settings.embedWikiLink
|
||||
const isWikiLink = (typeof wikilinkOverride !== "undefined")
|
||||
? wikilinkOverride
|
||||
: plugin.settings.embedWikiLink;
|
||||
return isWikiLink
|
||||
? `${embed ? "!" : ""}[[${path}${alias ? `|${alias}` : ""}]]`
|
||||
: `${embed ? "!" : ""}[${alias ?? ""}](${encodeURI(path)})`
|
||||
}
|
||||
|
||||
export const getAliasWithSize = (alias: string, size: string): string => {
|
||||
if(alias && alias !== "") {
|
||||
return `${alias}${size?`|${size}`:""}`;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
export const getCropFileNameAndFolder = async (plugin: ExcalidrawPlugin, hostPath: string, baseNewFileName: string):Promise<{folderpath: string, filename: string}> => {
|
||||
let prefix = plugin.settings.cropPrefix;
|
||||
if(!prefix || prefix.trim() === "") prefix = CROPPED_PREFIX;
|
||||
@@ -386,4 +396,47 @@ export const getCropFileNameAndFolder = async (plugin: ExcalidrawPlugin, hostPat
|
||||
const folderpath = normalizePath(plugin.settings.cropFolder);
|
||||
await checkAndCreateFolder(folderpath);
|
||||
return {folderpath, filename};
|
||||
}
|
||||
}
|
||||
|
||||
export const getAnnotationFileNameAndFolder = async (plugin: ExcalidrawPlugin, hostPath: string, baseNewFileName: string):Promise<{folderpath: string, filename: string}> => {
|
||||
let prefix = plugin.settings.annotatePrefix;
|
||||
if(!prefix || prefix.trim() === "") prefix = ANNOTATED_PREFIX;
|
||||
const filename = prefix + baseNewFileName + ".md";
|
||||
if(!plugin.settings.annotateFolder || plugin.settings.annotateFolder.trim() === "") {
|
||||
const folderpath = (await getAttachmentsFolderAndFilePath(plugin.app, hostPath, filename)).folder;
|
||||
return {folderpath, filename};
|
||||
}
|
||||
const folderpath = normalizePath(plugin.settings.annotateFolder);
|
||||
await checkAndCreateFolder(folderpath);
|
||||
return {folderpath, filename};
|
||||
}
|
||||
|
||||
export const getListOfTemplateFiles = (plugin: ExcalidrawPlugin):TFile[] | null => {
|
||||
const normalizedTemplatePath = normalizePath(plugin.settings.templateFilePath);
|
||||
const template = plugin.app.vault.getAbstractFileByPath(normalizedTemplatePath);
|
||||
if(template && template instanceof TFolder) {
|
||||
return plugin.app.vault.getFiles()
|
||||
.filter(f=>f.path.startsWith(template.path))
|
||||
.filter(f=>plugin.isExcalidrawFile(f))
|
||||
.sort((a,b)=>a.path.localeCompare(b.path))
|
||||
}
|
||||
if(template && template instanceof TFile) {
|
||||
return [template];
|
||||
}
|
||||
const templateFile = plugin.app.metadataCache.getFirstLinkpathDest(
|
||||
normalizedTemplatePath,
|
||||
"",
|
||||
);
|
||||
if(templateFile) {
|
||||
return [templateFile];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export const fileShouldDefaultAsExcalidraw = (path:string, app:App):boolean => {
|
||||
if(!path) return false;
|
||||
const cache = app.metadataCache.getCache(path);
|
||||
return cache?.frontmatter &&
|
||||
cache.frontmatter[FRONTMATTER_KEYS["plugin"].name] &&
|
||||
!Boolean(cache.frontmatter[FRONTMATTER_KEYS["open-as-markdown"].name]);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import {
|
||||
App,
|
||||
FrontMatterCache,
|
||||
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";
|
||||
|
||||
export const getParentOfClass = (element: Element, cssClass: string):HTMLElement | null => {
|
||||
let parent = element.parentElement;
|
||||
@@ -288,4 +290,50 @@ export const openLeaf = ({
|
||||
leaf = fnGetLeaf();
|
||||
const promise = leaf.openFile(file, openState);
|
||||
return {leaf, promise};
|
||||
}
|
||||
}
|
||||
|
||||
export const mergeMarkdownFiles = (template: string, target: string): string => {
|
||||
// Extract frontmatter from the template
|
||||
const templateFrontmatterEnd = template.indexOf('---', 4); // Find end of frontmatter
|
||||
const templateFrontmatter = template.substring(4, templateFrontmatterEnd).trim();
|
||||
const templateContent = template.substring(templateFrontmatterEnd + 3); // Skip frontmatter and ---
|
||||
|
||||
// Parse template frontmatter
|
||||
const templateFrontmatterObj: FrontMatterCache = yaml.load(templateFrontmatter) || {};
|
||||
|
||||
// Extract frontmatter from the target if it exists
|
||||
let targetFrontmatterObj: FrontMatterCache = {};
|
||||
let targetContent = '';
|
||||
if (target.includes('---')) {
|
||||
const targetFrontmatterEnd = target.indexOf('---', 4); // Find end of frontmatter
|
||||
const targetFrontmatter = target.substring(4, targetFrontmatterEnd).trim();
|
||||
targetContent = target.substring(targetFrontmatterEnd + 3); // Skip frontmatter and ---
|
||||
|
||||
// Parse target frontmatter
|
||||
targetFrontmatterObj = yaml.load(targetFrontmatter) || {};
|
||||
} else {
|
||||
// If target doesn't have frontmatter, consider the entire content as target content
|
||||
targetContent = target.trim();
|
||||
}
|
||||
|
||||
// Merge frontmatter with target values taking precedence
|
||||
const mergedFrontmatter: FrontMatterCache = { ...templateFrontmatterObj };
|
||||
|
||||
// Merge arrays by combining and removing duplicates
|
||||
for (const key in targetFrontmatterObj) {
|
||||
if (Array.isArray(targetFrontmatterObj[key]) && Array.isArray(mergedFrontmatter[key])) {
|
||||
const combinedArray = [...new Set([...mergedFrontmatter[key], ...targetFrontmatterObj[key]])];
|
||||
mergedFrontmatter[key] = combinedArray;
|
||||
} else {
|
||||
mergedFrontmatter[key] = targetFrontmatterObj[key];
|
||||
}
|
||||
}
|
||||
|
||||
// Convert merged frontmatter back to YAML
|
||||
const mergedFrontmatterYaml = yaml.dump(mergedFrontmatter);
|
||||
|
||||
// Concatenate frontmatter and content
|
||||
const mergedMarkdown = `---\n${mergedFrontmatterYaml}---\n${targetContent}\n\n${templateContent.trim()}\n`;
|
||||
|
||||
return mergedMarkdown;
|
||||
};
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
request,
|
||||
requestUrl,
|
||||
TFile,
|
||||
TFolder,
|
||||
} from "obsidian";
|
||||
import { Random } from "roughjs/bin/math";
|
||||
import { BinaryFileData, DataURL} from "@zsviczian/excalidraw/types/excalidraw/types";
|
||||
@@ -17,6 +18,7 @@ import {
|
||||
exportToBlob,
|
||||
IMAGE_TYPES,
|
||||
FRONTMATTER_KEYS,
|
||||
EXCALIDRAW_PLUGIN,
|
||||
} from "../constants/constants";
|
||||
import ExcalidrawPlugin from "../main";
|
||||
import { ExcalidrawElement } from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
@@ -29,6 +31,8 @@ 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;
|
||||
@@ -282,7 +286,7 @@ export const getSVG = async (
|
||||
svg = await cropObject.getCroppedSVG();
|
||||
} else {
|
||||
svg = await exportToSvg({
|
||||
elements,
|
||||
elements: elements.filter((el:ExcalidrawElement)=>el.isDeleted !== true),
|
||||
appState: {
|
||||
exportBackground: exportSettings.withBackground,
|
||||
exportWithDarkMode: exportSettings.withTheme
|
||||
@@ -334,7 +338,7 @@ export const getPNG = async (
|
||||
}
|
||||
|
||||
return await exportToBlob({
|
||||
elements: scene.elements,
|
||||
elements: scene.elements.filter((el:ExcalidrawElement)=>el.isDeleted !== true),
|
||||
appState: {
|
||||
exportBackground: exportSettings.withBackground,
|
||||
exportWithDarkMode: exportSettings.withTheme
|
||||
@@ -433,13 +437,21 @@ export const addAppendUpdateCustomData = (el: Mutable<ExcalidrawElement>, newDat
|
||||
|
||||
export const scaleLoadedImage = (
|
||||
scene: any,
|
||||
files: any,
|
||||
files: any
|
||||
): { dirty: boolean; scene: any } => {
|
||||
let dirty = false;
|
||||
if (!files || !scene) {
|
||||
return { dirty, scene };
|
||||
}
|
||||
for (const f of files) {
|
||||
|
||||
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;
|
||||
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)
|
||||
})) {
|
||||
const [w_image, h_image] = [f.size.width, f.size.height];
|
||||
const imageAspectRatio = f.size.width / f.size.height;
|
||||
scene.elements
|
||||
@@ -586,6 +598,22 @@ export const getExportTheme = (
|
||||
return plugin.settings.exportWithTheme ? theme : "light";
|
||||
};
|
||||
|
||||
export const shouldEmbedScene = (
|
||||
plugin: ExcalidrawPlugin,
|
||||
file: TFile
|
||||
): boolean => {
|
||||
if (file) {
|
||||
const fileCache = plugin.app.metadataCache.getFileCache(file);
|
||||
if (
|
||||
fileCache?.frontmatter &&
|
||||
fileCache.frontmatter[FRONTMATTER_KEYS["export-embed-scene"].name] != null
|
||||
) {
|
||||
return fileCache.frontmatter[FRONTMATTER_KEYS["export-embed-scene"].name];
|
||||
}
|
||||
}
|
||||
return plugin.settings.exportEmbedScene;
|
||||
};
|
||||
|
||||
export const hasExportBackground = (
|
||||
plugin: ExcalidrawPlugin,
|
||||
file: TFile,
|
||||
@@ -739,19 +767,19 @@ export const getContainerElement = (
|
||||
element:
|
||||
| (ExcalidrawElement & { containerId: ExcalidrawElement["id"] | null })
|
||||
| null,
|
||||
scene: ExcalidrawScene,
|
||||
scene: any,
|
||||
) => {
|
||||
if (!element) {
|
||||
return null;
|
||||
}
|
||||
if (element.containerId) {
|
||||
return scene.elements.filter(el=>el.id === element.containerId)[0] ?? null;
|
||||
return scene.elements.find((el:ExcalidrawElement)=>el.id === element.containerId) ?? null;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export const updateFrontmatterInString = (data:string, keyValuePairs: [string,string][]):string => {
|
||||
if(!data) return data;
|
||||
export const updateFrontmatterInString = (data:string, keyValuePairs?: [string,string][]):string => {
|
||||
if(!data || !keyValuePairs) return data;
|
||||
for(const kvp of keyValuePairs) {
|
||||
const r = new RegExp(`${kvp[0]}:\\s.*\\n`,"g");
|
||||
data = data.match(r)
|
||||
|
||||
20
styles.css
20
styles.css
@@ -559,4 +559,24 @@ img.excalidraw-cropped-pdfpage,
|
||||
.excalidraw .pdf-toolbar,
|
||||
.excalidraw .pdf-container {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ex-opacity-30 {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.ex-opacity-15 {
|
||||
opacity: 0.15;
|
||||
}
|
||||
|
||||
.ex-opacity-8 {
|
||||
opacity: 0.08;
|
||||
}
|
||||
|
||||
.ex-opacity-5 {
|
||||
opacity: 0.05;
|
||||
}
|
||||
|
||||
.ex-opacity-0 {
|
||||
opacity: 0;
|
||||
}
|
||||
Reference in New Issue
Block a user