mirror of
https://github.com/zsviczian/obsidian-excalidraw-plugin.git
synced 2025-08-06 05:46:28 +00:00
2.5.1-beta-1
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "obsidian-excalidraw-plugin",
|
||||
"name": "Excalidraw",
|
||||
"version": "2.5.0",
|
||||
"version": "2.5.1-beta-1",
|
||||
"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-54",
|
||||
"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);
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2152,6 +2217,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 +3204,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 +3753,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 +4828,7 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
null,
|
||||
{id: element.id, text: link},
|
||||
event,
|
||||
true,
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -4958,7 +5038,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);
|
||||
@@ -795,7 +797,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;
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
@@ -123,12 +123,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 +160,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 };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user