mirror of
https://github.com/zsviczian/obsidian-excalidraw-plugin.git
synced 2025-08-06 05:46:28 +00:00
Compare commits
30 Commits
style-twea
...
2.5.3-beta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7cac94bf2f | ||
|
|
43e98db174 | ||
|
|
253575bf23 | ||
|
|
7a08ced65a | ||
|
|
5a64e1c75e | ||
|
|
fc0ac92dd3 | ||
|
|
4e2d7eb637 | ||
|
|
f8f280c7d5 | ||
|
|
00b87f99c0 | ||
|
|
0b5c74dde8 | ||
|
|
906b3bdf92 | ||
|
|
0c28e82212 | ||
|
|
beb4301f14 | ||
|
|
e96fe9c491 | ||
|
|
268680f494 | ||
|
|
a1512fce26 | ||
|
|
c2e79f3439 | ||
|
|
01780a2bf8 | ||
|
|
10e54eb03e | ||
|
|
2760a9966b | ||
|
|
a297dbbe52 | ||
|
|
74c0af2032 | ||
|
|
813c85accd | ||
|
|
c97d08c997 | ||
|
|
097d1bcd1b | ||
|
|
7c91186ed5 | ||
|
|
904bc7c994 | ||
|
|
9fd4ae2615 | ||
|
|
18fbb0934e | ||
|
|
a5771625df |
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -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, it’s 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
|
||||
|
||||
17
README.md
17
README.md
@@ -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
|
||||
|
||||
|
||||
BIN
assets/excalidraw-fonts.zip
Normal file
BIN
assets/excalidraw-fonts.zip
Normal file
Binary file not shown.
@@ -2,7 +2,7 @@
|
||||
|
||||
> 此说明当前更新至 `5569cff`。
|
||||
|
||||
[English](./AutomateHowTo.md)
|
||||
[English](../../AutomateHowTo.md)
|
||||
|
||||
Excalidraw 自动化允许您使用 [Templater](https://github.com/SilentVoid13/Templater) 插件创建 Excalidraw 绘图。
|
||||
|
||||
|
||||
@@ -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)贡献你的力量吧
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "obsidian-excalidraw-plugin",
|
||||
"name": "Excalidraw",
|
||||
"version": "2.5.0-beta-3",
|
||||
"version": "2.5.3-beta-1",
|
||||
"minAppVersion": "1.1.6",
|
||||
"description": "An Obsidian plugin to edit and view Excalidraw drawings",
|
||||
"author": "Zsolt Viczian",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "obsidian-excalidraw-plugin",
|
||||
"name": "Excalidraw",
|
||||
"version": "2.4.3",
|
||||
"version": "2.5.2",
|
||||
"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-52",
|
||||
"@zsviczian/excalidraw": "0.17.6-2",
|
||||
"chroma-js": "^2.4.2",
|
||||
"clsx": "^2.0.0",
|
||||
"@zsviczian/colormaster": "^1.2.2",
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -93,6 +93,7 @@ import { EXCALIDRAW_AUTOMATE_INFO, EXCALIDRAW_SCRIPTENGINE_INFO } from "./dialog
|
||||
import { addBackOfTheNoteCard, getFrameBasedOnFrameNameOrId } from "./utils/ExcalidrawViewUtils";
|
||||
import { log } from "./utils/DebugHelper";
|
||||
import { ExcalidrawLib } from "./ExcalidrawLib";
|
||||
import { GlobalPoint } from "@zsviczian/excalidraw/types/math/types";
|
||||
|
||||
extendPlugins([
|
||||
HarmonyPlugin,
|
||||
@@ -281,8 +282,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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1393,8 +1403,8 @@ export class ExcalidrawAutomate {
|
||||
): string {
|
||||
const box = getLineBox(points);
|
||||
id = id ?? nanoid();
|
||||
const startPoint = points[0];
|
||||
const endPoint = points[points.length - 1];
|
||||
const startPoint = points[0] as GlobalPoint;
|
||||
const endPoint = points[points.length - 1] as GlobalPoint;
|
||||
this.elementsDict[id] = {
|
||||
points: normalizeLinePoints(points),
|
||||
lastCommittedPoint: null,
|
||||
@@ -1684,8 +1694,8 @@ export class ExcalidrawAutomate {
|
||||
if (!connectionA) {
|
||||
const intersect = intersectElementWithLine(
|
||||
elA,
|
||||
[bCenterX, bCenterY],
|
||||
[aCenterX, aCenterY],
|
||||
[bCenterX, bCenterY] as GlobalPoint,
|
||||
[aCenterX, aCenterY] as GlobalPoint,
|
||||
GAP,
|
||||
);
|
||||
if (intersect.length === 0) {
|
||||
@@ -1698,8 +1708,8 @@ export class ExcalidrawAutomate {
|
||||
if (!connectionB) {
|
||||
const intersect = intersectElementWithLine(
|
||||
elB,
|
||||
[aCenterX, aCenterY],
|
||||
[bCenterX, bCenterY],
|
||||
[aCenterX, aCenterY] as GlobalPoint,
|
||||
[bCenterX, bCenterY] as GlobalPoint,
|
||||
GAP,
|
||||
);
|
||||
if (intersect.length === 0) {
|
||||
@@ -2412,7 +2422,12 @@ export class ExcalidrawAutomate {
|
||||
b: readonly [number, number],
|
||||
gap?: number,
|
||||
): Point[] {
|
||||
return intersectElementWithLine(element, a, b, gap);
|
||||
return intersectElementWithLine(
|
||||
element,
|
||||
a as GlobalPoint,
|
||||
b as GlobalPoint,
|
||||
gap
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -2869,11 +2884,11 @@ function getFontFamily(id: number):string {
|
||||
}
|
||||
|
||||
export async function initFonts():Promise<void> {
|
||||
await excalidrawLib.registerFontsInCSS();
|
||||
/*await excalidrawLib.registerFontsInCSS();
|
||||
const fonts = excalidrawLib.getFontFamilies();
|
||||
for(let i=0;i<fonts.length;i++) {
|
||||
if(fonts[i] !== "Local Font") await (document as any).fonts.load(`16px ${fonts[i]}`);
|
||||
};
|
||||
};*/
|
||||
}
|
||||
|
||||
export function _measureText(
|
||||
@@ -3425,7 +3440,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;
|
||||
@@ -3506,4 +3521,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;
|
||||
};
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
FRONTMATTER_KEYS,
|
||||
refreshTextDimensions,
|
||||
getContainerElement,
|
||||
loadSceneFonts,
|
||||
} from "./constants/constants";
|
||||
import ExcalidrawPlugin from "./main";
|
||||
import { TextMode } from "./ExcalidrawView";
|
||||
@@ -51,6 +52,9 @@ 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";
|
||||
import { t } from "./lang/helpers";
|
||||
import { displayFontMessage } from "./utils/ExcalidrawViewUtils";
|
||||
|
||||
type SceneDataWithFiles = SceneData & { files: BinaryFiles };
|
||||
|
||||
@@ -744,6 +748,15 @@ export class ExcalidrawData {
|
||||
|
||||
this.deletedElements = this.scene.elements.filter((el:ExcalidrawElement)=>el.isDeleted);
|
||||
this.scene.elements = this.scene.elements.filter((el:ExcalidrawElement)=>!el.isDeleted);
|
||||
|
||||
const timer = window.setTimeout(()=>{
|
||||
const notice = new Notice(t("FONT_LOAD_SLOW"),15000);
|
||||
notice.noticeEl.oncontextmenu = () => {
|
||||
displayFontMessage(this.app);
|
||||
}
|
||||
},2000);
|
||||
await loadSceneFonts(this.scene.elements);
|
||||
clearTimeout(timer);
|
||||
|
||||
if (!this.scene.files) {
|
||||
this.scene.files = {}; //loading legacy scenes that do not yet have the files attribute.
|
||||
@@ -1506,31 +1519,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 +1679,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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
14
src/ExcalidrawLib.d.ts
vendored
14
src/ExcalidrawLib.d.ts
vendored
@@ -3,8 +3,9 @@ import { ImportedDataState } from "@zsviczian/excalidraw/types/excalidraw/data/t
|
||||
import { BoundingBox } from "@zsviczian/excalidraw/types/excalidraw/element/bounds";
|
||||
import { ElementsMap, ExcalidrawBindableElement, ExcalidrawElement, ExcalidrawFrameElement, ExcalidrawFrameLikeElement, ExcalidrawTextContainer, ExcalidrawTextElement, FontFamilyValues, FontString, NonDeleted, NonDeletedExcalidrawElement, Theme } from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
import { FontMetadata } from "@zsviczian/excalidraw/types/excalidraw/fonts/metadata";
|
||||
import { AppState, BinaryFiles, DataURL, GenerateDiagramToCode, Point, Zoom } from "@zsviczian/excalidraw/types/excalidraw/types";
|
||||
import { AppState, BinaryFiles, DataURL, GenerateDiagramToCode, Zoom } from "@zsviczian/excalidraw/types/excalidraw/types";
|
||||
import { Mutable } from "@zsviczian/excalidraw/types/excalidraw/utility-types";
|
||||
import { GlobalPoint } from "@zsviczian/excalidraw/types/math/types";
|
||||
|
||||
type EmbeddedLink =
|
||||
| ({
|
||||
@@ -75,16 +76,16 @@ declare namespace ExcalidrawLib {
|
||||
|
||||
function determineFocusDistance(
|
||||
element: ExcalidrawBindableElement,
|
||||
a: Point,
|
||||
b: Point,
|
||||
a: GlobalPoint,
|
||||
b: GlobalPoint,
|
||||
): number;
|
||||
|
||||
function intersectElementWithLine(
|
||||
element: ExcalidrawBindableElement,
|
||||
a: Point,
|
||||
b: Point,
|
||||
a: GlobalPoint,
|
||||
b: GlobalPoint,
|
||||
gap?: number,
|
||||
): Point[];
|
||||
): GlobalPoint[];
|
||||
|
||||
function getCommonBoundingBox(
|
||||
elements: ExcalidrawElement[] | readonly NonDeleted<ExcalidrawElement>[],
|
||||
@@ -186,5 +187,6 @@ declare namespace ExcalidrawLib {
|
||||
separator?: string,
|
||||
): string;
|
||||
function safelyParseJSON (json: string): Record<string, any> | null;
|
||||
function loadSceneFonts(elements: NonDeletedExcalidrawElement[]): Promise<void>;
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
@@ -2337,8 +2409,8 @@ export default class ExcalidrawView extends TextFileView {
|
||||
|
||||
if (this.plugin.settings.gridSettings.DYNAMIC_COLOR) {
|
||||
// Dynamic color: concatenate opacity to the HEX string
|
||||
Regular = (isDark ? cm.lighterBy(7) : cm.darkerBy(7)).alphaTo(opacity).stringRGB({ alpha: true });
|
||||
Bold = (isDark ? cm.lighterBy(14) : cm.darkerBy(14)).alphaTo(opacity).stringRGB({ alpha: true });
|
||||
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);
|
||||
@@ -2348,7 +2420,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
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(7) : customCM.darkerBy(7)).alphaTo(opacity).stringRGB({ alpha: true });
|
||||
Bold = (customIsDark ? customCM.lighterBy(10) : customCM.darkerBy(10)).alphaTo(opacity).stringRGB({ alpha: true });
|
||||
}
|
||||
|
||||
return { Bold, Regular };
|
||||
@@ -2673,6 +2745,8 @@ export default class ExcalidrawView extends TextFileView {
|
||||
}
|
||||
if(!DEVICE.isMobile) {
|
||||
if(requireApiVersion("0.16.0")) {
|
||||
//@ts-ignore
|
||||
this.leaf.tabHeaderInnerIconEl.style.color="var(--color-accent)"
|
||||
//@ts-ignore
|
||||
this.leaf.tabHeaderInnerTitleEl.style.color="var(--color-accent)"
|
||||
}
|
||||
@@ -2701,6 +2775,8 @@ export default class ExcalidrawView extends TextFileView {
|
||||
this.actionButtons['save'].querySelector("svg").removeClass("excalidraw-dirty");
|
||||
if(!DEVICE.isMobile) {
|
||||
if(requireApiVersion("0.16.0")) {
|
||||
//@ts-ignore
|
||||
this.leaf.tabHeaderInnerIconEl.style.color=""
|
||||
//@ts-ignore
|
||||
this.leaf.tabHeaderInnerTitleEl.style.color=""
|
||||
}
|
||||
@@ -3134,6 +3210,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?
|
||||
@@ -3362,6 +3448,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,
|
||||
@@ -3396,7 +3489,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
currentStrokeOptions: st.currentStrokeOptions,
|
||||
frameRendering: st.frameRendering,
|
||||
objectsSnapModeEnabled: st.objectsSnapModeEnabled,
|
||||
activeTool: st.activeTool,
|
||||
activeTool,
|
||||
},
|
||||
prevTextMode: this.prevTextMode,
|
||||
files,
|
||||
@@ -3424,7 +3517,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,
|
||||
@@ -3610,7 +3711,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,
|
||||
@@ -3639,6 +3740,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
|
||||
@@ -3657,6 +3759,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
|
||||
@@ -4729,6 +4834,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
null,
|
||||
{id: element.id, text: link},
|
||||
event,
|
||||
true,
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -4938,7 +5044,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
t("OPEN_LINK_CLICK"),
|
||||
() => {
|
||||
const event = emulateKeysForLinkClick("new-tab");
|
||||
this.handleLinkClick(event);
|
||||
this.handleLinkClick(event, true);
|
||||
},
|
||||
onClose
|
||||
),
|
||||
@@ -5072,7 +5178,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
|
||||
),
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -103,6 +103,7 @@ export const {
|
||||
getContainerElement,
|
||||
refreshTextDimensions,
|
||||
getCSSFontDefinition,
|
||||
loadSceneFonts,
|
||||
} = excalidrawLib;
|
||||
|
||||
export const FONTS_STYLE_ID = "excalidraw-custom-fonts";
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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:
|
||||
@@ -184,7 +187,7 @@ export default {
|
||||
|
||||
BASIC_HEAD: "Basic",
|
||||
BASIC_DESC: `In the "Basic" settings, you can configure options such as displaying release notes after updates, receiving plugin update notifications, setting the default location for new drawings, specifying the Excalidraw folder for embedding drawings into active documents, defining an Excalidraw template file, and designating an Excalidraw Automate script folder for managing automation scripts.`,
|
||||
FOLDER_NAME: "Excalidraw folder",
|
||||
FOLDER_NAME: "Excalidraw folder (CAsE sEnsITive!)",
|
||||
FOLDER_DESC:
|
||||
"Default location for new drawings. If empty, drawings will be created in the Vault root.",
|
||||
CROP_PREFIX_NAME: "Crop file prefix",
|
||||
@@ -198,10 +201,10 @@ export default {
|
||||
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_NAME: "Crop file folder (CaSE senSItive!)",
|
||||
CROP_FOLDER_DESC:
|
||||
"Default location for new drawings created when cropping an image. If empty, drawings will be created following the Vault attachments settings.",
|
||||
ANNOTATE_FOLDER_NAME: "Image annotation file folder",
|
||||
ANNOTATE_FOLDER_NAME: "Image annotation file folder (CaSe SeNSitIVe!)",
|
||||
ANNOTATE_FOLDER_DESC:
|
||||
"Default location for new drawings created when annotating an image. If empty, drawings will be created following the Vault attachments settings.",
|
||||
FOLDER_EMBED_NAME:
|
||||
@@ -210,7 +213,7 @@ export default {
|
||||
"Define which folder to place the newly inserted drawing into " +
|
||||
"when using the command palette action: 'Create a new drawing and embed into active document'.<br>" +
|
||||
"<b><u>Toggle ON:</u></b> Use Excalidraw folder<br><b><u>Toggle OFF:</u></b> Use the attachments folder defined in Obsidian settings.",
|
||||
TEMPLATE_NAME: "Excalidraw template file or folder",
|
||||
TEMPLATE_NAME: "Excalidraw template file or folder (caSe SenSiTive!)",
|
||||
TEMPLATE_DESC:
|
||||
"Full filepath or folderpath to the Excalidraw template.<br>" +
|
||||
"<b>Template File:</b>E.g.: If your template is in the default Excalidraw folder and its name is " +
|
||||
@@ -226,6 +229,15 @@ export default {
|
||||
"You can access your scripts from Excalidraw via the Obsidian Command Palette. Assign " +
|
||||
"hotkeys to your favorite scripts just like to any other Obsidian command. " +
|
||||
"The folder may not be the root folder of your Vault. ",
|
||||
ASSETS_FOLDER_NAME: "Local Font Assets Folder (cAsE sENsiTIvE!)",
|
||||
ASSETS_FOLDER_DESC: `Since version 2.5.3, following the implementation of CJK font support, Excalidraw downloads fonts from the internet.
|
||||
If you prefer to keep Excalidraw fully local, allowing it to work without internet access, or if your internet connection is slow
|
||||
and you want to improve performance, you can download the necessary
|
||||
<a href="https://github.com/zsviczian/obsidian-excalidraw-plugin/raw/refs/heads/master/assets/excalidraw-fonts.zip" target="_blank">font assets from GitHub</a>.
|
||||
After downloading, unzip the contents into a folder within your Vault.<br>
|
||||
You can specify the location of that folder here. For example, you may choose to place it under <code>Excalidraw/FontAssets</code>.<br><br>
|
||||
<strong>Important:</strong> Do not set this to the Vault root! Ensure that no other files are placed in this folder.<br><br>
|
||||
<strong>Note:</strong> If you're using Obsidian Sync and want to synchronize these font files across your devices, ensure that Obsidian Sync is set to synchronize "All other file types".`,
|
||||
AI_HEAD: "AI Settings - Experimental",
|
||||
AI_DESC: `In the "AI" settings, you can configure options for using OpenAI's GPT API. ` +
|
||||
`While the OpenAI API is in beta, its use is strictly limited — as such we require you use your own API key. ` +
|
||||
@@ -402,7 +414,7 @@ FILENAME_HEAD: "Filename",
|
||||
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, 1 is opaque.",
|
||||
"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",
|
||||
@@ -813,6 +825,36 @@ FILENAME_HEAD: "Filename",
|
||||
|
||||
//ExcalidrawData.ts
|
||||
LOAD_FROM_BACKUP: "Excalidraw file was corrupted. Loading from backup file.",
|
||||
FONT_LOAD_SLOW: "Loading Fonts...\n\n This is taking longer than expected. If this delay occurs regulary then you may download the fonts locally to your Vault. \n\n" +
|
||||
"(click=dismiss, right-click=Info)",
|
||||
FONT_INFO_TITLE: "Starting v2.5.3 fonts load from the Internet",
|
||||
FONT_INFO_DETAILED: `
|
||||
<p>
|
||||
To improve Obsidian's startup time and manage the large <strong>CJK font family</strong>,
|
||||
I've moved the fonts out of the plugin's <code>main.js</code>. Starting with version 2.5.3,
|
||||
fonts will be loaded from the internet. This typically shouldn't cause issues as Obsidian caches
|
||||
these files after first use.
|
||||
</p>
|
||||
<p>
|
||||
If you prefer to keep Obsidian 100% local or experience performance issues, you can download the font assets.
|
||||
</p>
|
||||
<h3>Instructions:</h3>
|
||||
<ol>
|
||||
<li>Download the fonts from <a href="https://github.com/zsviczian/obsidian-excalidraw-plugin/raw/refs/heads/master/assets/excalidraw-fonts.zip">GitHub</a>.</li>
|
||||
<li>Unzip and copy files into a Vault folder (default: <code>Excalidraw/FontAssets</code>; folder names are cAse-senSITive).</li>
|
||||
<li><mark>DO NOT</mark> set this folder to the Vault root or mix with other local fonts.</li>
|
||||
</ol>
|
||||
<h3>For Obsidian Sync Users:</h3>
|
||||
<p>
|
||||
Ensure Obsidian Sync is set to synchronize "All other file types" or download and unzip the file on all devices.
|
||||
</p>
|
||||
<h3>Note:</h3>
|
||||
<p>
|
||||
If you find this process cumbersome, please submit a feature request to Obsidian.md for supporting assets in the plugin folder.
|
||||
Currently, only a single <code>main.js</code> is supported, which leads to large files and slow startup times for complex plugins like Excalidraw.
|
||||
I apologize for the inconvenience.
|
||||
</p>
|
||||
`,
|
||||
|
||||
//ObsidianMenu.tsx
|
||||
GOTO_FULLSCREEN: "Goto fullscreen mode",
|
||||
|
||||
@@ -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.5(50%)且不能超过 10(1000%)。",
|
||||
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 秒)。",
|
||||
|
||||
@@ -313,6 +313,15 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
};
|
||||
}*/
|
||||
|
||||
public async loadFontFromFile(fontName: string): Promise<ArrayBuffer> {
|
||||
const assetsFoler = "Fonts/";
|
||||
const file = this.app.vault.getAbstractFileByPath(assetsFoler + fontName);
|
||||
if(!file || !(file instanceof TFile)) {
|
||||
return;
|
||||
}
|
||||
return await this.app.vault.readBinary(file);
|
||||
}
|
||||
|
||||
async onload() {
|
||||
initCompressionWorker();
|
||||
this.loadTimestamp = Date.now();
|
||||
|
||||
@@ -262,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() {
|
||||
|
||||
@@ -49,6 +49,7 @@ export interface ExcalidrawSettings {
|
||||
embedUseExcalidrawFolder: boolean;
|
||||
templateFilePath: string;
|
||||
scriptFolderPath: string;
|
||||
fontAssetsPath: string;
|
||||
compress: boolean;
|
||||
decompressForMDView: boolean;
|
||||
onceOffCompressFlagReset: boolean; //used to reset compress to true in 2.2.0
|
||||
@@ -220,13 +221,14 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
|
||||
embedUseExcalidrawFolder: false,
|
||||
templateFilePath: "Excalidraw/Template.excalidraw",
|
||||
scriptFolderPath: "Excalidraw/Scripts",
|
||||
fontAssetsPath: "Excalidraw/FontAssets",
|
||||
compress: true,
|
||||
decompressForMDView: false,
|
||||
onceOffCompressFlagReset: false,
|
||||
onceOffGPTVersionReset: false,
|
||||
autosave: true,
|
||||
autosaveIntervalDesktop: 30000,
|
||||
autosaveIntervalMobile: 20000,
|
||||
autosaveIntervalDesktop: 60000,
|
||||
autosaveIntervalMobile: 30000,
|
||||
drawingFilenamePrefix: "Drawing ",
|
||||
drawingEmbedPrefixWithFilename: true,
|
||||
drawingFilnameEmbedPostfix: " ",
|
||||
@@ -268,9 +270,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,
|
||||
@@ -720,6 +722,18 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
}),
|
||||
);
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("ASSETS_FOLDER_NAME"))
|
||||
.setDesc(fragWithHTML(t("ASSETS_FOLDER_DESC")))
|
||||
.addText((text) =>
|
||||
text
|
||||
.setPlaceholder("e.g.: Excalidraw/FontAssets")
|
||||
.setValue(this.plugin.settings.fontAssetsPath)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.fontAssetsPath = value;
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
|
||||
// ------------------------------------------------
|
||||
// Saving
|
||||
@@ -2484,7 +2498,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
d.addOption("Virgil", "Virgil");
|
||||
this.app.vault
|
||||
.getFiles()
|
||||
.filter((f) => ["ttf", "woff", "woff2", "otf"].contains(f.extension))
|
||||
.filter((f) => ["ttf", "woff", "woff2", "otf"].contains(f.extension) && !f.path.startsWith(this.plugin.settings.fontAssetsPath))
|
||||
.forEach((f: TFile) => {
|
||||
d.addOption(f.path, f.name);
|
||||
});
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
@@ -83,8 +83,9 @@ export const setDynamicStyle = (
|
||||
[`--color-on-primary-container`]: str(!isDark?accent().darkerBy(15):accent().lighterBy(15)),
|
||||
[`--color-surface-primary-container`]: str(isDark?accent().darkerBy(step):accent().lighterBy(step)),
|
||||
[`--bold-color`]: str(!isDark?accent().darkerBy(15):accent().lighterBy(15)),
|
||||
//[`--color-primary-darker`]: str(accent().darkerBy(step)),
|
||||
//[`--color-primary-darkest`]: str(accent().darkerBy(step)),
|
||||
[`--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()),
|
||||
@@ -97,12 +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(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()),
|
||||
@@ -118,9 +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
|
||||
[`--default-bg-color`]: str(text), //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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
import { MAX_IMAGE_SIZE, IMAGE_TYPES, ANIMATED_IMAGE_TYPES, MD_EX_SECTIONS } from "src/constants/constants";
|
||||
import { App, Notice, TFile, WorkspaceLeaf } from "obsidian";
|
||||
import { App, Modal, Notice, TFile, WorkspaceLeaf } from "obsidian";
|
||||
import { ExcalidrawAutomate } from "src/ExcalidrawAutomate";
|
||||
import { REGEX_LINK, REG_LINKINDEX_HYPERLINK, getExcalidrawMarkdownHeaderSection, REGEX_TAGS } from "src/ExcalidrawData";
|
||||
import ExcalidrawView from "src/ExcalidrawView";
|
||||
@@ -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,20 @@ export function isTextImageTransclusion (
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function displayFontMessage(app: App) {
|
||||
const modal = new Modal(app);
|
||||
|
||||
modal.onOpen = () => {
|
||||
const contentEl = modal.contentEl;
|
||||
contentEl.createEl("h2", { text: t("FONT_INFO_TITLE") });
|
||||
|
||||
const releaseNotesHTML = t("FONT_INFO_DETAILED");
|
||||
|
||||
const div = contentEl.createDiv({ cls: "release-notes" });
|
||||
div.innerHTML = releaseNotesHTML;
|
||||
}
|
||||
|
||||
modal.open();
|
||||
}
|
||||
@@ -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 };
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ export const getParentOfClass = (element: Element, cssClass: string):HTMLElement
|
||||
};
|
||||
|
||||
export function getExcalidrawViews(app: App): ExcalidrawView[] {
|
||||
const leaves = app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW).filter(l=>l instanceof ExcalidrawView);
|
||||
const leaves = app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW).filter(l=>l.view instanceof ExcalidrawView);
|
||||
return leaves.map(l=>l.view as ExcalidrawView);
|
||||
}
|
||||
|
||||
|
||||
18
src/utils/typechecks.ts
Normal file
18
src/utils/typechecks.ts
Normal 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
|
||||
}
|
||||
14
styles.css
14
styles.css
@@ -638,4 +638,16 @@ textarea.excalidraw-wysiwyg, .excalidraw input {
|
||||
|
||||
.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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user