mirror of
https://github.com/zsviczian/obsidian-excalidraw-plugin.git
synced 2025-08-06 05:46:28 +00:00
2.2.3
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "obsidian-excalidraw-plugin",
|
||||
"name": "Excalidraw",
|
||||
"version": "2.2.2",
|
||||
"version": "2.2.3",
|
||||
"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-23",
|
||||
"@zsviczian/excalidraw": "0.17.1-obsidian-25",
|
||||
"chroma-js": "^2.4.2",
|
||||
"clsx": "^2.0.0",
|
||||
"colormaster": "^1.2.1",
|
||||
|
||||
@@ -361,7 +361,8 @@ export class EmbeddedFilesLoader {
|
||||
isMask,
|
||||
};
|
||||
|
||||
const shouldUseCache = this.plugin.settings.allowImageCacheInScene && file && imageCache.isReady();
|
||||
const hasColorMap = Boolean(inFile instanceof EmbeddedFile ? inFile.colorMap : null);
|
||||
const shouldUseCache = !hasColorMap && this.plugin.settings.allowImageCacheInScene && file && imageCache.isReady();
|
||||
const cacheKey:ImageKey = {
|
||||
filepath: file.path,
|
||||
blockref: null,
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
StrokeRoundness,
|
||||
RoundnessType,
|
||||
ExcalidrawFrameElement,
|
||||
ExcalidrawTextContainer,
|
||||
} from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
import { Editor, normalizePath, Notice, OpenViewState, RequestUrlResponse, TFile, TFolder, WorkspaceLeaf } from "obsidian";
|
||||
import * as obsidian_module from "obsidian";
|
||||
@@ -35,6 +36,7 @@ import {
|
||||
REG_LINKINDEX_INVALIDCHARS,
|
||||
THEME_FILTER,
|
||||
mermaidToExcalidraw,
|
||||
refreshTextDimensions,
|
||||
} from "src/constants/constants";
|
||||
import { blobToBase64, checkAndCreateFolder, getDrawingFilename, getExcalidrawEmbeddedFilesFiletree, getListOfTemplateFiles, getNewUniqueFilepath, hasExcalidrawEmbeddedImagesTreeChanged, } from "src/utils/FileUtils";
|
||||
import {
|
||||
@@ -88,8 +90,9 @@ import {
|
||||
extractCodeBlocks as _extractCodeBlocks,
|
||||
} from "./utils/AIUtils";
|
||||
import { EXCALIDRAW_AUTOMATE_INFO, EXCALIDRAW_SCRIPTENGINE_INFO } from "./dialogs/SuggesterInfo";
|
||||
import { getFrameBasedOnFrameNameOrId } from "./utils/ExcalidrawViewUtils";
|
||||
import { addBackOfTheNoteCard, getFrameBasedOnFrameNameOrId } from "./utils/ExcalidrawViewUtils";
|
||||
import { log } from "./utils/DebugHelper";
|
||||
import { auto } from "@popperjs/core";
|
||||
|
||||
extendPlugins([
|
||||
HarmonyPlugin,
|
||||
@@ -1211,7 +1214,8 @@ export class ExcalidrawAutomate {
|
||||
topY: number,
|
||||
text: string,
|
||||
formatting?: {
|
||||
wrapAt?: number;
|
||||
autoResize?: boolean; //Default is true. Setting this to false will wrap the text in the text element without the need for the containser. If set to false, you must set a width value as well.
|
||||
wrapAt?: number; //wrapAt is ignored if autoResize is set to false (and width is provided)
|
||||
width?: number;
|
||||
height?: number;
|
||||
textAlign?: "left" | "center" | "right";
|
||||
@@ -1224,7 +1228,11 @@ export class ExcalidrawAutomate {
|
||||
): string {
|
||||
id = id ?? nanoid();
|
||||
const originalText = text;
|
||||
text = formatting?.wrapAt ? this.wrapText(text, formatting.wrapAt) : text;
|
||||
const autoresize = ((typeof formatting?.width === "undefined") || formatting?.box)
|
||||
? true
|
||||
: (formatting?.autoResize ?? true)
|
||||
text = (formatting?.wrapAt && autoresize) ? this.wrapText(text, formatting.wrapAt) : text;
|
||||
|
||||
const { w, h, baseline } = _measureText(
|
||||
text,
|
||||
this.style.fontSize,
|
||||
@@ -1235,9 +1243,9 @@ export class ExcalidrawAutomate {
|
||||
const height = formatting?.height ? formatting.height : h;
|
||||
|
||||
let boxId: string = null;
|
||||
const boxPadding = formatting?.boxPadding ?? 30;
|
||||
const strokeColor = this.style.strokeColor;
|
||||
this.style.strokeColor = formatting?.boxStrokeColor ?? strokeColor;
|
||||
const boxPadding = formatting?.boxPadding ?? 30;
|
||||
if (formatting?.box) {
|
||||
switch (formatting.box) {
|
||||
case "ellipse":
|
||||
@@ -1283,12 +1291,12 @@ export class ExcalidrawAutomate {
|
||||
? formatting.textAlign
|
||||
: this.style.textAlign ?? "left",
|
||||
verticalAlign: formatting?.textVerticalAlign ?? this.style.verticalAlign,
|
||||
baseline,
|
||||
...this.boxedElement(id, "text", topX, topY, width, height),
|
||||
containerId: isContainerBound ? boxId : null,
|
||||
originalText: isContainerBound ? originalText : text,
|
||||
rawText: isContainerBound ? originalText : text,
|
||||
lineHeight: getDefaultLineHeight(this.style.fontFamily),
|
||||
autoResize: formatting?.box ? true : (formatting?.autoResize ?? true),
|
||||
};
|
||||
if (boxId && formatting?.box === "blob") {
|
||||
this.addToGroup([id, boxId]);
|
||||
@@ -1300,6 +1308,25 @@ export class ExcalidrawAutomate {
|
||||
}
|
||||
box.boundElements.push({ type: "text", id });
|
||||
}
|
||||
const textElement = this.getElement(id) as Mutable<ExcalidrawTextElement>;
|
||||
const container = (boxId && formatting.box !== "blob") ? this.getElement(boxId) as Mutable<ExcalidrawTextContainer>: undefined;
|
||||
const dimensions = refreshTextDimensions(
|
||||
textElement,
|
||||
container,
|
||||
arrayToMap(this.getElements()),
|
||||
originalText,
|
||||
);
|
||||
if(dimensions) {
|
||||
textElement.width = dimensions.width;
|
||||
textElement.height = dimensions.height;
|
||||
textElement.x = dimensions.x;
|
||||
textElement.y = dimensions.y;
|
||||
textElement.text = dimensions.text;
|
||||
if(container) {
|
||||
container.width = dimensions.width + 2 * boxPadding;
|
||||
container.height = dimensions.height + 2 * boxPadding;
|
||||
}
|
||||
}
|
||||
return boxId ?? id;
|
||||
};
|
||||
|
||||
@@ -1839,6 +1866,24 @@ export class ExcalidrawAutomate {
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a back of the note card to the current active view
|
||||
* @param sectionTitle: string
|
||||
* @param activate:boolean = true; if true, the new Embedded Element will be activated after creation
|
||||
* @param sectionBody?: string;
|
||||
* @param embeddableCustomData?: EmbeddableMDCustomProps; formatting of the embeddable element
|
||||
* @returns embeddable element id
|
||||
*/
|
||||
async addBackOfTheCardNoteToView(sectionTitle: string, activate: boolean = false, sectionBody?: string, embeddableCustomData?: EmbeddableMDCustomProps): Promise<string> {
|
||||
//@ts-ignore
|
||||
if (!this.targetView || !this.targetView?._loaded) {
|
||||
errorMessage("targetView not set", "addBackOfTheCardNoteToView()");
|
||||
return null;
|
||||
}
|
||||
await this.targetView.forceSave(true);
|
||||
return addBackOfTheNoteCard(this.targetView, sectionTitle, activate, sectionBody, embeddableCustomData);
|
||||
}
|
||||
|
||||
/**
|
||||
* get the selected element in the view, if more are selected, get the first
|
||||
* @returns
|
||||
|
||||
@@ -240,26 +240,26 @@ const wrap = (text: string, lineLen: number) =>
|
||||
lineLen ? wrapTextAtCharLength(text, lineLen, false, 0) : text;
|
||||
|
||||
//WITHSECTION refers to back of the card note (see this.inputEl.onkeyup in SelectCard.ts)
|
||||
const RE_EXCALIDRAWDATA_WITHSECTION_OK = new RegExp(`^#\n%%\n# Excalidraw Data(?:\n|$)`, "m");
|
||||
const RE_EXCALIDRAWDATA_WITHSECTION_NOTOK = new RegExp(`#\n%%\n# Excalidraw Data(?:\n|$)`, "m");
|
||||
const RE_EXCALIDRAWDATA_NOSECTION_OK = new RegExp(`^(%%\n)?# Excalidraw Data(?:\n|$)`, "m");
|
||||
const RE_EXCALIDRAWDATA_WITHSECTION_OK = /^(#\n+)%%\n+# Excalidraw Data(?:\n|$)/m;
|
||||
const RE_EXCALIDRAWDATA_WITHSECTION_NOTOK = /#\n+%%\n+# Excalidraw Data(?:\n|$)/m;
|
||||
const RE_EXCALIDRAWDATA_NOSECTION_OK = /^(%%\n+)?# Excalidraw Data(?:\n|$)/m;
|
||||
|
||||
//WITHSECTION refers to back of the card note (see this.inputEl.onkeyup in SelectCard.ts)
|
||||
const RE_TEXTELEMENTS_WITHSECTION_OK = new RegExp(`^#\n%%\n##? Text Elements(?:\n|$)`, "m");
|
||||
const RE_TEXTELEMENTS_WITHSECTION_NOTOK = new RegExp(`#\n%%\n##? Text Elements(?:\n|$)`, "m");
|
||||
const RE_TEXTELEMENTS_NOSECTION_OK = new RegExp(`^(%%\n)?##? Text Elements(?:\n|$)`, "m");
|
||||
const RE_TEXTELEMENTS_WITHSECTION_OK = /^#\n+%%\n+##? Text Elements(?:\n|$)/m;
|
||||
const RE_TEXTELEMENTS_WITHSECTION_NOTOK = /#\n+%%\n+##? Text Elements(?:\n|$)/m;
|
||||
const RE_TEXTELEMENTS_NOSECTION_OK = /^(%%\n+)?##? Text Elements(?:\n|$)/m;
|
||||
|
||||
|
||||
//The issue is that when editing in markdown embeds the user can delete the last enter causing two sections
|
||||
//to collide. This is particularly problematic when the user is editing the last section before # Text Elements
|
||||
const RE_EXCALIDRAWDATA_FALLBACK_1 = new RegExp(`(.*)%%\n# Excalidraw Data(?:\n|$)`, "m");
|
||||
const RE_EXCALIDRAWDATA_FALLBACK_2 = new RegExp(`(.*)# Excalidraw Data(?:\n|$)`, "m");
|
||||
const RE_EXCALIDRAWDATA_FALLBACK_1 = /(.*)%%\n+# Excalidraw Data(?:\n|$)/m;
|
||||
const RE_EXCALIDRAWDATA_FALLBACK_2 = /(.*)# Excalidraw Data(?:\n|$)/m;
|
||||
|
||||
const RE_TEXTELEMENTS_FALLBACK_1 = new RegExp(`(.*)%%\n##? Text Elements(?:\n|$)`, "m");
|
||||
const RE_TEXTELEMENTS_FALLBACK_2 = new RegExp(`(.*)##? Text Elements(?:\n|$)`, "m");
|
||||
const RE_TEXTELEMENTS_FALLBACK_1 = /(.*)%%\n+##? Text Elements(?:\n|$)/m;
|
||||
const RE_TEXTELEMENTS_FALLBACK_2 = /(.*)##? Text Elements(?:\n|$)/m;
|
||||
|
||||
|
||||
const RE_DRAWING = new RegExp(`^(%%\n)?##? Drawing\n`, "m");
|
||||
const RE_DRAWING = /^(%%\n+)?##? Drawing\n/m;
|
||||
|
||||
export const getExcalidrawMarkdownHeaderSection = (data:string, keys?:[string,string][]):string => {
|
||||
//The base case scenario is at the top, continued with fallbacks in order of likelihood and file structure
|
||||
@@ -278,10 +278,11 @@ export const getExcalidrawMarkdownHeaderSection = (data:string, keys?:[string,st
|
||||
data = data.substring(0, drawingTrimLocation);
|
||||
}
|
||||
|
||||
let trimLocation = data.search(RE_EXCALIDRAWDATA_WITHSECTION_OK);
|
||||
const m1 = data.match(RE_EXCALIDRAWDATA_WITHSECTION_OK);
|
||||
let trimLocation = m1?.index ?? -1; //data.search(RE_EXCALIDRAWDATA_WITHSECTION_OK);
|
||||
let shouldFixTrailingHashtag = false;
|
||||
if(trimLocation > 0) {
|
||||
trimLocation += 2; //accounts for the "#\n" which I want to leave there untouched
|
||||
trimLocation += m1[1].length; //accounts for the "(#\n\s*)" which I want to leave there untouched
|
||||
}
|
||||
|
||||
/* Expected markdown structure (this happens when the user deletes the last empty line of the last back-of-the-card note):
|
||||
@@ -766,8 +767,8 @@ export class ExcalidrawData {
|
||||
return true; //Text Elements header does not exist
|
||||
}
|
||||
data = data.slice(position);
|
||||
const normalMatch = data.match(/^((%%\n)?# Excalidraw Data\n## Text Elements(?:\n|$))/m)
|
||||
??data.match(/^((%%\n)?##? Text Elements(?:\n|$))/m);
|
||||
const normalMatch = data.match(/^((%%\n*)?# Excalidraw Data\n## Text Elements(?:\n|$))/m)
|
||||
?? data.match(/^((%%\n*)?##? Text Elements(?:\n|$))/m);
|
||||
|
||||
const textElementsMatch = normalMatch
|
||||
? normalMatch[0]
|
||||
@@ -865,7 +866,7 @@ export class ExcalidrawData {
|
||||
? data.substring(indexOfNewEmbeddedFiles + embeddedFilesNewLength)
|
||||
: data.substring(indexOfOldEmbeddedFiles + embeddedFilesOldLength);
|
||||
//Load Embedded files
|
||||
const REG_FILEID_FILEPATH = /([\w\d]*):\s*\[\[([^\]]*)]]\s?(\{[^}]*})?\n/gm;
|
||||
const REG_FILEID_FILEPATH = /([\w\d]*):\s*\[\[([^\]]*)]]\s*(\{[^}]*})?\n/gm;
|
||||
res = data.matchAll(REG_FILEID_FILEPATH);
|
||||
while (!(parts = res.next()).done) {
|
||||
const embeddedFile = new EmbeddedFile(
|
||||
@@ -1345,6 +1346,9 @@ export class ExcalidrawData {
|
||||
generateMD(deletedElements: ExcalidrawElement[] = []): string {
|
||||
let outString = this.textElementCommentedOut ? "%%\n" : "";
|
||||
outString += `# Excalidraw Data\n## Text Elements\n`;
|
||||
if (this.plugin.settings.addDummyTextElement) {
|
||||
outString += `\n^_dummy!_\n\n`;
|
||||
}
|
||||
const textElementLinks = new Map<string, string>();
|
||||
for (const key of this.textElements.keys()) {
|
||||
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/566
|
||||
|
||||
@@ -127,7 +127,7 @@ import { anyModifierKeysPressed, emulateKeysForLinkClick, webbrowserDragModifier
|
||||
import { setDynamicStyle } from "./utils/DynamicStyling";
|
||||
import { InsertPDFModal } from "./dialogs/InsertPDFModal";
|
||||
import { CustomEmbeddable, renderWebView } from "./customEmbeddable";
|
||||
import { getExcalidrawFileForwardLinks, getFrameBasedOnFrameNameOrId, getLinkTextFromLink, insertEmbeddableToView, insertImageToView, openExternalLink, openTagSearch } from "./utils/ExcalidrawViewUtils";
|
||||
import { addBackOfTheNoteCard, getExcalidrawFileForwardLinks, getFrameBasedOnFrameNameOrId, getLinkTextFromLink, insertEmbeddableToView, insertImageToView, openExternalLink, openTagSearch } from "./utils/ExcalidrawViewUtils";
|
||||
import { imageCache } from "./utils/ImageCache";
|
||||
import { CanvasNodeFactory, ObsidianCanvasNode } from "./utils/CanvasNodeFactory";
|
||||
import { EmbeddableMenu } from "./menu/EmbeddableActionsMenu";
|
||||
@@ -670,7 +670,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
this.editingSelfResetTimer = setTimeout(()=>self.semaphores.embeddableIsEditingSelf = false,EMBEDDABLE_SEMAPHORE_TIMEOUT);
|
||||
}
|
||||
|
||||
async save(preventReload: boolean = true, forcesave: boolean = false) {
|
||||
async save(preventReload: boolean = true, forcesave: boolean = false, overrideEmbeddableIsEditingSelfDebounce: boolean = false) {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.save, "ExcalidrawView.save, enter", preventReload, forcesave);
|
||||
/*if(this.semaphores.viewunload && (this.ownerWindow !== window)) {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.save, `ExcalidrawView.save, view is unloading, aborting save`);
|
||||
@@ -680,7 +680,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
if(!this.isLoaded) {
|
||||
return;
|
||||
}
|
||||
if (this.semaphores.embeddableIsEditingSelf) {
|
||||
if (!overrideEmbeddableIsEditingSelfDebounce && this.semaphores.embeddableIsEditingSelf) {
|
||||
return;
|
||||
}
|
||||
//console.log("saving - embeddable not editing")
|
||||
@@ -1290,7 +1290,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
}
|
||||
this.semaphores.preventReload = false;
|
||||
this.semaphores.forceSaving = true;
|
||||
await this.save(false, true);
|
||||
await this.save(false, true, true);
|
||||
this.plugin.triggerEmbedUpdates();
|
||||
this.loadSceneFiles();
|
||||
this.semaphores.forceSaving = false;
|
||||
@@ -1617,6 +1617,9 @@ export default class ExcalidrawView extends TextFileView {
|
||||
//onClose happens after onunload
|
||||
protected async onClose(): Promise<void> {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.onClose,`ExcalidrawView.onClose, file:${this.file?.name}`);
|
||||
if(this.isDirty()) {
|
||||
await this.save(true,true,true);
|
||||
}
|
||||
await super.onClose();
|
||||
return;
|
||||
}
|
||||
@@ -2275,7 +2278,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
{
|
||||
elements: excalidrawData.elements.concat(deletedElements??[]), //need to preserve deleted elements during autosave if images, links, etc. are updated
|
||||
files: excalidrawData.files,
|
||||
storeAction: "update",
|
||||
storeAction: justloaded ? "update" : "none",
|
||||
},
|
||||
justloaded
|
||||
);
|
||||
@@ -2298,6 +2301,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
pinnedScripts: this.plugin.settings.pinnedScripts,
|
||||
customPens: this.plugin.settings.customPens.slice(0,this.plugin.settings.numberOfCustomPens),
|
||||
},
|
||||
storeAction: justloaded ? "update" : "none",
|
||||
},
|
||||
);
|
||||
if (
|
||||
@@ -2464,7 +2468,9 @@ export default class ExcalidrawView extends TextFileView {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.openAsMarkdown, "ExcalidrawView.openAsMarkdown", eState);
|
||||
if (this.plugin.settings.compress && this.plugin.settings.decompressForMDView) {
|
||||
this.excalidrawData.disableCompression = true;
|
||||
await this.save(true, true);
|
||||
await this.save(true, true, true);
|
||||
} else if (this.isDirty()) {
|
||||
await this.save(true, true, true);
|
||||
}
|
||||
this.setMarkdownView(eState);
|
||||
}
|
||||
@@ -3364,6 +3370,9 @@ export default class ExcalidrawView extends TextFileView {
|
||||
} else if(e.dataTransfer.types.length === 1 && e.dataTransfer.types.includes("Files")) {
|
||||
//drag from OS file manager
|
||||
msg = modifierKeyTooltipMessages().LocalFileDragAction[localFileDragModifierType(e)];
|
||||
if(DEVICE.isMacOS && isWinCTRLorMacCMD(e)) {
|
||||
msg = "CMD is reserved by MacOS for file system drag actions.\nCan't use it in Obsidian.\nUse a combination of SHIFT, CTRL, OPT instead."
|
||||
}
|
||||
} else {
|
||||
//drag from Internet
|
||||
msg = modifierKeyTooltipMessages().WebBrowserDragAction[webbrowserDragModifierType(e)];
|
||||
@@ -3371,7 +3380,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
if(!e.shiftKey && !e.ctrlKey && !e.altKey && !e.metaKey) {
|
||||
msg += DEVICE.isMacOS || DEVICE.isIOS
|
||||
? "\nTry SHIFT, OPT, CTRL combinations for other drop actions"
|
||||
: "\nTry SHIFT, CTRL, ALT combinations for other drop actions";
|
||||
: "\nTry SHIFT, CTRL, ALT, Meta combinations for other drop actions";
|
||||
}
|
||||
if(this.draginfoDiv.innerText !== msg) this.draginfoDiv.innerText = msg;
|
||||
const top = `${e.clientY-parseFloat(getComputedStyle(this.draginfoDiv).fontSize)*8}px`;
|
||||
@@ -3559,6 +3568,13 @@ export default class ExcalidrawView extends TextFileView {
|
||||
return false;
|
||||
}
|
||||
if(data && data.text && !this.modifierKeyDown.shiftKey) {
|
||||
const isCodeblock = Boolean(data.text.replaceAll("\r\n", "\n").replaceAll("\r", "\n").match(/^`{3}[^\n]*\n.+\n`{3}\s*$/ms));
|
||||
if(isCodeblock) {
|
||||
const clipboardText = data.text;
|
||||
setTimeout(()=>this.pasteCodeBlock(clipboardText));
|
||||
return false;
|
||||
}
|
||||
|
||||
const quoteWithRef = obsidianPDFQuoteWithRef(data.text);
|
||||
if(quoteWithRef) {
|
||||
const ea = getEA(this) as ExcalidrawAutomate;
|
||||
@@ -3873,8 +3889,38 @@ export default class ExcalidrawView extends TextFileView {
|
||||
if(localFileDragAction === "image-import") {
|
||||
if (IMAGE_TYPES.contains(extension)) {
|
||||
(async () => {
|
||||
const {folder:_, filepath} = await getAttachmentsFolderAndFilePath(this.app, this.file.path,event.dataTransfer.files[i].name);
|
||||
const file = await this.app.vault.createBinary(filepath, await event.dataTransfer.files[i].arrayBuffer())
|
||||
const droppedFilename = event.dataTransfer.files[i].name;
|
||||
const fileToImport = await event.dataTransfer.files[i].arrayBuffer();
|
||||
let {folder:_, filepath} = await getAttachmentsFolderAndFilePath(this.app, this.file.path, droppedFilename);
|
||||
const maybeFile = this.app.vault.getAbstractFileByPath(filepath);
|
||||
if(maybeFile && maybeFile instanceof TFile) {
|
||||
const action = await ScriptEngine.suggester(
|
||||
this.app,[
|
||||
"Use the file already in the Vault instead of importing",
|
||||
"Overwrite existing file in the Vault",
|
||||
"Import the file with a new name",
|
||||
],[
|
||||
"Use",
|
||||
"Overwrite",
|
||||
"Import",
|
||||
],
|
||||
"A file with the same name/path already exists in the Vault",
|
||||
);
|
||||
switch(action) {
|
||||
case "Import":
|
||||
const {folderpath,filename,basename:_,extension:__} = splitFolderAndFilename(filepath);
|
||||
filepath = getNewUniqueFilepath(this.app.vault, filename, folderpath);
|
||||
break;
|
||||
case "Overwrite":
|
||||
await this.app.vault.modifyBinary(maybeFile, fileToImport);
|
||||
// there is deliberately no break here
|
||||
case "Use":
|
||||
default:
|
||||
insertImageToView(getEA(this), pos, maybeFile);
|
||||
return;
|
||||
}
|
||||
}
|
||||
const file = await this.app.vault.createBinary(filepath, fileToImport)
|
||||
insertImageToView(getEA(this), pos, file);
|
||||
})();
|
||||
return false;
|
||||
@@ -4325,6 +4371,27 @@ export default class ExcalidrawView extends TextFileView {
|
||||
selectCardDialog.start();
|
||||
}
|
||||
|
||||
public async pasteCodeBlock(data: string) {
|
||||
try {
|
||||
data = data.replaceAll("\r\n", "\n").replaceAll("\r", "\n").trim();
|
||||
const isCodeblock = Boolean(data.match(/^`{3}[^\n]*\n.+\n`{3}\s*$/ms));
|
||||
if(!isCodeblock) {
|
||||
const codeblockType = await GenericInputPrompt.Prompt(this,this.plugin,this.app,"type codeblock type","javascript, html, python, etc.","");
|
||||
data = "```"+codeblockType.trim()+"\n"+data+"\n```";
|
||||
}
|
||||
let title = (await GenericInputPrompt.Prompt(this,this.plugin,this.app,"Code Block Title","Enter title or leave empty for automatic title","")).trim();
|
||||
if (title === "") {title = "Code Block";};
|
||||
const sections = await this.getBackOfTheNoteSections();
|
||||
if (sections.includes(title)) {
|
||||
let i=0;
|
||||
while (sections.includes(`${title} ${++i}`)) {};
|
||||
title = `${title} ${i}`;
|
||||
}
|
||||
addBackOfTheNoteCard(this, title, false, data);
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
|
||||
public async convertImageElWithURLToLocalFile(data: {imageEl: ExcalidrawImageElement, embeddedFile: EmbeddedFile}) {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.convertImageElWithURLToLocalFile, "ExcalidrawView.convertImageElWithURLToLocalFile", data);
|
||||
const {imageEl, embeddedFile} = data;
|
||||
@@ -4564,7 +4631,6 @@ export default class ExcalidrawView extends TextFileView {
|
||||
onClose
|
||||
),
|
||||
]);
|
||||
|
||||
contextMenuActions.push([
|
||||
renderContextMenuAction(
|
||||
t("UNIVERSAL_ADD_FILE"),
|
||||
@@ -4585,6 +4651,17 @@ export default class ExcalidrawView extends TextFileView {
|
||||
),
|
||||
// Add more context menu actions here if needed
|
||||
]);
|
||||
contextMenuActions.push([
|
||||
renderContextMenuAction(
|
||||
t("PASTE_CODEBLOCK"),
|
||||
async () => {
|
||||
const data = await navigator.clipboard?.readText();
|
||||
if(!data || data.trim() === "") return;
|
||||
this.pasteCodeBlock(data);
|
||||
},
|
||||
onClose
|
||||
),
|
||||
])
|
||||
}
|
||||
|
||||
if(contextMenuActions.length === 0) return;
|
||||
|
||||
@@ -614,7 +614,7 @@ const tmpObsidianWYSIWYG = async (
|
||||
) {
|
||||
internalEmbedDiv = internalEmbedDiv.parentElement;
|
||||
}
|
||||
|
||||
|
||||
if(
|
||||
internalEmbedDiv.hasClass("dataview") ||
|
||||
internalEmbedDiv.hasClass("cm-preview-code-block") ||
|
||||
@@ -623,6 +623,15 @@ const tmpObsidianWYSIWYG = async (
|
||||
return; //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/835
|
||||
}
|
||||
|
||||
|
||||
if(!plugin.settings.renderImageInHoverPreviewForMDNotes) {
|
||||
const isHoverPopover = internalEmbedDiv.parentElement?.hasClass("hover-popover");
|
||||
const shouldOpenMD = Boolean(ctx.frontmatter?.["excalidraw-open-md"]);
|
||||
if(isHoverPopover && shouldOpenMD) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const isPrinting = Boolean(internalEmbedDiv.hasClass("print"));
|
||||
|
||||
const attr: imgElementAttributes = {
|
||||
|
||||
@@ -17,6 +17,54 @@ I develop this plugin as a hobby, spending my free time doing this. If you find
|
||||
|
||||
<div class="ex-coffee-div"><a href="https://ko-fi.com/zsolt"><img src="https://cdn.ko-fi.com/cdn/kofi3.png?v=3" height=45></a></div>
|
||||
`,
|
||||
"2.2.3":`
|
||||
## Fixed
|
||||
- Undo history was not properly initialized [#1791](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1785)
|
||||
- Excalidraw did not save edits when switching to markdown view mode with a hotkey or terminating the popout window
|
||||
- SVG export did not maintain the aspect ratio of manually distorted images [#1780](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1780)
|
||||
|
||||
## New
|
||||
- In pen mode, double tapping the screen will toggle the eraser tool when using freedraw tool, or one of the other tools in locked mode.
|
||||
- New setting under "Excalidraw appearance and behavior" to disable rendering of Excalidraw drawings in hover previews, in case the file has the ${String.fromCharCode(96)}excalidraw-open-md: true${String.fromCharCode(96)} frontmatter property [#1795](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1795)
|
||||
- Additional foolproofing of ${String.fromCharCode(96)}# Excalidraw Data${String.fromCharCode(96)}. The file is now more resilient to automated linting and other changes. There is also a new setting under "Compatibility Features" to add a dummy first text element to ${String.fromCharCode(96)}## Text Elements${String.fromCharCode(96)}. You can use this feature if your auto-linter adds empty lines after section headings.
|
||||
- Pasting markdown code blocks will create a back-of-the-note card with the code block. CTRL+SHIFT+V will paste the text as a normal text element. When copying code from Chat GPT the markdown code fence (triple backtick) is missing. In this case, you may use the new context menu action "Paste code block" to create a back of the note card with the code block.
|
||||
- Pasting long text will be wrapped in the text element.
|
||||
|
||||
## New in ExcalidrawAutomate
|
||||
- Updated viewUpdateScene: This now implements the [new Excalidraw API](https://github.com/excalidraw/excalidraw/pull/7898)
|
||||
${String.fromCharCode(96, 96, 96)}ts
|
||||
viewUpdateScene (
|
||||
scene: {
|
||||
elements?: ExcalidrawElement[],
|
||||
appState?: AppState,
|
||||
files?: BinaryFileData,
|
||||
commitToHistory?: boolean,
|
||||
storeAction?: "capture" | "none" | "update",
|
||||
},
|
||||
restore: boolean = false,
|
||||
):void ;
|
||||
${String.fromCharCode(96, 96, 96)}
|
||||
- Updated addText. The function now supports the new text-wrapping feature
|
||||
${String.fromCharCode(96, 96, 96)}ts
|
||||
addText(
|
||||
topX: number,
|
||||
topY: number,
|
||||
text: string,
|
||||
formatting?: {
|
||||
autoResize?: boolean; //Default is true. Setting this to false will wrap the text in the text element without the need for the container. If set to false, you must set a width value as well.
|
||||
wrapAt?: number; //wrapAt is ignored if autoResize is set to false (and width is provided)
|
||||
width?: number;
|
||||
height?: number;
|
||||
textAlign?: "left" | "center" | "right";
|
||||
box?: boolean | "box" | "blob" | "ellipse" | "diamond";
|
||||
boxPadding?: number;
|
||||
boxStrokeColor?: string;
|
||||
textVerticalAlign?: "top" | "middle" | "bottom";
|
||||
},
|
||||
id?: string,
|
||||
): string
|
||||
${String.fromCharCode(96, 96, 96)}
|
||||
`,
|
||||
"2.2.2":`
|
||||
## Fixed
|
||||
- ExcaliBrain stopped working with 2.2.0
|
||||
|
||||
@@ -748,8 +748,6 @@ export const linkPrompt = async (linkText:string, app: App, view?: ExcalidrawVie
|
||||
return [file, linkText, subpath];
|
||||
}
|
||||
|
||||
|
||||
|
||||
export const templatePromt = async (files: TFile[], app: App): Promise<TFile> => {
|
||||
if(files.length === 1) return files[0];
|
||||
return ((await linkPrompt(
|
||||
|
||||
@@ -3,10 +3,8 @@ import { t } from "../lang/helpers";
|
||||
import ExcalidrawView from "src/ExcalidrawView";
|
||||
import { getEA } from "src";
|
||||
import { ExcalidrawAutomate } from "src/ExcalidrawAutomate";
|
||||
import { getExcalidrawMarkdownHeaderSection } from "src/ExcalidrawData";
|
||||
import { MD_EX_SECTIONS } from "src/constants/constants";
|
||||
import { ExcalidrawImperativeAPI } from "@zsviczian/excalidraw/types/excalidraw/types";
|
||||
import { cleanSectionHeading } from "src/utils/ObsidianUtils";
|
||||
import { addBackOfTheNoteCard } from "src/utils/ExcalidrawViewUtils";
|
||||
|
||||
export class SelectCard extends FuzzySuggestModal<string> {
|
||||
|
||||
@@ -32,48 +30,8 @@ export class SelectCard extends FuzzySuggestModal<string> {
|
||||
new Notice(t("INVALID_SECTION_NAME"));
|
||||
this.close();
|
||||
return;
|
||||
}
|
||||
(async () => {
|
||||
const data = view.data;
|
||||
const header = getExcalidrawMarkdownHeaderSection(data);
|
||||
const body = data.split(header)[1];
|
||||
const shouldAddHashtag = body && body.startsWith("%%");
|
||||
const shouldRemoveTrailingHashtag = header.endsWith("#\n");
|
||||
view.data = data.replace(
|
||||
header,
|
||||
(shouldRemoveTrailingHashtag ? header.substring(0,header.length-2) : header) +
|
||||
`\n# ${item}\n\n${shouldAddHashtag ? "#\n" : ""}`);
|
||||
await view.forceSave(true);
|
||||
let watchdog = 0;
|
||||
await sleep(200);
|
||||
let found:string;
|
||||
while (watchdog++ < 10 && !(found=(await this.app.metadataCache.blockCache
|
||||
.getForFile({ isCancelled: () => false },view.file))
|
||||
.blocks.filter((b: any) => b.display && b.node?.type === "heading")
|
||||
.filter((b: any) => !MD_EX_SECTIONS.includes(b.display))
|
||||
.map((b: any) => cleanSectionHeading(b.display))
|
||||
.find((b: any) => b === item))) {
|
||||
await sleep(200);
|
||||
}
|
||||
|
||||
const ea = getEA(this.view) as ExcalidrawAutomate;
|
||||
const id = ea.addEmbeddable(
|
||||
0,0,400,500,
|
||||
`[[${this.view.file.path}#${item}]]`
|
||||
);
|
||||
await ea.addElementsToView(true, false, true);
|
||||
|
||||
const api = view.excalidrawAPI as ExcalidrawImperativeAPI;
|
||||
const el = ea.getViewElements().find(el=>el.id === id);
|
||||
api.selectElements([el]);
|
||||
setTimeout(()=>{
|
||||
api.updateScene({appState: {activeEmbeddable: {element: el, state: "active"}}});
|
||||
if(found) view.getEmbeddableLeafElementById(el.id)?.editNode?.();
|
||||
});
|
||||
})();
|
||||
//create new section
|
||||
//`# ${this.inputEl.value}\n\n`;
|
||||
//Do not allow MD_EX_SECTIONS
|
||||
}
|
||||
addBackOfTheNoteCard(this.view, item);
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,9 @@ const hyperlink = (url: string, text: string) => {
|
||||
return `<a onclick='window.open("${url}")'>${text}</a>`;
|
||||
}
|
||||
|
||||
const EMBEDDABLE_MDCUSTOMPROPS = `type EmbeddableMDCustomProps = {<br>useObsidianDefaults: boolean;<br>backgroundMatchCanvas: boolean;<br>backgroundMatchElement: boolean;<br>backgroundColor: string;<br>backgroundOpacity: number;<br>borderMatchElement: boolean;<br>borderColor: string;<br>borderOpacity: number;<br>filenameVisible: boolean;<br>};<br>`;
|
||||
|
||||
|
||||
export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
|
||||
{
|
||||
field: "help",
|
||||
@@ -262,8 +265,10 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
|
||||
},
|
||||
{
|
||||
field: "addText",
|
||||
code: 'addText(topX: number, topY: number, text: string, formatting?: {wrapAt?: number; width?: number; height?: number; textAlign?: "left" | "center" | "right"; textVerticalAlign: "top" | "middle" | "bottom"; box?: boolean | "box" | "blob" | "ellipse" | "diamond"; boxPadding?: number; boxStrokeColor?: string;}, id?: string,): string;',
|
||||
desc: "If box is !null, then text will be boxed\nThe function returns the id of the TextElement. If the text element is boxed i.e. it is a sticky note, then the id of the container object",
|
||||
code: 'addText(topX: number, topY: number, text: string, formatting?: {autoResize?: boolean; wrapAt?: number; width?: number; height?: number; textAlign?: "left" | "center" | "right"; textVerticalAlign: "top" | "middle" | "bottom"; box?: boolean | "box" | "blob" | "ellipse" | "diamond"; boxPadding?: number; boxStrokeColor?: string;}, id?: string,): string;',
|
||||
desc: "If box is !null, then text will be boxed\nThe function returns the id of the TextElement. If the text element is boxed i.e. it is a sticky note, then the id of the container object.\n"+
|
||||
"Default value for autoResize is true. Setting autoResize to false will wrap the text in the text element without the need for the container. If set to false, you must provide a width value as well.\n" +
|
||||
"wrapAt will be ignored if autoResize is set to false (and a width is also provided)",
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
@@ -286,8 +291,9 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
|
||||
},
|
||||
{
|
||||
field: "addEmbeddable",
|
||||
code: "addEmbeddable(topX: number, topY: number, width: number, height: number, url?: string, file?: TFile): string;",
|
||||
desc: "Adds an iframe/webview (depending on content and platform) to the drawing. If url is not null then the iframe/webview will be loaded from the url. The url maybe a markdown link to an note in the Vault or a weblink. If url is null then the iframe/webview will be loaded from the file. Both the url and the file may not be null.",
|
||||
code: "addEmbeddable(topX: number, topY: number, width: number, height: number, url?: string, file?: TFile, embeddableCustomData?: EmbeddableMDCustomProps): string;",
|
||||
desc: "Adds an iframe/webview (depending on content and platform) to the drawing. If url is not null then the iframe/webview will be loaded from the url. The url maybe a markdown link to an note in the Vault or a weblink. " +
|
||||
"If url is null then the iframe/webview will be loaded from the file. Both the url and the file may not be null.<br>" + EMBEDDABLE_MDCUSTOMPROPS,
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
@@ -365,6 +371,14 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
|
||||
desc: null,
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
field: "addBackOfTheCardNoteToView",
|
||||
code: "async addBackOfTheCardNoteToView(sectionTitle: string, activate: boolean = false, sectionBody?: string, embeddableCustomData?: EmbeddableMDCustomProps): Promise<string>",
|
||||
desc: "Adds a back of the note card to the current active view. If <b>body</b> is provided the note will be created with the body text, otherwise the note will be created with the title only.<br>Returns the id of the created element.<br>" +
|
||||
"If <b>activate</b> is true, the embedded note will be activated for editing.<br>" +
|
||||
"This is an async function, if you need the element ID of the created element, the function should be awaited.<br>" + EMBEDDABLE_MDCUSTOMPROPS,
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
field: "getViewSelectedElement",
|
||||
code: "getViewSelectedElement(): ExcalidrawElement;",
|
||||
@@ -497,6 +511,12 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
|
||||
desc: "Measures text size based on current style settings",
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
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.",
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
field: "verifyMinimumPluginVersion",
|
||||
code: "verifyMinimumPluginVersion(requiredVersion: string): boolean;",
|
||||
|
||||
@@ -72,6 +72,7 @@ export default {
|
||||
INSERT_PDF: "Insert PDF file from vault",
|
||||
UNIVERSAL_ADD_FILE: "Insert ANY file",
|
||||
INSERT_CARD: "Add back-of-note card",
|
||||
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.`,
|
||||
ENTER_LATEX: "Enter a valid LaTeX expression",
|
||||
@@ -302,6 +303,9 @@ FILENAME_HEAD: "Filename",
|
||||
SHOW_PEN_MODE_FREEDRAW_CROSSHAIR_DESC:
|
||||
"Show crosshair in pen mode when using the freedraw tool. <b><u>Toggle ON:</u></b> SHOW <b><u>Toggle OFF:</u></b> HIDE<br>"+
|
||||
"The effect depends on the device. Crosshair is typically visible on drawing tablets, MS Surface, but not on iOS.",
|
||||
SHOW_DRAWING_OR_MD_IN_HOVER_PREVIEW_NAME: "Render image in hover preview for MD files",
|
||||
SHOW_DRAWING_OR_MD_IN_HOVER_PREVIEW_DESC:
|
||||
"This setting effects files that have the <b>excalidraw-open-md: true</b> frontmatter key.",
|
||||
SHOW_DRAWING_OR_MD_IN_READING_MODE_NAME: "Render image when in markdown reading mode",
|
||||
SHOW_DRAWING_OR_MD_IN_READING_MODE_DESC:
|
||||
"Must close the active excalidraw/markdown file and reopen it for this change to take effect.<br>When you are in markdown reading mode (aka. reading the back side of the drawing), should the Excalidraw drawing be rendered as an image? " +
|
||||
@@ -580,6 +584,13 @@ FILENAME_HEAD: "Filename",
|
||||
"Double files will be exported both if auto-export SVG or PNG (or both) are enabled, as well as when clicking export on a single image.",
|
||||
COMPATIBILITY_HEAD: "Compatibility features",
|
||||
COMPATIBILITY_DESC: "You should only enable these features if you have a strong reason for wanting to work with excalidraw.com files instead of markdown files. Many of the plugin features are not supported on legacy files. Typical usecase would be if you use set your vault up on top of a Visual Studio Code project folder and you have .excalidraw drawings you want to access from Visual Studio Code as well. Another usecase might be using Excalidraw in Logseq and Obsidian in parallel.",
|
||||
DUMMY_TEXT_ELEMENT_LINT_SUPPORT_NAME: "Insert dummy first text element to support linting",
|
||||
DUMMY_TEXT_ELEMENT_LINT_SUPPORT_DESC: "Excalidraw is sensitive to the file structure below <code># Excalidraw Data</code>. Automatic linting of documents can create errors in Excalidraw Data. " +
|
||||
"While I've made some effort to make the data loading resilient to " +
|
||||
"lint changes, this solution is not foolproof.<br><mark>The best is to avoid liniting or otherwise automatically changing Excalidraw documents using different plugins.</mark><br>" +
|
||||
"Use this setting if for good reasons you have decided to ignore my recommendation and configured linting of Excalidraw files.<br> " +
|
||||
"The <code>## Text Elements</code> section is sensitive to empty lines. A common linting approach is to add an empty line after section headings. In case of Excalidraw this will break/change the first text element in your drawing. " +
|
||||
"To overcome this, you can enable this setting. When enabled, Excalidraw will add a dummy element to the beginning of <code>## Text Elements</code> that the linter can safely modify." ,
|
||||
DEBUGMODE_NAME: "Enable debug messages",
|
||||
DEBUGMODE_DESC: "I recommend restarting Obsidian after enabling/disabling this setting. This enable debug messages in the console. This is useful for troubleshooting issues. " +
|
||||
"If you are experiencing problems with the plugin, please enable this setting, reproduce the issue, and include the console log in the issue you raise on <a href='https://github.com/zsviczian/obsidian-excalidraw-plugin/issues'>GitHub</a>",
|
||||
|
||||
@@ -77,6 +77,7 @@ export interface ExcalidrawSettings {
|
||||
defaultPenMode: "never" | "mobile" | "always";
|
||||
penModeCrosshairVisible: boolean;
|
||||
renderImageInMarkdownReadingMode: boolean,
|
||||
renderImageInHoverPreviewForMDNotes: boolean,
|
||||
renderImageInMarkdownToPDF: boolean,
|
||||
allowPinchZoom: boolean;
|
||||
allowWheelZoom: boolean;
|
||||
@@ -122,6 +123,7 @@ export interface ExcalidrawSettings {
|
||||
fadeOutExcalidrawMarkup: boolean;
|
||||
experimentalEnableFourthFont: boolean;
|
||||
experimantalFourthFont: string;
|
||||
addDummyTextElement: boolean;
|
||||
fieldSuggester: boolean;
|
||||
//loadCount: number; //version 1.2 migration counter
|
||||
drawingOpenCount: number;
|
||||
@@ -237,6 +239,7 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
|
||||
defaultPenMode: "never",
|
||||
penModeCrosshairVisible: true,
|
||||
renderImageInMarkdownReadingMode: false,
|
||||
renderImageInHoverPreviewForMDNotes: false,
|
||||
renderImageInMarkdownToPDF: false,
|
||||
allowPinchZoom: false,
|
||||
allowWheelZoom: false,
|
||||
@@ -281,6 +284,7 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
|
||||
fadeOutExcalidrawMarkup: false,
|
||||
experimentalEnableFourthFont: false,
|
||||
experimantalFourthFont: "Virgil",
|
||||
addDummyTextElement: false,
|
||||
fieldSuggester: true,
|
||||
compatibilityMode: false,
|
||||
//loadCount: 0,
|
||||
@@ -1001,6 +1005,18 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
}),
|
||||
);
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("SHOW_DRAWING_OR_MD_IN_HOVER_PREVIEW_NAME"))
|
||||
.setDesc(fragWithHTML(t("SHOW_DRAWING_OR_MD_IN_HOVER_PREVIEW_DESC")))
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings.renderImageInHoverPreviewForMDNotes)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.renderImageInHoverPreviewForMDNotes = value;
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("LEFTHANDED_MODE_NAME"))
|
||||
.setDesc(fragWithHTML(t("LEFTHANDED_MODE_DESC")))
|
||||
@@ -2497,6 +2513,19 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
cls: "excalidraw-setting-h1",
|
||||
});
|
||||
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("DUMMY_TEXT_ELEMENT_LINT_SUPPORT_NAME"))
|
||||
.setDesc(fragWithHTML(t("DUMMY_TEXT_ELEMENT_LINT_SUPPORT_DESC")))
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings.addDummyTextElement)
|
||||
.onChange((value) => {
|
||||
this.plugin.settings.addDummyTextElement = value;
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
new Setting(detailsEl)
|
||||
.setName(t("DEBUGMODE_NAME"))
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
|
||||
import { MAX_IMAGE_SIZE, IMAGE_TYPES, ANIMATED_IMAGE_TYPES } from "src/constants/constants";
|
||||
import { MAX_IMAGE_SIZE, IMAGE_TYPES, ANIMATED_IMAGE_TYPES, MD_EX_SECTIONS } from "src/constants/constants";
|
||||
import { App, TFile } from "obsidian";
|
||||
import { ExcalidrawAutomate } from "src/ExcalidrawAutomate";
|
||||
import { REGEX_LINK, REG_LINKINDEX_HYPERLINK } from "src/ExcalidrawData";
|
||||
import { REGEX_LINK, REG_LINKINDEX_HYPERLINK, getExcalidrawMarkdownHeaderSection } from "src/ExcalidrawData";
|
||||
import ExcalidrawView from "src/ExcalidrawView";
|
||||
import { ExcalidrawElement, ExcalidrawFrameElement } from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
import { getLinkParts } from "./Utils";
|
||||
import { cleanSectionHeading } from "./ObsidianUtils";
|
||||
import { getEA } from "src";
|
||||
import { ExcalidrawImperativeAPI } from "@zsviczian/excalidraw/types/excalidraw/types";
|
||||
import { EmbeddableMDCustomProps } from "src/dialogs/EmbeddableSettings";
|
||||
|
||||
export const insertImageToView = async (
|
||||
ea: ExcalidrawAutomate,
|
||||
@@ -129,4 +133,53 @@ export const getFrameBasedOnFrameNameOrId = (frameName: string, elements: Excali
|
||||
.filter((item:any) => item.id === frameName || item.name === frameName)
|
||||
.map((item:any)=>item.el as ExcalidrawFrameElement);
|
||||
return frames.length === 1 ? frames[0] : null;
|
||||
}
|
||||
|
||||
export const addBackOfTheNoteCard = async (view: ExcalidrawView, title: string, activate: boolean = true, cardBody?: string, embeddableCustomData?: EmbeddableMDCustomProps):Promise<string> => {
|
||||
const data = view.data;
|
||||
const header = getExcalidrawMarkdownHeaderSection(data);
|
||||
const body = data.split(header)[1];
|
||||
const shouldAddHashtag = body && body.startsWith("%%");
|
||||
const hastag = header.match(/#\n+$/m);
|
||||
const shouldRemoveTrailingHashtag = Boolean(hastag);
|
||||
view.data = data.replace(
|
||||
header,
|
||||
(shouldRemoveTrailingHashtag
|
||||
? header.substring(0,header.length-hastag[0].length)
|
||||
: header) +
|
||||
`\n# ${title}\n\n${cardBody ? cardBody+"\n\n" : ""}${
|
||||
shouldAddHashtag || shouldRemoveTrailingHashtag ? "#\n" : ""}`);
|
||||
|
||||
await view.forceSave(true);
|
||||
let watchdog = 0;
|
||||
await sleep(200);
|
||||
let found:string;
|
||||
while (watchdog++ < 10 && !(found=(await view.app.metadataCache.blockCache
|
||||
.getForFile({ isCancelled: () => false },view.file))
|
||||
.blocks.filter((b: any) => b.display && b.node?.type === "heading")
|
||||
.filter((b: any) => !MD_EX_SECTIONS.includes(b.display))
|
||||
.map((b: any) => cleanSectionHeading(b.display))
|
||||
.find((b: any) => b === title))) {
|
||||
await sleep(200);
|
||||
}
|
||||
|
||||
const ea = getEA(view) as ExcalidrawAutomate;
|
||||
const id = ea.addEmbeddable(
|
||||
0,0,400,500,
|
||||
`[[${view.file.path}#${title}]]`,
|
||||
undefined,
|
||||
embeddableCustomData
|
||||
);
|
||||
await ea.addElementsToView(true, false, true);
|
||||
|
||||
const api = view.excalidrawAPI as ExcalidrawImperativeAPI;
|
||||
const el = ea.getViewElements().find(el=>el.id === id);
|
||||
api.selectElements([el]);
|
||||
if(activate) {
|
||||
setTimeout(()=>{
|
||||
api.updateScene({appState: {activeEmbeddable: {element: el, state: "active"}}});
|
||||
if(found) view.getEmbeddableLeafElementById(el.id)?.editNode?.();
|
||||
});
|
||||
}
|
||||
return el.id;
|
||||
}
|
||||
Reference in New Issue
Block a user