Compare commits

..

26 Commits

Author SHA1 Message Date
zsviczian
fc0ac92dd3 2.5.2 2024-10-13 20:59:43 +02:00
zsviczian
4e2d7eb637 Update README.md 2024-09-28 22:32:18 +02:00
zsviczian
f8f280c7d5 2.5.1 2024-09-28 14:10:25 +02:00
zsviczian
00b87f99c0 Merge pull request #2038 from mxsdlr/master
Update color palette details in README
2024-09-25 17:14:56 +02:00
mxsdlr
0b5c74dde8 Add Decompress JSON hint to README.md
- Add hint to "Decompress Excalidraw JSON in Markdown View" setting when editing JSON content
2024-09-24 14:33:10 +02:00
mxsdlr
906b3bdf92 Update color palette details in README
- Change `customColorPalette` to `colorPalette`
- Add section about `topPicks`
2024-09-24 11:43:29 +02:00
zsviczian
0c28e82212 2.5.1-beta-2 2024-09-22 16:22:17 +02:00
zsviczian
beb4301f14 Merge pull request #2033 from dmscode/master
Update zh-cn.ts to 268680f
2024-09-22 15:21:11 +02:00
dmscode
e96fe9c491 Update zh-cn.ts to 268680f 2024-09-22 07:18:10 +08:00
zsviczian
268680f494 2.5.1-beta-1 2024-09-19 20:22:04 +02:00
zsviczian
a1512fce26 Merge pull request #2024 from dmscode/master
Update zh-cn.ts to 74c0af2
2024-09-19 20:08:58 +02:00
zsviczian
c2e79f3439 Update bug_report.yml 2024-09-14 11:25:36 +02:00
zsviczian
01780a2bf8 Update bug_report.yml 2024-09-14 11:24:44 +02:00
zsviczian
10e54eb03e Update bug_report.yml 2024-09-14 11:24:01 +02:00
zsviczian
2760a9966b Update bug_report.yml 2024-09-14 11:23:40 +02:00
dmscode
a297dbbe52 Update zh-cn.ts to 74c0af2
And fixed some link path
2024-09-14 08:09:10 +08:00
zsviczian
74c0af2032 2.5.0 2024-09-13 18:45:13 +02:00
zsviczian
813c85accd 2.5.0-rc-1 2024-09-13 08:18:19 +02:00
zsviczian
c97d08c997 Merge pull request #2017 from zsviczian/fix-getExcalidrawViews
fixed getExcalidrawView
2024-09-12 12:29:49 +02:00
zsviczian
097d1bcd1b fixed getExcalidrawView 2024-09-12 10:27:04 +00:00
zsviczian
7c91186ed5 Update ExcalidrawViewUtils.ts 2024-09-12 11:41:47 +02:00
zsviczian
904bc7c994 Update ExcalidrawView.ts 2024-09-12 11:39:25 +02:00
zsviczian
9fd4ae2615 Update manifest-beta.json 2024-09-12 11:16:51 +02:00
zsviczian
18fbb0934e Merge pull request #2016 from zsviczian/style-tweaks
Obsidian 1.7.2: rework getExcalidrawViews from leaves, fix tools pane…
2024-09-12 11:10:11 +02:00
zsviczian
3ae59c85d2 Obsidian 1.7.2: rework getExcalidrawViews from leaves, fix tools panel width, fix math types 2024-09-12 09:07:43 +00:00
zsviczian
55db9b0ddb 2.5.0-beta-3 2024-09-11 22:21:50 +02:00
33 changed files with 705 additions and 244 deletions

View File

@@ -1,5 +1,5 @@
name: Bug report
description: When something is clearly broken. Everything else is a feature request.
description: If something is clearly broken, its a bug. Everything else is a feature or support request. Most reported “bugs” are actually how-to questions or feature requests.
title: "BUG: "
body:
- type: markdown

View File

