mirror of
https://github.com/zsviczian/obsidian-excalidraw-plugin.git
synced 2025-08-06 05:46:28 +00:00
Compare commits
10 Commits
2.5.0
...
2.5.1-beta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0c28e82212 | ||
|
|
beb4301f14 | ||
|
|
e96fe9c491 | ||
|
|
268680f494 | ||
|
|
a1512fce26 | ||
|
|
c2e79f3439 | ||
|
|
01780a2bf8 | ||
|
|
10e54eb03e | ||
|
|
2760a9966b | ||
|
|
a297dbbe52 |
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
|
||||
|
||||
@@ -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",
|
||||
"version": "2.5.1-beta-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-53",
|
||||
"@zsviczian/excalidraw": "0.17.1-obsidian-55",
|
||||
"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);
|
||||
|
||||
@@ -3434,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;
|
||||
@@ -3515,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;
|
||||
};
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +65,8 @@ import {
|
||||
cloneElement,
|
||||
getFrameElementsMatchingQuery,
|
||||
getElementsWithLinkMatchingQuery,
|
||||
getImagesMatchingQuery
|
||||
getImagesMatchingQuery,
|
||||
getBoundTextElementId
|
||||
} from "./ExcalidrawAutomate";
|
||||
import { t } from "./lang/helpers";
|
||||
import {
|
||||
@@ -280,6 +281,7 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
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
|
||||
@@ -316,6 +318,7 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
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,
|
||||
@@ -523,8 +526,8 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
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 {
|
||||
@@ -1144,61 +1147,111 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
|
||||
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(
|
||||
@@ -1206,7 +1259,8 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
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};
|
||||
@@ -1219,10 +1273,17 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -1301,6 +1362,9 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
}
|
||||
|
||||
if (!linkText) {
|
||||
if(allowLinearElementClick) {
|
||||
return;
|
||||
}
|
||||
new Notice(t("LINK_BUTTON_CLICK_NO_TEXT"), 20000);
|
||||
return;
|
||||
}
|
||||
@@ -1367,7 +1431,7 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
@@ -1385,6 +1449,7 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
selectedImage,
|
||||
selectedElementWithLink,
|
||||
ev instanceof MouseEvent ? null : ev,
|
||||
allowLinearElementClick,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1548,7 +1613,9 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
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);
|
||||
@@ -2152,6 +2219,7 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
// 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();
|
||||
@@ -3138,6 +3206,16 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
};
|
||||
}
|
||||
|
||||
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?
|
||||
@@ -3677,6 +3755,9 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
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
|
||||
@@ -4749,6 +4830,7 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
null,
|
||||
{id: element.id, text: link},
|
||||
event,
|
||||
true,
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -4958,7 +5040,7 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
t("OPEN_LINK_CLICK"),
|
||||
() => {
|
||||
const event = emulateKeysForLinkClick("new-tab");
|
||||
this.handleLinkClick(event);
|
||||
this.handleLinkClick(event, true);
|
||||
},
|
||||
onClose
|
||||
),
|
||||
|
||||
@@ -374,7 +374,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);
|
||||
@@ -587,6 +589,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 +692,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 +885,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;
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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 秒)。",
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
@@ -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,12 +121,18 @@ 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://")) {
|
||||
@@ -154,7 +158,9 @@ export function parseObsidianLink(link: string, app: App, returnWikiLink: boolea
|
||||
}
|
||||
}
|
||||
|
||||
window.open(link, "_blank");
|
||||
if(openLink) {
|
||||
window.open(link, "_blank");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
|
||||
@@ -646,4 +646,8 @@ textarea.excalidraw-wysiwyg, .excalidraw input {
|
||||
|
||||
.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