mirror of
https://github.com/zsviczian/obsidian-excalidraw-plugin.git
synced 2025-08-06 05:46:28 +00:00
Compare commits
7 Commits
2.4.0-beta
...
2.4.0-beta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3ae890bd86 | ||
|
|
65a4cd4ba5 | ||
|
|
f63b473bc1 | ||
|
|
859a5ba03a | ||
|
|
832b97b179 | ||
|
|
e98d688d36 | ||
|
|
39318337fe |
@@ -19,10 +19,10 @@ if(!ea.verifyMinimumPluginVersion || !ea.verifyMinimumPluginVersion("2.0.25")) {
|
||||
// -------------------------------
|
||||
const excalidrawTemplates = ea.getListOfTemplateFiles();
|
||||
if(typeof window.ExcalidrawDeconstructElements === "undefined") {
|
||||
window.ExcalidrawDeconstructElements = {
|
||||
openDeconstructedImage: true,
|
||||
templatePath: excalidrawTemplates?.[0].path??""
|
||||
};
|
||||
window.ExcalidrawDeconstructElements = {
|
||||
openDeconstructedImage: true,
|
||||
templatePath: excalidrawTemplates?.[0].path??""
|
||||
};
|
||||
}
|
||||
|
||||
const splitFolderAndFilename = (filepath) => {
|
||||
@@ -36,13 +36,13 @@ const splitFolderAndFilename = (filepath) => {
|
||||
let settings = ea.getScriptSettings();
|
||||
//set default values on first run
|
||||
if(!settings["Templates"]) {
|
||||
settings = {
|
||||
"Templates" : {
|
||||
value: "",
|
||||
settings = {
|
||||
"Templates" : {
|
||||
value: "",
|
||||
description: "Comma-separated list of template filepaths"
|
||||
}
|
||||
};
|
||||
await ea.setScriptSettings(settings);
|
||||
}
|
||||
};
|
||||
await ea.setScriptSettings(settings);
|
||||
}
|
||||
|
||||
if(!settings["Default file name"]) {
|
||||
@@ -80,31 +80,31 @@ ea.getElements().filter(el=>el.type==="image").forEach(el=>{
|
||||
const img = ea.targetView.excalidrawData.getFile(el.fileId);
|
||||
const path = (img?.linkParts?.original)??(img?.file?.path);
|
||||
if(img && path) {
|
||||
ea.imagesDict[el.fileId] = {
|
||||
mimeType: img.mimeType,
|
||||
id: el.fileId,
|
||||
dataURL: img.img,
|
||||
created: img.mtime,
|
||||
file: path,
|
||||
hasSVGwithBitmap: img.isSVGwithBitmap,
|
||||
latex: null,
|
||||
};
|
||||
return;
|
||||
}
|
||||
const equation = ea.targetView.excalidrawData.getEquation(el.fileId);
|
||||
eqImg = ea.targetView.getScene()?.files[el.fileId]
|
||||
if(equation && eqImg) {
|
||||
ea.imagesDict[el.fileId] = {
|
||||
mimeType: eqImg.mimeType,
|
||||
id: el.fileId,
|
||||
dataURL: eqImg.dataURL,
|
||||
created: eqImg.created,
|
||||
file: null,
|
||||
hasSVGwithBitmap: null,
|
||||
latex: equation.latex,
|
||||
};
|
||||
return;
|
||||
}
|
||||
mimeType: img.mimeType,
|
||||
id: el.fileId,
|
||||
dataURL: img.img,
|
||||
created: img.mtime,
|
||||
file: path,
|
||||
hasSVGwithBitmap: img.isSVGwithBitmap,
|
||||
latex: null,
|
||||
};
|
||||
return;
|
||||
}
|
||||
const equation = ea.targetView.excalidrawData.getEquation(el.fileId);
|
||||
eqImg = ea.targetView.getScene()?.files[el.fileId]
|
||||
if(equation && eqImg) {
|
||||
ea.imagesDict[el.fileId] = {
|
||||
mimeType: eqImg.mimeType,
|
||||
id: el.fileId,
|
||||
dataURL: eqImg.dataURL,
|
||||
created: eqImg.created,
|
||||
file: null,
|
||||
hasSVGwithBitmap: null,
|
||||
latex: equation.latex,
|
||||
};
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -9,7 +9,11 @@ https://zsviczian.github.io/obsidian-excalidraw-plugin/ExcalidrawScriptsEngine.h
|
||||
```javascript
|
||||
*/
|
||||
let width = (ea.getViewSelectedElement().strokeWidth??1).toString();
|
||||
width = await utils.inputPrompt("Width?","number",width);
|
||||
width = parseFloat(await utils.inputPrompt("Width?","number",width));
|
||||
if(isNaN(width)) {
|
||||
new Notice("Invalid number");
|
||||
return;
|
||||
}
|
||||
const elements=ea.getViewSelectedElements();
|
||||
ea.copyViewElementsToEAforEditing(elements);
|
||||
ea.getElements().forEach((el)=>el.strokeWidth=width);
|
||||
|
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
|
Before Width: | Height: | Size: 341 KiB After Width: | Height: | Size: 861 KiB |
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "obsidian-excalidraw-plugin",
|
||||
"name": "Excalidraw",
|
||||
"version": "2.4.0-beta-7",
|
||||
"version": "2.4.0-beta-8",
|
||||
"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-40",
|
||||
"@zsviczian/excalidraw": "0.17.1-obsidian-41",
|
||||
"chroma-js": "^2.4.2",
|
||||
"clsx": "^2.0.0",
|
||||
"colormaster": "^1.2.1",
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
nanoid,
|
||||
THEME_FILTER,
|
||||
FRONTMATTER_KEYS,
|
||||
getFontDefinition,
|
||||
getCSSFontDefinition,
|
||||
} from "./constants/constants";
|
||||
import { createSVG } from "./ExcalidrawAutomate";
|
||||
import { ExcalidrawData, getTransclusion } from "./ExcalidrawData";
|
||||
@@ -836,29 +836,29 @@ export class EmbeddedFilesLoader {
|
||||
}
|
||||
switch (fontName) {
|
||||
case "Virgil":
|
||||
fontDef = await getFontDefinition(1);
|
||||
fontDef = await getCSSFontDefinition(1);
|
||||
break;
|
||||
case "Cascadia":
|
||||
fontDef = await getFontDefinition(3);
|
||||
fontDef = await getCSSFontDefinition(3);
|
||||
break;
|
||||
case "Assistant":
|
||||
case "Helvetica":
|
||||
fontDef = await getFontDefinition(2);
|
||||
fontDef = await getCSSFontDefinition(2);
|
||||
break;
|
||||
case "Excalifont":
|
||||
fontDef = await getFontDefinition(5);
|
||||
fontDef = await getCSSFontDefinition(5);
|
||||
break;
|
||||
case "Nunito":
|
||||
fontDef = await getFontDefinition(6);
|
||||
fontDef = await getCSSFontDefinition(6);
|
||||
break;
|
||||
case "Lilita One":
|
||||
fontDef = await getFontDefinition(7);
|
||||
fontDef = await getCSSFontDefinition(7);
|
||||
break;
|
||||
case "Comic Shanns":
|
||||
fontDef = await getFontDefinition(8);
|
||||
fontDef = await getCSSFontDefinition(8);
|
||||
break;
|
||||
case "Liberation Sans":
|
||||
fontDef = await getFontDefinition(9);
|
||||
fontDef = await getCSSFontDefinition(9);
|
||||
break;
|
||||
case "":
|
||||
fontDef = "";
|
||||
@@ -941,12 +941,14 @@ export class EmbeddedFilesLoader {
|
||||
mdDIV.style.display = "block";
|
||||
mdDIV.style.color = fontColor && fontColor !== "" ? fontColor : "initial";
|
||||
|
||||
await MarkdownRenderer.renderMarkdown(text, mdDIV, file.path, plugin);
|
||||
|
||||
//await MarkdownRenderer.renderMarkdown(text, mdDIV, file.path, plugin);
|
||||
await MarkdownRenderer.render(this.plugin.app,text,mdDIV,file.path,this.plugin);
|
||||
|
||||
mdDIV
|
||||
.querySelectorAll(":scope > *[class^='frontmatter']")
|
||||
.forEach((el) => mdDIV.removeChild(el));
|
||||
|
||||
await replaceBlobWithBase64(mdDIV); //because image cache returns a blob
|
||||
const internalEmbeds = Array.from(mdDIV.querySelectorAll("span[class='internal-embed']"))
|
||||
for(let i=0;i<internalEmbeds.length;i++) {
|
||||
const el = internalEmbeds[i];
|
||||
@@ -1107,3 +1109,19 @@ export const generateIdFromFile = async (file: ArrayBuffer, key?: string): Promi
|
||||
}
|
||||
return id;
|
||||
};
|
||||
|
||||
const replaceBlobWithBase64 = async (divElement: HTMLDivElement): Promise<void> => {
|
||||
const images = divElement.querySelectorAll<HTMLImageElement>('img[src^="blob:app://obsidian.md"]');
|
||||
|
||||
for (let img of images) {
|
||||
const blobUrl = img.src;
|
||||
try {
|
||||
const response = await fetch(blobUrl);
|
||||
const blob = await response.blob();
|
||||
const base64 = await blobToBase64(blob);
|
||||
img.src = `data:${blob.type};base64,${base64}`;
|
||||
} catch (error) {
|
||||
console.error(`Failed to fetch or convert blob: ${blobUrl}`, error);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -717,6 +717,12 @@ export class ExcalidrawAutomate {
|
||||
this.style.roundness ? "round":"sharp",
|
||||
gridSize: template?.appState?.gridSize ?? this.canvas.gridSize,
|
||||
colorPalette: template?.appState?.colorPalette ?? this.colorPalette,
|
||||
...template?.appState?.frameRendering
|
||||
? {frameRendering: template.appState.frameRendering}
|
||||
: {},
|
||||
...template?.appState?.objectsSnapModeEnabled
|
||||
? {objectsSnapModeEnabled: template.appState.objectsSnapModeEnabled}
|
||||
: {},
|
||||
},
|
||||
files: template?.files ?? {},
|
||||
};
|
||||
@@ -2560,10 +2566,11 @@ export class ExcalidrawAutomate {
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the size of the image element at 100% (i.e. the original size)
|
||||
* Returns the size of the image element at 100% (i.e. the original size), or undefined if the data URL is not available
|
||||
* @param imageElement an image element from the active scene on targetView
|
||||
* @param shouldWaitForImage if true, the function will wait for the image to load before returning the size
|
||||
*/
|
||||
async getOriginalImageSize(imageElement: ExcalidrawImageElement): Promise<{width: number; height: number}> {
|
||||
async getOriginalImageSize(imageElement: ExcalidrawImageElement, shouldWaitForImage: boolean=false): Promise<{width: number; height: number}> {
|
||||
//@ts-ignore
|
||||
if (!this.targetView || !this.targetView?._loaded) {
|
||||
errorMessage("targetView not set", "getOriginalImageSize()");
|
||||
@@ -2579,10 +2586,59 @@ export class ExcalidrawAutomate {
|
||||
return null;
|
||||
}
|
||||
const isDark = this.getExcalidrawAPI().getAppState().theme === "dark";
|
||||
const dataURL = ef.getImage(isDark);
|
||||
let dataURL = ef.getImage(isDark);
|
||||
if(!dataURL && !shouldWaitForImage) return;
|
||||
if(!dataURL) {
|
||||
let watchdog = 0;
|
||||
while(!dataURL && watchdog < 50) {
|
||||
await sleep(100);
|
||||
dataURL = ef.getImage(isDark);
|
||||
watchdog++;
|
||||
}
|
||||
if(!dataURL) return;
|
||||
}
|
||||
return await getImageSize(dataURL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the image to its original aspect ratio.
|
||||
* If the image is resized then the function returns true.
|
||||
* If the image element is not in EA (only in the view), then if image is resized, the element is copied to EA for Editing using copyViewElementsToEAforEditing([imgEl]).
|
||||
* Note you need to run await ea.addElementsToView(false); to add the modified image to the view.
|
||||
* @param imageElement - the EA image element to be resized
|
||||
* returns true if image was changed, false if image was not changed
|
||||
*/
|
||||
async resetImageAspectRatio(imgEl: ExcalidrawImageElement): Promise<boolean> {
|
||||
//@ts-ignore
|
||||
if (!this.targetView || !this.targetView?._loaded) {
|
||||
errorMessage("targetView not set", "resetImageAspectRatio()");
|
||||
return null;
|
||||
}
|
||||
|
||||
const size = await this.getOriginalImageSize(imgEl, true);
|
||||
if (size) {
|
||||
const originalArea = imgEl.width * imgEl.height;
|
||||
const originalAspectRatio = size.width / size.height;
|
||||
let newWidth = Math.sqrt(originalArea * originalAspectRatio);
|
||||
let newHeight = Math.sqrt(originalArea / originalAspectRatio);
|
||||
const centerX = imgEl.x + imgEl.width / 2;
|
||||
const centerY = imgEl.y + imgEl.height / 2;
|
||||
|
||||
if (newWidth !== imgEl.width || newHeight !== imgEl.height) {
|
||||
if(!this.getElement(imgEl.id)) {
|
||||
this.copyViewElementsToEAforEditing([imgEl]);
|
||||
}
|
||||
const eaEl = this.getElement(imgEl.id);
|
||||
eaEl.width = newWidth;
|
||||
eaEl.height = newHeight;
|
||||
eaEl.x = centerX - newWidth / 2;
|
||||
eaEl.y = centerY - newHeight / 2;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* verifyMinimumPluginVersion returns true if plugin version is >= than required
|
||||
* recommended use:
|
||||
@@ -2953,6 +3009,7 @@ async function getTemplate(
|
||||
}
|
||||
|
||||
excalidrawData.destroy();
|
||||
const filehead = data.substring(0, trimLocation);
|
||||
return {
|
||||
elements: convertMarkdownLinksToObsidianURLs
|
||||
? updateElementLinksToObsidianLinks({
|
||||
@@ -2960,7 +3017,7 @@ async function getTemplate(
|
||||
hostFile: file,
|
||||
}) : groupElements,
|
||||
appState: scene.appState,
|
||||
frontmatter: data.substring(0, trimLocation),
|
||||
frontmatter: filehead.match(/^---\n.*\n---\n/ms)?.[0] ?? filehead,
|
||||
files: scene.files,
|
||||
hasSVGwithBitmap,
|
||||
};
|
||||
@@ -3278,7 +3335,7 @@ export const search = async (view: ExcalidrawView) => {
|
||||
const ea = view.plugin.ea;
|
||||
ea.reset();
|
||||
ea.setView(view);
|
||||
const elements = ea.getViewElements().filter((el) => el.type === "text" || el.type === "frame");
|
||||
const elements = ea.getViewElements().filter((el) => el.type === "text" || el.type === "frame" || el.link);
|
||||
if (elements.length === 0) {
|
||||
return;
|
||||
}
|
||||
@@ -3372,6 +3429,32 @@ export const getFrameElementsMatchingQuery = (
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param elements
|
||||
* @param query
|
||||
* @param exactMatch - when searching for section header exactMatch should be set to true
|
||||
* @returns the elements matching the query
|
||||
*/
|
||||
export const getElementsWithLinkMatchingQuery = (
|
||||
elements: ExcalidrawElement[],
|
||||
query: string[],
|
||||
exactMatch: boolean = false, //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/530
|
||||
): ExcalidrawElement[] => {
|
||||
if (!elements || elements.length === 0 || !query || query.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return elements.filter((el: any) =>
|
||||
el.link &&
|
||||
query.some((q) => {
|
||||
const text = el.link.toLowerCase().trim();
|
||||
return exactMatch
|
||||
? (text === q.toLowerCase())
|
||||
: text.match(q.toLowerCase());
|
||||
}));
|
||||
}
|
||||
|
||||
export const cloneElement = (el: ExcalidrawElement):any => {
|
||||
const newEl = JSON.parse(JSON.stringify(el));
|
||||
newEl.version = el.version + 1;
|
||||
|
||||
@@ -68,6 +68,27 @@ export enum AutoexportPreference {
|
||||
inherit
|
||||
}
|
||||
|
||||
export const REGEX_TAGS = {
|
||||
// #[\p{Letter}\p{Emoji_Presentation}\p{Number}\/_-]+
|
||||
// 1
|
||||
EXPR: /(#[\p{Letter}\p{Emoji_Presentation}\p{Number}\/_-]+)/gu,
|
||||
getResList: (text: string): IteratorResult<RegExpMatchArray, any>[] => {
|
||||
const res = text.matchAll(REGEX_TAGS.EXPR);
|
||||
let parts: IteratorResult<RegExpMatchArray, any>;
|
||||
const resultList = [];
|
||||
while (!(parts = res.next()).done) {
|
||||
resultList.push(parts);
|
||||
}
|
||||
return resultList;
|
||||
},
|
||||
getTag: (parts: IteratorResult<RegExpMatchArray, any>): string => {
|
||||
return parts.value[1];
|
||||
},
|
||||
isTag: (parts: IteratorResult<RegExpMatchArray, any>): boolean => {
|
||||
return parts.value[1]?.startsWith("#")
|
||||
},
|
||||
};
|
||||
|
||||
export const REGEX_LINK = {
|
||||
//![[link|alias]] [alias](link){num}
|
||||
// 1 2 3 4 5 67 8 9
|
||||
@@ -720,6 +741,24 @@ export class ExcalidrawData {
|
||||
this.scene.appState.theme = isObsidianThemeDark() ? "dark" : "light";
|
||||
}
|
||||
|
||||
//girdSize, gridStep, previousGridSize, gridModeEnabled migration
|
||||
if(this.scene.appState.hasOwnProperty("previousGridSize")) { //if previousGridSize was present this is legacy data
|
||||
if(this.scene.appState.gridSize === null) {
|
||||
this.scene.appState.gridSize = this.scene.appState.previousGridSize;
|
||||
this.scene.appState.gridModeEnabled = false;
|
||||
} else {
|
||||
this.scene.appState.gridModeEnabled = true;
|
||||
}
|
||||
delete this.scene.appState.previousGridSize;
|
||||
}
|
||||
|
||||
if(this.scene.appState?.gridColor?.hasOwnProperty("MajorGridFrequency")) { //if this is present, this is legacy data
|
||||
if(this.scene.appState.gridColor.MajorGridFrequency>1) {
|
||||
this.scene.gridStep = this.scene.appState.gridColor.MajorGridFrequency;
|
||||
}
|
||||
delete this.scene.appState.gridColor.MajorGridFrequency;
|
||||
}
|
||||
|
||||
//once off migration of legacy scenes
|
||||
if(this.scene?.elements?.some((el:any)=>el.type==="iframe" && !el.customData)) {
|
||||
const prompt = new ConfirmationPrompt(
|
||||
@@ -810,7 +849,7 @@ export class ExcalidrawData {
|
||||
? data.substring(indexOfNewElementLinks + lengthOfNewElementLinks)
|
||||
: data.substring(indexOfOldElementLinks + lengthOfOldElementLinks);
|
||||
//Load Embedded files
|
||||
const RE_ELEMENT_LINKS = /^(.{8}):\s*(\[\[[^\]]*]])$/gm;
|
||||
const RE_ELEMENT_LINKS = /^(.{8}):\s*(.*)$/gm;
|
||||
const linksRes = elementLinksData.matchAll(RE_ELEMENT_LINKS);
|
||||
while (!(parts = linksRes.next()).done) {
|
||||
elementLinkMap.set(parts.value[1], parts.value[2]);
|
||||
@@ -1043,7 +1082,7 @@ export class ExcalidrawData {
|
||||
return (
|
||||
el.type !== "text" &&
|
||||
el.link &&
|
||||
el.link.startsWith("[[") &&
|
||||
//el.link.startsWith("[[") &&
|
||||
!this.elementLinks.has(el.id)
|
||||
);
|
||||
});
|
||||
@@ -1134,8 +1173,8 @@ export class ExcalidrawData {
|
||||
(el: any) =>
|
||||
el.type !== "text" &&
|
||||
el.id === key &&
|
||||
el.link &&
|
||||
el.link.startsWith("[["),
|
||||
el.link, //&&
|
||||
//el.link.startsWith("[["),
|
||||
);
|
||||
if (el.length === 0) {
|
||||
this.elementLinks.delete(key); //if no longer in the scene, delete the text element
|
||||
@@ -1376,10 +1415,10 @@ export class ExcalidrawData {
|
||||
const element = this.scene.elements.filter((el:any)=>el.id===key);
|
||||
let elementString = this.textElements.get(key).raw;
|
||||
if(element && element.length===1 && element[0].link && element[0].rawText === element[0].originalText) {
|
||||
if(element[0].link.match(/^\[\[[^\]]*]]$/g)) { //apply this only to markdown links
|
||||
//if(element[0].link.match(/^\[\[[^\]]*]]$/g)) { //apply this only to markdown links
|
||||
textElementLinks.set(key, element[0].link);
|
||||
//elementString = `%%***>>>text element-link:${element[0].link}<<<***%%` + elementString;
|
||||
}
|
||||
//}
|
||||
}
|
||||
outString += `${elementString} ^${key}\n\n`;
|
||||
}
|
||||
|
||||
2
src/ExcalidrawLib.d.ts
vendored
2
src/ExcalidrawLib.d.ts
vendored
@@ -175,6 +175,6 @@ declare namespace ExcalidrawLib {
|
||||
function registerLocalFont(fontMetrics: FontMetadata, uri: string): void;
|
||||
function getFontFamilies(): string[];
|
||||
function registerFontsInCSS(): Promise<void>;
|
||||
function getFontDefinition(fontFamily: number): Promise<string>;
|
||||
function getCSSFontDefinition(fontFamily: number): Promise<string>;
|
||||
}
|
||||
|
||||
|
||||
@@ -60,7 +60,8 @@ import {
|
||||
ExcalidrawAutomate,
|
||||
getTextElementsMatchingQuery,
|
||||
cloneElement,
|
||||
getFrameElementsMatchingQuery
|
||||
getFrameElementsMatchingQuery,
|
||||
getElementsWithLinkMatchingQuery
|
||||
} from "./ExcalidrawAutomate";
|
||||
import { t } from "./lang/helpers";
|
||||
import {
|
||||
@@ -126,7 +127,7 @@ import { anyModifierKeysPressed, emulateKeysForLinkClick, webbrowserDragModifier
|
||||
import { setDynamicStyle } from "./utils/DynamicStyling";
|
||||
import { InsertPDFModal } from "./dialogs/InsertPDFModal";
|
||||
import { CustomEmbeddable, renderWebView } from "./customEmbeddable";
|
||||
import { addBackOfTheNoteCard, getExcalidrawFileForwardLinks, getFrameBasedOnFrameNameOrId, getLinkTextFromLink, insertEmbeddableToView, insertImageToView, isTextImageTransclusion, openExternalLink, openTagSearch, parseObsidianLink, renderContextMenuAction, tmpBruteForceCleanup } from "./utils/ExcalidrawViewUtils";
|
||||
import { addBackOfTheNoteCard, getExcalidrawFileForwardLinks, getFrameBasedOnFrameNameOrId, getLinkTextFromLink, insertEmbeddableToView, insertImageToView, isTextImageTransclusion, openExternalLink, parseObsidianLink, renderContextMenuAction, tmpBruteForceCleanup } from "./utils/ExcalidrawViewUtils";
|
||||
import { imageCache } from "./utils/ImageCache";
|
||||
import { CanvasNodeFactory, ObsidianCanvasNode } from "./utils/CanvasNodeFactory";
|
||||
import { EmbeddableMenu } from "./menu/EmbeddableActionsMenu";
|
||||
@@ -900,6 +901,90 @@ export default class ExcalidrawView extends TextFileView {
|
||||
}
|
||||
}
|
||||
|
||||
async openLaTeXEditor(eqId: string) {
|
||||
const el = this.getViewElements().find((el:ExcalidrawElement)=>el.id === eqId && el.type==="image") as ExcalidrawImageElement;
|
||||
if(!el) {
|
||||
return;
|
||||
}
|
||||
|
||||
const fileId = el.fileId;
|
||||
|
||||
let equation = this.excalidrawData.getEquation(fileId)?.latex;
|
||||
if(!equation) {
|
||||
await this.save(false);
|
||||
equation = this.excalidrawData.getEquation(fileId)?.latex;
|
||||
if(!equation) return;
|
||||
}
|
||||
|
||||
GenericInputPrompt.Prompt(this,this.plugin,this.app,t("ENTER_LATEX"),undefined,equation, undefined, 3).then(async (formula: string) => {
|
||||
if (!formula || formula === equation) {
|
||||
return;
|
||||
}
|
||||
this.excalidrawData.setEquation(fileId, {
|
||||
latex: formula,
|
||||
isLoaded: false,
|
||||
});
|
||||
await this.save(false);
|
||||
await updateEquation(
|
||||
formula,
|
||||
fileId,
|
||||
this,
|
||||
addFiles,
|
||||
);
|
||||
this.setDirty(1);
|
||||
});
|
||||
}
|
||||
|
||||
async openEmbeddedLinkEditor(imgId:string) {
|
||||
const el = this.getViewElements().find((el:ExcalidrawElement)=>el.id === imgId && el.type==="image") as ExcalidrawImageElement;
|
||||
if(!el) {
|
||||
return;
|
||||
}
|
||||
const fileId = el.fileId;
|
||||
const ef = this.excalidrawData.getFile(fileId);
|
||||
if(!ef) {
|
||||
return
|
||||
}
|
||||
if (!ef.isHyperLink && !ef.isLocalLink && ef.file) {
|
||||
const handler = async (link:string) => {
|
||||
if (!link || ef.linkParts.original === link) {
|
||||
return;
|
||||
}
|
||||
ef.resetImage(this.file.path, link);
|
||||
this.excalidrawData.setFile(fileId, ef);
|
||||
this.setDirty(2);
|
||||
await this.save(false);
|
||||
await sleep(100);
|
||||
if(!this.plugin.isExcalidrawFile(ef.file) && !link.endsWith("|100%")) {
|
||||
const ea = getEA(this) as ExcalidrawAutomate;
|
||||
let imgEl = this.getViewElements().find((x:ExcalidrawElement)=>x.id === el.id) as ExcalidrawImageElement;
|
||||
if(!imgEl) {
|
||||
ea.destroy();
|
||||
return;
|
||||
}
|
||||
if(imgEl && await ea.resetImageAspectRatio(imgEl)) {
|
||||
await ea.addElementsToView(false);
|
||||
}
|
||||
ea.destroy();
|
||||
}
|
||||
}
|
||||
GenericInputPrompt.Prompt(
|
||||
this,
|
||||
this.plugin,
|
||||
this.app,
|
||||
t("MARKDOWN_EMBED_CUSTOMIZE_LINK_PROMPT_TITLE"),
|
||||
undefined,
|
||||
ef.linkParts.original,
|
||||
[{caption: "✅", action: (x:string)=>{x.replaceAll("\n","").trim()}}],
|
||||
3,
|
||||
false,
|
||||
(container) => container.createEl("p",{text: fragWithHTML(t("MARKDOWN_EMBED_CUSTOMIZE_LINK_PROMPT"))}),
|
||||
false
|
||||
).then(handler.bind(this),()=>{});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
toggleDisableBinding() {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.toggleDisableBinding, "ExcalidrawView.toggleDisableBinding");
|
||||
const newState = !this.excalidrawAPI.getAppState().invertBindingBehaviour;
|
||||
@@ -1042,6 +1127,10 @@ export default class ExcalidrawView extends TextFileView {
|
||||
? this.excalidrawData.getRawText(selectedText.id)
|
||||
: selectedText.text);
|
||||
|
||||
if(linkText.startsWith("#")) {
|
||||
return {linkText, selectedElement: selectedTextElement ?? selectedElement};
|
||||
}
|
||||
|
||||
const maybeObsidianLink = parseObsidianLink(linkText, this.app);
|
||||
if(typeof maybeObsidianLink === "string") {
|
||||
linkText = maybeObsidianLink;
|
||||
@@ -1054,6 +1143,15 @@ export default class ExcalidrawView extends TextFileView {
|
||||
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) {
|
||||
@@ -1108,30 +1206,8 @@ export default class ExcalidrawView extends TextFileView {
|
||||
if (selectedImage?.id) {
|
||||
const imageElement = this.getScene().elements.find((el:ExcalidrawElement)=>el.id === selectedImage.id) as ExcalidrawImageElement;
|
||||
if (this.excalidrawData.hasEquation(selectedImage.fileId)) {
|
||||
(async () => {
|
||||
await this.save(false);
|
||||
selectedImage.fileId = imageElement.fileId;
|
||||
const equation = this.excalidrawData.getEquation(
|
||||
selectedImage.fileId,
|
||||
).latex;
|
||||
GenericInputPrompt.Prompt(this,this.plugin,this.app,t("ENTER_LATEX"),undefined,equation, undefined, 3).then(async (formula: string) => {
|
||||
if (!formula || formula === equation) {
|
||||
return;
|
||||
}
|
||||
this.excalidrawData.setEquation(selectedImage.fileId, {
|
||||
latex: formula,
|
||||
isLoaded: false,
|
||||
});
|
||||
await this.save(false);
|
||||
await updateEquation(
|
||||
formula,
|
||||
selectedImage.fileId,
|
||||
this,
|
||||
addFiles,
|
||||
);
|
||||
this.setDirty(1);
|
||||
});
|
||||
})();
|
||||
this.updateScene({appState: {contextMenu: null}});
|
||||
this.openLaTeXEditor(selectedImage.id);
|
||||
return;
|
||||
}
|
||||
if (this.excalidrawData.hasMermaid(selectedImage.fileId) || getMermaidText(imageElement)) {
|
||||
@@ -1144,38 +1220,13 @@ export default class ExcalidrawView extends TextFileView {
|
||||
|
||||
await this.save(false); //in case pasted images haven't been saved yet
|
||||
if (this.excalidrawData.hasFile(selectedImage.fileId)) {
|
||||
const ef = this.excalidrawData.getFile(selectedImage.fileId);
|
||||
if (!ef.isHyperLink && !ef.isLocalLink && linkClickType === "md-properties") {
|
||||
if (
|
||||
ef.file.extension === "md" &&
|
||||
!this.plugin.isExcalidrawFile(ef.file)
|
||||
) {
|
||||
const handler = async (link:string) => {
|
||||
if (!link || ef.linkParts.original === link) {
|
||||
return;
|
||||
}
|
||||
ef.resetImage(this.file.path, link);
|
||||
this.setDirty(2);
|
||||
await this.save(false);
|
||||
await this.loadSceneFiles();
|
||||
}
|
||||
GenericInputPrompt.Prompt(
|
||||
this,
|
||||
this.plugin,
|
||||
this.app,
|
||||
t("MARKDOWN_EMBED_CUSTOMIZE_LINK_PROMPT_TITLE"),
|
||||
undefined,
|
||||
ef.linkParts.original,
|
||||
[{caption: "✅", action: handler}],
|
||||
1,
|
||||
false,
|
||||
(container) => container.createEl("p",{text: fragWithHTML(t("MARKDOWN_EMBED_CUSTOMIZE_LINK_PROMPT"))}),
|
||||
false
|
||||
).then(handler, () => {});
|
||||
return;
|
||||
}
|
||||
const fileId = selectedImage.fileId;
|
||||
const ef = this.excalidrawData.getFile(fileId);
|
||||
if (!ef.isHyperLink && !ef.isLocalLink && ef.file && linkClickType === "md-properties") {
|
||||
this.updateScene({appState: {contextMenu: null}});
|
||||
this.openEmbeddedLinkEditor(selectedImage.id);
|
||||
return;
|
||||
}
|
||||
|
||||
let secondOrderLinks: string = " ";
|
||||
|
||||
const backlinks = this.app.metadataCache?.getBacklinksForFile(ef.file)?.data;
|
||||
@@ -1928,7 +1979,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
state.match &&
|
||||
state.match.content &&
|
||||
state.match.matches &&
|
||||
state.match.matches.length === 1 &&
|
||||
state.match.matches.length >= 1 &&
|
||||
state.match.matches[0].length === 2
|
||||
) {
|
||||
query = [
|
||||
@@ -2023,7 +2074,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
)) {
|
||||
const cleanQuery = cleanSectionHeading(query[0]);
|
||||
const sections = await this.getBackOfTheNoteSections();
|
||||
if(sections.includes(cleanQuery)) {
|
||||
if(sections.includes(cleanQuery) || this.data.includes(query[0])) {
|
||||
this.setMarkdownView(state);
|
||||
return;
|
||||
}
|
||||
@@ -2213,13 +2264,13 @@ export default class ExcalidrawView extends TextFileView {
|
||||
});
|
||||
}
|
||||
|
||||
private getGridColor(bgColor: string, st: AppState):{Bold: string, Regular: string, MajorGridFrequency: number} {
|
||||
private getGridColor(bgColor: string, st: AppState):{Bold: string, Regular: string} {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.getGridColor, "ExcalidrawView.getGridColor", bgColor, st);
|
||||
const cm = this.plugin.ea.getCM(bgColor);
|
||||
const isDark = cm.isDark();
|
||||
const Regular = (isDark ? cm.lighterBy(7) : cm.darkerBy(7)).stringHEX({alpha: false});
|
||||
const Bold = (isDark ? cm.lighterBy(14) : cm.darkerBy(14)).stringHEX({alpha: false});
|
||||
return {Bold, Regular, MajorGridFrequency:st.gridColor.MajorGridFrequency};
|
||||
return {Bold, Regular};
|
||||
}
|
||||
|
||||
public activeLoader: EmbeddedFilesLoader = null;
|
||||
@@ -3230,6 +3281,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
version: 2,
|
||||
source: GITHUB_RELEASES+PLUGIN_VERSION,
|
||||
elements: el,
|
||||
//see also ExcalidrawAutomate async create(
|
||||
appState: {
|
||||
theme: st.theme,
|
||||
viewBackgroundColor: st.viewBackgroundColor,
|
||||
@@ -3250,10 +3302,11 @@ export default class ExcalidrawView extends TextFileView {
|
||||
zoom: st.zoom,
|
||||
currentItemRoundness: st.currentItemRoundness,
|
||||
gridSize: st.gridSize,
|
||||
gridStep: st.gridStep,
|
||||
gridModeEnabled: st.gridModeEnabled,
|
||||
gridColor: st.gridColor,
|
||||
colorPalette: st.colorPalette,
|
||||
currentStrokeOptions: st.currentStrokeOptions,
|
||||
previousGridSize: st.previousGridSize,
|
||||
frameRendering: st.frameRendering,
|
||||
objectsSnapModeEnabled: st.objectsSnapModeEnabled,
|
||||
},
|
||||
@@ -5645,10 +5698,14 @@ export default class ExcalidrawView extends TextFileView {
|
||||
elements.filter((el: ExcalidrawElement) => el.type === "frame"),
|
||||
query,
|
||||
exactMatch
|
||||
)).concat(getElementsWithLinkMatchingQuery(
|
||||
elements.filter((el: ExcalidrawElement) => el.link),
|
||||
query,
|
||||
exactMatch
|
||||
));
|
||||
|
||||
if (match.length === 0) {
|
||||
new Notice("I could not find a matching text element");
|
||||
new Notice(t("NO_SEARCH_RESULT"));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -5673,7 +5730,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
|
||||
const zoomLevel = this.plugin.settings.zoomToFitMaxLevel;
|
||||
if (selectResult) {
|
||||
api.selectElements(elements);
|
||||
api.selectElements(elements, true);
|
||||
}
|
||||
api.zoomToFit(elements, zoomLevel, 0.05);
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ export const {
|
||||
getFontFamilyString,
|
||||
getContainerElement,
|
||||
refreshTextDimensions,
|
||||
getFontDefinition,
|
||||
getCSSFontDefinition,
|
||||
} = excalidrawLib;
|
||||
|
||||
export const FONTS_STYLE_ID = "excalidraw-custom-fonts";
|
||||
|
||||
@@ -20,7 +20,7 @@ import { t } from "src/lang/helpers";
|
||||
import { ExcalidrawElement, getEA } from "src";
|
||||
import { ExcalidrawAutomate } from "src/ExcalidrawAutomate";
|
||||
import { MAX_IMAGE_SIZE, REG_LINKINDEX_INVALIDCHARS } from "src/constants/constants";
|
||||
import { REGEX_LINK } from "src/ExcalidrawData";
|
||||
import { REGEX_LINK, REGEX_TAGS } from "src/ExcalidrawData";
|
||||
import { ScriptEngine } from "src/Scripts";
|
||||
import { openExternalLink, openTagSearch, parseObsidianLink } from "src/utils/ExcalidrawViewUtils";
|
||||
|
||||
@@ -211,15 +211,15 @@ export class GenericInputPrompt extends Modal {
|
||||
}, 30);
|
||||
}
|
||||
|
||||
textComponent.inputEl.addEventListener("keydown", this.keyDownCallback);
|
||||
textComponent.inputEl.addEventListener('keyup', checkcaret); // Every character written
|
||||
textComponent.inputEl.addEventListener('pointerup', checkcaret); // Click down
|
||||
textComponent.inputEl.addEventListener('touchend', checkcaret); // Click down
|
||||
textComponent.inputEl.addEventListener('input', checkcaret); // Other input events
|
||||
textComponent.inputEl.addEventListener('paste', checkcaret); // Clipboard actions
|
||||
textComponent.inputEl.addEventListener('cut', checkcaret);
|
||||
textComponent.inputEl.addEventListener('select', checkcaret); // Some browsers support this event
|
||||
textComponent.inputEl.addEventListener('selectionchange', checkcaret);// Some browsers support this event
|
||||
textComponent.inputEl.addEventListener("keydown", this.keyDownCallback.bind(this));
|
||||
textComponent.inputEl.addEventListener('keyup', checkcaret.bind(this)); // Every character written
|
||||
textComponent.inputEl.addEventListener('pointerup', checkcaret.bind(this)); // Click down
|
||||
textComponent.inputEl.addEventListener('touchend', checkcaret.bind(this)); // Click down
|
||||
textComponent.inputEl.addEventListener('input', checkcaret.bind(this)); // Other input events
|
||||
textComponent.inputEl.addEventListener('paste', checkcaret.bind(this)); // Clipboard actions
|
||||
textComponent.inputEl.addEventListener('cut', checkcaret.bind(this));
|
||||
textComponent.inputEl.addEventListener('select', checkcaret.bind(this)); // Some browsers support this event
|
||||
textComponent.inputEl.addEventListener('selectionchange', checkcaret.bind(this));// Some browsers support this event
|
||||
|
||||
return textComponent;
|
||||
}
|
||||
@@ -272,18 +272,18 @@ export class GenericInputPrompt extends Modal {
|
||||
this.createButton(
|
||||
actionButtonContainer,
|
||||
"✅",
|
||||
this.submitClickCallback,
|
||||
this.submitClickCallback.bind(this),
|
||||
).setCta().buttonEl.style.marginRight = "0";
|
||||
}
|
||||
this.createButton(actionButtonContainer, "❌", this.cancelClickCallback, t("PROMPT_BUTTON_CANCEL"));
|
||||
this.createButton(actionButtonContainer, "❌", this.cancelClickCallback.bind(this), t("PROMPT_BUTTON_CANCEL"));
|
||||
if(this.displayEditorButtons) {
|
||||
this.createButton(editorButtonContainer, "⏎", ()=>this.insertStringBtnClickCallback("\n"), t("PROMPT_BUTTON_INSERT_LINE"), "0");
|
||||
this.createButton(editorButtonContainer, "⌫", this.delBtnClickCallback, "Delete");
|
||||
this.createButton(editorButtonContainer, "⌫", this.delBtnClickCallback.bind(this), "Delete");
|
||||
this.createButton(editorButtonContainer, "⎵", ()=>this.insertStringBtnClickCallback(" "), t("PROMPT_BUTTON_INSERT_SPACE"));
|
||||
if(this.view) {
|
||||
this.createButton(editorButtonContainer, "🔗", this.linkBtnClickCallback, t("PROMPT_BUTTON_INSERT_LINK"));
|
||||
this.createButton(editorButtonContainer, "🔗", this.linkBtnClickCallback.bind(this), t("PROMPT_BUTTON_INSERT_LINK"));
|
||||
}
|
||||
this.createButton(editorButtonContainer, "🔠", this.uppercaseBtnClickCallback, t("PROMPT_BUTTON_UPPERCASE"));
|
||||
this.createButton(editorButtonContainer, "🔠", this.uppercaseBtnClickCallback.bind(this), t("PROMPT_BUTTON_UPPERCASE"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -342,8 +342,13 @@ export class GenericInputPrompt extends Modal {
|
||||
this.inputComponent.inputEl.setSelectionRange(this.selectionStart, this.selectionEnd);
|
||||
}
|
||||
|
||||
private submitClickCallback = () => this.submit();
|
||||
private cancelClickCallback = () => this.cancel();
|
||||
private submitClickCallback () {
|
||||
this.submit();
|
||||
}
|
||||
|
||||
private cancelClickCallback () {
|
||||
this.cancel();
|
||||
}
|
||||
|
||||
private keyDownCallback = (evt: KeyboardEvent) => {
|
||||
if ((evt.key === "Enter" && this.lines === 1) || (isWinCTRLorMacCMD(evt) && evt.key === "Enter")) {
|
||||
@@ -668,10 +673,10 @@ export class ConfirmationPrompt extends Modal {
|
||||
buttonContainer.style.display = "flex";
|
||||
buttonContainer.style.justifyContent = "flex-end";
|
||||
|
||||
const cancelButton = this.createButton(buttonContainer, t("PROMPT_BUTTON_CANCEL"), this.cancelClickCallback);
|
||||
const cancelButton = this.createButton(buttonContainer, t("PROMPT_BUTTON_CANCEL"), this.cancelClickCallback.bind(this));
|
||||
cancelButton.buttonEl.style.marginRight = "0.5rem";
|
||||
|
||||
const confirmButton = this.createButton(buttonContainer, t("PROMPT_BUTTON_OK"), this.confirmClickCallback);
|
||||
const confirmButton = this.createButton(buttonContainer, t("PROMPT_BUTTON_OK"), this.confirmClickCallback.bind(this));
|
||||
confirmButton.buttonEl.style.marginRight = "0";
|
||||
|
||||
cancelButton.buttonEl.focus();
|
||||
@@ -683,12 +688,12 @@ export class ConfirmationPrompt extends Modal {
|
||||
return button;
|
||||
}
|
||||
|
||||
private cancelClickCallback = () => {
|
||||
private cancelClickCallback() {
|
||||
this.didConfirm = false;
|
||||
this.close();
|
||||
};
|
||||
|
||||
private confirmClickCallback = () => {
|
||||
private confirmClickCallback() {
|
||||
this.didConfirm = true;
|
||||
this.close();
|
||||
};
|
||||
@@ -714,18 +719,28 @@ export async function linkPrompt (
|
||||
view?: ExcalidrawView,
|
||||
message: string = "Select link to open",
|
||||
):Promise<[file:TFile, linkText:string, subpath: string]> {
|
||||
const partsArray = REGEX_LINK.getResList(linkText);
|
||||
const linksArray = REGEX_LINK.getResList(linkText);
|
||||
const tagsArray = REGEX_TAGS.getResList(linkText);
|
||||
let subpath: string = null;
|
||||
let file: TFile = null;
|
||||
let parts = partsArray[0];
|
||||
if (partsArray.length > 1) {
|
||||
let parts = linksArray[0] ?? tagsArray[0];
|
||||
const itemsDisplay = [
|
||||
...linksArray.filter(p=> Boolean(p.value)).map(p => {
|
||||
const alias = REGEX_LINK.getAliasOrLink(p);
|
||||
return alias === "100%" ? REGEX_LINK.getLink(p) : alias;
|
||||
}),
|
||||
...tagsArray.filter(x=> Boolean(x.value)).map(x => REGEX_TAGS.getTag(x)),
|
||||
];
|
||||
const items = [
|
||||
...linksArray.filter(p=>Boolean(p.value)),
|
||||
...tagsArray.filter(x=> Boolean(x.value)),
|
||||
];
|
||||
|
||||
if (items.length>1) {
|
||||
parts = await ScriptEngine.suggester(
|
||||
app,
|
||||
partsArray.filter(p=>Boolean(p.value)).map(p => {
|
||||
const alias = REGEX_LINK.getAliasOrLink(p);
|
||||
return alias === "100%" ? REGEX_LINK.getLink(p) : alias;
|
||||
}),
|
||||
partsArray.filter(p=>Boolean(p.value)),
|
||||
itemsDisplay,
|
||||
items,
|
||||
message,
|
||||
);
|
||||
if(!parts) return;
|
||||
@@ -735,8 +750,8 @@ export async function linkPrompt (
|
||||
return;
|
||||
}
|
||||
|
||||
if (!parts.value) {
|
||||
openTagSearch(linkText, app);
|
||||
if (REGEX_TAGS.isTag(parts)) {
|
||||
openTagSearch(REGEX_TAGS.getTag(parts), app);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -550,8 +550,19 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
|
||||
},
|
||||
{
|
||||
field: "getOriginalImageSize",
|
||||
code: "async getOriginalImageSize(imageElement: ExcalidrawImageElement): Promise<{width: number; height: number}>",
|
||||
desc: "Returns the size of the image element at 100% (i.e. the original size). This is an async function, you need to await the result.",
|
||||
code: "async getOriginalImageSize(imageElement: ExcalidrawImageElement, shouldWaitForImage: boolean=false): Promise<{width: number; height: number}>",
|
||||
desc: "Returns the size of the image element at 100% (i.e. the original size) or undefined if the data URL is not available.\n"+
|
||||
"If shouldWaitForImage is true, the function will wait for the view to load the image before returning the size.\n"+
|
||||
"This is an async function, you need to await the result.",
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
field: "resetImageAspectRatio",
|
||||
code: "async resetImageAspectRatio(imgEl: ExcalidrawImageElement): Promise<boolean>",
|
||||
desc: "Resets the image to its original aspect ratio.\n" +
|
||||
"If the image is resized then the function returns true.\n" +
|
||||
"If the image element is not in EA (only in the view), then if the image is resized, the element is copied to EA for Editing using copyViewElementsToEAforEditing([imgEl]).\n" +
|
||||
"Note you need to run await ea.addElementsToView(false); to add the modified image to the view.",
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
|
||||
@@ -54,7 +54,7 @@ export default {
|
||||
COPY_ELEMENT_LINK: "Copy [[link]] for selected element(s)",
|
||||
COPY_DRAWING_LINK: "Copy ![[embed link]] for this drawing",
|
||||
INSERT_LINK_TO_ELEMENT:
|
||||
`Copy [[link]] for selected element to clipboard. ${labelCTRL()}+CLICK to copy 'group=' link. ${labelSHIFT()}+CLICK to copy an 'area=' link. ${labelALT()}+CLICK to watch a help video.`,
|
||||
`Copy [[link]] for selected element to clipboard. ${labelCTRL()}+CLICK to copy 'group=' link. ${labelSHIFT()}+CLICK to copy an 'area=' link.`,
|
||||
INSERT_LINK_TO_ELEMENT_GROUP:
|
||||
"Copy 'group=' ![[link]] for selected element to clipboard.",
|
||||
INSERT_LINK_TO_ELEMENT_AREA:
|
||||
@@ -80,7 +80,7 @@ export default {
|
||||
ERROR_TRY_AGAIN: "Please try again.",
|
||||
PASTE_CODEBLOCK: "Paste code block",
|
||||
INSERT_LATEX:
|
||||
`Insert LaTeX formula (e.g. \\binom{n}{k} = \\frac{n!}{k!(n-k)!}). ${labelALT()}+CLICK to watch a help video.`,
|
||||
`Insert LaTeX formula (e.g. \\binom{n}{k} = \\frac{n!}{k!(n-k)!}).`,
|
||||
ENTER_LATEX: "Enter a valid LaTeX expression",
|
||||
READ_RELEASE_NOTES: "Read latest release notes",
|
||||
RUN_OCR: "OCR full drawing: Grab text from freedraw + images to clipboard and doc.props",
|
||||
@@ -93,14 +93,20 @@ export default {
|
||||
ANNOTATE_IMAGE : "Annotate image in Excalidraw",
|
||||
INSERT_ACTIVE_PDF_PAGE_AS_IMAGE: "Insert active PDF page as image",
|
||||
RESET_IMG_TO_100: "Set selected image element size to 100% of original",
|
||||
RESET_IMG_ASPECT_RATIO: "Reset selected image element aspect ratio",
|
||||
TEMPORARY_DISABLE_AUTOSAVE: "Disable autosave until next time Obsidian starts (only set this if you know what you are doing)",
|
||||
TEMPORARY_ENABLE_AUTOSAVE: "Enable autosave",
|
||||
|
||||
//ExcalidrawView.ts
|
||||
NO_SEARCH_RESULT: "Didn't find a matching element in the drawing",
|
||||
FORCE_SAVE_ABORTED: "Force Save aborted because saving is in progress",
|
||||
LINKLIST_SECOND_ORDER_LINK: "Second Order Link",
|
||||
MARKDOWN_EMBED_CUSTOMIZE_LINK_PROMPT_TITLE: "Customize the link",
|
||||
MARKDOWN_EMBED_CUSTOMIZE_LINK_PROMPT: "Do not add [[square brackets]] around the filename!<br>Follow this format when editing your link:<br><mark>filename#^blockref|WIDTHxMAXHEIGHT</mark>",
|
||||
MARKDOWN_EMBED_CUSTOMIZE_LINK_PROMPT_TITLE: "Customize the Embedded File link",
|
||||
MARKDOWN_EMBED_CUSTOMIZE_LINK_PROMPT: "Do not add [[square brackets]] around the filename!<br>" +
|
||||
"For markdown-page images follow this format when editing your link: <mark>filename#^blockref|WIDTHxMAXHEIGHT</mark><br>" +
|
||||
"You can anchor Excalidraw images to 100% of their size by adding <code>|100%</code> to the end of the link.<br>" +
|
||||
"You can change the PDF page by changing <code>#page=1</code> to <code>#page=2</code> etc.<br>" +
|
||||
"PDF rect crop values are: <code>left, bottom, right, top</code>. Eg.: <code>#rect=0,0,500,500</code><br>",
|
||||
FRAME_CLIPPING_ENABLED: "Frame Rendering: Enabled",
|
||||
FRAME_CLIPPING_DISABLED: "Frame Rendering: Disabled",
|
||||
ARROW_BINDING_INVERSE_MODE: "Inverted Mode: Default arrow binding is now disabled. Use CTRL/CMD to temporarily enable binding when needed.",
|
||||
@@ -773,7 +779,7 @@ FILENAME_HEAD: "Filename",
|
||||
TOGGLE_FRAME_RENDERING: "Toggle frame rendering",
|
||||
TOGGLE_FRAME_CLIPPING: "Toggle frame clipping",
|
||||
OPEN_LINK_CLICK: "Open Link",
|
||||
OPEN_LINK_PROPS: "Open markdown-embed properties or open link in new window",
|
||||
OPEN_LINK_PROPS: "Open the image-link or LaTeX-formula editor",
|
||||
|
||||
//IFrameActionsMenu.tsx
|
||||
NARROW_TO_HEADING: "Narrow to heading...",
|
||||
|
||||
@@ -2,6 +2,7 @@ import {
|
||||
DEVICE,
|
||||
FRONTMATTER_KEYS,
|
||||
} from "src/constants/constants";
|
||||
import { TAG_AUTOEXPORT, TAG_MDREADINGMODE, TAG_PDFEXPORT } from "src/constants/constSettingsTags";
|
||||
import { labelALT, labelCTRL, labelMETA, labelSHIFT } from "src/utils/ModifierkeyHelper";
|
||||
|
||||
// 简体中文
|
||||
@@ -336,14 +337,14 @@ FILENAME_HEAD: "文件名",
|
||||
SHOW_DRAWING_OR_MD_IN_READING_MODE_DESC:
|
||||
"当您处于 Markdown 阅读模式(即查看绘图的背景笔记)时,Excalidraw 绘图是否应该渲染为图像?" +
|
||||
"此设置不会影响您在 Excalidraw 模式下的绘图显示,或者在将绘图嵌入 Markdown 文档时,或在渲染悬停预览时。<br><ul>" +
|
||||
"<li>请参阅下面‘嵌入和导出’部分的 <b>PDF 导出</b> 相关设置。</li></ul><br>" +
|
||||
"<li>请参阅下面‘嵌入和导出’部分的 <a href='#"+TAG_PDFEXPORT+"'>PDF 导出</a> 相关设置。</li></ul><br>" +
|
||||
"您必须关闭当前的 Excalidraw/Markdown 文件并重新打开,以使此更改生效。",
|
||||
SHOW_DRAWING_OR_MD_IN_EXPORTPDF_NAME: "在将 Excalidraw 文件导出为 PDF 时将文件渲染为图像",
|
||||
SHOW_DRAWING_OR_MD_IN_EXPORTPDF_DESC:
|
||||
"处于 Markdown 视图模式时,此设置控制 Excalidraw 在使用 Obsidian 的 <b>导出为 PDF</b> 功能时,将 Excalidraw 文件导出为 PDF 的行为。<br>" +
|
||||
"<ul><li>当 <b>启用</b> 时,PDF 将仅显示 Excalidraw 绘图;</li>" +
|
||||
"<li>当 <b>禁用</b> 时,PDF 将显示文档的 Markdown 部分(背景笔记)。</li></ul>" +
|
||||
"请参阅上面‘外观和行为’部分的 <b>Markdown 阅读模式</b> 相关设置。" +
|
||||
"请参阅上面‘外观和行为’部分的 <<a href='#"+TAG_MDREADINGMODE+"'>>Markdown 阅读模式</a> 相关设置。" +
|
||||
"⚠️ 注意,您必须关闭当前的 Excalidraw/Markdown 文件并重新打开,以使此更改生效。⚠️",
|
||||
THEME_HEAD: "主题和样式",
|
||||
ZOOM_HEAD: "缩放",
|
||||
@@ -531,7 +532,7 @@ FILENAME_HEAD: "文件名",
|
||||
EMBED_REUSE_EXPORTED_IMAGE_NAME:
|
||||
"将之前已导出的图像作为预览图",
|
||||
EMBED_REUSE_EXPORTED_IMAGE_DESC:
|
||||
"该选项与“自动导出 SVG/PNG 副本”选项配合使用。如果嵌入到 Markdown 文档中的绘图文件存在同名的 SVG/PNG 副本,则将其作为预览图,而不再重新生成。<br>" +
|
||||
"该选项与<a href='#"+TAG_AUTOEXPORT+"'>自动导出 SVG/PNG 副本</a>选项配合使用。如果嵌入到 Markdown 文档中的绘图文件存在同名的 SVG/PNG 副本,则将其作为预览图,而不再重新生成。<br>" +
|
||||
"该选项能够提高 Markdown 文档的打开速度,尤其是当嵌入到 Markdown 文档中的绘图文件中含有大量图像或 MD-Embed 时。" +
|
||||
"但是,该选项也可能导致预览图无法立即响应你对绘图文件或者 Obsidian 主题风格的修改。<br>" +
|
||||
"该选项仅作用于嵌入到 Markdown 文档中的绘图。" +
|
||||
@@ -562,7 +563,7 @@ FILENAME_HEAD: "文件名",
|
||||
EMBED_TYPE_NAME: "“嵌入绘图到当前 Markdown 文档中”系列命令的源文件类型",
|
||||
EMBED_TYPE_DESC:
|
||||
"在命令面板中执行“嵌入绘图到当前 Markdown 文档中”系列命令时,要嵌入绘图文件本身,还是嵌入其 PNG 或 SVG 副本。<br>" +
|
||||
"如果您想选择 PNG 或 SVG 副本,需要先开启下方的“自动导出 PNG 副本”或“自动导出 SVG 副本”。<br>" +
|
||||
"如果您想选择 PNG 或 SVG 副本,需要先开启下方的<a href='#"+TAG_AUTOEXPORT+"'>自动导出 PNG / SVG 副本</a>。<br>" +
|
||||
"如果您选择了 PNG 或 SVG 副本,当副本不存在时,该命令将会插入一条损坏的链接,您需要打开绘图文件并手动导出副本才能修复 —— " +
|
||||
"也就是说,该选项不会自动帮您生成 PNG/SVG 副本,而只会引用已有的 PNG/SVG 副本。",
|
||||
EMBED_MARKDOWN_COMMENT_NAME: "将链接作为注释嵌入",
|
||||
@@ -772,7 +773,7 @@ FILENAME_HEAD: "文件名",
|
||||
TOGGLE_FRAME_RENDERING: "开启或关闭框架渲染",
|
||||
TOGGLE_FRAME_CLIPPING: "开启或关闭框架裁剪",
|
||||
OPEN_LINK_CLICK: "打开所选的图形或文本元素里的链接",
|
||||
OPEN_LINK_PROPS: "编辑所选 MD-Embed 的内部链接,或者打开所选的图形或文本元素里的链接",
|
||||
OPEN_LINK_PROPS: "打开 markdown-embed 属性或 LaTeX 编辑器,或在新窗口中打开链接",
|
||||
|
||||
//IFrameActionsMenu.tsx
|
||||
NARROW_TO_HEADING: "缩放至标题",
|
||||
|
||||
70
src/main.ts
70
src/main.ts
@@ -1794,12 +1794,80 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
const eaEl = ea.getElement(el.id);
|
||||
//@ts-ignore
|
||||
eaEl.width = size.width; eaEl.height = size.height;
|
||||
ea.addElementsToView(false,false,false);
|
||||
await ea.addElementsToView(false,false,false);
|
||||
}
|
||||
ea.destroy();
|
||||
})()
|
||||
}
|
||||
})
|
||||
|
||||
this.addCommand({
|
||||
id: "reset-image-ar",
|
||||
name: t("RESET_IMG_ASPECT_RATIO"),
|
||||
checkCallback: (checking: boolean) => {
|
||||
const view = this.app.workspace.getActiveViewOfType(ExcalidrawView);
|
||||
if (!view) return false;
|
||||
if (!view.excalidrawAPI) return false;
|
||||
const els = view.getViewSelectedElements().filter(el => el.type === "image");
|
||||
if (els.length !== 1) {
|
||||
if (checking) return false;
|
||||
new Notice("Select a single image element and try again");
|
||||
return false;
|
||||
}
|
||||
if (checking) return true;
|
||||
|
||||
(async () => {
|
||||
const el = els[0] as ExcalidrawImageElement;
|
||||
let ef = view.excalidrawData.getFile(el.fileId);
|
||||
if (!ef) {
|
||||
await view.forceSave();
|
||||
let ef = view.excalidrawData.getFile(el.fileId);
|
||||
new Notice("Select a single image element and try again");
|
||||
return false;
|
||||
}
|
||||
|
||||
const ea = new ExcalidrawAutomate(this, view);
|
||||
if (await ea.resetImageAspectRatio(el)) {
|
||||
await ea.addElementsToView(false, false, false);
|
||||
}
|
||||
ea.destroy();
|
||||
})();
|
||||
}
|
||||
});
|
||||
|
||||
this.addCommand({
|
||||
id: "open-link-props",
|
||||
name: t("OPEN_LINK_PROPS"),
|
||||
checkCallback: (checking: boolean) => {
|
||||
const view = this.app.workspace.getActiveViewOfType(ExcalidrawView);
|
||||
if (!view) return false;
|
||||
if (!view.excalidrawAPI) return false;
|
||||
const els = view.getViewSelectedElements().filter(el => el.type === "image");
|
||||
if (els.length !== 1) {
|
||||
if (checking) return false;
|
||||
new Notice("Select a single image element and try again");
|
||||
return false;
|
||||
}
|
||||
if (checking) return true;
|
||||
|
||||
const el = els[0] as ExcalidrawImageElement;
|
||||
let ef = view.excalidrawData.getFile(el.fileId);
|
||||
let eq = view.excalidrawData.getEquation(el.fileId);
|
||||
if (!ef && !eq) {
|
||||
view.forceSave();
|
||||
new Notice("Please try again.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if(ef) {
|
||||
view.openEmbeddedLinkEditor(el.id);
|
||||
}
|
||||
if(eq) {
|
||||
view.openLaTeXEditor(el.id);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.addCommand({
|
||||
id: "convert-card-to-file",
|
||||
name: t("CONVERT_CARD_TO_FILE"),
|
||||
|
||||
@@ -365,13 +365,10 @@ export const ICONS = {
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<path d="M4 22h14a2 2 0 0 0 2-2V7.5L14.5 2H6a2 2 0 0 0-2 2v7"/>
|
||||
<polyline
|
||||
points="14 2 14 8 20 8"
|
||||
fill="var(--icon-fill-color)"
|
||||
/>
|
||||
<path d="m10 18 3-3-3-3"/>
|
||||
<path d="M4 18v-1a2 2 0 0 1 2-2h6"/>
|
||||
<path d="M10 12.5 8 15l2 2.5"/>
|
||||
<path d="m14 12.5 2 2.5-2 2.5"/>
|
||||
<path d="M14 2v4a2 2 0 0 0 2 2h4"/>
|
||||
<path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7z"/>
|
||||
</svg>
|
||||
),
|
||||
//fa-brands fa-markdown
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { MAX_IMAGE_SIZE, IMAGE_TYPES, ANIMATED_IMAGE_TYPES, MD_EX_SECTIONS } from "src/constants/constants";
|
||||
import { App, Notice, TFile, WorkspaceLeaf } from "obsidian";
|
||||
import { ExcalidrawAutomate } from "src/ExcalidrawAutomate";
|
||||
import { REGEX_LINK, REG_LINKINDEX_HYPERLINK, getExcalidrawMarkdownHeaderSection } from "src/ExcalidrawData";
|
||||
import { REGEX_LINK, REG_LINKINDEX_HYPERLINK, getExcalidrawMarkdownHeaderSection, REGEX_TAGS } from "src/ExcalidrawData";
|
||||
import ExcalidrawView from "src/ExcalidrawView";
|
||||
import { ExcalidrawElement, ExcalidrawFrameElement } from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
import { getLinkParts } from "./Utils";
|
||||
@@ -72,24 +72,26 @@ export function getLinkTextFromLink (text: string): string {
|
||||
return linktext;
|
||||
}
|
||||
|
||||
export function openTagSearch (link:string, app: App, view?: ExcalidrawView) {
|
||||
const tags = link
|
||||
.matchAll(/#([\p{Letter}\p{Emoji_Presentation}\p{Number}\/_-]+)/gu)
|
||||
.next();
|
||||
if (!tags.value || tags.value.length < 2) {
|
||||
export function openTagSearch(link: string, app: App, view?: ExcalidrawView) {
|
||||
const tags = REGEX_TAGS.getResList(link);
|
||||
|
||||
if (!tags.length || !tags[0].value || tags[0].value.length < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
const search = app.workspace.getLeavesOfType("search");
|
||||
if (search.length == 0) {
|
||||
if (search.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
//@ts-ignore
|
||||
search[0].view.setQuery(`tag:${tags.value[1]}`);
|
||||
search[0].view.setQuery(`tag:${tags[0].value[1]}`);
|
||||
app.workspace.revealLeaf(search[0]);
|
||||
|
||||
if (view && view.isFullscreen()) {
|
||||
view.exitFullscreen();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -409,4 +409,4 @@ export async function closeLeafView(leaf: WorkspaceLeaf) {
|
||||
type: "empty",
|
||||
state: {},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user