mirror of
https://github.com/zsviczian/obsidian-excalidraw-plugin.git
synced 2025-08-06 05:46:28 +00:00
Compare commits
10 Commits
2.1.1
...
2.1.6.1-be
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
491eb83d35 | ||
|
|
35cf0802d1 | ||
|
|
f768548f60 | ||
|
|
37789f9907 | ||
|
|
131294464e | ||
|
|
b7652a41f8 | ||
|
|
91c5f85ec6 | ||
|
|
985983b31d | ||
|
|
9a27e38ce2 | ||
|
|
0017ed7c92 |
12
.github/ISSUE_TEMPLATE/bug_report.md
vendored
12
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,12 +1,22 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help me improve Excalidraw
|
||||
about: When something is clearly broken. Everything else is a feature request.
|
||||
title: 'BUG: '
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
Help me help you. I am a one man show doing this plugin as a part time hobby. There is no point in flooding me with issues, if there are too many, and they are poorly documented, I will just ignore them. Sorry...
|
||||
|
||||
Before creating a bug report, please
|
||||
1. review recent release notes - maybe there is already an answer,
|
||||
2. search issues (including closed ones) to see if there is anything similar.
|
||||
|
||||
⚠️ I will have to close all recorded bugs that do not provide this background information. Sorry, I need to control my workload/time. ⚠️
|
||||
|
||||
--------
|
||||
|
||||
**Your environment**
|
||||
Please run `Command Palette/Show Debug info` in Obsidian and paste the result here.
|
||||
|
||||
|
||||
@@ -45,6 +45,16 @@ 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(",")
|
||||
@@ -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) {
|
||||
|
||||
@@ -41,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;
|
||||
@@ -738,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) {
|
||||
@@ -766,4 +775,4 @@ if(window.ExcalidrawSlideshow && (window.ExcalidrawSlideshow.script === utils.sc
|
||||
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.1.1",
|
||||
"version": "2.1.6",
|
||||
"minAppVersion": "1.1.6",
|
||||
"description": "An Obsidian plugin to edit and view Excalidraw drawings",
|
||||
"author": "Zsolt Viczian",
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@zsviczian/excalidraw": "0.17.1-obsidian-19",
|
||||
"@zsviczian/excalidraw": "0.17.1-obsidian-20",
|
||||
"chroma-js": "^2.4.2",
|
||||
"clsx": "^2.0.0",
|
||||
"colormaster": "^1.2.1",
|
||||
|
||||
@@ -3,8 +3,6 @@ import ExcalidrawPlugin from "src/main";
|
||||
import { HideTextBetweenCommentsExtension } from "./Fadeout";
|
||||
export const EDITOR_FADEOUT = "fadeOutExcalidrawMarkup";
|
||||
|
||||
export let excalidrawPlugin: ExcalidrawPlugin = null;
|
||||
|
||||
const editorExtensions: {[key:string]:Extension}= {
|
||||
[EDITOR_FADEOUT]: HideTextBetweenCommentsExtension,
|
||||
}
|
||||
@@ -12,9 +10,7 @@ const editorExtensions: {[key:string]:Extension}= {
|
||||
export class EditorHandler {
|
||||
private activeEditorExtensions: Extension[] = [];
|
||||
|
||||
constructor(private plugin: ExcalidrawPlugin) {
|
||||
excalidrawPlugin = plugin;
|
||||
}
|
||||
constructor(private plugin: ExcalidrawPlugin) {}
|
||||
|
||||
setup(): void {
|
||||
this.plugin.registerEditorExtension(this.activeEditorExtensions);
|
||||
|
||||
@@ -601,6 +601,7 @@ export class ExcalidrawAutomate {
|
||||
"excalidraw-export-dark"?: boolean;
|
||||
"excalidraw-export-padding"?: number;
|
||||
"excalidraw-export-pngscale"?: number;
|
||||
"excalidraw-export-embed-scene"?: boolean;
|
||||
"excalidraw-default-mode"?: "view" | "zen";
|
||||
"excalidraw-onload-script"?: string;
|
||||
"excalidraw-linkbutton-opacity"?: number;
|
||||
|
||||
@@ -51,6 +51,7 @@ import { BinaryFiles, DataURL, SceneData } from "@zsviczian/excalidraw/types/exc
|
||||
import { EmbeddedFile, MimeType } from "./EmbeddedFileLoader";
|
||||
import { ConfirmationPrompt } from "./dialogs/Prompt";
|
||||
import { getMermaidImageElements, getMermaidText, shouldRenderMermaid } from "./utils/MermaidUtils";
|
||||
import { add } from "@zsviczian/excalidraw/types/excalidraw/ga";
|
||||
|
||||
type SceneDataWithFiles = SceneData & { files: BinaryFiles };
|
||||
|
||||
@@ -240,7 +241,11 @@ const estimateMaxLineLen = (text: string, originalText: string): number => {
|
||||
const wrap = (text: string, lineLen: number) =>
|
||||
lineLen ? wrapTextAtCharLength(text, lineLen, false, 0) : text;
|
||||
|
||||
const RE_TEXTELEMENTS = new RegExp(`^(%%\n)?${MD_TEXTELEMENTS}(?:\n|$)`, "m");
|
||||
//WITHSECTION refers to back of the card note (see this.inputEl.onkeyup in SelectCard.ts)
|
||||
const RE_TEXTELEMENTS_WITHSECTION_OK = new RegExp(`^#\n%%\n${MD_TEXTELEMENTS}(?:\n|$)`, "m");
|
||||
const RE_TEXTELEMENTS_WITHSECTION_NOTOK = new RegExp(`#\n%%\n${MD_TEXTELEMENTS}(?:\n|$)`, "m");
|
||||
const RE_TEXTELEMENTS_NOSECTION_OK = 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
|
||||
@@ -251,19 +256,70 @@ const RE_TEXTELEMENTS_FALLBACK_2 = new RegExp(`(.*)${MD_TEXTELEMENTS}(?:\n|$)`,
|
||||
const RE_DRAWING = new RegExp(`(%%\n)?${MD_DRAWING}\n`);
|
||||
|
||||
export const getExcalidrawMarkdownHeaderSection = (data:string, keys?:[string,string][]):string => {
|
||||
let trimLocation = data.search(RE_TEXTELEMENTS);
|
||||
|
||||
/* Expected markdown structure:
|
||||
bla bla bla
|
||||
#
|
||||
%%
|
||||
# Text Elements
|
||||
*/
|
||||
let trimLocation = data.search(RE_TEXTELEMENTS_WITHSECTION_OK);
|
||||
let shouldFixTrailingHashtag = false;
|
||||
if(trimLocation > 0) {
|
||||
trimLocation += 2;
|
||||
}
|
||||
|
||||
/* Expected markdown structure:
|
||||
bla bla bla#
|
||||
%%
|
||||
# Text Elements
|
||||
*/
|
||||
if(trimLocation === -1) {
|
||||
trimLocation = data.search(RE_TEXTELEMENTS_WITHSECTION_NOTOK);
|
||||
if(trimLocation > 0) {
|
||||
shouldFixTrailingHashtag = true;
|
||||
}
|
||||
}
|
||||
/* Expected markdown structure
|
||||
a)
|
||||
bla bla bla
|
||||
%%
|
||||
# Text Elements
|
||||
b)
|
||||
bla bla bla
|
||||
# Text Elements
|
||||
*/
|
||||
if(trimLocation === -1) {
|
||||
trimLocation = data.search(RE_TEXTELEMENTS_NOSECTION_OK);
|
||||
}
|
||||
/* Expected markdown structure:
|
||||
bla bla bla%%
|
||||
# Text Elements
|
||||
*/
|
||||
if(trimLocation === -1) {
|
||||
const res = data.match(RE_TEXTELEMENTS_FALLBACK_1);
|
||||
if(res && Boolean(res[1])) {
|
||||
trimLocation = res.index + res[1].length;
|
||||
}
|
||||
}
|
||||
/* Expected markdown structure:
|
||||
bla bla bla# Text Elements
|
||||
*/
|
||||
if(trimLocation === -1) {
|
||||
const res = data.match(RE_TEXTELEMENTS_FALLBACK_2);
|
||||
if(res && Boolean(res[1])) {
|
||||
trimLocation = res.index + res[1].length;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Expected markdown structure:
|
||||
a)
|
||||
bla bla bla
|
||||
# Drawing
|
||||
b)
|
||||
bla bla bla
|
||||
%%
|
||||
# Drawing
|
||||
*/
|
||||
if (trimLocation === -1) {
|
||||
trimLocation = data.search(RE_DRAWING);
|
||||
}
|
||||
@@ -273,12 +329,14 @@ export const getExcalidrawMarkdownHeaderSection = (data:string, keys?:[string,st
|
||||
|
||||
let header = updateFrontmatterInString(data.substring(0, trimLocation),keys);
|
||||
//this should be removed at a later time. Left it here to remediate 1.4.9 mistake
|
||||
const REG_IMG = /(^---[\w\W]*?---\n)(!\[\[.*?]]\n(%%\n)?)/m; //(%%\n)? because of 1.4.8-beta... to be backward compatible with anyone who installed that version
|
||||
/*const REG_IMG = /(^---[\w\W]*?---\n)(!\[\[.*?]]\n(%%\n)?)/m; //(%%\n)? because of 1.4.8-beta... to be backward compatible with anyone who installed that version
|
||||
if (header.match(REG_IMG)) {
|
||||
header = header.replace(REG_IMG, "$1");
|
||||
}
|
||||
}*/
|
||||
//end of remove
|
||||
return header.endsWith("\n") ? header : (header + "\n");
|
||||
return shouldFixTrailingHashtag
|
||||
? header + "\n#\n"
|
||||
: header.endsWith("\n") ? header : (header + "\n");
|
||||
}
|
||||
|
||||
|
||||
@@ -580,19 +638,36 @@ export class ExcalidrawData {
|
||||
|
||||
data = data.substring(0, sceneJSONandPOS.pos);
|
||||
|
||||
//The Markdown # Text Elements take priority over the JSON text elements. Assuming the scenario in which the link was updated due to filename changes
|
||||
//The Markdown # Text Elements take priority over the JSON text elements. Assuming the scenario in which the
|
||||
//link was updated due to filename changes
|
||||
//The .excalidraw JSON is modified to reflect the MD in case of difference
|
||||
//Read the text elements into the textElements Map
|
||||
let position = data.search(RE_TEXTELEMENTS);
|
||||
let position = data.search(RE_TEXTELEMENTS_NOSECTION_OK);
|
||||
if (position === -1) {
|
||||
//resillience in case back of the note was saved right on top of text elements
|
||||
// # back of note section
|
||||
// ....# Text Elements
|
||||
// ....
|
||||
// --------------
|
||||
// instead of
|
||||
// --------------
|
||||
// # back of note section
|
||||
// ....
|
||||
// # Text Elements
|
||||
position = data.search(RE_TEXTELEMENTS_FALLBACK_2);
|
||||
}
|
||||
if (position === -1) {
|
||||
await this.setTextMode(textMode, false);
|
||||
this.loaded = true;
|
||||
return true; //Text Elements header does not exist
|
||||
}
|
||||
const textElementsMatch = data.match(new RegExp(`^((%%\n)?${MD_TEXTELEMENTS}(?:\n|$))`, "m"))[0]
|
||||
position += textElementsMatch.length;
|
||||
|
||||
data = data.slice(position);
|
||||
const normalMatch = data.match(new RegExp(`^((%%\n)?${MD_TEXTELEMENTS}(?:\n|$))`, "m"));
|
||||
const textElementsMatch = normalMatch
|
||||
? normalMatch[0]
|
||||
: data.match(new RegExp(`(.*${MD_TEXTELEMENTS}(?:\n|$))`, "m"))[0];
|
||||
|
||||
data = data.slice(textElementsMatch.length);
|
||||
this.textElementCommentedOut = textElementsMatch.startsWith("%%\n");
|
||||
position = 0;
|
||||
let parts;
|
||||
@@ -1360,14 +1435,18 @@ export class ExcalidrawData {
|
||||
const processedIds = new Set<string>();
|
||||
fileIds.forEach((fileId,idx)=>{
|
||||
if(processedIds.has(fileId)) {
|
||||
const file = this.getFile(fileId);
|
||||
const embeddedFile = this.getFile(fileId);
|
||||
const equation = this.getEquation(fileId);
|
||||
const mermaid = this.getMermaid(fileId);
|
||||
|
||||
|
||||
|
||||
//images should have a single reference, but equations, and markdown embeds should have as many as instances of the file in the scene
|
||||
if(file && (file.isHyperLink || file.isLocalLink || (file.file && (file.file.extension !== "md" || this.plugin.isExcalidrawFile(file.file))))) {
|
||||
if (embeddedFile &&
|
||||
(embeddedFile.isHyperLink || embeddedFile.isLocalLink ||
|
||||
(embeddedFile.file &&
|
||||
(embeddedFile.file.extension !== "md" || this.plugin.isExcalidrawFile(embeddedFile.file))
|
||||
)
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if(mermaid) {
|
||||
@@ -1379,6 +1458,11 @@ export class ExcalidrawData {
|
||||
return;
|
||||
}
|
||||
|
||||
if(!embeddedFile && !equation && !mermaid) {
|
||||
//processing freshly pasted images from likely anotehr instance of excalidraw (e.g. Excalidraw.com, or another Obsidian instance)
|
||||
return;
|
||||
}
|
||||
|
||||
const newId = fileid();
|
||||
(scene
|
||||
.elements
|
||||
@@ -1387,8 +1471,8 @@ export class ExcalidrawData {
|
||||
.fileId = newId;
|
||||
dirty = true;
|
||||
processedIds.add(newId);
|
||||
if(file) {
|
||||
this.setFile(newId as FileId,new EmbeddedFile(this.plugin,this.file.path,file.linkParts.original));
|
||||
if(embeddedFile) {
|
||||
this.setFile(newId as FileId,new EmbeddedFile(this.plugin,this.file.path,embeddedFile.linkParts.original));
|
||||
}
|
||||
if(equation) {
|
||||
this.setEquation(newId as FileId, {latex:equation.latex, isLoaded:false});
|
||||
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
MarkdownView,
|
||||
request,
|
||||
requireApiVersion,
|
||||
requestUrl,
|
||||
} from "obsidian";
|
||||
//import * as React from "react";
|
||||
//import * as ReactDOM from "react-dom";
|
||||
@@ -81,12 +80,10 @@ import {
|
||||
} from "./utils/FileUtils";
|
||||
import {
|
||||
checkExcalidrawVersion,
|
||||
debug,
|
||||
embedFontsInSVG,
|
||||
errorlog,
|
||||
getEmbeddedFilenameParts,
|
||||
getExportTheme,
|
||||
getLinkParts,
|
||||
getPNG,
|
||||
getPNGScale,
|
||||
getSVG,
|
||||
@@ -101,8 +98,10 @@ import {
|
||||
isContainer,
|
||||
fragWithHTML,
|
||||
isMaskFile,
|
||||
shouldEmbedScene,
|
||||
getContainerElement,
|
||||
} from "./utils/Utils";
|
||||
import { cleanSectionHeading, getLeaf, getParentOfClass, obsidianPDFQuoteWithRef, openLeaf } from "./utils/ObsidianUtils";
|
||||
import { cleanBlockRef, 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";
|
||||
@@ -138,6 +137,9 @@ import { extractCodeBlocks, postOpenAI } from "./utils/AIUtils";
|
||||
import { Mutable } from "@zsviczian/excalidraw/types/excalidraw/utility-types";
|
||||
import { SelectCard } from "./dialogs/SelectCard";
|
||||
|
||||
const EMBEDDABLE_SEMAPHORE_TIMEOUT = 2000;
|
||||
const PREVENT_RELOAD_TIMEOUT = 2000;
|
||||
|
||||
declare const PLUGIN_VERSION:string;
|
||||
|
||||
declare module "obsidian" {
|
||||
@@ -390,7 +392,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
if (!this.excalidrawAPI || !this.file) {
|
||||
return;
|
||||
}
|
||||
if (this.app.isMobile) {
|
||||
if (DEVICE.isMobile) {
|
||||
const prompt = new Prompt(
|
||||
this.app,
|
||||
"Please provide filename",
|
||||
@@ -433,6 +435,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,
|
||||
@@ -511,6 +517,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,
|
||||
@@ -607,7 +618,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
public setPreventReload() {
|
||||
this.semaphores.preventReload = true;
|
||||
const self = this;
|
||||
this.preventReloadResetTimer = setTimeout(()=>self.semaphores.preventReload = false,2000);
|
||||
this.preventReloadResetTimer = setTimeout(()=>self.semaphores.preventReload = false,PREVENT_RELOAD_TIMEOUT);
|
||||
}
|
||||
|
||||
public clearPreventReloadTimer() {
|
||||
@@ -634,7 +645,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
public clearEmbeddableIsEditingSelf() {
|
||||
const self = this;
|
||||
this.clearEmbeddableIsEditingSelfTimer();
|
||||
this.editingSelfResetTimer = setTimeout(()=>self.semaphores.embeddableIsEditingSelf = false,2000);
|
||||
this.editingSelfResetTimer = setTimeout(()=>self.semaphores.embeddableIsEditingSelf = false,EMBEDDABLE_SEMAPHORE_TIMEOUT);
|
||||
}
|
||||
|
||||
async save(preventReload: boolean = true, forcesave: boolean = false) {
|
||||
@@ -668,30 +679,28 @@ export default class ExcalidrawView extends TextFileView {
|
||||
}
|
||||
|
||||
try {
|
||||
const allowSave = Boolean (
|
||||
(this.semaphores.dirty !== null && this.semaphores.dirty) ||
|
||||
this.semaphores.autosaving ||
|
||||
forcesave
|
||||
); //dirty == false when view.file == null;
|
||||
const scene = this.getScene();
|
||||
|
||||
if (this.compatibilityMode) {
|
||||
await this.excalidrawData.syncElements(scene);
|
||||
} else if (
|
||||
await this.excalidrawData.syncElements(scene, this.excalidrawAPI.getAppState().selectedElementIds)
|
||||
&& !this.semaphores.popoutUnload //Obsidian going black after REACT 18 migration when closing last leaf on popout
|
||||
) {
|
||||
await this.loadDrawing(
|
||||
false,
|
||||
this.excalidrawAPI.getSceneElementsIncludingDeleted().filter((el:ExcalidrawElement)=>el.isDeleted)
|
||||
);
|
||||
}
|
||||
const allowSave = this.isDirty() || forcesave; //removed this.semaphores.autosaving
|
||||
if(isDebugMode) console.log({allowSave, isDirty: this.isDirty(), autosaving: this.semaphores.autosaving, forcesave});
|
||||
|
||||
if (allowSave) {
|
||||
const scene = this.getScene();
|
||||
|
||||
if (this.compatibilityMode) {
|
||||
await this.excalidrawData.syncElements(scene);
|
||||
} else if (
|
||||
await this.excalidrawData.syncElements(scene, this.excalidrawAPI.getAppState().selectedElementIds)
|
||||
&& !this.semaphores.popoutUnload //Obsidian going black after REACT 18 migration when closing last leaf on popout
|
||||
) {
|
||||
await this.loadDrawing(
|
||||
false,
|
||||
this.excalidrawAPI.getSceneElementsIncludingDeleted().filter((el:ExcalidrawElement)=>el.isDeleted)
|
||||
);
|
||||
}
|
||||
|
||||
//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.
|
||||
|
||||
this.clearDirty();
|
||||
this.clearPreventReloadTimer();
|
||||
|
||||
this.semaphores.preventReload = preventReload;
|
||||
@@ -704,7 +713,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
triggerReload = (this.lastSaveTimestamp === this.file.stat.mtime) &&
|
||||
!preventReload && forcesave;
|
||||
this.lastSaveTimestamp = this.file.stat.mtime;
|
||||
this.clearDirty();
|
||||
//this.clearDirty(); //moved to right after allow save, to avoid autosave collision with load drawing
|
||||
|
||||
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/629
|
||||
//there were odd cases when preventReload semaphore did not get cleared and consequently a synchronized image
|
||||
@@ -777,6 +786,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
[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["export-embed-scene"].name, this.exportDialog.embedScene ? "true" : "false"],
|
||||
]
|
||||
: [
|
||||
[FRONTMATTER_KEYS["plugin"].name, this.textMode === TextMode.raw ? "raw" : "parsed"]
|
||||
@@ -928,6 +938,46 @@ export default class ExcalidrawView extends TextFileView {
|
||||
return false;
|
||||
}
|
||||
|
||||
private getLinkTextForElement(
|
||||
selectedText:SelectedElementWithLink,
|
||||
selectedElementWithLink?:SelectedElementWithLink
|
||||
): {
|
||||
linkText: string,
|
||||
selectedElement: ExcalidrawElement,
|
||||
} {
|
||||
if (selectedText?.id || selectedElementWithLink?.id) {
|
||||
const selectedTextElement: ExcalidrawTextElement = selectedText.id
|
||||
? this.excalidrawAPI.getSceneElements().find((el:ExcalidrawElement)=>el.id === selectedText.id)
|
||||
: null;
|
||||
|
||||
const selectedElement = selectedElementWithLink.id
|
||||
? this.excalidrawAPI.getSceneElements().find((el:ExcalidrawElement)=>el.id === selectedElementWithLink.id)
|
||||
: null;
|
||||
|
||||
let 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 || partsArray.length === 0) {
|
||||
linkText = selectedTextElement?.link;
|
||||
}
|
||||
}
|
||||
return {linkText, selectedElement: selectedTextElement ?? selectedElement};
|
||||
}
|
||||
return {linkText: null, selectedElement: null};
|
||||
}
|
||||
|
||||
async linkClick(
|
||||
ev: MouseEvent | null,
|
||||
selectedText: SelectedElementWithLink,
|
||||
@@ -945,23 +995,16 @@ export default class ExcalidrawView extends TextFileView {
|
||||
|
||||
let file = null;
|
||||
let subpath: string = null;
|
||||
let linkText: string = null;
|
||||
|
||||
if (selectedText?.id || selectedElementWithLink?.id) {
|
||||
linkText =
|
||||
selectedElementWithLink?.text ??
|
||||
(this.textMode === TextMode.parsed
|
||||
? this.excalidrawData.getRawText(selectedText.id)
|
||||
: selectedText.text);
|
||||
let {linkText, selectedElement} = this.getLinkTextForElement(selectedText, selectedElementWithLink);
|
||||
|
||||
//if (selectedText?.id || selectedElementWithLink?.id) {
|
||||
if (selectedElement) {
|
||||
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(selectedElement,linkText,ev)) return;
|
||||
if(openExternalLink(linkText, this.app)) return;
|
||||
|
||||
const result = await linkPrompt(linkText, this.app, this);
|
||||
@@ -978,8 +1021,6 @@ export default class ExcalidrawView extends TextFileView {
|
||||
selectedImage.fileId,
|
||||
).latex;
|
||||
GenericInputPrompt.Prompt(this,this.plugin,this.app,t("ENTER_LATEX"),undefined,equation, undefined, 3).then(async (formula: string) => {
|
||||
// const prompt = new Prompt(this.app, t("ENTER_LATEX"), equation, "");
|
||||
// prompt.openAndGetValue(async (formula: string) => {
|
||||
if (!formula || formula === equation) {
|
||||
return;
|
||||
}
|
||||
@@ -1150,7 +1191,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
? null
|
||||
: this.getSelectedImageElement();
|
||||
const selectedElementWithLink =
|
||||
selectedImage?.id || selectedText?.id
|
||||
(selectedImage?.id || selectedText?.id)
|
||||
? null
|
||||
: this.getSelectedElementWithLink();
|
||||
this.linkClick(
|
||||
@@ -1215,8 +1256,8 @@ export default class ExcalidrawView extends TextFileView {
|
||||
onload() {
|
||||
const apiMissing = Boolean(typeof this.containerEl.onWindowMigrated === "undefined")
|
||||
//@ts-ignore
|
||||
if(!app.isMobile && !apiMissing) this.containerEl.onWindowMigrated(()=>this.leaf.rebuildView());
|
||||
const doc = app.isMobile?document:this.containerEl.ownerDocument;
|
||||
if(!DEVICE.isMobile && !apiMissing) this.containerEl.onWindowMigrated(()=>this.leaf.rebuildView());
|
||||
const doc = DEVICE.isMobile?document:this.containerEl.ownerDocument;
|
||||
this.ownerDocument = doc;
|
||||
this.ownerWindow = this.ownerDocument.defaultView;
|
||||
this.plugin.getPackage(this.ownerWindow);
|
||||
@@ -1304,7 +1345,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
const self = this;
|
||||
this.slidingPanesListner = () => {
|
||||
if (self.excalidrawAPI) {
|
||||
self.refresh();
|
||||
self.refreshCanvasOffset();
|
||||
}
|
||||
};
|
||||
let rootSplit = this.app.workspace.rootSplit as WorkspaceItem as WorkspaceItemExt;
|
||||
@@ -1348,7 +1389,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
const { offsetLeft, offsetTop } = target;
|
||||
if (offsetLeft !== self.offsetLeft || offsetTop != self.offsetTop) {
|
||||
if (self.excalidrawAPI) {
|
||||
self.refresh();
|
||||
self.refreshCanvasOffset();
|
||||
}
|
||||
self.offsetLeft = offsetLeft;
|
||||
self.offsetTop = offsetTop;
|
||||
@@ -1442,19 +1483,19 @@ export default class ExcalidrawView extends TextFileView {
|
||||
return;
|
||||
}
|
||||
const st = api.getAppState();
|
||||
const editing = st.editingElement !== null;
|
||||
const isEditing = st.editingElement !== null;
|
||||
const isDragging = st.draggingElement !== null;
|
||||
//this will reset positioning of the cursor in case due to the popup keyboard,
|
||||
//or the command palette, or some other unexpected reason the onResize would not fire...
|
||||
this.refresh();
|
||||
this.refreshCanvasOffset();
|
||||
if (
|
||||
this.semaphores.dirty &&
|
||||
this.semaphores.dirty == this.file?.path &&
|
||||
this.isDirty() &&
|
||||
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
|
||||
!isEditing &&
|
||||
!isDragging //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/630
|
||||
) {
|
||||
//console.log("autosave");
|
||||
this.autosaveTimer = null;
|
||||
@@ -1481,14 +1522,22 @@ export default class ExcalidrawView extends TextFileView {
|
||||
};
|
||||
|
||||
this.autosaveFunction = timer;
|
||||
this.resetAutosaveTimer();
|
||||
}
|
||||
|
||||
|
||||
private resetAutosaveTimer() {
|
||||
if(!this.autosaveFunction) return;
|
||||
|
||||
if (this.autosaveTimer) {
|
||||
clearTimeout(this.autosaveTimer);
|
||||
this.autosaveTimer = null;
|
||||
} // clear previous timer if one exists
|
||||
this.autosaveTimer = setTimeout(
|
||||
timer,
|
||||
this.autosaveFunction,
|
||||
this.plugin.settings.autosaveInterval,
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
//save current drawing when user closes workspace leaf
|
||||
@@ -1651,10 +1700,26 @@ export default class ExcalidrawView extends TextFileView {
|
||||
) await sleep(50); //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/734
|
||||
}
|
||||
|
||||
const filenameParts = getEmbeddedFilenameParts(state.subpath);
|
||||
const filenameParts = getEmbeddedFilenameParts(
|
||||
(state.subpath && state.subpath.startsWith("#^group") && !state.subpath.startsWith("#^group="))
|
||||
? "#^group=" + state.subpath.substring(7)
|
||||
: (state.subpath && state.subpath.startsWith("#^area") && !state.subpath.startsWith("#^area="))
|
||||
? "#^area=" + state.subpath.substring(6)
|
||||
: state.subpath
|
||||
);
|
||||
if(filenameParts.hasBlockref) {
|
||||
setTimeout(async () => {
|
||||
await waitForExcalidraw();
|
||||
if(filenameParts.blockref && !filenameParts.hasGroupref) {
|
||||
if(!self.getScene()?.elements.find(el=>el.id === filenameParts.blockref)) {
|
||||
const cleanQuery = cleanSectionHeading(filenameParts.blockref).replaceAll(" ","");
|
||||
const blocks = await self.getBackOfTheNoteBlocks();
|
||||
if(blocks.includes(cleanQuery)) {
|
||||
this.setMarkdownView(state);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
setTimeout(()=>self.zoomToElementId(filenameParts.blockref, filenameParts.hasGroupref));
|
||||
});
|
||||
}
|
||||
@@ -1699,13 +1764,20 @@ export default class ExcalidrawView extends TextFileView {
|
||||
}
|
||||
}
|
||||
|
||||
self.selectElementsMatchingQuery(
|
||||
if(!self.selectElementsMatchingQuery(
|
||||
elements,
|
||||
query,
|
||||
!api.getAppState().viewModeEnabled,
|
||||
filenameParts.hasSectionref,
|
||||
filenameParts.hasGroupref
|
||||
);
|
||||
)) {
|
||||
const cleanQuery = cleanSectionHeading(query[0]).replaceAll(" ","");
|
||||
const sections = await self.getBackOfTheNoteSections();
|
||||
if(sections.includes(cleanQuery)) {
|
||||
self.setMarkdownView(state);
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2089,7 +2161,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
this.semaphores.preventReload = false;
|
||||
const penEnabled =
|
||||
this.plugin.settings.defaultPenMode === "always" ||
|
||||
(this.plugin.settings.defaultPenMode === "mobile" && app.isMobile);
|
||||
(this.plugin.settings.defaultPenMode === "mobile" && DEVICE.isMobile);
|
||||
const api = this.excalidrawAPI;
|
||||
if (api) {
|
||||
//isLoaded flags that a new file is being loaded, isLoaded will be true after loadDrawing completes
|
||||
@@ -2191,13 +2263,14 @@ export default class ExcalidrawView extends TextFileView {
|
||||
}
|
||||
|
||||
public setDirty(debug?:number) {
|
||||
if(this.semaphores.saving) return; //do not set dirty if saving
|
||||
if(isDebugMode) console.log(debug);
|
||||
this.semaphores.dirty = this.file?.path;
|
||||
this.diskIcon.querySelector("svg").addClass("excalidraw-dirty");
|
||||
if(!this.semaphores.viewunload && this.toolsPanelRef?.current) {
|
||||
this.toolsPanelRef.current.setDirty(true);
|
||||
}
|
||||
if(!this.app.isMobile) {
|
||||
if(!DEVICE.isMobile) {
|
||||
if(requireApiVersion("0.16.0")) {
|
||||
//@ts-ignore
|
||||
this.leaf.tabHeaderInnerTitleEl.style.color="var(--color-accent)"
|
||||
@@ -2205,6 +2278,10 @@ export default class ExcalidrawView extends TextFileView {
|
||||
}
|
||||
}
|
||||
|
||||
public isDirty() {
|
||||
return Boolean(this.semaphores.dirty) && (this.semaphores.dirty === this.file?.path);
|
||||
}
|
||||
|
||||
public clearDirty() {
|
||||
if(this.semaphores.viewunload) return;
|
||||
const api = this.excalidrawAPI;
|
||||
@@ -2220,7 +2297,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
this.previousSceneVersion = this.getSceneVersion(el);
|
||||
}
|
||||
this.diskIcon.querySelector("svg").removeClass("excalidraw-dirty");
|
||||
if(!app.isMobile) {
|
||||
if(!DEVICE.isMobile) {
|
||||
if(requireApiVersion("0.16.0")) {
|
||||
//@ts-ignore
|
||||
this.leaf.tabHeaderInnerTitleEl.style.color=""
|
||||
@@ -2270,17 +2347,17 @@ export default class ExcalidrawView extends TextFileView {
|
||||
return ICON_NAME;
|
||||
}
|
||||
|
||||
setMarkdownView() {
|
||||
setMarkdownView(eState?: any) {
|
||||
this.plugin.excalidrawFileModes[this.id || this.file.path] = "markdown";
|
||||
this.plugin.setMarkdownView(this.leaf);
|
||||
this.plugin.setMarkdownView(this.leaf, eState);
|
||||
}
|
||||
|
||||
public async openAsMarkdown() {
|
||||
public async openAsMarkdown(eState?: any) {
|
||||
if (this.plugin.settings.compress === true) {
|
||||
this.excalidrawData.disableCompression = true;
|
||||
await this.save(true, true);
|
||||
}
|
||||
this.setMarkdownView();
|
||||
this.setMarkdownView(eState);
|
||||
}
|
||||
|
||||
public async convertExcalidrawToMD() {
|
||||
@@ -2859,6 +2936,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
currentStrokeOptions: st.currentStrokeOptions,
|
||||
previousGridSize: st.previousGridSize,
|
||||
frameRendering: st.frameRendering,
|
||||
objectsSnapModeEnabled: st.objectsSnapModeEnabled,
|
||||
},
|
||||
prevTextMode: this.prevTextMode,
|
||||
files,
|
||||
@@ -2869,7 +2947,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
* ExcalidrawAPI refreshes canvas offsets
|
||||
* @returns
|
||||
*/
|
||||
private refresh() {
|
||||
private refreshCanvasOffset() {
|
||||
if(this.contentEl.clientWidth === 0 || this.contentEl.clientHeight === 0) return;
|
||||
const api = this.excalidrawAPI;
|
||||
if (!api) {
|
||||
@@ -2970,41 +3048,52 @@ export default class ExcalidrawView extends TextFileView {
|
||||
if (!linktext) {
|
||||
if(!this.currentPosition) return;
|
||||
linktext = "";
|
||||
const selectedElement = getTextElementAtPointer(this.currentPosition, this);
|
||||
if (!selectedElement || !selectedElement.text) {
|
||||
const selectedEl = getTextElementAtPointer(this.currentPosition, this);
|
||||
if (!selectedEl || !selectedEl.text) {
|
||||
const selectedImgElement =
|
||||
getImageElementAtPointer(this.currentPosition, this);
|
||||
const selectedElementWithLink = (selectedImgElement?.id || selectedImgElement?.id)
|
||||
? null
|
||||
: getElementWithLinkAtPointer(this.currentPosition, this);
|
||||
element = this.excalidrawAPI.getSceneElements().find((el:ExcalidrawElement)=>el.id === selectedImgElement.id);
|
||||
if (!selectedImgElement || !selectedImgElement.fileId) {
|
||||
if ((!selectedImgElement || !selectedImgElement.fileId) && !selectedElementWithLink?.id) {
|
||||
return;
|
||||
}
|
||||
if (!this.excalidrawData.hasFile(selectedImgElement.fileId)) {
|
||||
return;
|
||||
if (selectedImgElement?.id) {
|
||||
if (!this.excalidrawData.hasFile(selectedImgElement.fileId)) {
|
||||
return;
|
||||
}
|
||||
const ef = this.excalidrawData.getFile(selectedImgElement.fileId);
|
||||
if (
|
||||
(ef.isHyperLink || ef.isLocalLink) || //web images don't have a preview
|
||||
(IMAGE_TYPES.contains(ef.file.extension)) || //images don't have a preview
|
||||
(ef.file.extension.toLowerCase() === "pdf") || //pdfs don't have a preview
|
||||
(this.plugin.ea.isExcalidrawFile(ef.file))
|
||||
) {//excalidraw files don't have a preview
|
||||
linktext = getLinkTextFromLink(element.link);
|
||||
if(!linktext) return;
|
||||
} else {
|
||||
const ref = ef.linkParts.ref
|
||||
? `#${ef.linkParts.isBlockRef ? "^" : ""}${ef.linkParts.ref}`
|
||||
: "";
|
||||
linktext =
|
||||
ef.file.path + ref;
|
||||
}
|
||||
}
|
||||
const ef = this.excalidrawData.getFile(selectedImgElement.fileId);
|
||||
if (
|
||||
(ef.isHyperLink || ef.isLocalLink) || //web images don't have a preview
|
||||
(IMAGE_TYPES.contains(ef.file.extension)) || //images don't have a preview
|
||||
(ef.file.extension.toLowerCase() === "pdf") || //pdfs don't have a preview
|
||||
(this.plugin.ea.isExcalidrawFile(ef.file))
|
||||
) {//excalidraw files don't have a preview
|
||||
linktext = getLinkTextFromLink(element.link);
|
||||
if (selectedElementWithLink?.id) {
|
||||
linktext = getLinkTextFromLink(selectedElementWithLink.text);
|
||||
if(!linktext) return;
|
||||
} else {
|
||||
const ref = ef.linkParts.ref
|
||||
? `#${ef.linkParts.isBlockRef ? "^" : ""}${ef.linkParts.ref}`
|
||||
: "";
|
||||
linktext =
|
||||
ef.file.path + ref;
|
||||
}
|
||||
} else {
|
||||
element = this.excalidrawAPI.getSceneElements().filter((el:ExcalidrawElement)=>el.id === selectedElement.id)[0];
|
||||
const text: string =
|
||||
this.textMode === TextMode.parsed
|
||||
? this.excalidrawData.getRawText(selectedElement.id)
|
||||
: selectedElement.text;
|
||||
const {linkText, selectedElement} = this.getLinkTextForElement(selectedEl, selectedEl);
|
||||
element = selectedElement;
|
||||
/*this.excalidrawAPI.getSceneElements().filter((el:ExcalidrawElement)=>el.id === selectedElement.id)[0];
|
||||
const text: string =
|
||||
this.textMode === TextMode.parsed
|
||||
? this.excalidrawData.getRawText(selectedElement.id)
|
||||
: selectedElement.text;*/
|
||||
|
||||
linktext = getLinkTextFromLink(text);
|
||||
linktext = getLinkTextFromLink(linkText);
|
||||
if(!linktext) return;
|
||||
}
|
||||
}
|
||||
@@ -3093,7 +3182,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.
|
||||
@@ -3349,7 +3438,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
}
|
||||
if (data.elements) {
|
||||
const self = this;
|
||||
setTimeout(() => self.save(false), 300);
|
||||
setTimeout(() => self.save(), 300); //removed prevent reload = false, as reload was triggered when pasted containers were processed and there was a conflict with the new elements
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -4020,6 +4109,19 @@ export default class ExcalidrawView extends TextFileView {
|
||||
}
|
||||
}
|
||||
|
||||
private async getBackOfTheNoteSections() {
|
||||
return (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));
|
||||
}
|
||||
|
||||
private async getBackOfTheNoteBlocks() {
|
||||
return (await this.app.metadataCache.blockCache.getForFile({ isCancelled: () => false },this.file))
|
||||
.blocks.filter((b:any) => b.display && b.node && b.node.hasOwnProperty("type") && b.node.hasOwnProperty("id"))
|
||||
.map((b:any) => cleanBlockRef(b.node.id));
|
||||
}
|
||||
|
||||
public getSingleSelectedImage(): {imageEl: ExcalidrawImageElement, embeddedFile: EmbeddedFile} {
|
||||
if(!this.excalidrawAPI) return null;
|
||||
const els = this.getViewSelectedElements().filter(el=>el.type==="image");
|
||||
@@ -4032,13 +4134,9 @@ export default class ExcalidrawView extends TextFileView {
|
||||
}
|
||||
|
||||
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 sections = await this.getBackOfTheNoteSections();
|
||||
const selectCardDialog = new SelectCard(this.app,this,sections);
|
||||
selectCardDialog.start();
|
||||
selectCardDialog.start();
|
||||
}
|
||||
|
||||
public async convertImageElWithURLToLocalFile(data: {imageEl: ExcalidrawImageElement, embeddedFile: EmbeddedFile}) {
|
||||
@@ -4144,6 +4242,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
const onContextMenu = (elements: readonly ExcalidrawElement[], appState: AppState, onClose: (callback?: () => void) => void) => {
|
||||
const contextMenuActions = [];
|
||||
const api = this.excalidrawAPI as ExcalidrawImperativeAPI;
|
||||
const areElementsSelected = Object.keys(api.getAppState().selectedElementIds).length>0
|
||||
|
||||
if(this.isLinkSelected()) {
|
||||
contextMenuActions.push([
|
||||
@@ -4258,6 +4357,17 @@ export default class ExcalidrawView extends TextFileView {
|
||||
]);
|
||||
}
|
||||
|
||||
if(areElementsSelected) {
|
||||
contextMenuActions.push([
|
||||
renderContextMenuAction(
|
||||
t("COPY_ELEMENT_LINK"),
|
||||
() => {
|
||||
this.copyLinkToSelectedElementToClipboard("");
|
||||
},
|
||||
onClose
|
||||
),
|
||||
]);
|
||||
}
|
||||
contextMenuActions.push([
|
||||
renderContextMenuAction(
|
||||
t("INSERT_CARD"),
|
||||
@@ -4638,7 +4748,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
this.toolsPanelRef.current.updatePosition();
|
||||
}
|
||||
if(this.ownerDocument !== document) {
|
||||
this.refresh(); //because resizeobserver in Excalidraw does not seem to work when in Obsidian Window
|
||||
this.refreshCanvasOffset(); //because resizeobserver in Excalidraw does not seem to work when in Obsidian Window
|
||||
}
|
||||
} catch (err) {
|
||||
errorlog({
|
||||
@@ -4804,7 +4914,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
}
|
||||
const maxZoom = this.plugin.settings.zoomToFitMaxLevel;
|
||||
const elements = api.getSceneElements().filter((el:ExcalidrawElement)=>el.width<10000 && el.height<10000);
|
||||
if((app.isMobile && elements.length>1000) || elements.length>2500) {
|
||||
if((DEVICE.isMobile && elements.length>1000) || elements.length>2500) {
|
||||
if(justLoaded) api.scrollToContent();
|
||||
return;
|
||||
}
|
||||
@@ -4877,13 +4987,23 @@ export default class ExcalidrawView extends TextFileView {
|
||||
this.plugin.saveSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param elements
|
||||
* @param query
|
||||
* @param selectResult
|
||||
* @param exactMatch
|
||||
* @param selectGroup
|
||||
* @returns true if element found, false if no element is found.
|
||||
*/
|
||||
|
||||
public selectElementsMatchingQuery(
|
||||
elements: ExcalidrawElement[],
|
||||
query: string[],
|
||||
selectResult: boolean = true,
|
||||
exactMatch: boolean = false, //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/530
|
||||
selectGroup: boolean = false,
|
||||
) {
|
||||
):boolean {
|
||||
let match = getTextElementsMatchingQuery(
|
||||
elements.filter((el: ExcalidrawElement) => el.type === "text"),
|
||||
query,
|
||||
@@ -4896,7 +5016,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
|
||||
if (match.length === 0) {
|
||||
new Notice("I could not find a matching text element");
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if(selectGroup) {
|
||||
@@ -4907,6 +5027,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
}
|
||||
|
||||
this.zoomToElements(selectResult,match);
|
||||
return true;
|
||||
}
|
||||
|
||||
public zoomToElements(
|
||||
|
||||
@@ -592,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") &&
|
||||
@@ -613,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"));
|
||||
|
||||
@@ -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,13 @@ 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 = "```";
|
||||
@@ -159,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"},
|
||||
|
||||
@@ -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,75 @@ 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.6":`
|
||||
## Two minor fixes
|
||||
- Scaling of LaTeX formulas when the formula is changed
|
||||
- If the back of the note card only contains a block embed ${String.fromCharCode(96)}![[embed]]${String.fromCharCode(96)} this got removed when saving the Excalidraw file. This issue has been present since November, 2021 (v1.4.9).
|
||||
`,
|
||||
"2.1.5":`
|
||||
## New
|
||||
- Save "Snap to objects" with the scene state. If this is the only change you make to the scene, force save it using CTRL+S (note, use CTRL on Mac as well).
|
||||
- Added "Copy markdown link" to the context menu.
|
||||
|
||||
## Fixed
|
||||
- Paste operation occasionally duplicated text elements.
|
||||
- Pasting multiple instances of the same image from excalidraw.com or another instance of Obsidian, or pasting an image from anywhere and making copies with ALT/OPT + drag immediately after pasting (before autosave triggered) led to broken images when reopening the drawing.
|
||||
- CTRL/CMD+Click on a Text Element with an element link did not work (previously, you had to click the top right link indicator). Now, you can click anywhere on the element.
|
||||
- Hover preview for elements with a link only worked when hovering over the element link. Now, you can hover anywhere. If there are multiple elements with links, the top-level element will take precedence.
|
||||
- Link navigation within drawing when the "Focus on Existing Tab" feature is enabled under "Links, transclusion and TODOs" in settings works again.
|
||||
- If a link points to a back-of-the-card section or block the drawing will automatically switch to markdown view mode and navigate to the block or section.
|
||||
- DynamicSytle, dark mode when canvas background is set to transparent.
|
||||
- Scale to maintain the aspect ratio of a markdown notes embedded as images.
|
||||
- You can now borrow interactive markdown embeds to tables, blockquotes, list elements and callouts - not just paragraphs.
|
||||
- Back of the drawing cards:
|
||||
- Leaving the Section Name empty when creating the first back of the card note resulted in an error.
|
||||
- If you add the markdown comment (${String.fromCharCode(96)}%%${String.fromCharCode(96)}) directly before ${String.fromCharCode(96)}# Text Elements${String.fromCharCode(96)}, a trailing ${String.fromCharCode(96)}#${String.fromCharCode(96)} will be added to your document, when adding a back of the card note. This is to hide the markdown comment from the card. The trailing (empty) ${String.fromCharCode(96)}#${String.fromCharCode(96)} will not be visible in reading mode, pdf exports, and when publishing with Obsidian Publish.
|
||||
Here's a sample markdown structure of your document:
|
||||
|
||||
${String.fromCharCode(96,96,96)}markdown
|
||||
---
|
||||
excalidraw-plugin: parsed
|
||||
---
|
||||
# Your back of the card section
|
||||
bla bla bla
|
||||
|
||||
#
|
||||
%%
|
||||
# Text Elements
|
||||
... the rest of the Excalidraw file
|
||||
${String.fromCharCode(96,96,96)}
|
||||
`,
|
||||
"2.1.4":`
|
||||
## Fixed
|
||||
- Fixed the **aspect ratio** of an Excalidraw embedded within another Excalidraw **not updating**. [#1707](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1707)
|
||||
- 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 **automatically embed the scene in exported PNG and SVG image files**. Including the scene will allow users to open the picture on Excalidraw.com or in another Obsidian Vault as an editable Excalidraw file.New setting is under the Export category. The new frontmatter tag is: ${String.fromCharCode(96)}excalidraw-export-embed-scene: true/false${String.fromCharCode(96)}.
|
||||
`,
|
||||
"2.1.3":`
|
||||
This is a republish of 2.1.2 with a minor change. Sorry about the frequent releases. I will hold back for a few weeks now.
|
||||
`,
|
||||
"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.
|
||||
|
||||
@@ -707,19 +707,22 @@ export const linkPrompt = async (linkText:string, app: App, view?: ExcalidrawVie
|
||||
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)),
|
||||
message,
|
||||
);
|
||||
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);
|
||||
|
||||
@@ -5,7 +5,6 @@ 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";
|
||||
|
||||
@@ -29,7 +28,7 @@ export class SelectCard extends FuzzySuggestModal<string> {
|
||||
if (e.key == "Enter") {
|
||||
if (this.containerEl.innerText.includes(t("EMPTY_SECTION_MESSAGE"))) {
|
||||
const item = this.inputEl.value;
|
||||
if(MD_EX_SECTIONS.includes(item)) {
|
||||
if(item === "" || MD_EX_SECTIONS.includes(item)) {
|
||||
new Notice(t("INVALID_SECTION_NAME"));
|
||||
this.close();
|
||||
return;
|
||||
@@ -37,7 +36,13 @@ export class SelectCard extends FuzzySuggestModal<string> {
|
||||
(async () => {
|
||||
const data = view.data;
|
||||
const header = getExcalidrawMarkdownHeaderSection(data);
|
||||
view.data = data.replace(header, header + `\n# ${item}\n\n`);
|
||||
const body = data.split(header)[1];
|
||||
const shouldAddHashtag = body && body.startsWith("%%");
|
||||
const shouldRemoveTrailingHashtag = header.endsWith("#\n");
|
||||
view.data = data.replace(
|
||||
header,
|
||||
(shouldRemoveTrailingHashtag ? header.substring(0,header.length-2) : header) +
|
||||
`\n# ${item}\n\n${shouldAddHashtag ? "#\n" : ""}`);
|
||||
await view.forceSave(true);
|
||||
let watchdog = 0;
|
||||
await sleep(200);
|
||||
|
||||
@@ -197,6 +197,7 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
|
||||
' "excalidraw-export-dark"?: boolean;\n' +
|
||||
' "excalidraw-export-padding"?: number;\n' +
|
||||
' "excalidraw-export-pngscale"?: number;\n' +
|
||||
' "excalidraw-export-embed-scene"?: boolean;\n' +
|
||||
' "excalidraw-default-mode"?: "view" | "zen";\n' +
|
||||
' "excalidraw-onload-script"?: string;\n' +
|
||||
' "excalidraw-linkbutton-opacity"?: number;\n' +
|
||||
@@ -821,6 +822,12 @@ export const FRONTMATTER_KEYS_INFO: SuggesterInfo[] = [
|
||||
desc: "If this key is present it will override the default excalidraw embed and export setting. This only affects export to PNG. Specify the export scale for the image. The typical range is between 0.5 and 5, but you can experiment with other values as well.",
|
||||
after: ": 1",
|
||||
},
|
||||
{
|
||||
field: "excalidraw-export-embed-scene",
|
||||
code: null,
|
||||
desc: "If this key is present it will override the default excalidraw embed and export setting.",
|
||||
after: ": false",
|
||||
},
|
||||
{
|
||||
field: "open-md",
|
||||
code: null,
|
||||
|
||||
@@ -86,7 +86,7 @@ export class UniversalInsertFileModal extends Modal {
|
||||
const isPDF = file && file.extension === "pdf";
|
||||
const isExcalidraw = file && ea.isExcalidrawFile(file);
|
||||
|
||||
const sections = file && file.extension === "md"
|
||||
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")
|
||||
|
||||
@@ -48,6 +48,7 @@ export default {
|
||||
NEW_IN_POPOUT_WINDOW_EMBED: "Create new drawing - IN A POPOUT WINDOW - and embed into active document",
|
||||
TOGGLE_LOCK: "Toggle Text Element between edit RAW and PREVIEW",
|
||||
DELETE_FILE: "Delete selected image or Markdown file from Obsidian Vault",
|
||||
COPY_ELEMENT_LINK: "Copy markdown link for selected element(s)",
|
||||
INSERT_LINK_TO_ELEMENT:
|
||||
`Copy markdown link for selected element to clipboard. ${labelCTRL()}+CLICK to copy 'group=' link. ${labelSHIFT()}+CLICK to copy an 'area=' link. ${labelALT()}+CLICK to watch a help video.`,
|
||||
INSERT_LINK_TO_ELEMENT_GROUP:
|
||||
@@ -139,6 +140,9 @@ export default {
|
||||
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.",
|
||||
@@ -285,6 +289,17 @@ FILENAME_HEAD: "Filename",
|
||||
SHOW_PEN_MODE_FREEDRAW_CROSSHAIR_DESC:
|
||||
"Show crosshair in pen mode when using the freedraw tool. <b><u>Toggle ON:</u></b> SHOW <b><u>Toggle OFF:</u></b> HIDE<br>"+
|
||||
"The effect depends on the device. Crosshair is typically visible on drawing tablets, MS Surface, but not on iOS.",
|
||||
SHOW_DRAWING_OR_MD_IN_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",
|
||||
@@ -381,7 +396,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. " +
|
||||
@@ -523,6 +538,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",
|
||||
@@ -614,7 +633,9 @@ FILENAME_HEAD: "Filename",
|
||||
"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",
|
||||
"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:
|
||||
@@ -664,7 +685,7 @@ FILENAME_HEAD: "Filename",
|
||||
SELECT_SECTION_OR_TYPE_NEW:
|
||||
"Select existing section or type name of a new section then press Enter.",
|
||||
INVALID_SECTION_NAME: "Invalid section name.",
|
||||
EMPTY_SECTION_MESSAGE: "Hit enter to create a new Section",
|
||||
EMPTY_SECTION_MESSAGE: "Type the Section Name and hit enter to create a new Section",
|
||||
|
||||
//EmbeddedFileLoader.ts
|
||||
INFINITE_LOOP_WARNING:
|
||||
|
||||
106
src/main.ts
106
src/main.ts
@@ -42,6 +42,8 @@ import {
|
||||
LOCALE,
|
||||
IMAGE_TYPES,
|
||||
MD_TEXTELEMENTS,
|
||||
setExcalidrawPlugin,
|
||||
DEVICE
|
||||
} from "./constants/constants";
|
||||
import {
|
||||
VIRGIL_FONT,
|
||||
@@ -80,6 +82,7 @@ import {
|
||||
checkAndCreateFolder,
|
||||
download,
|
||||
fileShouldDefaultAsExcalidraw,
|
||||
getAliasWithSize,
|
||||
getAnnotationFileNameAndFolder,
|
||||
getCropFileNameAndFolder,
|
||||
getDrawingFilename,
|
||||
@@ -89,7 +92,6 @@ import {
|
||||
getListOfTemplateFiles,
|
||||
getNewUniqueFilepath,
|
||||
getURLImageExtension,
|
||||
splitFolderAndFilename,
|
||||
} from "./utils/FileUtils";
|
||||
import {
|
||||
getFontDataURL,
|
||||
@@ -101,6 +103,7 @@ import {
|
||||
getExportTheme,
|
||||
isCallerFromTemplaterPlugin,
|
||||
decompress,
|
||||
getImageSize,
|
||||
} from "./utils/Utils";
|
||||
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";
|
||||
@@ -192,6 +195,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
>();
|
||||
this.equationsMaster = new Map<FileId, string>();
|
||||
this.mermaidsMaster = new Map<FileId, string>();
|
||||
setExcalidrawPlugin(this);
|
||||
}
|
||||
|
||||
get locale() {
|
||||
@@ -378,7 +382,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
private getOpenObsidianDocuments(): Document[] {
|
||||
const visitedDocs = new Set<Document>();
|
||||
this.app.workspace.iterateAllLeaves((leaf)=>{
|
||||
const ownerDocument = this.app.isMobile?document:leaf.view.containerEl.ownerDocument;
|
||||
const ownerDocument = DEVICE.isMobile?document:leaf.view.containerEl.ownerDocument;
|
||||
if(!ownerDocument) return;
|
||||
if(visitedDocs.has(ownerDocument)) return;
|
||||
visitedDocs.add(ownerDocument);
|
||||
@@ -1084,7 +1088,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
name: t("NEW_IN_POPOUT_WINDOW"),
|
||||
checkCallback: (checking: boolean) => {
|
||||
if (checking) {
|
||||
return !app.isMobile
|
||||
return !DEVICE.isMobile;
|
||||
}
|
||||
this.createAndOpenDrawing(getDrawingFilename(this.settings), "popout-window");
|
||||
},
|
||||
@@ -1156,7 +1160,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
name: t("NEW_IN_POPOUT_WINDOW_EMBED"),
|
||||
checkCallback: (checking: boolean) => {
|
||||
if (checking) {
|
||||
return !app.isMobile && Boolean(this.app.workspace.getActiveViewOfType(MarkdownView));
|
||||
return !DEVICE.isMobile && Boolean(this.app.workspace.getActiveViewOfType(MarkdownView));
|
||||
}
|
||||
insertDrawingToDoc("popout-window");
|
||||
return true;
|
||||
@@ -1540,16 +1544,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) {
|
||||
@@ -1732,7 +1738,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(
|
||||
@@ -1756,11 +1766,11 @@ 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);
|
||||
}
|
||||
@@ -1789,8 +1799,11 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
new Notice(`Can't load image\n\n${imageURL}`);
|
||||
return;
|
||||
}
|
||||
ea.getElement(imageID).locked = true;
|
||||
|
||||
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) {
|
||||
@@ -1838,7 +1851,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
|
||||
if(!newFile) return;
|
||||
const link = this.app.metadataCache.fileToLinktext(newFile,sourceFile.path, true);
|
||||
replacer(link, newFile);
|
||||
replacer(link, newFile, size ? `${size.width}` : null);
|
||||
}
|
||||
|
||||
if(isCanvas) {
|
||||
@@ -1880,7 +1893,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(
|
||||
@@ -1899,16 +1916,19 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
if(extension !== "pdf" && !IMAGE_TYPES.contains(extension) && !isExcalidraw) return false;
|
||||
if(checking) return true;
|
||||
const ref = imagePathParts[1];
|
||||
const replacer = (link:string) => {
|
||||
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: `${imageFile.basename}, ${ref.replace("="," ")}`,
|
||||
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}) + pdfLink + lineparts[1]);
|
||||
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);
|
||||
}
|
||||
@@ -2034,7 +2054,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
});
|
||||
|
||||
this.addCommand({
|
||||
id: "universal-add-file",
|
||||
id: "universal-card",
|
||||
name: t("INSERT_CARD"),
|
||||
checkCallback: (checking: boolean) => {
|
||||
if (checking) {
|
||||
@@ -2093,10 +2113,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;
|
||||
}
|
||||
},
|
||||
@@ -2122,6 +2145,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
}
|
||||
|
||||
(async () => {
|
||||
await activeView.save();
|
||||
const template = await this.getBlankDrawing();
|
||||
const target = await this.app.vault.read(activeFile);
|
||||
const mergedTarget = mergeMarkdownFiles(template, target);
|
||||
@@ -2249,7 +2273,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);
|
||||
@@ -2260,7 +2285,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;
|
||||
@@ -2270,7 +2297,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);
|
||||
@@ -2553,14 +2581,14 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
|
||||
//!Temporary hack
|
||||
//https://discord.com/channels/686053708261228577/817515900349448202/1031101635784613968
|
||||
if (this.app.isMobile && newActiveviewEV && !previouslyActiveEV) {
|
||||
if (DEVICE.isMobile && newActiveviewEV && !previouslyActiveEV) {
|
||||
const navbar = document.querySelector("body>.app-container>.mobile-navbar");
|
||||
if(navbar && navbar instanceof HTMLDivElement) {
|
||||
navbar.style.position="relative";
|
||||
}
|
||||
}
|
||||
|
||||
if (this.app.isMobile && !newActiveviewEV && previouslyActiveEV) {
|
||||
if (DEVICE.isMobile && !newActiveviewEV && previouslyActiveEV) {
|
||||
const navbar = document.querySelector("body>.app-container>.mobile-navbar");
|
||||
if(navbar && navbar instanceof HTMLDivElement) {
|
||||
navbar.style.position="";
|
||||
@@ -2964,7 +2992,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
}
|
||||
if(opts.applyLefthandedMode) setLeftHandedMode(this.settings.isLeftHanded);
|
||||
if(opts.reEnableAutosave) this.settings.autosave = true;
|
||||
this.settings.autosaveInterval = app.isMobile
|
||||
this.settings.autosaveInterval = DEVICE.isMobile
|
||||
? this.settings.autosaveIntervalMobile
|
||||
: this.settings.autosaveIntervalDesktop;
|
||||
}
|
||||
@@ -2991,7 +3019,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
public triggerEmbedUpdates(filepath?: string) {
|
||||
const visitedDocs = new Set<Document>();
|
||||
app.workspace.iterateAllLeaves((leaf)=>{
|
||||
const ownerDocument = app.isMobile?document:leaf.view.containerEl.ownerDocument;
|
||||
const ownerDocument = DEVICE.isMobile?document:leaf.view.containerEl.ownerDocument;
|
||||
if(!ownerDocument) return;
|
||||
if(visitedDocs.has(ownerDocument)) return;
|
||||
visitedDocs.add(ownerDocument);
|
||||
@@ -3166,7 +3194,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
return file.path;
|
||||
}
|
||||
|
||||
public async setMarkdownView(leaf: WorkspaceLeaf) {
|
||||
public async setMarkdownView(leaf: WorkspaceLeaf, eState?: any) {
|
||||
const state = leaf.view.getState();
|
||||
|
||||
//Note v2.0.19: I have absolutely no idea why I thought this is necessary. Removing this.
|
||||
@@ -3182,7 +3210,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
state,
|
||||
popstate: true,
|
||||
} as ViewState,
|
||||
{ focus: true },
|
||||
eState ? eState : { focus: true },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3204,7 +3232,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
}
|
||||
|
||||
public async exportLibrary() {
|
||||
if (this.app.isMobile) {
|
||||
if (DEVICE.isMobile) {
|
||||
const prompt = new Prompt(
|
||||
this.app,
|
||||
"Please provide a filename",
|
||||
|
||||
@@ -172,7 +172,9 @@ export class EmbeddableMenu {
|
||||
view.updateScene({appState: {activeEmbeddable: null}});
|
||||
const paragraphs = (await app.metadataCache.blockCache
|
||||
.getForFile({ isCancelled: () => false },file))
|
||||
.blocks.filter((b: any) => b.display && b.node?.type === "paragraph");
|
||||
.blocks.filter((b: any) => b.display && b.node &&
|
||||
(b.node.type === "paragraph" || b.node.type === "blockquote" || b.node.type === "listItem" || b.node.type === "table" || b.node.type === "callout")
|
||||
);
|
||||
const values = ["entire-file"].concat(paragraphs);
|
||||
const display = [t("SHOW_ENTIRE_FILE")].concat(
|
||||
paragraphs.map((b: any) => `${b.node?.id ? `#^${b.node.id}: ` : ``}${b.display.trim()}`));
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
TextComponent,
|
||||
TFile,
|
||||
} from "obsidian";
|
||||
import { GITHUB_RELEASES, VIEW_TYPE_EXCALIDRAW } from "./constants/constants";
|
||||
import { DEVICE, GITHUB_RELEASES, VIEW_TYPE_EXCALIDRAW } from "./constants/constants";
|
||||
import ExcalidrawView from "./ExcalidrawView";
|
||||
import { t } from "./lang/helpers";
|
||||
import type ExcalidrawPlugin from "./main";
|
||||
@@ -55,6 +55,7 @@ export interface ExcalidrawSettings {
|
||||
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;
|
||||
@@ -71,6 +72,8 @@ export interface ExcalidrawSettings {
|
||||
defaultMode: string;
|
||||
defaultPenMode: "never" | "mobile" | "always";
|
||||
penModeCrosshairVisible: boolean;
|
||||
renderImageInMarkdownReadingMode: boolean,
|
||||
renderImageInMarkdownToPDF: boolean,
|
||||
allowPinchZoom: boolean;
|
||||
allowWheelZoom: boolean;
|
||||
zoomToFitOnOpen: boolean;
|
||||
@@ -98,6 +101,7 @@ export interface ExcalidrawSettings {
|
||||
exportWithTheme: boolean;
|
||||
exportWithBackground: boolean;
|
||||
exportPaddingSVG: number;
|
||||
exportEmbedScene: boolean;
|
||||
keepInSync: boolean;
|
||||
autoexportSVG: boolean;
|
||||
autoexportPNG: boolean;
|
||||
@@ -207,6 +211,7 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
|
||||
useExcalidrawExtension: true,
|
||||
cropPrefix: CROPPED_PREFIX,
|
||||
annotatePrefix: ANNOTATED_PREFIX,
|
||||
annotatePreserveSize: false,
|
||||
displaySVGInPreview: undefined,
|
||||
previewImageType: undefined,
|
||||
allowImageCache: true,
|
||||
@@ -222,7 +227,9 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
|
||||
matchThemeTrigger: false,
|
||||
defaultMode: "normal",
|
||||
defaultPenMode: "never",
|
||||
penModeCrosshairVisible: false,
|
||||
penModeCrosshairVisible: true,
|
||||
renderImageInMarkdownReadingMode: false,
|
||||
renderImageInMarkdownToPDF: false,
|
||||
allowPinchZoom: false,
|
||||
allowWheelZoom: false,
|
||||
zoomToFitOnOpen: true,
|
||||
@@ -250,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,
|
||||
@@ -666,7 +674,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
.setValue(this.plugin.settings.autosaveIntervalDesktop.toString())
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.autosaveIntervalDesktop = parseInt(value);
|
||||
this.plugin.settings.autosaveInterval = app.isMobile
|
||||
this.plugin.settings.autosaveInterval = DEVICE.isMobile
|
||||
? this.plugin.settings.autosaveIntervalMobile
|
||||
: this.plugin.settings.autosaveIntervalDesktop;
|
||||
this.applySettingsUpdate();
|
||||
@@ -685,7 +693,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
.setValue(this.plugin.settings.autosaveIntervalMobile.toString())
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.autosaveIntervalMobile = parseInt(value);
|
||||
this.plugin.settings.autosaveInterval = app.isMobile
|
||||
this.plugin.settings.autosaveInterval = DEVICE.isMobile
|
||||
? this.plugin.settings.autosaveIntervalMobile
|
||||
: this.plugin.settings.autosaveIntervalDesktop;
|
||||
this.applySettingsUpdate();
|
||||
@@ -831,6 +839,19 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
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
|
||||
//------------------------------------------------
|
||||
@@ -947,6 +968,18 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
}),
|
||||
);
|
||||
|
||||
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")))
|
||||
@@ -1708,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")))
|
||||
|
||||
@@ -97,16 +97,12 @@ export class CropImage {
|
||||
withTheme: false,
|
||||
isMask: false,
|
||||
}
|
||||
const isRotated = this.imageEA.getElements().some(el=>el.type === "image" && el.angle !== 0);
|
||||
const images = Object.values(this.imageEA.imagesDict);
|
||||
if(images.length === 1) {
|
||||
if(!isRotated && (images.length === 1)) {
|
||||
return images[0].dataURL;
|
||||
}
|
||||
return await this.imageEA.createPNGBase64(null,1,exportSettings,null,null,0);
|
||||
const imageSVG = await this.imageEA.createSVG(null,false,exportSettings,null,null,0);
|
||||
const svgData = new XMLSerializer().serializeToString(imageSVG);
|
||||
return `data:image/svg+xml;base64,${btoa(unescape(encodeURIComponent(svgData)))}`;
|
||||
// const blob = new Blob([svgString], { type: 'image/svg+xml' });
|
||||
// return `data:image/svg+xml;base64,${await blobToBase64(blob)}`;
|
||||
}
|
||||
|
||||
private async buildSVG(): Promise<SVGSVGElement> {
|
||||
|
||||
@@ -32,6 +32,10 @@ export const setDynamicStyle = (
|
||||
view?.excalidrawAPI?.getAppState?.()?.theme === "light" ||
|
||||
view?.excalidrawData?.scene?.appState?.theme === "light";
|
||||
|
||||
if (color==="transparent") {
|
||||
color = "#ffffff";
|
||||
}
|
||||
|
||||
const darker = "#101010";
|
||||
const lighter = "#f0f0f0";
|
||||
const step = 10;
|
||||
|
||||
@@ -367,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;
|
||||
|
||||
@@ -22,7 +22,7 @@ export const getElementsAtPointer = (
|
||||
y <= pointer.y &&
|
||||
y + h >= pointer.y
|
||||
);
|
||||
});
|
||||
}).reverse();
|
||||
};
|
||||
|
||||
export const getTextElementAtPointer = (pointer: any, view: ExcalidrawView) => {
|
||||
|
||||
@@ -285,7 +285,13 @@ export const openLeaf = ({
|
||||
leaf = l;
|
||||
}
|
||||
});
|
||||
if(leaf) return {leaf, promise: Promise.resolve()};
|
||||
if(leaf) {
|
||||
if(openState) {
|
||||
const promise = leaf.openFile(file, openState);
|
||||
return {leaf, promise};
|
||||
}
|
||||
return {leaf, promise: Promise.resolve()};
|
||||
}
|
||||
}
|
||||
leaf = fnGetLeaf();
|
||||
const promise = leaf.openFile(file, openState);
|
||||
|
||||
@@ -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,9 @@ import {
|
||||
exportToBlob,
|
||||
IMAGE_TYPES,
|
||||
FRONTMATTER_KEYS,
|
||||
EXCALIDRAW_PLUGIN,
|
||||
getCommonBoundingBox,
|
||||
DEVICE,
|
||||
} from "../constants/constants";
|
||||
import ExcalidrawPlugin from "../main";
|
||||
import { ExcalidrawElement } from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
@@ -29,6 +33,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;
|
||||
@@ -153,6 +159,10 @@ const rotate = (
|
||||
export const rotatedDimensions = (
|
||||
element: ExcalidrawElement,
|
||||
): [number, number, number, number] => {
|
||||
const bb = getCommonBoundingBox([element]);
|
||||
return [bb.minX, bb.minY, bb.maxX - bb.minX, bb.maxY - bb.minY];
|
||||
|
||||
//removed with 2.1.5... will delete later
|
||||
if (element.angle === 0) {
|
||||
return [element.x, element.y, element.width, element.height];
|
||||
}
|
||||
@@ -282,7 +292,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 +344,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 +443,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 true; //mermaid SVG or equation
|
||||
const file = EXCALIDRAW_PLUGIN.app.vault.getAbstractFileByPath(ef.path.replace(/#.*$/,"").replace(/\|.*$/,""));
|
||||
if(!file || (file instanceof TFolder)) return false;
|
||||
return (file as TFile).extension==="md" || EXCALIDRAW_PLUGIN.isExcalidrawFile(file as TFile)
|
||||
})) {
|
||||
const [w_image, h_image] = [f.size.width, f.size.height];
|
||||
const imageAspectRatio = f.size.width / f.size.height;
|
||||
scene.elements
|
||||
@@ -491,7 +509,7 @@ export const setDocLeftHandedMode = (isLeftHanded: boolean, ownerDocument:Docume
|
||||
export const setLeftHandedMode = (isLeftHanded: boolean) => {
|
||||
const visitedDocs = new Set<Document>();
|
||||
app.workspace.iterateAllLeaves((leaf) => {
|
||||
const ownerDocument = app.isMobile?document:leaf.view.containerEl.ownerDocument;
|
||||
const ownerDocument = DEVICE.isMobile?document:leaf.view.containerEl.ownerDocument;
|
||||
if(!ownerDocument) return;
|
||||
if(visitedDocs.has(ownerDocument)) return;
|
||||
visitedDocs.add(ownerDocument);
|
||||
@@ -586,6 +604,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,13 +773,13 @@ 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;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user