@@ -2,7 +2,7 @@
[简体中文](./docs/zh-cn/README.md)
👉👉👉 Check out and contribute to the new [Obsidian-Excalidraw Community Wiki](https://excalidraw-obsidian.online/Hobbies/Excalidraw+Blog/WIKI/Welcome+to+the+WIKI)
👉👉👉 Check out and contribute to the new [Obsidian-Excalidraw Community Wiki](https://excalidraw-obsidian.online/WIKI/Welcome+to+the+WIKI)
The Obsidian-Excalidraw plugin integrates [Excalidraw](https://excalidraw.com/), a feature rich sketching tool, into Obsidian. You can store and edit Excalidraw files in your vault, you can embed drawings into your documents, and you can link to documents and other drawings to/and from Excalidraw. For a showcase of Excalidraw features, please read my blog post [here](https://www.zsolt.blog/2021/03/showcasing-excalidraw.html) and/or watch the videos below.
@@ -100,15 +100,17 @@ Plugin settings are grouped into the following sections:
#### Templates
- Template for new drawings. The template will restore stroke properties. This means you can set up defaults in your template for stroke color, stroke width, opacity, font family, font size, fill style, stroke style, etc. This also applies to ExcalidrawAutomate.
- Template for new drawings. The template will restore stroke properties. This means you can set up defaults in your template for stroke color, stroke width, opacity, font family, font size, fill style, stroke style, etc. This also applies to ExcalidrawAutomate. With versions 1.6.13 or higher make sure to enable "Decompress Excalidraw JSON in Markdown View" in the settings before editing the JSON in the template. This can be disabled after the canges are performed.
- Via the template, you can customize the color palette used by Excalidraw.
- Switch to Markdown view.
- Scroll down to the bottom of the file and find `"AppState": {`.
- Find `"customColorPalette": {` at the end of the AppState section.
- You may specify the 3 palettes used in Excalidraw by adding any or all of the following 3 variables:
- `"canvasBackground":[], "elementBackground":[], "elementStroke": []`.
- Add a comma-separated list of valid HTML colors (e.g. `#FF0000` for red).
in the array for each of the variables.
- Find `"colorPalette": {` at the end of the AppState section.
- You may specify the 3 palettes used in Excalidraw by adding any or all of the following 3 variables:
- `"canvasBackground":[], "elementBackground":[], "elementStroke": []`.
- Add a comma-separated list of valid HTML colors (e.g. `#FF0000` for red) in the array for each of the variables.
- To change the previewed colors, a `"topPicks": {` may be specified containing the same three keys:
- `"canvasBackground":[], "elementBackground":[], "elementStroke": []`.
- Note that the corresponding arrays must contain 5 elements.
- See my videos above for further help.
#### Export
@@ -227,6 +229,7 @@ For more details, see this [video](https://youtu.be/yZQoJg2RCKI)
- `excalidraw-export-dark`: true == Dark mode / false == light mode.
- `excalidraw-export-padding`: Specify the export padding for the image.
- `excalidraw-export-pngscale`: 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.
- Since 1.6.13, enable "Decompress Excalidraw JSON in Markdown View" in the settings if you want to change any JSON content.
### Embed complete markdown files into your drawings

View File

@@ -2,7 +2,7 @@
> 此说明当前更新至 `5569cff`。
[English](./AutomateHowTo.md)
[English](../../AutomateHowTo.md)
Excalidraw 自动化允许您使用 [Templater](https://github.com/SilentVoid13/Templater) 插件创建 Excalidraw 绘图。

View File

@@ -2,7 +2,7 @@
> 此说明当前更新至 `5569cff`。
[English](./README.md)
[English](../../README.md)
👉👉👉 快来查看并为新的 [Obsidian-Excalidraw 社区维基](https://excalidraw-obsidian.online/Hobbies/Excalidraw+Blog/WIKI/Welcome+to+the+WIKI)贡献你的力量吧

View File

@@ -1,7 +1,7 @@
{
"id": "obsidian-excalidraw-plugin",
"name": "Excalidraw",
"version": "2.5.0-beta-2",
"version": "2.5.2",
"minAppVersion": "1.1.6",
"description": "An Obsidian plugin to edit and view Excalidraw drawings",
"author": "Zsolt Viczian",

View File

@@ -1,7 +1,7 @@
{
"id": "obsidian-excalidraw-plugin",
"name": "Excalidraw",
"version": "2.4.3",
"version": "2.5.2",
"minAppVersion": "1.1.6",
"description": "An Obsidian plugin to edit and view Excalidraw drawings",
"author": "Zsolt Viczian",

View File

@@ -19,7 +19,7 @@
"license": "MIT",
"dependencies": {
"@popperjs/core": "^2.11.8",
"@zsviczian/excalidraw": "0.17.1-obsidian-51",
"@zsviczian/excalidraw": "0.17.1-obsidian-57",
"chroma-js": "^2.4.2",
"clsx": "^2.0.0",
"@zsviczian/colormaster": "^1.2.2",

View File

@@ -655,7 +655,7 @@ export class EmbeddedFilesLoader {
let equation;
const equations = excalidrawData.getEquationEntries();
while (!this.terminate && !(equation = equations.next()).done) {
if(fileIDWhiteList && !fileIDWhiteList.has(entry.value[0])) continue;
if(fileIDWhiteList && !fileIDWhiteList.has(equation.value[0])) continue;
if (!excalidrawData.getEquation(equation.value[0]).isLoaded) {
const latex = equation.value[1].latex;
const data = await tex2dataURL(latex);

View File

@@ -21,7 +21,6 @@ import { ExcalidrawData, getMarkdownDrawingSection, REGEX_LINK } from "src/Excal
import {
FRONTMATTER,
nanoid,
VIEW_TYPE_EXCALIDRAW,
MAX_IMAGE_SIZE,
COLOR_NAMES,
fileid,
@@ -55,14 +54,14 @@ import {
wrapTextAtCharLength,
arrayToMap,
} from "src/utils/Utils";
import { getAttachmentsFolderAndFilePath, getLeaf, getNewOrAdjacentLeaf, isObsidianThemeDark, mergeMarkdownFiles, openLeaf } from "src/utils/ObsidianUtils";
import { AppState, BinaryFileData, DataURL, ExcalidrawImperativeAPI, Point } from "@zsviczian/excalidraw/types/excalidraw/types";
import { getAttachmentsFolderAndFilePath, getExcalidrawViews, getLeaf, getNewOrAdjacentLeaf, isObsidianThemeDark, mergeMarkdownFiles, openLeaf } from "src/utils/ObsidianUtils";
import { AppState, BinaryFileData, DataURL, ExcalidrawImperativeAPI } from "@zsviczian/excalidraw/types/excalidraw/types";
import { EmbeddedFile, EmbeddedFilesLoader, FileData } from "src/EmbeddedFileLoader";
import { tex2dataURL } from "src/LaTeX";
import { GenericInputPrompt, NewFileActions } from "src/dialogs/Prompt";
import { t } from "src/lang/helpers";
import { ScriptEngine } from "src/Scripts";
import { ConnectionPoint, DeviceType } from "src/types/types";
import { ConnectionPoint, DeviceType, Point } from "src/types/types";
import CM, { ColorMaster, extendPlugins } from "@zsviczian/colormaster";
import HarmonyPlugin from "@zsviczian/colormaster/plugins/harmony";
import MixPlugin from "@zsviczian/colormaster/plugins/mix"
@@ -282,8 +281,17 @@ export class ExcalidrawAutomate {
return LZString.compressToBase64(str);
}
public decompressFromBase64(str:string): string {
return LZString.decompressFromBase64(str);
public decompressFromBase64(data:string): string {
if (!data) throw new Error("No input string provided for decompression.");
let cleanedData = '';
const length = data.length;
for (let i = 0; i < length; i++) {
const char = data[i];
if (char !== '\\n' && char !== '\\r') {
cleanedData += char;
}
}
return LZString.decompressFromBase64(cleanedData);
}
/**
@@ -1826,33 +1834,23 @@ export class ExcalidrawAutomate {
*/
setView(view?: ExcalidrawView | "first" | "active"): ExcalidrawView {
if(!view) {
const v = app.workspace.getActiveViewOfType(ExcalidrawView);
const v = this.plugin.app.workspace.getActiveViewOfType(ExcalidrawView);
if (v instanceof ExcalidrawView) {
this.targetView = v;
}
else {
const leaves =
app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW);
if (!leaves || leaves.length == 0) {
return;
}
this.targetView = leaves[0].view as ExcalidrawView;
this.targetView = getExcalidrawViews(this.plugin.app)[0];
}
}
if (view == "active") {
const v = app.workspace.getActiveViewOfType(ExcalidrawView);
const v = this.plugin.app.workspace.getActiveViewOfType(ExcalidrawView);
if (!(v instanceof ExcalidrawView)) {
return;
}
this.targetView = v;
}
if (view == "first") {
const leaves =
app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW);
if (!leaves || leaves.length == 0) {
return;
}
this.targetView = leaves[0].view as ExcalidrawView;
this.targetView = getExcalidrawViews(this.plugin.app)[0];
}
if (view instanceof ExcalidrawView) {
this.targetView = view;
@@ -2989,8 +2987,12 @@ async function getTemplate(
));
}
const fileIDWhiteList = new Set<FileId>();
groupElements.filter(el=>el.type==="image").forEach((el:ExcalidrawImageElement)=>fileIDWhiteList.add(el.fileId));
let fileIDWhiteList:Set<FileId>;
if(groupElements.length < scene.elements.length) {
fileIDWhiteList = new Set<FileId>();
groupElements.filter(el=>el.type==="image").forEach((el:ExcalidrawImageElement)=>fileIDWhiteList.add(el.fileId));
}
if (loadFiles) {
//debug({where:"getTemplate",template:file.name,loader:loader.uid});
@@ -3019,11 +3021,15 @@ async function getTemplate(
let files:any = {};
const sceneFilesSize = Object.values(scene.files).length;
if (sceneFilesSize > 0) {
if(sceneFilesSize === fileIDWhiteList.size)
Object.values(scene.files).filter((f: any) => fileIDWhiteList.has(f.id)).forEach((f: any) => {
files[f.id] = f;
});
if(fileIDWhiteList && (sceneFilesSize > fileIDWhiteList.size)) {
Object.values(scene.files).filter((f: any) => fileIDWhiteList.has(f.id)).forEach((f: any) => {
files[f.id] = f;
});
} else {
files = scene.files;
}
}
return {
elements: convertMarkdownLinksToObsidianURLs
? updateElementLinksToObsidianLinks({
@@ -3428,7 +3434,7 @@ export const getFrameElementsMatchingQuery = (
el.type === "frame" &&
query.some((q) => {
if (exactMatch) {
const text = el.name.toLowerCase().split("\n")[0].trim();
const text = el.name?.toLowerCase().split("\n")[0].trim() ?? "";
const m = text.match(/^#*(# .*)/);
if (!m || m.length !== 2) {
return false;
@@ -3509,4 +3515,10 @@ export const cloneElement = (el: ExcalidrawElement):any => {
export const verifyMinimumPluginVersion = (requiredVersion: string): boolean => {
return PLUGIN_VERSION === requiredVersion || isVersionNewerThanOther(PLUGIN_VERSION,requiredVersion);
}
}
export const getBoundTextElementId = (container: ExcalidrawElement | null) => {
return container?.boundElements?.length
? container?.boundElements?.find((ele) => ele.type === "text")?.id || null
: null;
};

View File

@@ -51,6 +51,7 @@ import { getMermaidImageElements, getMermaidText, shouldRenderMermaid } from "./
import { DEBUGGING, debug } from "./utils/DebugHelper";
import { Mutable } from "@zsviczian/excalidraw/types/excalidraw/utility-types";
import { updateElementIdsInScene } from "./utils/ExcalidrawSceneUtils";
import { getNewUniqueFilepath } from "./utils/FileUtils";
type SceneDataWithFiles = SceneData & { files: BinaryFiles };
@@ -1506,31 +1507,40 @@ export class ExcalidrawData {
return result;
}
public async saveDataURLtoVault(dataURL: DataURL, mimeType: MimeType, key: FileId) {
public async saveDataURLtoVault(dataURL: DataURL, mimeType: MimeType, key: FileId, name?:string) {
const scene = this.scene as SceneDataWithFiles;
let fname = `Pasted Image ${window
.moment()
.format("YYYYMMDDHHmmss_SSS")}`;
switch (mimeType) {
case "image/png":
fname += ".png";
break;
case "image/jpeg":
fname += ".jpg";
break;
case "image/svg+xml":
fname += ".svg";
break;
case "image/gif":
fname += ".gif";
break;
default:
fname += ".png";
let fname = name;
if(!fname) {
fname = `Pasted Image ${window
.moment()
.format("YYYYMMDDHHmmss_SSS")}`;
switch (mimeType) {
case "image/png":
fname += ".png";
break;
case "image/jpeg":
fname += ".jpg";
break;
case "image/svg+xml":
fname += ".svg";
break;
case "image/gif":
fname += ".gif";
break;
default:
fname += ".png";
}
}
const x = await getAttachmentsFolderAndFilePath(this.app, this.file.path, fname);
const filepath = getNewUniqueFilepath(this.app.vault,fname,x.folder);
/*
const filepath = (
await getAttachmentsFolderAndFilePath(this.app, this.file.path, fname)
).filepath;
).filepath;*/
const arrayBuffer = await getBinaryFileFromDataURL(dataURL);
if(!arrayBuffer) return null;
@@ -1657,7 +1667,9 @@ export class ExcalidrawData {
await this.saveDataURLtoVault(
scene.files[key].dataURL,
scene.files[key].mimeType,
key as FileId
key as FileId,
//@ts-ignore
scene.files[key].name,
);
}
}

View File

@@ -9,6 +9,8 @@ import {
MarkdownView,
request,
requireApiVersion,
HoverParent,
HoverPopover,
} from "obsidian";
//import * as React from "react";
//import * as ReactDOM from "react-dom";
@@ -63,7 +65,8 @@ import {
cloneElement,
getFrameElementsMatchingQuery,
getElementsWithLinkMatchingQuery,
getImagesMatchingQuery
getImagesMatchingQuery,
getBoundTextElementId
} from "./ExcalidrawAutomate";
import { t } from "./lang/helpers";
import {
@@ -251,7 +254,8 @@ type ActionButtons = "save" | "isParsed" | "isRaw" | "link" | "scriptInstall";
let windowMigratedDisableZoomOnce = false;
export default class ExcalidrawView extends TextFileView {
export default class ExcalidrawView extends TextFileView implements HoverParent{
public hoverPopover: HoverPopover;
private freedrawLastActiveTimestamp: number = 0;
public exportDialog: ExportDialog;
public excalidrawData: ExcalidrawData;
@@ -277,6 +281,7 @@ export default class ExcalidrawView extends TextFileView {
private embeddableLeafRefs = new Map<ExcalidrawElement["id"], any>();
public semaphores: {
warnAboutLinearElementLinkClick: boolean;
//flag to prevent overwriting the changes the user makes in an embeddable view editing the back side of the drawing
embeddableIsEditingSelf: boolean;
popoutUnload: boolean; //the unloaded Excalidraw view was the last leaf in the popout window
@@ -313,6 +318,7 @@ export default class ExcalidrawView extends TextFileView {
hoverSleep: boolean; //flag with timer to prevent hover preview from being triggered dozens of times
wheelTimeout:number; //used to avoid hover preview while zooming
} | null = {
warnAboutLinearElementLinkClick: true,
embeddableIsEditingSelf: false,
popoutUnload: false,
viewunload: false,
@@ -520,8 +526,8 @@ export default class ExcalidrawView extends TextFileView {
if (!svg) {
return;
}
const serializer = new XMLSerializer();
const svgString = serializer.serializeToString(svg);
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2026
const svgString = svg.outerHTML;
if (file && file instanceof TFile) {
await this.app.vault.modify(file, svgString);
} else {
@@ -1141,61 +1147,111 @@ export default class ExcalidrawView extends TextFileView {
private getLinkTextForElement(
selectedText:SelectedElementWithLink,
selectedElementWithLink?:SelectedElementWithLink
selectedElementWithLink?:SelectedElementWithLink,
allowLinearElementClick: boolean = false,
): {
linkText: string,
selectedElement: ExcalidrawElement,
isLinearElement: boolean,
} {
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.getLinkTextForElement, "ExcalidrawView.getLinkTextForElement", selectedText, selectedElementWithLink);
if (selectedText?.id || selectedElementWithLink?.id) {
const selectedTextElement: ExcalidrawTextElement = selectedText.id
let 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)
let selectedElement = selectedElementWithLink.id
? this.excalidrawAPI.getSceneElements().find((el:ExcalidrawElement)=>
el.id === selectedElementWithLink.id)
: null;
//if the user clicked on the label of an arrow then the label will be captured in selectedElement, because
//Excalidraw returns the container as the selected element. But in this case we want this to be treated as the
//text element, as the assumption is, if the user wants to invoke the linear element editor for an arrow that has
//a label with a link, then he/she should rather CTRL+click on the arrow line, not the label. CTRL+Click on
//the label is an indication of wanting to navigate.
if (!Boolean(selectedTextElement) && selectedElement?.type === "text") {
const container = getContainerElement(selectedElement, arrayToMap(this.excalidrawAPI.getSceneElements()));
if(container?.type === "arrow") {
const x = getTextElementAtPointer(this.currentPosition,this);
if(x?.id === selectedElement.id) {
selectedTextElement = selectedElement;
selectedElement = null;
}
}
}
//CTRL click on a linear element with a link will navigate instead of line editor
if(!allowLinearElementClick && ["arrow", "line"].includes(selectedElement?.type)) {
return {linkText: selectedElement.link, selectedElement: selectedElement, isLinearElement: true};
}
if (!selectedTextElement && selectedElement?.type === "text") {
if(!allowLinearElementClick) {
//CTRL click on a linear element with a link will navigate instead of line editor
const container = getContainerElement(selectedElement, arrayToMap(this.excalidrawAPI.getSceneElements()));
if(container?.type !== "arrow") {
selectedTextElement = selectedElement as ExcalidrawTextElement;
selectedElement = null;
} else {
const x = this.processLinkText(selectedElement.rawText, selectedElement as ExcalidrawTextElement, container, false);
return {linkText: x.linkText, selectedElement: container, isLinearElement: true};
}
} else {
selectedTextElement = selectedElement as ExcalidrawTextElement;
selectedElement = null;
}
}
let linkText =
selectedElementWithLink?.text ??
(this.textMode === TextMode.parsed
? this.excalidrawData.getRawText(selectedText.id)
: selectedText.text);
if(linkText.startsWith("#")) {
return {linkText, selectedElement: selectedTextElement ?? selectedElement};
}
return {...this.processLinkText(linkText, selectedTextElement, selectedElement), isLinearElement: false};
}
return {linkText: null, selectedElement: null, isLinearElement: false};
}
const maybeObsidianLink = parseObsidianLink(linkText, this.app);
if(typeof maybeObsidianLink === "string") {
linkText = maybeObsidianLink;
}
processLinkText(linkText: string, selectedTextElement: ExcalidrawTextElement, selectedElement: ExcalidrawElement, shouldOpenLink: boolean = true) {
if(!linkText) {
return {linkText: null, selectedElement: null};
}
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?.startsWith("#")) {
return {linkText, selectedElement: selectedTextElement ?? selectedElement};
}
const maybeObsidianLink = parseObsidianLink(linkText, this.app);
if(typeof maybeObsidianLink === "string") {
linkText = maybeObsidianLink;
}
}
}
if(!linkText || partsArray.length === 0) {
linkText = selectedTextElement?.link;
}
}
if(linkText.startsWith("#")) {
return {linkText, selectedElement: selectedTextElement ?? selectedElement};
}
return {linkText: null, selectedElement: null};
const maybeObsidianLink = parseObsidianLink(linkText, this.app, shouldOpenLink);
if(typeof maybeObsidianLink === "string") {
linkText = maybeObsidianLink;
}
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?.startsWith("#")) {
return {linkText, selectedElement: selectedTextElement ?? selectedElement};
}
const maybeObsidianLink = parseObsidianLink(linkText, this.app, shouldOpenLink);
if(typeof maybeObsidianLink === "string") {
linkText = maybeObsidianLink;
}
}
}
if(!linkText || partsArray.length === 0) {
linkText = selectedTextElement?.link;
}
}
return {linkText, selectedElement: selectedTextElement ?? selectedElement};
}
async linkClick(
@@ -1203,7 +1259,8 @@ export default class ExcalidrawView extends TextFileView {
selectedText: SelectedElementWithLink,
selectedImage: SelectedImage,
selectedElementWithLink: SelectedElementWithLink,
keys?: ModifierKeys
keys?: ModifierKeys,
allowLinearElementClick: boolean = false,
) {
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.linkClick, "ExcalidrawView.linkClick", ev, selectedText, selectedImage, selectedElementWithLink, keys);
if(!selectedText) selectedText = {id:null, text: null};
@@ -1216,10 +1273,17 @@ export default class ExcalidrawView extends TextFileView {
let file = null;
let subpath: string = null;
let {linkText, selectedElement} = this.getLinkTextForElement(selectedText, selectedElementWithLink);
let {linkText, selectedElement, isLinearElement} = this.getLinkTextForElement(selectedText, selectedElementWithLink, allowLinearElementClick);
//if (selectedText?.id || selectedElementWithLink?.id) {
if (selectedElement) {
if (!allowLinearElementClick && linkText && isLinearElement) {
if(this.semaphores.warnAboutLinearElementLinkClick) {
new Notice(t("LINEAR_ELEMENT_LINK_CLICK_ERROR"), 20000);
this.semaphores.warnAboutLinearElementLinkClick = false;
}
return;
}
if (!linkText) {
return;
}
@@ -1298,6 +1362,9 @@ export default class ExcalidrawView extends TextFileView {
}
if (!linkText) {
if(allowLinearElementClick) {
return;
}
new Notice(t("LINK_BUTTON_CLICK_NO_TEXT"), 20000);
return;
}
@@ -1348,7 +1415,7 @@ export default class ExcalidrawView extends TextFileView {
//if link will open in the same pane I want to save the drawing before opening the link
await this.forceSaveIfRequired();
const {leaf, promise} = openLeaf({
const { promise } = openLeaf({
plugin: this.plugin,
fnGetLeaf: () => getLeaf(this.plugin,this.leaf,keys),
file,
@@ -1364,7 +1431,7 @@ export default class ExcalidrawView extends TextFileView {
}
}
async handleLinkClick(ev: MouseEvent | ModifierKeys) {
async handleLinkClick(ev: MouseEvent | ModifierKeys, allowLinearElementClick: boolean = false) {
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.handleLinkClick, "ExcalidrawView.handleLinkClick", ev);
this.removeLinkTooltip();
@@ -1382,6 +1449,7 @@ export default class ExcalidrawView extends TextFileView {
selectedImage,
selectedElementWithLink,
ev instanceof MouseEvent ? null : ev,
allowLinearElementClick,
);
}
@@ -1545,7 +1613,9 @@ export default class ExcalidrawView extends TextFileView {
if(!this.excalidrawAPI || !this.excalidrawData.loaded || !this.isDirty()) {
return;
}
this.forceSave(true);
if((this.excalidrawAPI as ExcalidrawImperativeAPI).getAppState().activeTool.type !== "image") {
this.forceSave(true);
}
};
this.registerDomEvent(this.ownerWindow, "keydown", onKeyDown, false);
@@ -1802,6 +1872,7 @@ export default class ExcalidrawView extends TextFileView {
new Notice("Unknown error, save is taking too long");
return;
}
await this.forceSaveIfRequired();
}
private async forceSaveIfRequired():Promise<boolean> {
@@ -1810,9 +1881,9 @@ export default class ExcalidrawView extends TextFileView {
let dirty = false;
//if saving was already in progress
//the function awaits the save to finish.
while (this.semaphores.saving && watchdog++ < 10) {
while (this.semaphores.saving && watchdog++ < 200) {
dirty = true;
await sleep(20);
await sleep(40);
}
if(this.excalidrawAPI) {
this.checkSceneVersion(this.excalidrawAPI.getSceneElements());
@@ -2148,6 +2219,7 @@ export default class ExcalidrawView extends TextFileView {
// clear the view content
clear() {
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.clear, "ExcalidrawView.clear");
this.semaphores.warnAboutLinearElementLinkClick = true;
this.viewSaveData = "";
this.canvasNodeFactory.purgeNodes();
this.embeddableRefs.clear();
@@ -2325,14 +2397,35 @@ export default class ExcalidrawView extends TextFileView {
});
}
private getGridColor(bgColor: string, st: AppState):{Bold: string, Regular: string} {
private getGridColor(bgColor: string, st: AppState): { Bold: string, Regular: string } {
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.getGridColor, "ExcalidrawView.getGridColor", bgColor, st);
const cm = this.plugin.ea.getCM(bgColor);
const isDark = cm.isDark();
const Regular = (isDark ? cm.lighterBy(7) : cm.darkerBy(7)).stringHEX({alpha: false});
const Bold = (isDark ? cm.lighterBy(14) : cm.darkerBy(14)).stringHEX({alpha: false});
return {Bold, Regular};
let Regular: string;
let Bold: string;
const opacity = this.plugin.settings.gridSettings.OPACITY/100;
if (this.plugin.settings.gridSettings.DYNAMIC_COLOR) {
// Dynamic color: concatenate opacity to the HEX string
Regular = (isDark ? cm.lighterBy(10) : cm.darkerBy(10)).alphaTo(opacity).stringRGB({ alpha: true });
Bold = (isDark ? cm.lighterBy(5) : cm.darkerBy(5)).alphaTo(opacity).stringRGB({ alpha: true });
} else {
// Custom color handling
const customCM = this.plugin.ea.getCM(this.plugin.settings.gridSettings.COLOR);
const customIsDark = customCM.isDark();
// Regular uses the custom color directly
Regular = customCM.alphaTo(opacity).stringRGB({ alpha: true });
// Bold is 7 shades lighter or darker based on the custom color's darkness
Bold = (customIsDark ? customCM.lighterBy(10) : customCM.darkerBy(10)).alphaTo(opacity).stringRGB({ alpha: true });
}
return { Bold, Regular };
}
public activeLoader: EmbeddedFilesLoader = null;
private nextLoader: EmbeddedFilesLoader = null;
@@ -3113,6 +3206,16 @@ export default class ExcalidrawView extends TextFileView {
};
}
const textId = getBoundTextElementId(selectedElement[0]);
if (textId) {
const textElement = api
.getSceneElements()
.filter((el: any) => el.id === textId && el.link);
if (textElement.length > 0) {
return { id: textElement[0].id, text: textElement[0].text };
}
}
if (selectedElement[0].groupIds.length === 0) {
return { id: null, text: null };
} //is the selected element part of a group?
@@ -3341,6 +3444,13 @@ export default class ExcalidrawView extends TextFileView {
toDelete.forEach((k) => delete files[k]);
}
const activeTool = {...st.activeTool};
if(!["freedraw","hand"].includes(activeTool.type)) {
activeTool.type = "selection";
}
activeTool.customType = null;
activeTool.lastActiveTool = null;
return {
type: "excalidraw",
version: 2,
@@ -3375,7 +3485,7 @@ export default class ExcalidrawView extends TextFileView {
currentStrokeOptions: st.currentStrokeOptions,
frameRendering: st.frameRendering,
objectsSnapModeEnabled: st.objectsSnapModeEnabled,
activeTool: st.activeTool,
activeTool,
},
prevTextMode: this.prevTextMode,
files,
@@ -3403,7 +3513,15 @@ export default class ExcalidrawView extends TextFileView {
}
private clearHoverPreview() {
if (this.hoverPreviewTarget) {
if (this.hoverPopover) {
this.hoverPreviewTarget = null;
//@ts-ignore
if(this.hoverPopover.embed?.editor) {
return;
}
//@ts-ignore
this.hoverPopover?.hide();
} else if (this.hoverPreviewTarget) {
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.clearHoverPreview, "ExcalidrawView.clearHoverPreview", this);
const event = new MouseEvent("click", {
view: this.ownerWindow,
@@ -3589,7 +3707,7 @@ export default class ExcalidrawView extends TextFileView {
this.app.workspace.trigger("hover-link", {
event: this.lastMouseEvent,
source: VIEW_TYPE_EXCALIDRAW,
hoverParent: this.hoverPreviewTarget,
hoverParent: this,
targetEl: this.hoverPreviewTarget, //null //0.15.0 hover editor!!
linktext: this.plugin.hover.linkText,
sourcePath: this.plugin.hover.sourcePath,
@@ -3618,6 +3736,7 @@ export default class ExcalidrawView extends TextFileView {
private excalidrawDIVonKeyDown(event: KeyboardEvent) {
//(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.excalidrawDIVonKeyDown, "ExcalidrawView.excalidrawDIVonKeyDown", event);
if (this.semaphores?.viewunload) return;
if (event.target === this.excalidrawWrapperRef.current) {
return;
} //event should originate from the canvas
@@ -3636,6 +3755,9 @@ export default class ExcalidrawView extends TextFileView {
if (!this.plugin.settings.allowCtrlClick && !isWinMETAorMacCTRL(e)) {
return;
}
if (Boolean((this.excalidrawAPI as ExcalidrawImperativeAPI)?.getAppState().contextMenu)) {
return;
}
//added setTimeout when I changed onClick(e: MouseEvent) to onPointerDown() in 1.7.9.
//Timeout is required for Excalidraw to first complete the selection action before execution
//of the link click continues
@@ -3744,10 +3866,18 @@ export default class ExcalidrawView extends TextFileView {
}
}
public updateGridColor(canvasColor?: string, st?: any) {
if(!canvasColor) {
st = (this.excalidrawAPI as ExcalidrawImperativeAPI).getAppState();
canvasColor = canvasColor ?? st.viewBackgroundColor === "transparent" ? "white" : st.viewBackgroundColor;
}
window.setTimeout(()=>this.updateScene({appState:{gridColor: this.getGridColor(canvasColor, st)}, storeAction: "update"}));
}
private canvasColorChangeHook(st: AppState) {
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.canvasColorChangeHook, "ExcalidrawView.canvasColorChangeHook", st);
const canvasColor = st.viewBackgroundColor === "transparent" ? "white" : st.viewBackgroundColor;
window.setTimeout(()=>this.updateScene({appState:{gridColor: this.getGridColor(canvasColor, st)}, storeAction: "update"}));
this.updateGridColor(canvasColor,st);
setDynamicStyle(this.plugin.ea,this,canvasColor,this.plugin.settings.dynamicStyling);
if(this.plugin.ea.onCanvasColorChangeHook) {
try {
@@ -4700,6 +4830,7 @@ export default class ExcalidrawView extends TextFileView {
null,
{id: element.id, text: link},
event,
true,
);
return;
}
@@ -4909,7 +5040,7 @@ export default class ExcalidrawView extends TextFileView {
t("OPEN_LINK_CLICK"),
() => {
const event = emulateKeysForLinkClick("new-tab");
this.handleLinkClick(event);
this.handleLinkClick(event, true);
},
onClose
),
@@ -5043,7 +5174,8 @@ export default class ExcalidrawView extends TextFileView {
React,
t("COPY_DRAWING_LINK"),
() => {
navigator.clipboard.writeText(`![[${this.file.path}]]`);
const path = this.file.path.match(/(.*)(\.md)$/)?.[1];
navigator.clipboard.writeText(`![[${path ?? this.file.path}]]`);
},
onClose
),

View File

@@ -29,6 +29,7 @@ import { FILENAMEPARTS, PreviewImageType } from "./utils/UtilTypes";
import { CustomMutationObserver, debug, DEBUGGING } from "./utils/DebugHelper";
import { getExcalidrawFileForwardLinks } from "./utils/ExcalidrawViewUtils";
import { linkPrompt } from "./dialogs/Prompt";
import { isHTMLElement } from "./utils/typechecks";
interface imgElementAttributes {
file?: TFile;
@@ -374,7 +375,9 @@ const getIMG = async (
const addSVGToImgSrc = (img: HTMLImageElement, svg: SVGSVGElement, cacheReady: boolean, cacheKey: ImageKey):HTMLImageElement => {
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(addSVGToImgSrc, `MarkdownPostProcessor.ts > addSVGToImgSrc`);
const svgString = new XMLSerializer().serializeToString(svg);
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2026
//const svgString = new XMLSerializer().serializeToString(svg);
const svgString = svg.outerHTML;
const blob = new Blob([svgString], { type: 'image/svg+xml' });
const blobUrl = URL.createObjectURL(blob);
img.setAttribute("src", blobUrl);
@@ -403,12 +406,13 @@ const createImgElement = async (
let timer:number;
const clickEvent = (ev:PointerEvent) => {
if(!(ev.target instanceof Element)) {
if (!isHTMLElement(ev.target)) {
return;
}
const containerElement = ev.target.hasClass("excalidraw-embedded-img")
const targetElement = ev.target as HTMLElement;
const containerElement = targetElement.hasClass("excalidraw-embedded-img")
? ev.target
: getParentOfClass(ev.target, "excalidraw-embedded-img");
: getParentOfClass(targetElement, "excalidraw-embedded-img");
if (!containerElement) {
return;
}
@@ -587,6 +591,102 @@ const processInternalEmbed = async (internalEmbedEl: Element, file: TFile ):Prom
return await createImageDiv(attr);
}
function getDimensionsFromAliasString(data: string) {
const dimensionRegex = /^(?<width>\d+%|\d+)(x(?<height>\d+%|\d+))?$/;
const heightOnlyRegex = /^x(?<height>\d+%|\d+)$/;
const match = data.match(dimensionRegex) || data.match(heightOnlyRegex);
if (match) {
const { width, height } = match.groups;
// Ensure width and height do not start with '0'
if ((width && width.startsWith('0') && width !== '0') ||
(height && height.startsWith('0') && height !== '0')) {
return null;
}
return {
width: width || undefined,
height: height || undefined,
};
}
// If the input starts with a 0 or is a decimal, return null
if (/^0\d|^\d+\.\d+/.test(data)) {
return null;
}
return null;
}
type AliasParts = { alias?: string, width?: string, height?: string, style?: string };
function parseAlias(input: string):AliasParts {
const result:AliasParts = {};
const parts = input.split('|').map(part => part.trim());
switch (parts.length) {
case 1:
const singleMatch = getDimensionsFromAliasString(parts[0]);
if (singleMatch) {
return singleMatch; // Return dimensions if valid
}
result.style = parts[0]; // Otherwise, return as style
break;
case 2:
const firstDim = getDimensionsFromAliasString(parts[0]);
const secondDim = getDimensionsFromAliasString(parts[1]);
if (secondDim) {
result.alias = parts[0];
result.width = secondDim.width;
result.height = secondDim.height;
} else if (firstDim) {
result.width = firstDim.width;
result.height = firstDim.height;
result.style = parts[1]; // Second part is style
} else {
result.alias = parts[0];
result.style = parts[1]; // Assuming second part is style
}
break;
case 3:
const middleMatch = getDimensionsFromAliasString(parts[1]);
if (middleMatch) {
result.alias = parts[0];
result.width = middleMatch.width;
result.height = middleMatch.height;
result.style = parts[2];
} else {
result.alias = parts[0];
result.style = parts[2]; // Last part is style
}
break;
default:
const secondValue = getDimensionsFromAliasString(parts[1]);
if (secondValue) {
result.alias = parts[0];
result.width = secondValue.width;
result.height = secondValue.height;
result.style = parts[parts.length - 1]; // Last part is style
} else {
result.alias = parts[0];
result.style = parts[parts.length - 1]; // Last part is style
}
break;
}
// Clean up the result to remove undefined properties
Object.keys(result).forEach((key: keyof AliasParts) => {
if (result[key] === undefined) {
delete result[key];
}
});
return result;
}
const processAltText = (
fname: string,
alt:string,
@@ -594,19 +694,11 @@ const processAltText = (
) => {
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(processAltText, `MarkdownPostProcessor.ts > processAltText`);
if (alt && !alt.startsWith(fname)) {
//2:width, 3:height, 4:style 12 3 4
const parts = alt.match(/[^\|\d]*\|?((\d*%?)x?(\d*%?))?\|?(.*)/);
attr.fwidth = parts[2] ?? attr.fwidth;
attr.fheight = parts[3] ?? attr.fheight;
if (parts[4] && !parts[4].startsWith(fname)) {
attr.style = [`excalidraw-svg${`-${parts[4]}`}`];
}
if (
(!parts[4] || parts[4]==="") &&
(!parts[2] || parts[2]==="") &&
parts[0] && parts[0] !== ""
) {
attr.style = [`excalidraw-svg${`-${parts[0]}`}`];
const aliasParts = parseAlias(alt);
attr.fwidth = aliasParts.width ?? attr.fwidth;
attr.fheight = aliasParts.height ?? attr.fheight;
if (aliasParts.style && !aliasParts.style.startsWith(fname)) {
attr.style = [`excalidraw-svg${`-${aliasParts.style}`}`];
}
}
}
@@ -795,7 +887,9 @@ export const markdownPostProcessor = async (
) => {
const isPrinting = Boolean(document.body.querySelectorAll("body > .print").length>0);
//firstElementChild: https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1956
const isFrontmatter = el.hasClass("mod-frontmatter") || el.firstElementChild?.hasClass("frontmatter");
const isFrontmatter = el.hasClass("mod-frontmatter") ||
el.firstElementChild?.hasClass("frontmatter") ||
el.firstElementChild?.hasClass("block-language-yaml");
if(isPrinting && isFrontmatter) {
return;
}

View File

@@ -5,7 +5,7 @@ import {
TFile,
WorkspaceLeaf,
} from "obsidian";
import { PLUGIN_ID, VIEW_TYPE_EXCALIDRAW } from "./constants/constants";
import { PLUGIN_ID } from "./constants/constants";
import ExcalidrawView from "./ExcalidrawView";
import ExcalidrawPlugin from "./main";
import { ButtonDefinition, GenericInputPrompt, GenericSuggester } from "./dialogs/Prompt";
@@ -14,6 +14,7 @@ import { splitFolderAndFilename } from "./utils/FileUtils";
import { getEA } from "src";
import { ExcalidrawAutomate } from "./ExcalidrawAutomate";
import { WeakArray } from "./utils/WeakArray";
import { getExcalidrawViews } from "./utils/ObsidianUtils";
export type ScriptIconMap = {
[key: string]: { name: string; group: string; svgString: string };
@@ -303,10 +304,8 @@ export class ScriptEngine {
}
private updateToolPannels() {
const leaves =
this.plugin.app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW);
leaves.forEach((leaf: WorkspaceLeaf) => {
const excalidrawView = leaf.view as ExcalidrawView;
const excalidrawViews = getExcalidrawViews(this.plugin.app);
excalidrawViews.forEach(excalidrawView => {
excalidrawView.toolsPanelRef?.current?.updateScriptIconMap(
this.scriptIconMap,
);

View File

@@ -17,6 +17,57 @@ 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.5.2": `
## Fixed
- Text became disconnected from sticky notes (rectangle/ellipse/diamond + text) if the sticky note contained a link (e.g., URL or wiki link), and in some cases, triggered a save error warning. [#2054](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2054)
- Long-clicking to open an Excalidraw drawing from a markdown note did not work when the note was in an Obsidian pop-out window.
- Active tool was deactivated after autosave, requiring the user to reselect the tool.
## Minor changes to default settings
- I adjusted some of the default settings. This change only affects new installs of Excalidraw; existing installs and settings remain unchanged:
- **Reuse Adjacent Pane** is now the default for opening new drawings. Excalidraw will try to open the drawing in the most recently used adjacent pane, if available.
- **Focus on Existing Tab** is the default for reopening an already open drawing. Excalidraw will switch to the existing tab where the drawing is open, instead of creating a new one.
- **Autosave Interval** is now set to a default value of 1 minute on Desktop and 30 seconds on mobile platforms.
`,
"2.5.1": `
## New
- Excalidraw will now save images using the filename from the file system when adding an image via the image tool (in the top toolbar).
- Increased the maximum image size from a width/height of 1440 to 2880 when adding an image via the image tool in the top toolbar.
- Flip arrowheads: If you have an arrow bound to elements, select only the arrow (not the bound elements) and press SHIFT+H or SHIFT+V to swap the arrowheads. [#8525](https://github.com/excalidraw/excalidraw/pull/8525)
## Fixed
- Zoom
- "Zoom to Fit" did not work correctly when multiple Obsidian tabs were open, and Excalidraw was in a lower tab. Additionally, there was an offset when the left side panel was open, especially if the panel was relatively large compared to the canvas area. [#2039](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2039)
- SHIFT+1 and SHIFT+2 will now honor the max zoom setting in Plugin Settings.
- Adding images using the image tool in the toolbar was unreliable. Sometimes it worked, sometimes it didn't, depending on whether the drawing had unsaved changes. Autosave was causing the issue with the image tool. [#1992](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1992)
- Frame related issues
- Fixed an issue where links to the back of the note were broken if an unnamed frame was present in the scene. [#2027](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2027)
- Frame transclusion was not working when there was a LaTeX equation anywhere in the scene. [#2028](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2028)
- Frame settings and rounded image corners were not honored when exporting (and auto-exporting) SVGs. [#2026](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2026)
- Resolved issues with the width, height, and style parsing of Excalidraw drawings embedded in Markdown notes. ${String.fromCharCode(96)}![[my file|10 - my alias]]${String.fromCharCode(96)} was incorrectly parsed as a width of 10 and a style of "- my alias."
- Links
- When navigating element links, selecting a #tag from the link-list did not open the Obsidian tag in the search.
- False-positive tag results in second-order links list.
- Arrow label links did not work as expected. Since CTRL/CMD+Click is used in Excalidraw to start the line editor, the solution is not straightforward from a UX perspective. [#2023](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2023)
- You can open arrow links by ctrl+clicking on the label itself. If the arrow or line element contains the link, ctrl+click on the link indicator in the top right.
- You can also right-click the linear element and select "Open Link" from the context menu.
- Various elbow-arrow fixes and QoL improvements from excalidraw.com [#8324](https://github.com/excalidraw/excalidraw/pull/8324), [#8448](https://github.com/excalidraw/excalidraw/pull/8448), [#8440](https://github.com/excalidraw/excalidraw/pull/8440)
`,
"2.5.0": `
The new [Community Wiki](https://excalidraw-obsidian.online/Hobbies/Excalidraw+Blog/WIKI/Welcome+to+the+WIKI) is waiting for your contribution!
## Fixed
- Regression from 2.4.3: Text flickers when editing text in a container [#2015](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2015).
- Significantly improved the performance of group and frame [image fragments](https://youtu.be/sjZfdqpxqsg) when the source drawing includes many images, but the fragment does not.
- Minor styling tweaks. Note that with Obsidian 1.7.1, the font size and zoom settings in Obsidian > Appearance will affect the size of buttons and menu items in Excalidraw as well.
## New
- New Canvas Search from Excalidraw.com (CTRL/CMD+F). The "old" search is still available in the Obsidian Command Palette _"Search for text in drawing"_. The old search will also search in image-file names and frame titles, but the result set is not as sophisticated as the one built by Excalidraw.com. If you want to use the old search, you can set up a hotkey in Obsidian settings, e.g., CTRL+ALT/CMD+OPT+F. [#8438](https://github.com/excalidraw/excalidraw/pull/8438)
- Grid Color settings under **Excalidraw Appearance and Behavior**. Note that the grid color and opacity also affect the color and transparency of the binding box when using the arrow tool. [#2007](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2007)
- Refactoring the code to be compatible with the upcoming Obsidian 1.7.2.
- ${String.fromCharCode(96)}ExcalidrawAutomate.decompressFromBase64()${String.fromCharCode(96)} will now remove line breaks from the input string so you can directly supply the compressed JSON string for decompression by script.
`,
"2.4.3": `
Check out the [Excalidraw Plugin's Community WIKI](https://excalidraw-obsidian.online/Hobbies/Excalidraw+Blog/WIKI/Welcome+to+the+WIKI) and help with your content contribution.

View File

@@ -1,12 +1,13 @@
import { ExcalidrawImperativeAPI } from "@zsviczian/excalidraw/types/excalidraw/types";
import { ColorComponent, Modal, Setting, SliderComponent, TextComponent, ToggleComponent } from "obsidian";
import { COLOR_NAMES, VIEW_TYPE_EXCALIDRAW } from "src/constants/constants";
import { ColorComponent, Modal, Setting, TextComponent, ToggleComponent } from "obsidian";
import { COLOR_NAMES } from "src/constants/constants";
import ExcalidrawView from "src/ExcalidrawView";
import ExcalidrawPlugin from "src/main";
import { setPen } from "src/menu/ObsidianMenu";
import { ExtendedFillStyle, PenStyle, PenType } from "src/PenTypes";
import { ExtendedFillStyle, PenType } from "src/PenTypes";
import { getExcalidrawViews } from "src/utils/ObsidianUtils";
import { PENS } from "src/utils/Pens";
import { fragWithHTML, getExportPadding, getExportTheme, getPNGScale, getWithBackground } from "src/utils/Utils";
import { fragWithHTML } from "src/utils/Utils";
import { __values } from "tslib";
const EASINGFUNCTIONS: Record<string,string> = {
@@ -65,9 +66,7 @@ export class PenSettingsModal extends Modal {
async onClose() {
if(this.dirty) {
app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW).forEach(v=> {
if (v.view instanceof ExcalidrawView) v.view.updatePinnedCustomPens()
})
getExcalidrawViews(this.app).forEach(excalidrawView=>excalidrawView.updatePinnedCustomPens());
this.plugin.saveSettings();
const pen = this.plugin.settings.customPens[this.pen]
const api = this.view.excalidrawAPI;

View File

@@ -720,7 +720,7 @@ export async function linkPrompt (
message: string = "Select link to open",
):Promise<[file:TFile, linkText:string, subpath: string]> {
const linksArray = REGEX_LINK.getResList(linkText);
const tagsArray = REGEX_TAGS.getResList(linkText);
const tagsArray = REGEX_TAGS.getResList(linkText.replaceAll(/([^\s])#/g,"$1 "));
let subpath: string = null;
let file: TFile = null;
let parts = linksArray[0] ?? tagsArray[0];

View File

@@ -3,7 +3,7 @@ import "obsidian";
//export ExcalidrawAutomate from "./ExcalidrawAutomate";
//export {ExcalidrawAutomate} from "./ExcaildrawAutomate";
export type { ExcalidrawBindableElement, ExcalidrawElement, FileId, FillStyle, StrokeRoundness, StrokeStyle } from "@zsviczian/excalidraw/types/excalidraw/element/types";
export type { Point } from "@zsviczian/excalidraw/types/excalidraw/types";
export type { Point } from "src/types/types";
export const getEA = (view?:any): any => {
try {
return window.ExcalidrawAutomate.getAPI(view);

View File

@@ -128,7 +128,10 @@ export default {
OPEN_LINK: "Open selected text as link\n(SHIFT+CLICK to open in a new pane)",
EXPORT_EXCALIDRAW: "Export to an .Excalidraw file",
LINK_BUTTON_CLICK_NO_TEXT:
"Select an ImageElement, or select a TextElement that contains an internal or external link.\n",
"Select an element that contains an internal or external link.\n",
LINEAR_ELEMENT_LINK_CLICK_ERROR:
"Arrow- and Line-Element links cannot be navigated by " + labelCTRL() + " + CLICKing on the element because that also activates the line editor.\n" +
"Use the right-click context menu to open the link, or click the link indicator in the top right corner of the element.\n",
FILENAME_INVALID_CHARS:
'File name cannot contain any of the following characters: * " \\ < > : | ? #',
FORCE_SAVE:
@@ -395,6 +398,14 @@ FILENAME_HEAD: "Filename",
ZOOM_TO_FIT_MAX_LEVEL_NAME: "Zoom to fit max ZOOM level",
ZOOM_TO_FIT_MAX_LEVEL_DESC:
"Set the maximum level to which zoom to fit will enlarge the drawing. Minimum is 0.5 (50%) and maximum is 10 (1000%).",
GRID_HEAD: "Grid",
GRID_DYNAMIC_COLOR_NAME: "Dynamic grid color",
GRID_DYNAMIC_COLOR_DESC:
"<b><u>Toggle ON:</u></b>Change grid color to match the canvas color<br><b><u>Toggle OFF:</u></b>Use the color below as the grid color",
GRID_COLOR_NAME: "Grid color",
GRID_OPACITY_NAME: "Grid opacity",
GRID_OPACITY_DESC: "Grid opacity will also control the opacity of the binding box when binding an arrow to an element.<br>" +
"Set the opacity of the grid. 0 is transparent, 100 is opaque.",
LASER_HEAD: "Laser pointer",
LASER_COLOR: "Laser pointer color",
LASER_DECAY_TIME_NAME: "Laser pointer decay time",

View File

@@ -128,7 +128,10 @@ export default {
OPEN_LINK: "打开所选元素里的链接 \n按住 Shift 在新面板打开)",
EXPORT_EXCALIDRAW: "导出为 .excalidraw 文件(旧版绘图文件格式)",
LINK_BUTTON_CLICK_NO_TEXT:
"请选择一个含有链接的图形或文本元素。",
"请选择一个包含内部或外部链接的元素。\n",
LINEAR_ELEMENT_LINK_CLICK_ERROR:
"箭头和线元素的链接无法通过 " + labelCTRL() + " + 点击元素来导航,因为这也会激活线编辑器。\n" +
"请使用右键上下文菜单打开链接,或点击元素右上角的链接指示器。\n",
FILENAME_INVALID_CHARS:
'文件名不能含有以下符号: * " \\ < > : | ? #',
FORCE_SAVE:
@@ -395,7 +398,15 @@ FILENAME_HEAD: "文件名",
ZOOM_TO_FIT_MAX_LEVEL_NAME: "自动缩放的最高级别",
ZOOM_TO_FIT_MAX_LEVEL_DESC:
"自动缩放画布时,允许放大的最高级别。该值不能低于 0.550%)且不能超过 101000%)。",
LASER_HEAD: "激光笔工具More Tools > Laser pointer",
GRID_HEAD: "网格",
GRID_DYNAMIC_COLOR_NAME: "动态网格颜色",
GRID_DYNAMIC_COLOR_DESC:
"<b><u>开启:</u></b>更改网格颜色以匹配画布颜色<br><b><u>关闭:</u></b>将以下颜色用作网格颜色",
GRID_COLOR_NAME: "网格颜色",
GRID_OPACITY_NAME: "网格透明度",
GRID_OPACITY_DESC: "网格透明度还将控制将箭头绑定到元素时绑定框的透明度。<br>"+
"设置网格的不透明度。 0 表示完全透明100 表示完全不透明。",
LASER_HEAD: "激光笔工具(更多工具 > 激光笔)",
LASER_COLOR: "激光笔颜色",
LASER_DECAY_TIME_NAME: "激光笔消失时间",
LASER_DECAY_TIME_DESC: "单位是毫秒,默认是 1000即 1 秒)。",

View File

@@ -101,7 +101,7 @@ import {
versionUpdateCheckTimer,
getFontMetrics,
} from "./utils/Utils";
import { editorInsertText, extractSVGPNGFileName, foldExcalidrawSection, getActivePDFPageNumberFromPDFView, getAttachmentsFolderAndFilePath, getNewOrAdjacentLeaf, getParentOfClass, isObsidianThemeDark, mergeMarkdownFiles, openLeaf, setExcalidrawView } from "./utils/ObsidianUtils";
import { editorInsertText, extractSVGPNGFileName, foldExcalidrawSection, getActivePDFPageNumberFromPDFView, getAttachmentsFolderAndFilePath, getExcalidrawViews, getNewOrAdjacentLeaf, getParentOfClass, isObsidianThemeDark, mergeMarkdownFiles, openLeaf, setExcalidrawView } from "./utils/ObsidianUtils";
import { ExcalidrawElement, ExcalidrawEmbeddableElement, ExcalidrawImageElement, ExcalidrawTextElement, FileId } from "@zsviczian/excalidraw/types/excalidraw/element/types";
import { ScriptEngine } from "./Scripts";
import {
@@ -758,9 +758,8 @@ export default class ExcalidrawPlugin extends Plugin {
setTimeout(()=>{ //run async to avoid blocking the UI
const theme = isObsidianThemeDark() ? "dark" : "light";
const leaves = this.app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW);
leaves.forEach((leaf: WorkspaceLeaf) => {
const excalidrawView = leaf.view as ExcalidrawView;
const excalidrawViews = getExcalidrawViews(this.app);
excalidrawViews.forEach(excalidrawView => {
if (excalidrawView.file && excalidrawView.excalidrawAPI) {
excalidrawView.setTheme(theme);
}
@@ -1495,7 +1494,6 @@ export default class ExcalidrawPlugin extends Plugin {
this.addCommand({
id: "toggle-lock",
hotkeys: [{ modifiers: ["Ctrl" || "Meta", "Shift"], key: "e" }],
name: t("TOGGLE_LOCK"),
checkCallback: (checking: boolean) => {
if (checking) {
@@ -1585,7 +1583,7 @@ export default class ExcalidrawPlugin extends Plugin {
this.addCommand({
id: "insert-link",
hotkeys: [{ modifiers: ["Ctrl" || "Meta", "Shift"], key: "k" }],
hotkeys: [{ modifiers: ["Mod", "Shift"], key: "k" }],
name: t("INSERT_LINK"),
checkCallback: (checking: boolean) => {
if (checking) {
@@ -1618,7 +1616,6 @@ export default class ExcalidrawPlugin extends Plugin {
this.addCommand({
id: "insert-link-to-element",
hotkeys: [{ modifiers: ["Ctrl" || "Meta", "Shift"], key: "k" }],
name: t("INSERT_LINK_TO_ELEMENT_NORMAL"),
checkCallback: (checking: boolean) => {
if (checking) {
@@ -2951,9 +2948,8 @@ export default class ExcalidrawPlugin extends Plugin {
const modifyEventHandler = async (file: TFile) => {
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(modifyEventHandler,`ExcalidrawPlugin.modifyEventHandler`, file);
const leaves = this.app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW);
leaves.forEach(async (leaf: WorkspaceLeaf) => {
const excalidrawView = leaf.view as ExcalidrawView;
const excalidrawViews = getExcalidrawViews(this.app);
excalidrawViews.forEach(async (excalidrawView) => {
if(excalidrawView.semaphores?.viewunload) {
return;
}
@@ -3013,10 +3009,10 @@ export default class ExcalidrawPlugin extends Plugin {
}
//close excalidraw view where this file is open
const leaves = this.app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW);
for (let i = 0; i < leaves.length; i++) {
if ((leaves[i].view as ExcalidrawView).file.path == file.path) {
await leaves[i].setViewState({
const excalidrawViews = getExcalidrawViews(this.app);
for (const excalidrawView of excalidrawViews) {
if (excalidrawView.file.path === file.path) {
await excalidrawView.leaf.setViewState({
type: VIEW_TYPE_EXCALIDRAW,
state: { file: null },
});
@@ -3212,8 +3208,8 @@ export default class ExcalidrawPlugin extends Plugin {
}
onunload() {
const excalidrawLeaves = this.app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW);
excalidrawLeaves.forEach((leaf) => {
const excalidrawViews = getExcalidrawViews(this.app);
excalidrawViews.forEach(({leaf}) => {
this.setMarkdownView(leaf);
});

View File

@@ -2,7 +2,7 @@ import { AppState, ExcalidrawImperativeAPI } from "@zsviczian/excalidraw/types/e
import clsx from "clsx";
import { TFile } from "obsidian";
import * as React from "react";
import { DEVICE, VIEW_TYPE_EXCALIDRAW } from "src/constants/constants";
import { DEVICE } from "src/constants/constants";
import { PenSettingsModal } from "src/dialogs/PenSettingsModal";
import ExcalidrawView from "src/ExcalidrawView";
import { PenStyle } from "src/PenTypes";
@@ -11,8 +11,7 @@ import ExcalidrawPlugin from "../main";
import { ICONS, penIcon, stringToSVG } from "./ActionIcons";
import { UniversalInsertFileModal } from "src/dialogs/UniversalInsertFileModal";
import { t } from "src/lang/helpers";
declare const PLUGIN_VERSION:string;
import { getExcalidrawViews } from "src/utils/ObsidianUtils";
export function setPen (pen: PenStyle, api: any) {
const st = api.getAppState();
@@ -134,10 +133,8 @@ export class ObsidianMenu {
this.view.excalidrawAPI?.setToast({message:`Pin removed: ${name}`, duration: 3000, closable: true});
}
await this.plugin.saveSettings();
this.plugin.app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW).forEach(v=> {
if (v.view instanceof ExcalidrawView) v.view.updatePinnedScripts()
})
})()
getExcalidrawViews(this.plugin.app).forEach(excalidrawView=>excalidrawView.updatePinnedScripts());
})()
},
1500
)

View File

@@ -3,7 +3,7 @@ import { Notice, TFile } from "obsidian";
import * as React from "react";
import { ActionButton } from "./ActionButton";
import { ICONS, saveIcon, stringToSVG } from "./ActionIcons";
import { DEVICE, SCRIPT_INSTALL_FOLDER, VIEW_TYPE_EXCALIDRAW } from "../constants/constants";
import { DEVICE, SCRIPT_INSTALL_FOLDER } from "../constants/constants";
import { insertLaTeXToView, search } from "../ExcalidrawAutomate";
import ExcalidrawView, { TextMode } from "../ExcalidrawView";
import { t } from "../lang/helpers";
@@ -18,10 +18,9 @@ import { openExternalLink } from "src/utils/ExcalidrawViewUtils";
import { UniversalInsertFileModal } from "src/dialogs/UniversalInsertFileModal";
import { DEBUGGING, debug } from "src/utils/DebugHelper";
import { REM_VALUE } from "src/utils/StylesManager";
import { getExcalidrawViews } from "src/utils/ObsidianUtils";
declare const PLUGIN_VERSION:string;
const dark = '<svg style="stroke:#ced4da;#212529;color:#ced4da;fill:#ced4da" ';
const light = '<svg style="stroke:#212529;color:#212529;fill:#212529" ';
type PanelProps = {
visible: boolean;
@@ -43,7 +42,7 @@ export type PanelState = {
scriptIconMap: ScriptIconMap | null;
};
const TOOLS_PANEL_WIDTH = () => REM_VALUE * 14.2;
const TOOLS_PANEL_WIDTH = () => REM_VALUE * 14.4;
export class ToolsPanel extends React.Component<PanelProps, PanelState> {
pos1: number = 0;
@@ -263,7 +262,7 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
shiftKey: e.shiftKey,
altKey: e.altKey,
});
this.view.handleLinkClick(event);
this.view.handleLinkClick(event, true);
}
actionOpenLinkProperties() {
@@ -367,11 +366,11 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
async actionRunScript(key: string) {
const view = this.view;
const plugin = view.plugin;
const f = app.vault.getAbstractFileByPath(key);
const f = plugin.app.vault.getAbstractFileByPath(key);
if (f && f instanceof TFile) {
plugin.scriptEngine.executeScript(
view,
await app.vault.read(f),
await plugin.app.vault.read(f),
plugin.scriptEngine.getScriptName(f),
f
);
@@ -392,9 +391,7 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
api?.setToast({message:`Pinned: ${scriptName}`, duration: 3000, closable: true})
}
await plugin.saveSettings();
plugin.app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW).forEach(v=> {
if (v.view instanceof ExcalidrawView) v.view.updatePinnedScripts()
})
getExcalidrawViews(plugin.app).forEach(excalidrawView=>excalidrawView.updatePinnedScripts());
}
private islandOnClick(event: React.MouseEvent<HTMLDivElement, MouseEvent>) {
@@ -454,7 +451,7 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
style={{
top: `${this.state.top}px`,
left: `${this.state.left}px`,
width: `13.75rem`,
width: `14.4rem`,
display:
this.state.visible && !this.state.excalidrawViewMode
? "block"
@@ -493,7 +490,7 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
maxHeight: "350px",
width: "initial",
//@ts-ignore
"--padding": 2,
"--padding": "0.125rem",
display: this.state.minimized ? "none" : "block",
}}
>

View File

@@ -10,12 +10,11 @@ import {
TextComponent,
TFile,
} from "obsidian";
import { GITHUB_RELEASES, VIEW_TYPE_EXCALIDRAW } from "./constants/constants";
import ExcalidrawView from "./ExcalidrawView";
import { GITHUB_RELEASES } from "./constants/constants";
import { t } from "./lang/helpers";
import type ExcalidrawPlugin from "./main";
import { PenStyle } from "./PenTypes";
import { DynamicStyle } from "./types/types";
import { DynamicStyle, GridSettings } from "./types/types";
import { PreviewImageType } from "./utils/UtilTypes";
import { setDynamicStyle } from "./utils/DynamicStyling";
import {
@@ -41,6 +40,7 @@ import { setDebugging } from "./utils/DebugHelper";
import { Rank } from "./menu/ActionIcons";
import { TAG_AUTOEXPORT, TAG_MDREADINGMODE, TAG_PDFEXPORT } from "src/constants/constSettingsTags";
import { HotkeyEditor } from "./dialogs/HotkeyEditor";
import { getExcalidrawViews } from "./utils/ObsidianUtils";
export interface ExcalidrawSettings {
folder: string;
@@ -179,6 +179,7 @@ export interface ExcalidrawSettings {
pdfNumRows: number;
pdfDirection: "down" | "right";
pdfImportScale: number;
gridSettings: GridSettings;
laserSettings: {
DECAY_TIME: number,
DECAY_LENGTH: number,
@@ -224,8 +225,8 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
onceOffCompressFlagReset: false,
onceOffGPTVersionReset: false,
autosave: true,
autosaveIntervalDesktop: 30000,
autosaveIntervalMobile: 20000,
autosaveIntervalDesktop: 60000,
autosaveIntervalMobile: 30000,
drawingFilenamePrefix: "Drawing ",
drawingEmbedPrefixWithFilename: true,
drawingFilnameEmbedPostfix: " ",
@@ -267,9 +268,9 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
done: "🗹",
hoverPreviewWithoutCTRL: false,
linkOpacity: 1,
openInAdjacentPane: false,
openInAdjacentPane: true,
showSecondOrderLinks: true,
focusOnFileTab: false,
focusOnFileTab: true,
openInMainWorkspace: true,
showLinkBrackets: true,
allowCtrlClick: true,
@@ -355,6 +356,11 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
pdfNumRows: 1,
pdfDirection: "right",
pdfImportScale: 0.3,
gridSettings: {
DYNAMIC_COLOR: true,
COLOR: "#000000",
OPACITY: 50,
},
laserSettings: {
DECAY_LENGTH: 50,
DECAY_TIME: 1000,
@@ -509,31 +515,25 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
}
this.plugin.saveSettings();
if (this.requestUpdatePinnedPens) {
this.app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW).forEach(v=> {
if (v.view instanceof ExcalidrawView) v.view.updatePinnedCustomPens()
})
getExcalidrawViews(this.app).forEach(excalidrawView =>
excalidrawView.updatePinnedCustomPens()
)
}
if (this.requestUpdateDynamicStyling) {
this.app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW).forEach(v=> {
if (v.view instanceof ExcalidrawView) {
setDynamicStyle(this.plugin.ea,v.view,v.view.previousBackgroundColor,this.plugin.settings.dynamicStyling);
}
})
getExcalidrawViews(this.app).forEach(excalidrawView =>
setDynamicStyle(this.plugin.ea,excalidrawView,excalidrawView.previousBackgroundColor,this.plugin.settings.dynamicStyling)
)
}
this.hotkeyEditor.unload();
if (this.hotkeyEditor.isDirty) {
this.plugin.registerHotkeyOverrides();
}
if (this.requestReloadDrawings) {
const exs =
this.app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW);
for (const v of exs) {
if (v.view instanceof ExcalidrawView) {
await v.view.save(false);
//debug({where:"ExcalidrawSettings.hide",file:v.view.file.name,before:"reload(true)"})
await v.view.reload(true);
}
const excalidrawViews = getExcalidrawViews(this.app);
for (const excalidrawView of excalidrawViews) {
await excalidrawView.save(false);
//debug({where:"ExcalidrawSettings.hide",file:v.view.file.name,before:"reload(true)"})
await excalidrawView.reload(true);
}
this.requestEmbedUpdate = true;
}
@@ -1241,9 +1241,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
.setValue(this.plugin.settings.allowPinchZoom)
.onChange(async (value) => {
this.plugin.settings.allowPinchZoom = value;
app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW).forEach(v=> {
if (v.view instanceof ExcalidrawView) v.view.updatePinchZoom()
})
getExcalidrawViews(this.app).forEach(excalidrawView=>excalidrawView.updatePinchZoom())
this.applySettingsUpdate();
}),
);
@@ -1257,9 +1255,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
.setValue(this.plugin.settings.allowWheelZoom)
.onChange(async (value) => {
this.plugin.settings.allowWheelZoom = value;
app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW).forEach(v=> {
if (v.view instanceof ExcalidrawView) v.view.updateWheelZoom()
})
getExcalidrawViews(this.app).forEach(excalidrawView=>excalidrawView.updateWheelZoom());
this.applySettingsUpdate();
}),
);
@@ -1310,7 +1306,79 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
el.innerText = ` ${this.plugin.settings.zoomToFitMaxLevel.toString()}`;
});
// ------------------------------------------------
// Grid
// ------------------------------------------------
detailsEl = displayDetailsEl.createEl("details");
detailsEl.createEl("summary", {
text: t("GRID_HEAD"),
cls: "excalidraw-setting-h3",
});
const updateGridColor = () => {
getExcalidrawViews(this.app).forEach(excalidrawView=>excalidrawView.updateGridColor());
};
// Dynamic color toggle
let gridColorSection: HTMLDivElement;
new Setting(detailsEl)
.setName(t("GRID_DYNAMIC_COLOR_NAME"))
.setDesc(fragWithHTML(t("GRID_DYNAMIC_COLOR_DESC")))
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.gridSettings.DYNAMIC_COLOR)
.onChange(async (value) => {
this.plugin.settings.gridSettings.DYNAMIC_COLOR = value;
gridColorSection.style.display = value ? "none" : "block";
this.applySettingsUpdate();
updateGridColor();
}),
);
// Create a div to contain color and opacity settings
gridColorSection = detailsEl.createDiv();
gridColorSection.style.display = this.plugin.settings.gridSettings.DYNAMIC_COLOR ? "none" : "block";
// Grid color picker
new Setting(gridColorSection)
.setName(t("GRID_COLOR_NAME"))
.addColorPicker((colorPicker) =>
colorPicker
.setValue(this.plugin.settings.gridSettings.COLOR)
.onChange(async (value) => {
this.plugin.settings.gridSettings.COLOR = value;
this.applySettingsUpdate();
updateGridColor();
}),
);
// Grid opacity slider (hex value between 00 and FF)
let opacityValue: HTMLDivElement;
new Setting(detailsEl)
.setName(t("GRID_OPACITY_NAME"))
.setDesc(fragWithHTML(t("GRID_OPACITY_DESC")))
.addSlider((slider) =>
slider
.setLimits(0, 100, 1) // 0 to 100 in decimal
.setValue(this.plugin.settings.gridSettings.OPACITY)
.onChange(async (value) => {
opacityValue.innerText = ` ${value.toString()}`;
this.plugin.settings.gridSettings.OPACITY = value;
this.applySettingsUpdate();
updateGridColor();
}),
)
.settingEl.createDiv("", (el) => {
opacityValue = el;
el.style.minWidth = "3em";
el.style.textAlign = "right";
el.innerText = ` ${this.plugin.settings.gridSettings.OPACITY}`;
});
// ------------------------------------------------
// Laser Pointer
// ------------------------------------------------
detailsEl = displayDetailsEl.createEl("details");
detailsEl.createEl("summary", {
text: t("LASER_HEAD"),

View File

@@ -13,6 +13,12 @@ export type ValueOf<T> = T[keyof T];
export type DynamicStyle = "none" | "gray" | "colorful";
export type GridSettings = {
DYNAMIC_COLOR: boolean; // Whether the grid color is dynamic
COLOR: string; // The grid color (in hex format)
OPACITY: number; // The grid opacity (hex value between "00" and "FF")
};
export type DeviceType = {
isDesktop: boolean,
isPhone: boolean,
@@ -25,6 +31,8 @@ export type DeviceType = {
isAndroid: boolean,
};
export type Point = [number, number];
declare global {
interface Window {
ExcalidrawAutomate: ExcalidrawAutomate;

View File

@@ -4,6 +4,7 @@ import { getEA } from "src";
import { ExcalidrawAutomate } from "src/ExcalidrawAutomate";
import { getCropFileNameAndFolder, getListOfTemplateFiles, splitFolderAndFilename } from "./FileUtils";
import { Notice, TFile } from "obsidian";
import { Radians } from "@zsviczian/excalidraw/types/math";
export const CROPPED_PREFIX = "cropped_";
export const ANNOTATED_PREFIX = "annotated_";
@@ -30,7 +31,7 @@ export const carveOutImage = async (sourceEA: ExcalidrawAutomate, viewImageEl: E
const scale = newImage.scale;
const angle = newImage.angle;
newImage.scale = [1,1];
newImage.angle = 0;
newImage.angle = 0 as Radians;
const ef = sourceEA.targetView.excalidrawData.getFile(viewImageEl.fileId);
let imageLink = "";

View File

@@ -139,7 +139,9 @@ export class CropImage {
const PLUGIN = app.plugins.plugins["obsidian-excalidraw-plugin"];
const svg = await this.buildSVG();
return new Promise((resolve, reject) => {
const svgData = new XMLSerializer().serializeToString(svg);
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2026
const svgData = svg.outerHTML;
//const svgData = new XMLSerializer().serializeToString(svg);
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');

View File

@@ -82,8 +82,10 @@ export const setDynamicStyle = (
[`--color-surface-high`]: str(gray1().lighterBy(step)),
[`--color-on-primary-container`]: str(!isDark?accent().darkerBy(15):accent().lighterBy(15)),
[`--color-surface-primary-container`]: str(isDark?accent().darkerBy(step):accent().lighterBy(step)),
//[`--color-primary-darker`]: str(accent().darkerBy(step)),
//[`--color-primary-darkest`]: str(accent().darkerBy(step)),
[`--bold-color`]: str(!isDark?accent().darkerBy(15):accent().lighterBy(15)),
[`--color-primary-darker`]: str(accent().darkerBy(step)),
[`--color-primary-darkest`]: str(accent().darkerBy(2*step)),
['--button-bg-color']: str(gray1()),
[`--button-gray-1`]: str(gray1()),
[`--button-gray-2`]: str(gray2()),
[`--input-border-color`]: str(gray1()),
@@ -96,11 +98,11 @@ export const setDynamicStyle = (
[`--overlay-bg-color`]: gray2().alphaTo(0.6).stringHEX(),
[`--popup-bg-color`]: str(gray1()),
[`--color-on-surface`]: str(text),
[`--default-border-color`]: str(gray1()),
//[`--color-gray-100`]: str(text),
[`--color-gray-40`]: str(text), //frame
[`--color-gray-50`]: str(text), //frame
[`--color-surface-highlight`]: str(gray1()),
//[`--color-gray-30`]: str(gray1),
[`--color-gray-20`]: str(gray1()),
[`--sidebar-border-color`]: str(gray1()),
[`--color-primary-light`]: str(accent().lighterBy(step)),
[`--button-hover-bg`]: str(gray1()),
@@ -116,7 +118,11 @@ export const setDynamicStyle = (
[`color`]: str(text),
['--excalidraw-caret-color']: str(isLightTheme ? text : cmBG()),
[`--select-highlight-color`]: str(gray1()),
[`--color-gray-80`]: str(isDark?text.darkerBy(40):text.lighterBy(40)), //frame
[`--color-gray-90`]: str(isDark?text.darkerBy(5):text.lighterBy(5)), //search background
[`--color-gray-80`]: str(isDark?text.darkerBy(10):text.lighterBy(10)), //frame
[`--color-gray-70`]: str(isDark?text.darkerBy(10):text.lighterBy(10)), //frame
[`--default-bg-color`]: str(isDark?text.darkerBy(20):text.lighterBy(20)), //search background,
[`--color-gray-50`]: str(text), //frame
};
const styleString = Object.keys(styleObject)

View File

@@ -23,6 +23,9 @@ export function updateElementIdsInScene(
boundEl.boundElements?.filter(x=>x.id === elementToChange.id).forEach( x => {
(x.id as Mutable<string>) = newID;
});
if(boundEl.type === "text") {
boundEl.containerId = newID;
}
if(boundEl.type === "arrow") {
const arrow = boundEl as Mutable<ExcalidrawArrowElement>;
if(arrow.startBinding?.elementId === elementToChange.id) {

View File

@@ -81,20 +81,18 @@ export function openTagSearch(link: string, app: App, view?: ExcalidrawView) {
return;
}
const search = app.workspace.getLeavesOfType("search");
if (search.length === 0) {
return;
const query = `tag:${tags[0].value[1]}`;
const searchPlugin = app.internalPlugins.getPluginById("global-search");
if (searchPlugin) {
const searchInstance = searchPlugin.instance;
if (searchInstance) {
searchInstance.openGlobalSearch(query);
}
}
//@ts-ignore
search[0].view.setQuery(`tag:${tags[0].value[1]}`);
app.workspace.revealLeaf(search[0]);
if (view && view.isFullscreen()) {
view.exitFullscreen();
}
return;
}
function getLinkFromMarkdownLink(link: string): string {
@@ -123,14 +121,21 @@ export function openExternalLink (link:string, app: App, element?: ExcalidrawEle
* @param link
* @param app
* @param returnWikiLink
* @param openLink: if set to false, the link will not be opened just true will be returned for an obsidian link.
* @returns
* false if the link is not an obsidian link,
* true if the link is an obsidian link and it was opened (i.e. it is a link to another Vault or not a file link e.g. plugin link), or
* the link to the file path. By default as a wiki link, or as a file path if returnWikiLink is false.
*/
export function parseObsidianLink(link: string, app: App, returnWikiLink: boolean = true): boolean | string {
export function parseObsidianLink(
link: string,
app: App,
returnWikiLink: boolean = true,
openLink: boolean = true,
): boolean | string {
if(!link) return false;
link = getLinkFromMarkdownLink(link);
if (!link.startsWith("obsidian://")) {
if (!link?.startsWith("obsidian://")) {
return false;
}
const url = new URL(link);
@@ -153,7 +158,9 @@ export function parseObsidianLink(link: string, app: App, returnWikiLink: boolea
}
}
window.open(link, "_blank");
if(openLink) {
window.open(link, "_blank");
}
return true;
}
@@ -394,4 +401,4 @@ export function isTextImageTransclusion (
}
}
return false;
}
}

View File

@@ -2,6 +2,7 @@ import { ExcalidrawElement, ExcalidrawImageElement, ExcalidrawTextElement } from
import { REGEX_LINK, REG_LINKINDEX_HYPERLINK } from "src/ExcalidrawData";
import ExcalidrawView, { TextMode } from "src/ExcalidrawView";
import { rotatedDimensions } from "./Utils";
import { getBoundTextElementId } from "src/ExcalidrawAutomate";
export const getElementsAtPointer = (
pointer: any,
@@ -93,13 +94,24 @@ const api = view.excalidrawAPI;
if (!api) {
return;
}
const elements = (
let elements = (
getElementsAtPointer(
pointer,
api.getSceneElements(),
) as ExcalidrawImageElement[]
) as ExcalidrawElement[]
).filter((el) => el.link);
//as a fallback let's check if any of the elements at pointer are containers with a text element that has a link.
if (elements.length === 0) {
const textElIDs = (
getElementsAtPointer(
pointer,
api.getSceneElements(),
) as ExcalidrawImageElement[]
).map((el) => getBoundTextElementId(el));
elements = view.getViewElements().filter((el) => el.type==="text" && el.link && textElIDs.includes(el.id));
}
if (elements.length === 0) {
return { id: null, text: null };
}

View File

@@ -9,8 +9,9 @@ import ExcalidrawPlugin from "../main";
import { checkAndCreateFolder, splitFolderAndFilename } from "./FileUtils";
import { linkClickModifierType, ModifierKeys } from "./ModifierkeyHelper";
import { REG_BLOCK_REF_CLEAN, REG_SECTION_REF_CLEAN, VIEW_TYPE_EXCALIDRAW } from "src/constants/constants";
import yaml, { Mark } from "js-yaml";
import yaml from "js-yaml";
import { debug, DEBUGGING } from "./DebugHelper";
import ExcalidrawView from "src/ExcalidrawView";
export const getParentOfClass = (element: Element, cssClass: string):HTMLElement | null => {
let parent = element.parentElement;
@@ -24,6 +25,11 @@ export const getParentOfClass = (element: Element, cssClass: string):HTMLElement
return parent?.classList?.contains(cssClass) ? parent : null;
};
export function getExcalidrawViews(app: App): ExcalidrawView[] {
const leaves = app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW).filter(l=>l.view instanceof ExcalidrawView);
return leaves.map(l=>l.view as ExcalidrawView);
}
export const getLeaf = (
plugin: ExcalidrawPlugin,
origo: WorkspaceLeaf,

18
src/utils/typechecks.ts Normal file
View File

@@ -0,0 +1,18 @@
/**
* Checks if a given target is an HTMLElement.
*
* This function is necessary because `instanceof HTMLElement` can fail
* in environments with multiple execution contexts (e.g., popout windows),
* where `HTMLElement` comes from different global objects.
* Instead, we use feature detection by checking for key properties
* common to all HTML elements (nodeType and tagName).
*
* @param target - The target to check.
* @returns True if the target is an HTMLElement, false otherwise.
*/
export function isHTMLElement (target: any): target is HTMLElement {
return target &&
typeof target === 'object' &&
target.nodeType === 1 && // nodeType 1 means it's an element
typeof target.tagName === 'string'; // tagName exists on HTML elements
}

View File

@@ -634,4 +634,20 @@ textarea.excalidraw-wysiwyg, .excalidraw input {
.excalidraw .color-picker-content input[type="color"] {
filter: var(--theme-filter);
}
}
.ExcTextField__input input::placeholder {
color: var(--select-highlight-color);
}
.excalidraw textarea::placeholder {
color: var(--color-gray-50);
}
.excalidraw textarea.ttd-dialog-input {
caret-color: var(--excalidraw-caret-color);
}
.excalidraw .ToolIcon_type_button {
color: var(--text-primary-color);
}