diff --git a/manifest.json b/manifest.json index 092e510..f57ce89 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "obsidian-excalidraw-plugin", "name": "Excalidraw", - "version": "2.0.18", + "version": "2.0.19", "minAppVersion": "1.1.6", "description": "An Obsidian plugin to edit and view Excalidraw drawings", "author": "Zsolt Viczian", diff --git a/package.json b/package.json index aec32e7..1801080 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "author": "", "license": "MIT", "dependencies": { - "@zsviczian/excalidraw": "0.17.1-obsidian-12", + "@zsviczian/excalidraw": "0.17.1-obsidian-13", "chroma-js": "^2.4.2", "clsx": "^2.0.0", "colormaster": "^1.2.1", diff --git a/src/EmbeddedFileLoader.ts b/src/EmbeddedFileLoader.ts index 6549c65..1cc2097 100644 --- a/src/EmbeddedFileLoader.ts +++ b/src/EmbeddedFileLoader.ts @@ -12,13 +12,10 @@ import { import { DEFAULT_MD_EMBED_CSS, fileid, - FRONTMATTER_KEY_BORDERCOLOR, - FRONTMATTER_KEY_FONT, - FRONTMATTER_KEY_FONTCOLOR, - FRONTMATTER_KEY_MD_STYLE, IMAGE_TYPES, nanoid, THEME_FILTER, + FRONTMATTER_KEYS, } from "./constants/constants"; import { createSVG } from "./ExcalidrawAutomate"; import { ExcalidrawData, getTransclusion } from "./ExcalidrawData"; @@ -748,9 +745,9 @@ export class EmbeddedFilesLoader { let fontName = plugin.settings.mdFont; if ( fileCache?.frontmatter && - Boolean(fileCache.frontmatter[FRONTMATTER_KEY_FONT]) + Boolean(fileCache.frontmatter[FRONTMATTER_KEYS["font"].name]) ) { - fontName = fileCache.frontmatter[FRONTMATTER_KEY_FONT]; + fontName = fileCache.frontmatter[FRONTMATTER_KEYS["font"].name]; } switch (fontName) { case "Virgil": @@ -779,12 +776,12 @@ export class EmbeddedFilesLoader { } const fontColor = fileCache?.frontmatter - ? fileCache.frontmatter[FRONTMATTER_KEY_FONTCOLOR] ?? + ? fileCache.frontmatter[FRONTMATTER_KEYS["font-color"].name] ?? plugin.settings.mdFontColor : plugin.settings.mdFontColor; let style = fileCache?.frontmatter - ? fileCache.frontmatter[FRONTMATTER_KEY_MD_STYLE] ?? "" + ? fileCache.frontmatter[FRONTMATTER_KEYS["md-css"].name] ?? "" : ""; let frontmatterCSSisAfile = false; if (style && style != "") { @@ -807,7 +804,7 @@ export class EmbeddedFilesLoader { } const borderColor = fileCache?.frontmatter - ? fileCache.frontmatter[FRONTMATTER_KEY_BORDERCOLOR] ?? + ? fileCache.frontmatter[FRONTMATTER_KEYS["border-color"].name] ?? plugin.settings.mdBorderColor : plugin.settings.mdBorderColor; diff --git a/src/ExcalidrawAutomate.ts b/src/ExcalidrawAutomate.ts index a9e24b9..447da68 100644 --- a/src/ExcalidrawAutomate.ts +++ b/src/ExcalidrawAutomate.ts @@ -89,6 +89,7 @@ import { } from "./utils/AIUtils"; import { EXCALIDRAW_AUTOMATE_INFO, EXCALIDRAW_SCRIPTENGINE_INFO } from "./dialogs/SuggesterInfo"; import { CropImage } from "./utils/CropImage"; +import { has } from "./svgToExcalidraw/attributes"; extendPlugins([ HarmonyPlugin, @@ -588,6 +589,7 @@ export class ExcalidrawAutomate { "excalidraw-linkbutton-opacity"?: number; "excalidraw-autoexport"?: boolean; "excalidraw-mask"?: boolean; + "cssclasses"?: string; }; plaintext?: string; //text to insert above the `# Text Elements` section }): Promise { @@ -706,7 +708,16 @@ export class ExcalidrawAutomate { outString += `${key}: [[${item.file}]]\n`; } } else { - outString += `${key}: ${item.hyperlink}\n`; + const hyperlinkSplit = item.hyperlink.split("#"); + const file = this.plugin.app.vault.getAbstractFileByPath(hyperlinkSplit[0]); + if(file && file instanceof TFile) { + const hasFileRef = hyperlinkSplit.length === 2 + outString += hasFileRef + ? `${key}: [[${file.path}#${hyperlinkSplit[1]}]]\n` + : `${key}: [[${file.path}]]\n`; + } else { + outString += `${key}: ${item.hyperlink}\n`; + } } } }) @@ -1413,7 +1424,7 @@ export class ExcalidrawAutomate { async addImage( topX: number, topY: number, - imageFile: TFile | string, + imageFile: TFile | string, //string may also be an Obsidian filepath with a reference such as folder/path/my.pdf#page=2 scale: boolean = true, //default is true which will scale the image to MAX_IMAGE_SIZE, false will insert image at 100% of its size anchor: boolean = true, //only has effect if scale is false. If anchor is true the image path will include |100%, if false the image will be inserted at 100%, but if resized by the user it won't pop back to 100% the next time Excalidraw is opened. ): Promise { @@ -1861,6 +1872,7 @@ export class ExcalidrawAutomate { this.elementsDict[el.id] = cloneElement(el); if(el.type === "image") { const ef = this.targetView.excalidrawData.getFile(el.fileId); + const imageWithRef = ef && ef.file && ef.linkParts && ef.linkParts.ref; const equation = this.targetView.excalidrawData.getEquation(el.fileId); const sceneFile = sceneFiles?.[el.fileId]; this.imagesDict[el.fileId] = { @@ -1869,9 +1881,9 @@ export class ExcalidrawAutomate { dataURL: sceneFile.dataURL, created: sceneFile.created, ...ef ? { - isHyperLink: ef.isHyperLink, - hyperlink: ef.hyperlink, - file: ef.file, + isHyperLink: ef.isHyperLink || imageWithRef, + hyperlink: imageWithRef ? `${ef.file.path}#${ef.linkParts.ref}` : ef.hyperlink, + file: imageWithRef ? null : ef.file, hasSVGwithBitmap: ef.isSVGwithBitmap, latex: null, } : {}, diff --git a/src/ExcalidrawData.ts b/src/ExcalidrawData.ts index 99af39d..07140eb 100644 --- a/src/ExcalidrawData.ts +++ b/src/ExcalidrawData.ts @@ -8,15 +8,7 @@ import { App, Notice, TFile } from "obsidian"; import { nanoid, - FRONTMATTER_KEY_CUSTOM_PREFIX, - FRONTMATTER_KEY_CUSTOM_LINK_BRACKETS, - FRONTMATTER_KEY_CUSTOM_URL_PREFIX, - FRONTMATTER_KEY_DEFAULT_MODE, fileid, - FRONTMATTER_KEY_LINKBUTTON_OPACITY, - FRONTMATTER_KEY_ONLOAD_SCRIPT, - FRONTMATTER_KEY_AUTOEXPORT, - FRONTMATTER_KEY_EMBEDDABLE_THEME, DEVICE, EMBEDDABLE_THEME_FRONTMATTER_VALUES, getBoundTextMaxWidth, @@ -25,6 +17,7 @@ import { wrapText, ERROR_IFRAME_CONVERSION_CANCELED, JSON_parse, + FRONTMATTER_KEYS, } from "./constants/constants"; import { _measureText } from "./ExcalidrawAutomate"; import ExcalidrawPlugin from "./main"; @@ -1480,9 +1473,9 @@ export class ExcalidrawData { : this.plugin.settings.defaultMode; if ( fileCache?.frontmatter && - fileCache.frontmatter[FRONTMATTER_KEY_DEFAULT_MODE] != null + fileCache.frontmatter[FRONTMATTER_KEYS["default-mode"].name] != null ) { - mode = fileCache.frontmatter[FRONTMATTER_KEY_DEFAULT_MODE]; + mode = fileCache.frontmatter[FRONTMATTER_KEYS["default-mode"].name]; } switch (mode) { @@ -1500,9 +1493,9 @@ export class ExcalidrawData { let opacity = this.plugin.settings.linkOpacity; if ( fileCache?.frontmatter && - fileCache.frontmatter[FRONTMATTER_KEY_LINKBUTTON_OPACITY] != null + fileCache.frontmatter[FRONTMATTER_KEYS["linkbutton-opacity"].name] != null ) { - opacity = fileCache.frontmatter[FRONTMATTER_KEY_LINKBUTTON_OPACITY]; + opacity = fileCache.frontmatter[FRONTMATTER_KEYS["linkbutton-opacity"].name]; } return opacity; } @@ -1511,9 +1504,9 @@ export class ExcalidrawData { const fileCache = this.app.metadataCache.getFileCache(this.file); if ( fileCache?.frontmatter && - fileCache.frontmatter[FRONTMATTER_KEY_ONLOAD_SCRIPT] != null + fileCache.frontmatter[FRONTMATTER_KEYS["onload-script"].name] != null ) { - return fileCache.frontmatter[FRONTMATTER_KEY_ONLOAD_SCRIPT]; + return fileCache.frontmatter[FRONTMATTER_KEYS["onload-script"].name]; } return null; } @@ -1523,9 +1516,9 @@ export class ExcalidrawData { const fileCache = this.app.metadataCache.getFileCache(this.file); if ( fileCache?.frontmatter && - fileCache.frontmatter[FRONTMATTER_KEY_CUSTOM_PREFIX] != null + fileCache.frontmatter[FRONTMATTER_KEYS["link-prefix"].name] != null ) { - this.linkPrefix = fileCache.frontmatter[FRONTMATTER_KEY_CUSTOM_PREFIX]; + this.linkPrefix = fileCache.frontmatter[FRONTMATTER_KEYS["link-prefix"].name]; } else { this.linkPrefix = this.plugin.settings.linkPrefix; } @@ -1537,9 +1530,9 @@ export class ExcalidrawData { const fileCache = this.app.metadataCache.getFileCache(this.file); if ( fileCache?.frontmatter && - fileCache.frontmatter[FRONTMATTER_KEY_CUSTOM_URL_PREFIX] != null + fileCache.frontmatter[FRONTMATTER_KEYS["url-prefix"].name] != null ) { - this.urlPrefix = fileCache.frontmatter[FRONTMATTER_KEY_CUSTOM_URL_PREFIX]; + this.urlPrefix = fileCache.frontmatter[FRONTMATTER_KEYS["url-prefix"].name]; } else { this.urlPrefix = this.plugin.settings.urlPrefix; } @@ -1550,9 +1543,9 @@ export class ExcalidrawData { const fileCache = this.app.metadataCache.getFileCache(this.file); if ( fileCache?.frontmatter && - fileCache.frontmatter[FRONTMATTER_KEY_AUTOEXPORT] != null + fileCache.frontmatter[FRONTMATTER_KEYS["autoexport"].name] != null ) { - switch ((fileCache.frontmatter[FRONTMATTER_KEY_AUTOEXPORT]).toLowerCase()) { + switch ((fileCache.frontmatter[FRONTMATTER_KEYS["autoexport"].name]).toLowerCase()) { case "none": this.autoexportPreference = AutoexportPreference.none; break; case "both": this.autoexportPreference = AutoexportPreference.both; break; case "png": this.autoexportPreference = AutoexportPreference.png; break; @@ -1569,9 +1562,9 @@ export class ExcalidrawData { const fileCache = this.app.metadataCache.getFileCache(this.file); if ( fileCache?.frontmatter && - fileCache.frontmatter[FRONTMATTER_KEY_EMBEDDABLE_THEME] != null + fileCache.frontmatter[FRONTMATTER_KEYS["iframe-theme"].name] != null ) { - this.embeddableTheme = fileCache.frontmatter[FRONTMATTER_KEY_EMBEDDABLE_THEME].toLowerCase(); + this.embeddableTheme = fileCache.frontmatter[FRONTMATTER_KEYS["iframe-theme"].name].toLowerCase(); if (!EMBEDDABLE_THEME_FRONTMATTER_VALUES.includes(this.embeddableTheme)) { this.embeddableTheme = "default"; } @@ -1586,10 +1579,10 @@ export class ExcalidrawData { const fileCache = this.app.metadataCache.getFileCache(this.file); if ( fileCache?.frontmatter && - fileCache.frontmatter[FRONTMATTER_KEY_CUSTOM_LINK_BRACKETS] != null + fileCache.frontmatter[FRONTMATTER_KEYS["link-brackets"].name] != null ) { this.showLinkBrackets = - fileCache.frontmatter[FRONTMATTER_KEY_CUSTOM_LINK_BRACKETS] != false; + fileCache.frontmatter[FRONTMATTER_KEYS["link-brackets"].name] != false; } else { this.showLinkBrackets = this.plugin.settings.showLinkBrackets; } diff --git a/src/ExcalidrawView.ts b/src/ExcalidrawView.ts index 85bea80..95aa7b9 100644 --- a/src/ExcalidrawView.ts +++ b/src/ExcalidrawView.ts @@ -35,16 +35,12 @@ import { ICON_NAME, DISK_ICON_NAME, SCRIPTENGINE_ICON_NAME, - FRONTMATTER_KEY, TEXT_DISPLAY_RAW_ICON_NAME, TEXT_DISPLAY_PARSED_ICON_NAME, IMAGE_TYPES, REG_LINKINDEX_INVALIDCHARS, KEYCODE, - FRONTMATTER_KEY_EXPORT_PADDING, - FRONTMATTER_KEY_EXPORT_PNGSCALE, - FRONTMATTER_KEY_EXPORT_DARK, - FRONTMATTER_KEY_EXPORT_TRANSPARENT, + FRONTMATTER_KEYS, DEVICE, GITHUB_RELEASES, EXPORT_IMG_ICON_NAME, @@ -106,7 +102,7 @@ import { } from "./utils/Utils"; import { getLeaf, getParentOfClass, obsidianPDFQuoteWithRef } from "./utils/ObsidianUtils"; import { splitFolderAndFilename } from "./utils/FileUtils"; -import { ConfirmationPrompt, GenericInputPrompt, NewFileActions, Prompt } from "./dialogs/Prompt"; +import { ConfirmationPrompt, GenericInputPrompt, NewFileActions, Prompt, linkPrompt } from "./dialogs/Prompt"; import { ClipboardData } from "@zsviczian/excalidraw/types/excalidraw/clipboard"; import { updateEquation } from "./LaTeX"; import { @@ -127,7 +123,7 @@ import { anyModifierKeysPressed, emulateKeysForLinkClick, webbrowserDragModifier import { setDynamicStyle } from "./utils/DynamicStyling"; import { InsertPDFModal } from "./dialogs/InsertPDFModal"; import { CustomEmbeddable, renderWebView } from "./customEmbeddable"; -import { getLinkTextFromLink, insertEmbeddableToView, insertImageToView } from "./utils/ExcalidrawViewUtils"; +import { getExcalidrawFileForwardLinks, getLinkTextFromLink, insertEmbeddableToView, insertImageToView, openExternalLink, openTagSearch } from "./utils/ExcalidrawViewUtils"; import { imageCache } from "./utils/ImageCache"; import { CanvasNodeFactory, ObsidianCanvasNode } from "./utils/CanvasNodeFactory"; import { EmbeddableMenu } from "./menu/EmbeddableActionsMenu"; @@ -735,14 +731,14 @@ export default class ExcalidrawView extends TextFileView { if (!this.compatibilityMode) { const keys:[string,string][] = this.exportDialog?.dirty && this.exportDialog?.saveSettings ? [ - [FRONTMATTER_KEY_EXPORT_PADDING, this.exportDialog.padding.toString()], - [FRONTMATTER_KEY_EXPORT_PNGSCALE, this.exportDialog.scale.toString()], - [FRONTMATTER_KEY_EXPORT_DARK, this.exportDialog.theme === "dark" ? "true" : "false"], - [FRONTMATTER_KEY_EXPORT_TRANSPARENT, this.exportDialog.transparent ? "true" : "false"], - [FRONTMATTER_KEY, this.textMode === TextMode.raw ? "raw" : "parsed"] + [FRONTMATTER_KEYS["export-padding"].name, this.exportDialog.padding.toString()], + [FRONTMATTER_KEYS["export-pngscale"].name, this.exportDialog.scale.toString()], + [FRONTMATTER_KEYS["export-dark"].name, this.exportDialog.theme === "dark" ? "true" : "false"], + [FRONTMATTER_KEYS["export-transparent"].name, this.exportDialog.transparent ? "true" : "false"], + [FRONTMATTER_KEYS["plugin"].name, this.textMode === TextMode.raw ? "raw" : "parsed"] ] : [ - [FRONTMATTER_KEY, this.textMode === TextMode.raw ? "raw" : "parsed"] + [FRONTMATTER_KEYS["plugin"].name, this.textMode === TextMode.raw ? "raw" : "parsed"] ]; if(this.exportDialog?.dirty) { @@ -891,84 +887,6 @@ export default class ExcalidrawView extends TextFileView { return false; } - openExternalLink(link:string, element?: ExcalidrawElement):boolean { - if (link.match(/^cmd:\/\/.*/)) { - const cmd = link.replace("cmd://", ""); - //@ts-ignore - this.app.commands.executeCommandById(cmd); - return true; - } - if (link.match(REG_LINKINDEX_HYPERLINK)) { - window.open(link, "_blank"); - return true; - } - return false; - } - - openTagSearch(link:string) { - const tags = link - .matchAll(/#([\p{Letter}\p{Emoji_Presentation}\p{Number}\/_-]+)/gu) - .next(); - if (!tags.value || tags.value.length < 2) { - return; - } - const search = app.workspace.getLeavesOfType("search"); - if (search.length == 0) { - return; - } - //@ts-ignore - search[0].view.setQuery(`tag:${tags.value[1]}`); - this.app.workspace.revealLeaf(search[0]); - - if (this.isFullscreen()) { - this.exitFullscreen(); - } - return; - } - - async linkPrompt(linkText:string):Promise<[file:TFile, linkText:string, subpath: string]> { - const partsArray = REGEX_LINK.getResList(linkText); - let subpath: string = null; - let file: TFile = null; - let parts = partsArray[0]; - if (partsArray.length > 1) { - parts = await ScriptEngine.suggester( - this.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)), - "Select link to open" - ); - if(!parts) return; - } - if(!parts) return; - - if (!parts.value) { - this.openTagSearch(linkText); - return; - } - - linkText = REGEX_LINK.getLink(parts); - if(this.openExternalLink(linkText)) return; - - if (linkText.search("#") > -1) { - const linkParts = getLinkParts(linkText, this.file); - subpath = `#${linkParts.isBlockRef ? "^" : ""}${linkParts.ref}`; - linkText = linkParts.path; - } - if (linkText.match(REG_LINKINDEX_INVALIDCHARS)) { - new Notice(t("FILENAME_INVALID_CHARS"), 4000); - return; - } - file = this.app.metadataCache.getFirstLinkpathDest( - linkText, - this.file.path, - ); - return [file, linkText, subpath]; - } - async linkClick( ev: MouseEvent | null, selectedText: SelectedElementWithLink, @@ -1003,9 +921,9 @@ export default class ExcalidrawView extends TextFileView { const id = selectedText.id??selectedElementWithLink.id; const el = this.excalidrawAPI.getSceneElements().filter((el:ExcalidrawElement)=>el.id === id)[0]; if(this.handleLinkHookCall(el,linkText,ev)) return; - if(this.openExternalLink(linkText)) return; + if(openExternalLink(linkText, this.app)) return; - const result = await this.linkPrompt(linkText); + const result = await linkPrompt(linkText, this.app, this); if(!result) return; [file, linkText, subpath] = result; } @@ -1096,6 +1014,10 @@ export default class ExcalidrawView extends TextFileView { secondOrderLinks += linkPaths.join(" "); } + if(this.plugin.isExcalidrawFile(ef.file)) { + secondOrderLinks += getExcalidrawFileForwardLinks(this.app, ef.file); + } + const linkString = (ef.isHyperLink || ef.isLocalLink ? `[](${ef.hyperlink}) ` : `[[${ef.linkParts.original}]] ` @@ -1105,10 +1027,9 @@ export default class ExcalidrawView extends TextFileView { : imageElement.link : ""); - const result = await this.linkPrompt(linkString + secondOrderLinks); + const result = await linkPrompt(linkString + secondOrderLinks, this.app, this); if(!result) return; [file, linkText, subpath] = result; - } } @@ -1142,7 +1063,7 @@ export default class ExcalidrawView extends TextFileView { try { //@ts-ignore - const drawIO = app.plugins.plugins["drawio-obsidian"]; + const drawIO = this.app.plugins.plugins["drawio-obsidian"]; if(drawIO && drawIO._loaded) { if(file.extension === "svg") { const svg = await this.app.vault.cachedRead(file); @@ -3951,7 +3872,7 @@ export default class ExcalidrawView extends TextFileView { let event = e?.detail?.nativeEvent; if(this.handleLinkHookCall(element,element.link,event)) return; - if(this.openExternalLink(element.link, !isSHIFT(event) && !isWinCTRLorMacCMD(event) && !isWinMETAorMacCTRL(event) && !isWinALTorMacOPT(event) ? element : undefined)) return; + if(openExternalLink(element.link, this.app, !isSHIFT(event) && !isWinCTRLorMacCMD(event) && !isWinMETAorMacCTRL(event) && !isWinALTorMacOPT(event) ? element : undefined)) return; //if element is type text and element has multiple links, then submit the element text to linkClick to trigger link suggester if(element.type === "text") { @@ -5021,7 +4942,6 @@ export default class ExcalidrawView extends TextFileView { return embeddable.leaf.view.editor; } } - app.workspace.openLinkText return null; } } diff --git a/src/MarkdownPostProcessor.ts b/src/MarkdownPostProcessor.ts index e277661..dd5377a 100644 --- a/src/MarkdownPostProcessor.ts +++ b/src/MarkdownPostProcessor.ts @@ -1,6 +1,7 @@ import { MarkdownPostProcessorContext, MetadataCache, + PaneType, TFile, Vault, } from "obsidian"; @@ -26,6 +27,8 @@ import { linkClickModifierType } from "./utils/ModifierkeyHelper"; import { ImageKey, imageCache } from "./utils/ImageCache"; import { FILENAMEPARTS, PreviewImageType } from "./utils/UtilTypes"; import { CustomMutationObserver, isDebugMode } from "./utils/DebugHelper"; +import { getExcalidrawFileForwardLinks } from "./utils/ExcalidrawViewUtils"; +import { linkPrompt } from "./dialogs/Prompt"; interface imgElementAttributes { file?: TFile; @@ -356,12 +359,31 @@ const createImgElement = async ( if (src) { const srcParts = src.match(/([^#]*)(.*)/); if(!srcParts) return; - plugin.openDrawing( - vault.getAbstractFileByPath(srcParts[1]) as TFile, - linkClickModifierType(ev), - true, - srcParts[2], - ); + const f = vault.getAbstractFileByPath(srcParts[1]) as TFile; + const linkModifier = linkClickModifierType(ev); + if (plugin.isExcalidrawFile(f) && isMaskFile(plugin, f)) { + (async () => { + const linkString = `[[${f.path}${srcParts[2]?"#"+srcParts[2]:""}]] ${getExcalidrawFileForwardLinks(plugin.app, f)}`; + const result = await linkPrompt(linkString, plugin.app); + if(!result) return; + const [file, linkText, subpath] = result; + if(plugin.isExcalidrawFile(file)) { + plugin.openDrawing(file,linkModifier, true, subpath); + return; + } + let paneType: boolean | PaneType = false; + switch(linkModifier) { + case "active-pane": paneType = false; break; + case "new-pane": paneType = "split"; break; + case "popout-window": paneType = "window"; break; + case "new-tab": paneType = "tab"; break; + case "md-properties": paneType = "tab"; break; + } + plugin.app.workspace.openLinkText(linkText,"",paneType,subpath ? {eState: {subpath}} : {}); + })() + return; + } + plugin.openDrawing(f,linkModifier,true,srcParts[2]); } //.ctrlKey||ev.metaKey); }; //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1003 diff --git a/src/constants/constants.ts b/src/constants/constants.ts index a8c51f0..90ba897 100644 --- a/src/constants/constants.ts +++ b/src/constants/constants.ts @@ -143,26 +143,29 @@ export const IMAGE_TYPES = ["jpeg", "jpg", "png", "gif", "svg", "webp", "bmp", " export const ANIMATED_IMAGE_TYPES = ["gif", "webp", "apng", "svg"]; export const EXPORT_TYPES = ["svg", "dark.svg", "light.svg", "png", "dark.png", "light.png"]; export const MAX_IMAGE_SIZE = 500; -export const FRONTMATTER_KEY = "excalidraw-plugin"; -export const FRONTMATTER_KEY_EXPORT_TRANSPARENT = - "excalidraw-export-transparent"; -export const FRONTMATTER_KEY_MASK = "excalidraw-mask"; -export const FRONTMATTER_KEY_EXPORT_DARK = "excalidraw-export-dark"; -export const FRONTMATTER_KEY_EXPORT_SVGPADDING = "excalidraw-export-svgpadding"; //depricated -export const FRONTMATTER_KEY_EXPORT_PADDING = "excalidraw-export-padding"; -export const FRONTMATTER_KEY_EXPORT_PNGSCALE = "excalidraw-export-pngscale"; -export const FRONTMATTER_KEY_CUSTOM_PREFIX = "excalidraw-link-prefix"; -export const FRONTMATTER_KEY_CUSTOM_URL_PREFIX = "excalidraw-url-prefix"; -export const FRONTMATTER_KEY_CUSTOM_LINK_BRACKETS = "excalidraw-link-brackets"; -export const FRONTMATTER_KEY_ONLOAD_SCRIPT = "excalidraw-onload-script"; -export const FRONTMATTER_KEY_LINKBUTTON_OPACITY = "excalidraw-linkbutton-opacity"; -export const FRONTMATTER_KEY_DEFAULT_MODE = "excalidraw-default-mode"; -export const FRONTMATTER_KEY_FONT = "excalidraw-font"; -export const FRONTMATTER_KEY_FONTCOLOR = "excalidraw-font-color"; -export const FRONTMATTER_KEY_BORDERCOLOR = "excalidraw-border-color"; -export const FRONTMATTER_KEY_MD_STYLE = "excalidraw-css"; -export const FRONTMATTER_KEY_AUTOEXPORT = "excalidraw-autoexport" -export const FRONTMATTER_KEY_EMBEDDABLE_THEME = "excalidraw-iframe-theme"; + +export const FRONTMATTER_KEYS:{[key:string]: {name: string, type: string, depricated?:boolean}} = { + "plugin": {name: "excalidraw-plugin", type: "text"}, + "export-transparent": {name: "excalidraw-export-transparent", type: "checkbox"}, + "mask": {name: "excalidraw-mask", type: "checkbox"}, + "export-dark": {name: "excalidraw-export-dark", type: "checkbox"}, + "export-svgpadding": {name: "excalidraw-export-svgpadding", type: "number", depricated: true}, + "export-padding": {name: "excalidraw-export-padding", type: "number"}, + "export-pngscale": {name: "excalidraw-export-pngscale", type: "number"}, + "link-prefix": {name: "excalidraw-link-prefix", type: "text"}, + "url-prefix": {name: "excalidraw-url-prefix", type: "text"}, + "link-brackets": {name: "excalidraw-link-brackets", type: "checkbox"}, + "onload-script": {name: "excalidraw-onload-script", type: "text"}, + "linkbutton-opacity": {name: "excalidraw-linkbutton-opacity", type: "number"}, + "default-mode": {name: "excalidraw-default-mode", type: "text"}, + "font": {name: "excalidraw-font", type: "text"}, + "font-color": {name: "excalidraw-font-color", type: "text"}, + "border-color": {name: "excalidraw-border-color", type: "text"}, + "md-css": {name: "excalidraw-css", type: "text"}, + "autoexport": {name: "excalidraw-autoexport", type: "checkbox"}, + "iframe-theme": {name: "excalidraw-iframe-theme", type: "text"}, +}; + export const EMBEDDABLE_THEME_FRONTMATTER_VALUES = ["light", "dark", "auto", "dafault"]; export const VIEW_TYPE_EXCALIDRAW = "excalidraw"; export const ICON_NAME = "excalidraw-icon"; @@ -176,7 +179,7 @@ export const DARK_BLANK_DRAWING = export const FRONTMATTER = [ "---", "", - `${FRONTMATTER_KEY}: parsed`, + `${FRONTMATTER_KEYS["plugin"].name}: parsed`, "tags: [excalidraw]", "", "---", diff --git a/src/dialogs/InsertPDFModal.ts b/src/dialogs/InsertPDFModal.ts index bfba1d0..4b4054e 100644 --- a/src/dialogs/InsertPDFModal.ts +++ b/src/dialogs/InsertPDFModal.ts @@ -162,6 +162,19 @@ export class InsertPDFModal extends Modal { numPagesMessage.innerHTML = `There are ${numPages} pages in the selected document.`; } + let pageRangesTextComponent: TextComponent + let importPagesMessage: HTMLParagraphElement; + + const rangeOnChange = (value:string) => { + const pages = this.createPageListFromString(value); + if(pages.length > 15) { + importPagesMessage.innerHTML = `You are importing ${pages.length} pages. ⚠️ This may take a while. ⚠️`; + } else { + importPagesMessage.innerHTML = `You are importing ${pages.length} pages.`; + } + importButtonMessages(); + } + const setFile = async (file: TFile) => { if(this.pdfDoc) await this.pdfDoc.destroy(); this.pdfDoc = null; @@ -171,6 +184,8 @@ export class InsertPDFModal extends Modal { this.pdfFile = file; if(this.pdfDoc) { numPages = this.pdfDoc.numPages; + pageRangesTextComponent.setValue(`1-${numPages}`); + rangeOnChange(`1-${numPages}`); importButtonMessages(); numPagesMessages(); this.getPageDimensions(this.pdfDoc); @@ -190,23 +205,14 @@ export class InsertPDFModal extends Modal { numPagesMessage = ce.createEl("p", {text: ""}); numPagesMessages(); - let importPagesMessage: HTMLParagraphElement; - let pageRangesTextComponent: TextComponent new Setting(ce) .setName("Pages to import") + .setDesc("e.g.: 1,3-5,7,9-10") .addText(text => { pageRangesTextComponent = text; text - .setPlaceholder("e.g.: 1,3-5,7,9-10") - .onChange((value) => { - const pages = this.createPageListFromString(value); - if(pages.length > 15) { - importPagesMessage.innerHTML = `You are importing ${pages.length} pages. ⚠️ This may take a while. ⚠️`; - } else { - importPagesMessage.innerHTML = `You are importing ${pages.length} pages.`; - } - importButtonMessages(); - }) + .setValue("") + .onChange((value) => rangeOnChange(value)) text.inputEl.style.width = "100%"; }) importPagesMessage = ce.createEl("p", {text: ""}); diff --git a/src/dialogs/Messages.ts b/src/dialogs/Messages.ts index 3541917..38a6e94 100644 --- a/src/dialogs/Messages.ts +++ b/src/dialogs/Messages.ts @@ -17,6 +17,28 @@ I develop this plugin as a hobby, spending my free time doing this. If you find
`, +"2.0.19":` +
+ +
+ +## Fixed +- When updating Excalidraw, some open drawings weren't automatically reopening. I hope I got this fixed (note this change will only have an effect when you receive the update after this). +- In dark mode, the frame header is challenging to see when modified [#1568](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1568). + +## New +- Crop PDF pages: + - Available in Excalidraw, Markdown Notes, and on the Canvas. + - Crop the active page from the embedded PDF viewer and insert the cropped image into the current view, both in Excalidraw and on Canvas. +- New Command Palette Action: "Insert active PDF page as image." This action is functional in Excalidraw. If an embedded Obsidian-PDF-viewer is present, executing this command will insert the active page as an image into the Excalidraw scene. +- Two new settings introduced: + - "Basic" section allows setting the folder for crop files. + - "Saving/filename" section enables setting the prefix for crop files. +- PDF import now defaults to importing all pages. +- Rounded corners now available for images. +- Second-order links now encompass forward links from embedded Excalidraw Files. +- Clicking a cropped file in a markdown note or on Canvas will prompt to open the original file, not just the cropper. +`, "2.0.18":` ## New diff --git a/src/dialogs/Prompt.ts b/src/dialogs/Prompt.ts index 526f31f..c58f12d 100644 --- a/src/dialogs/Prompt.ts +++ b/src/dialogs/Prompt.ts @@ -11,14 +11,17 @@ import { } from "obsidian"; import ExcalidrawView from "../ExcalidrawView"; import ExcalidrawPlugin from "../main"; -import { escapeRegExp, sleep } from "../utils/Utils"; +import { escapeRegExp, getLinkParts, sleep } from "../utils/Utils"; import { getLeaf } from "../utils/ObsidianUtils"; import { checkAndCreateFolder, splitFolderAndFilename } from "src/utils/FileUtils"; import { KeyEvent, isWinCTRLorMacCMD } from "src/utils/ModifierkeyHelper"; import { t } from "src/lang/helpers"; import { ExcalidrawElement, getEA } from "src"; import { ExcalidrawAutomate } from "src/ExcalidrawAutomate"; -import { MAX_IMAGE_SIZE } from "src/constants/constants"; +import { MAX_IMAGE_SIZE, REG_LINKINDEX_INVALIDCHARS } from "src/constants/constants"; +import { REGEX_LINK } from "src/ExcalidrawData"; +import { ScriptEngine } from "src/Scripts"; +import { openExternalLink, openTagSearch } from "src/utils/ExcalidrawViewUtils"; export type ButtonDefinition = { caption: string; tooltip?:string; action: Function }; @@ -693,3 +696,46 @@ export class ConfirmationPrompt extends Modal { } } } + +export const linkPrompt = async (linkText:string, app: App, view?: ExcalidrawView):Promise<[file:TFile, linkText:string, subpath: string]> => { + const partsArray = REGEX_LINK.getResList(linkText); + let subpath: string = null; + let file: TFile = null; + let parts = partsArray[0]; + if (partsArray.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)), + "Select link to open" + ); + if(!parts) return; + } + if(!parts) return; + + if (!parts.value) { + openTagSearch(linkText, app); + return; + } + + linkText = REGEX_LINK.getLink(parts); + if(openExternalLink(linkText, app)) return; + + if (linkText.search("#") > -1) { + const linkParts = getLinkParts(linkText, view ? view.file : undefined); + subpath = `#${linkParts.isBlockRef ? "^" : ""}${linkParts.ref}`; + linkText = linkParts.path; + } + if (linkText.match(REG_LINKINDEX_INVALIDCHARS)) { + new Notice(t("FILENAME_INVALID_CHARS"), 4000); + return; + } + file = app.metadataCache.getFirstLinkpathDest( + linkText, + view ? view.file.path : "", + ); + return [file, linkText, subpath]; +} \ No newline at end of file diff --git a/src/dialogs/SuggesterInfo.ts b/src/dialogs/SuggesterInfo.ts index 4cc54b5..6341bb8 100644 --- a/src/dialogs/SuggesterInfo.ts +++ b/src/dialogs/SuggesterInfo.ts @@ -186,8 +186,23 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [ }, { field: "create", - code: 'async create(params?: {filename?: string, foldername?: string, templatePath?: string, onNewPane?: boolean, silent?: boolean, frontmatterKeys?: { "excalidraw-plugin"?: "raw" | "parsed", "excalidraw-link-prefix"?: string, "excalidraw-link-brackets"?: boolean, "excalidraw-url-prefix"?: string,},}): Promise;', - desc: "Create a drawing and save it to filename.\nIf filename is null: default filename as defined in Excalidraw settings.\nIf folder is null: default folder as defined in Excalidraw settings\nReturns the path to the created file", + code: 'async create(params?: {filename?: string, foldername?: string, templatePath?: string, onNewPane?: boolean, silent?: boolean, frontmatterKeys?: {},}): Promise;', + desc: "Create a drawing and save it to filename.\nIf filename is null: default filename as defined in Excalidraw settings.\nIf folder is null: default folder as defined in Excalidraw settings\nReturns the path to the created file.\n" + + 'frontmatterKeys: {\n' + + ' "excalidraw-plugin"?: "raw" | "parsed";\n' + + ' "excalidraw-link-prefix"?: string;\n' + + ' "excalidraw-link-brackets"?: boolean;\n' + + ' "excalidraw-url-prefix"?: string;\n' + + ' "excalidraw-export-transparent"?: boolean;\n' + + ' "excalidraw-export-dark"?: boolean;\n' + + ' "excalidraw-export-padding"?: number;\n' + + ' "excalidraw-export-pngscale"?: number;\n' + + ' "excalidraw-default-mode"?: "view" | "zen";\n' + + ' "excalidraw-onload-script"?: string;\n' + + ' "excalidraw-linkbutton-opacity"?: number;\n' + + ' "excalidraw-autoexport"?: boolean;\n' + + ' "excalidraw-mask"?: boolean;\n' + + ' "cssclasses"?: string;\n}', after: "", }, { @@ -264,8 +279,8 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [ }, { field: "addImage", - code: "async addImage(topX: number, topY: number, imageFile: TFile, scale?: boolean, anchor?: boolean): Promise;", - desc: "set scale to false if you want to embed the image at 100% of its original size. Default is true which will insert a scaled image. anchor will only be evaluated if scale is false. anchor true will add |100% to the end of the filename, resulting in an image that will always pop back to 100% when the source file is updated or when the Excalidraw file is reopened. ", + code: "async addImage(topX: number, topY: number, imageFile: TFile|string, scale?: boolean, anchor?: boolean): Promise;", + desc: "imageFile may be a TFile or a string that contains a hyperlink. imageFile may also be an obsidian filepath including a reference eg.: 'path/my.pdf#page=3'\nSet scale to false if you want to embed the image at 100% of its original size. Default is true which will insert a scaled image.\nanchor will only be evaluated if scale is false. anchor true will add |100% to the end of the filename, resulting in an image that will always pop back to 100% when the source file is updated or when the Excalidraw file is reopened.", after: "", }, { diff --git a/src/lang/locale/en.ts b/src/lang/locale/en.ts index 113578e..5caa24b 100644 --- a/src/lang/locale/en.ts +++ b/src/lang/locale/en.ts @@ -1,8 +1,6 @@ import { DEVICE, - FRONTMATTER_KEY_CUSTOM_LINK_BRACKETS, - FRONTMATTER_KEY_CUSTOM_PREFIX, - FRONTMATTER_KEY_CUSTOM_URL_PREFIX, + FRONTMATTER_KEYS, } from "src/constants/constants"; import { labelALT, labelCTRL, labelMETA, labelSHIFT } from "src/utils/ModifierkeyHelper"; @@ -78,6 +76,7 @@ export default { TRAY_MODE: "Toggle property-panel tray-mode", SEARCH: "Search for text in drawing", CROP_IMAGE: "Crop and mask image", + 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", 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", @@ -126,6 +125,13 @@ export default { FOLDER_NAME: "Excalidraw folder", FOLDER_DESC: "Default location for new drawings. If empty, drawings will be created in the Vault root.", + CROP_PREFIX_NAME: "Crop file prefix", + CROP_PREFIX_DESC: + "The first part of the filename for new drawings created when cropping an image. " + + "If empty the default 'cropped_' will be used.", + CROP_FOLDER_NAME: "Crop file folder", + CROP_FOLDER_DESC: + "Default location for new drawings created when cropping an image. If empty, drawings will be created following the Vault attachments settings.", FOLDER_EMBED_NAME: "Use Excalidraw folder when embedding a drawing into the active document", FOLDER_EMBED_DESC: @@ -314,17 +320,17 @@ FILENAME_HEAD: "Filename", LINK_BRACKETS_DESC: `${ "In PREVIEW mode, when parsing Text Elements, place brackets around links. " + "You can override this setting for a specific drawing by adding " - }${FRONTMATTER_KEY_CUSTOM_LINK_BRACKETS}: true/false to the file's frontmatter.`, + }${FRONTMATTER_KEYS["link-brackets"].name}: true/false to the file's frontmatter.`, LINK_PREFIX_NAME: "Link prefix", LINK_PREFIX_DESC: `${ "In PREVIEW mode, if the Text Element contains a link, precede the text with these characters. " + "You can override this setting for a specific drawing by adding " - }${FRONTMATTER_KEY_CUSTOM_PREFIX}: "📍 " to the file's frontmatter.`, + }${FRONTMATTER_KEYS["link-prefix"].name}: "📍 " to the file's frontmatter.`, URL_PREFIX_NAME: "URL prefix", URL_PREFIX_DESC: `${ "In PREVIEW mode, if the Text Element contains a URL link, precede the text with these characters. " + "You can override this setting for a specific drawing by adding " - }${FRONTMATTER_KEY_CUSTOM_URL_PREFIX}: "🌐 " to the file's frontmatter.`, + }${FRONTMATTER_KEYS["url-prefix"].name}: "🌐 " to the file's frontmatter.`, PARSE_TODO_NAME: "Parse todo", PARSE_TODO_DESC: "Convert '- [ ] ' and '- [x] ' to checkbox and tick in the box.", TODO_NAME: "Open TODO icon", diff --git a/src/lang/locale/zh-cn.ts b/src/lang/locale/zh-cn.ts index 5116c20..02efc07 100644 --- a/src/lang/locale/zh-cn.ts +++ b/src/lang/locale/zh-cn.ts @@ -1,8 +1,6 @@ import { DEVICE, - FRONTMATTER_KEY_CUSTOM_LINK_BRACKETS, - FRONTMATTER_KEY_CUSTOM_PREFIX, - FRONTMATTER_KEY_CUSTOM_URL_PREFIX, + FRONTMATTER_KEYS, } from "src/constants/constants"; import { labelALT, labelCTRL, labelMETA, labelSHIFT } from "src/utils/ModifierkeyHelper"; @@ -313,17 +311,17 @@ FILENAME_HEAD: "文件名", LINK_BRACKETS_DESC: `${ "文本元素处于预览(PREVIEW)模式时,在内部链接的两侧显示中括号。
" + "您可为某个绘图单独设置此项,方法是在其 frontmatter 中添加形如 " - }${FRONTMATTER_KEY_CUSTOM_LINK_BRACKETS}: true/false 的键值对。`, + }${FRONTMATTER_KEYS["link-brackets"].name}: true/false 的键值对。`, LINK_PREFIX_NAME: "内部链接的前缀", LINK_PREFIX_DESC: `${ "文本元素处于预览(PREVIEW)模式时,如果其中包含链接,则添加此前缀。
" + "您可为某个绘图单独设置此项,方法是在其 frontmatter 中添加形如 " - }${FRONTMATTER_KEY_CUSTOM_PREFIX}: "📍 " 的键值对。`, + }${FRONTMATTER_KEYS["link-prefix"].name}: "📍 " 的键值对。`, URL_PREFIX_NAME: "外部链接的前缀", URL_PREFIX_DESC: `${ "文本元素处于预览(PREVIEW)模式时,如果其中包含外部链接,则添加此前缀。
" + "您可为某个绘图单独设置此项,方法是在其 frontmatter 中添加形如 " - }${FRONTMATTER_KEY_CUSTOM_URL_PREFIX}: "🌐 " 的键值对。`, + }${FRONTMATTER_KEYS["url-prefix"].name}: "🌐 " 的键值对。`, PARSE_TODO_NAME: "待办任务(Todo)", PARSE_TODO_DESC: "将文本元素中的 - [ ]- [x] 前缀显示为方框。", TODO_NAME: "未完成项目", diff --git a/src/main.ts b/src/main.ts index c2fad3a..b167a09 100644 --- a/src/main.ts +++ b/src/main.ts @@ -29,7 +29,7 @@ import { SCRIPTENGINE_ICON, SCRIPTENGINE_ICON_NAME, RERENDER_EVENT, - FRONTMATTER_KEY, + FRONTMATTER_KEYS, FRONTMATTER, JSON_parse, nanoid, @@ -78,6 +78,7 @@ import { t } from "./lang/helpers"; import { checkAndCreateFolder, download, + getCropFileNameAndFolder, getDrawingFilename, getEmbedFilename, getIMGFilename, @@ -97,7 +98,7 @@ import { isCallerFromTemplaterPlugin, decompress, } from "./utils/Utils"; -import { extractSVGPNGFileName, getAttachmentsFolderAndFilePath, getNewOrAdjacentLeaf, getParentOfClass, isObsidianThemeDark } from "./utils/ObsidianUtils"; +import { extractSVGPNGFileName, getActivePDFPageNumberFromPDFView, getAttachmentsFolderAndFilePath, getNewOrAdjacentLeaf, getParentOfClass, isObsidianThemeDark } from "./utils/ObsidianUtils"; import { ExcalidrawElement, ExcalidrawEmbeddableElement, ExcalidrawImageElement, ExcalidrawTextElement, FileId } from "@zsviczian/excalidraw/types/excalidraw/element/types"; import { ScriptEngine } from "./Scripts"; import { @@ -126,7 +127,7 @@ import { getEA } from "src"; import { ExcalidrawImperativeAPI } from "@zsviczian/excalidraw/types/excalidraw/types"; import { Mutable } from "@zsviczian/excalidraw/types/excalidraw/utility-types"; import { CustomMutationObserver, durationTreshold, isDebugMode } from "./utils/DebugHelper"; -import { carveOutImage, createImageCropperFile, CROPPED_PREFIX } from "./utils/CarveOut"; +import { carveOutImage, carveOutPDF, createImageCropperFile, CROPPED_PREFIX } from "./utils/CarveOut"; import { ExcalidrawConfig } from "./utils/ExcalidrawConfig"; declare const EXCALIDRAW_PACKAGES:string; @@ -248,7 +249,7 @@ export default class ExcalidrawPlugin extends Plugin { // Register the modified event super.registerEvent(event); } - + async onload() { addIcon(ICON_NAME, EXCALIDRAW_ICON); addIcon(SCRIPTENGINE_ICON_NAME, SCRIPTENGINE_ICON); @@ -281,6 +282,7 @@ export default class ExcalidrawPlugin extends Plugin { this.runStartupScript(); this.initializeFonts(); this.registerEditorSuggest(new FieldSuggester(this)); + this.setPropertyTypes(); //inspiration taken from kanban: //https://github.com/mgmeyers/obsidian-kanban/blob/44118e25661bff9ebfe54f71ae33805dc88ffa53/src/main.ts#L267 @@ -312,6 +314,17 @@ export default class ExcalidrawPlugin extends Plugin { this.taskbone = new Taskbone(this); } + private setPropertyTypes() { + const app = this.app; + this.app.workspace.onLayoutReady(() => { + Object.keys(FRONTMATTER_KEYS).forEach((key) => { + if(FRONTMATTER_KEYS[key].depricated === true) return; + const {name, type} = FRONTMATTER_KEYS[key]; + app.metadataTypeManager.setType(name,type); + }); + }); + } + public initializeFonts() { this.app.workspace.onLayoutReady(async () => { const font = await getFontDataURL( @@ -1541,6 +1554,44 @@ export default class ExcalidrawPlugin extends Plugin { } }) + this.addCommand({ + id: "insert-active-pdfpage", + name: t("INSERT_ACTIVE_PDF_PAGE_AS_IMAGE"), + checkCallback: (checking:boolean) => { + const excalidrawView = this.app.workspace.getActiveViewOfType(ExcalidrawView); + if(!excalidrawView) return false; + const embeddables = excalidrawView.getViewSelectedElements().filter(el=>el.type==="embeddable"); + if(embeddables.length !== 1) { + if(checking) return false; + new Notice("Select a single PDF embeddable and try again"); + return false; + } + const isPDF = excalidrawView.getEmbeddableLeafElementById(embeddables[0].id)?.leaf?.view?.getViewType() === "pdf" + if(!isPDF) return false; + const page = getActivePDFPageNumberFromPDFView(excalidrawView.getEmbeddableLeafElementById(embeddables[0].id)?.leaf?.view); + if(!page) return false; + if(checking) return true; + + const embeddableEl = embeddables[0] as ExcalidrawEmbeddableElement; + const ea = new ExcalidrawAutomate(this,excalidrawView); + //@ts-ignore + const pdfFile: TFile = excalidrawView.getEmbeddableLeafElementById(embeddableEl.id)?.leaf?.view?.file; + (async () => { + const imgID = await ea.addImage(embeddableEl.x + embeddableEl.width + 10, embeddableEl.y, `${pdfFile?.path}#page=${page}`, false, false); + const imgEl = ea.getElement(imgID) as Mutable; + const imageAspectRatio = imgEl.width / imgEl.height; + if(imageAspectRatio > 1) { + imgEl.width = embeddableEl.width; + imgEl.height = embeddableEl.width / imageAspectRatio; + } else { + imgEl.height = embeddableEl.height; + imgEl.width = embeddableEl.height * imageAspectRatio; + } + ea.addElementsToView(false, true, true); + })() + } + }) + this.addCommand({ id: "crop-image", name: t("CROP_IMAGE"), @@ -1553,57 +1604,81 @@ export default class ExcalidrawPlugin extends Plugin { if(excalidrawView) { if(!excalidrawView.excalidrawAPI) return false; - const els = excalidrawView.getViewSelectedElements().filter(el=>el.type==="image"); - if(els.length !== 1) { + const embeddables = excalidrawView.getViewSelectedElements().filter(el=>el.type==="embeddable"); + const imageEls = excalidrawView.getViewSelectedElements().filter(el=>el.type==="image"); + const isPDF = (imageEls.length === 0 && embeddables.length === 1 && excalidrawView.getEmbeddableLeafElementById(embeddables[0].id)?.leaf?.view?.getViewType() === "pdf") + const isImage = (imageEls.length === 1 && embeddables.length === 0) + + if(!isPDF && !isImage) { if(checking) return false; - new Notice("Select a single image element and try again"); + new Notice("Select a single image element or single PDF embeddable and try again"); return false; } - const el = els[0] as ExcalidrawImageElement; - if(el.type !== "image") return false; - + + //@ts-ignore + const page = isPDF ? getActivePDFPageNumberFromPDFView(excalidrawView.getEmbeddableLeafElementById(embeddables[0].id)?.leaf?.view) : undefined; + if(isPDF && !page) { + return false; + } + if(checking) return true; + if(isPDF) { + const embeddableEl = embeddables[0] as ExcalidrawEmbeddableElement; + const ea = new ExcalidrawAutomate(this,excalidrawView); + //@ts-ignore + const pdfFile: TFile = excalidrawView.getEmbeddableLeafElementById(embeddableEl.id)?.leaf?.view?.file; + carveOutPDF(ea, embeddableEl, `${pdfFile?.path}#page=${page}`, pdfFile); + return; + } + + const imageEl = imageEls[0] as ExcalidrawImageElement; (async () => { - let ef = excalidrawView.excalidrawData.getFile(el.fileId); + let ef = excalidrawView.excalidrawData.getFile(imageEl.fileId); if(!ef) { await excalidrawView.save(); await sleep(500); - ef = excalidrawView.excalidrawData.getFile(el.fileId); + ef = excalidrawView.excalidrawData.getFile(imageEl.fileId); if(!ef) { new Notice("Select a single image element and try again"); return false; } } const ea = new ExcalidrawAutomate(this,excalidrawView); - carveOutImage(ea, el); + carveOutImage(ea, imageEl); })(); } - const carveout = async (isFile: boolean, sourceFile: TFile, imageFile: TFile, imageURL: string, replacer: Function) => { + const carveout = async (isFile: boolean, sourceFile: TFile, imageFile: TFile, imageURL: string, replacer: Function, ref?: string) => { const ea = getEA() as ExcalidrawAutomate; - const imageID = await ea.addImage(0 , 0, isFile ? imageFile : imageURL, false, false); + const imageID = await ea.addImage( + 0, 0, + isFile + ? ((isFile && imageFile.extension === "pdf" && ref) ? `${imageFile.path}#${ref}` : imageFile) + : imageURL, + false, false + ); if(!imageID) { new Notice(`Can't load image\n\n${imageURL}`); return; } - let fname = ""; + let fnBase = ""; let imageLink = ""; if(isFile) { - fname = CROPPED_PREFIX + imageFile.basename + ".md"; - imageLink = `[[${imageFile.path}]]`; + fnBase = imageFile.basename; + imageLink = ref + ? `[[${imageFile.path}#${ref}]]` + : `[[${imageFile.path}]]`; } else { imageLink = imageURL; const imagename = imageURL.match(/^.*\/([^?]*)\??.*$/)?.[1]; - fname = CROPPED_PREFIX + imagename.substring(0,imagename.lastIndexOf(".")) + ".md"; + fnBase = imagename.substring(0,imagename.lastIndexOf(".")); } - const { folderpath } = isFile - ? splitFolderAndFilename(imageFile.path) - : {folderpath: ((await getAttachmentsFolderAndFilePath(this.app, sourceFile.path, fname)).folder)}; - const newFile = await createImageCropperFile(ea,imageID,imageLink,folderpath,fname); + const {folderpath, filename} = await getCropFileNameAndFolder(this,sourceFile.path,fnBase) + const newFile = await createImageCropperFile(ea,imageID,imageLink,folderpath,filename); if(!newFile) return; const link = this.app.metadataCache.fileToLinktext(newFile,sourceFile.path, true); replacer(link, newFile); @@ -1617,24 +1692,29 @@ export default class ExcalidrawPlugin extends Plugin { if(selectedNodes.length !== 1) return false; const node = selectedNodes[0]; let extension = ""; + let isExcalidraw = false; if(node.file) { extension = node.file.extension; + isExcalidraw = this.isExcalidrawFile(node.file); } if(node.url) { extension = getURLImageExtension(node.url); } - if(!IMAGE_TYPES.contains(extension)) return false; + const page = extension === "pdf" ? getActivePDFPageNumberFromPDFView(node?.child) : undefined; + if(!page && !IMAGE_TYPES.contains(extension) && !isExcalidraw) return false; if(checking) return true; const replacer = (link:string, file: TFile) => { if(node.file) { - node.setFile(file); + (node.file.extension === "pdf") + ? node.canvas.createFileNode({pos:{x:node.x + node.width + 10,y: node.y}, file}) + : node.setFile(file); } if(node.url) { node.canvas.createFileNode({pos:{x:node.x + 20,y: node.y+20}, file}); } } - carveout(Boolean(node.file), canvasView.file, node.file, node.url, replacer); + carveout(Boolean(node.file), canvasView.file, node.file, node.url, replacer, page ? `page=${page}` : undefined); } if (markdownView) { @@ -1644,8 +1724,14 @@ export default class ExcalidrawPlugin extends Plugin { const parts = REGEX_LINK.getResList(line); if(parts.length === 0) return false; const imgpath = REGEX_LINK.getLink(parts[0]); - const imageFile = this.app.metadataCache.getFirstLinkpathDest(imgpath, markdownView.file.path); + const imagePathParts = imgpath.split("#"); + const hasRef = imagePathParts.length === 2; + const imageFile = this.app.metadataCache.getFirstLinkpathDest( + hasRef ? imagePathParts[0] : imgpath, + markdownView.file.path + ); const isFile = (imageFile && imageFile instanceof TFile); + const isExcalidraw = isFile ? this.isExcalidrawFile(imageFile) : false; let imagepath = isFile ? imageFile.path : ""; let extension = isFile ? imageFile.extension : ""; if(imgpath.match(/^https?|file/)) { @@ -1653,13 +1739,21 @@ export default class ExcalidrawPlugin extends Plugin { extension = getURLImageExtension(imgpath); } if(imagepath === "") return false; - if(!IMAGE_TYPES.contains(extension)) return false; + if(extension !== "pdf" && !IMAGE_TYPES.contains(extension) && !isExcalidraw) return false; if(checking) return true; + const ref = imagePathParts[1]; const replacer = (link:string) => { const lineparts = line.split(parts[0].value[0]) - editor.setLine(cursor.line,lineparts[0] + getLink(this ,{embed: true, path:link}) +lineparts[1]); + const pdfLink = isFile && ref + ? "\n" + getLink(this ,{ + embed: false, + alias: `${imageFile.basename}, ${ref.replace("="," ")}`, + path:`${imageFile.path}#${ref}` + }) + : ""; + editor.setLine(cursor.line,lineparts[0] + getLink(this ,{embed: true, path:link}) + pdfLink + lineparts[1]); } - carveout(isFile, markdownView.file, imageFile, imagepath, replacer); + carveout(isFile, markdownView.file, imageFile, imagepath, replacer, ref); } } }) @@ -1973,7 +2067,7 @@ export default class ExcalidrawPlugin extends Plugin { const leaf = view.leaf; if (!view.file) return; const cache = this.app.metadataCache.getFileCache(file); - if (!cache?.frontmatter || !cache.frontmatter[FRONTMATTER_KEY]) return; + if (!cache?.frontmatter || !cache.frontmatter[FRONTMATTER_KEYS["plugin"].name]) return; menu.addItem(item => item .setTitle(t("OPEN_AS_EXCALIDRAW")) @@ -1993,7 +2087,7 @@ export default class ExcalidrawPlugin extends Plugin { if (!leaf || !(leaf.view instanceof MarkdownView)) return; if (!(file instanceof TFile)) return; const cache = this.app.metadataCache.getFileCache(file); - if (!cache?.frontmatter || !cache.frontmatter[FRONTMATTER_KEY]) return; + if (!cache?.frontmatter || !cache.frontmatter[FRONTMATTER_KEYS["plugin"].name]) return; menu.addItem(item => { item @@ -2047,7 +2141,7 @@ export default class ExcalidrawPlugin extends Plugin { // Then check for the excalidraw frontMatterKey const cache = app.metadataCache.getCache(state.state.file); - if (cache?.frontmatter && cache.frontmatter[FRONTMATTER_KEY]) { + if (cache?.frontmatter && cache.frontmatter[FRONTMATTER_KEYS["plugin"].name]) { // If we have it, force the view type to excalidraw const newState = { ...state, @@ -2405,7 +2499,7 @@ export default class ExcalidrawPlugin extends Plugin { metaCache.getCachedFiles().forEach((filename: string) => { const fm = metaCache.getCache(filename)?.frontmatter; if ( - (fm && typeof fm[FRONTMATTER_KEY] !== "undefined") || + (fm && typeof fm[FRONTMATTER_KEYS["plugin"].name] !== "undefined") || filename.match(/\.excalidraw$/) ) { self.updateFileCache( @@ -2546,7 +2640,7 @@ export default class ExcalidrawPlugin extends Plugin { frontmatter?: FrontMatterCache, deleted: boolean = false, ) { - if (frontmatter && typeof frontmatter[FRONTMATTER_KEY] !== "undefined") { + if (frontmatter && typeof frontmatter[FRONTMATTER_KEYS["plugin"].name] !== "undefined") { this.excalidrawFiles.add(file); return; } @@ -2558,6 +2652,15 @@ export default class ExcalidrawPlugin extends Plugin { } onunload() { + const excalidrawLeaves = this.app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW); + excalidrawLeaves.forEach(async (leaf) => { + const ev: ExcalidrawView = leaf.view as ExcalidrawView; + console.log(ev.file.name, ev.semaphores.dirty); + await this.setMarkdownView(leaf); + //@ts-ignore + console.log(leaf?.view?.file); + }); + document.body.removeChild(this.textMeasureDiv); this.stylesManager.unload(); this.removeFonts(); @@ -2583,12 +2686,6 @@ export default class ExcalidrawPlugin extends Plugin { if (this.fileExplorerObserver) { this.fileExplorerObserver.disconnect(); } - const excalidrawLeaves = - this.app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW); - excalidrawLeaves.forEach((leaf) => { - this.setMarkdownView(leaf); - }); - Object.values(this.packageMap).forEach((p:Packages)=>{ delete p.excalidrawLib; delete p.reactDOM; @@ -2890,10 +2987,12 @@ export default class ExcalidrawPlugin extends Plugin { public async setMarkdownView(leaf: WorkspaceLeaf) { const state = leaf.view.getState(); - await leaf.setViewState({ + //Note v2.0.19: I have absolutely no idea why I thought this is necessary. Removing this. + //This was added in 1.4.2 but there is no hint in Release notes why. + /*await leaf.setViewState({ type: VIEW_TYPE_EXCALIDRAW, state: { file: null }, - }); + });*/ await leaf.setViewState( { @@ -2919,7 +3018,7 @@ export default class ExcalidrawPlugin extends Plugin { return true; } const fileCache = f ? this.app.metadataCache.getFileCache(f) : null; - return !!fileCache?.frontmatter && !!fileCache.frontmatter[FRONTMATTER_KEY]; + return !!fileCache?.frontmatter && !!fileCache.frontmatter[FRONTMATTER_KEYS["plugin"].name]; } public async exportLibrary() { diff --git a/src/menu/EmbeddableActionsMenu.tsx b/src/menu/EmbeddableActionsMenu.tsx index 8e00ae3..83039ef 100644 --- a/src/menu/EmbeddableActionsMenu.tsx +++ b/src/menu/EmbeddableActionsMenu.tsx @@ -12,6 +12,7 @@ import { REGEX_LINK, REG_LINKINDEX_HYPERLINK } from "src/ExcalidrawData"; import { processLinkText, useDefaultExcalidrawFrame } from "src/utils/CustomEmbeddableUtils"; import { cleanSectionHeading } from "src/utils/ObsidianUtils"; import { EmbeddableSettings } from "src/dialogs/EmbeddableSettings"; +import { openExternalLink } from "src/utils/ExcalidrawViewUtils"; export class EmbeddableMenu { @@ -257,10 +258,11 @@ export class EmbeddableMenu { key={"Open"} title={t("OPEN_IN_BROWSER")} action={() => { - view.openExternalLink( + openExternalLink( !iframe.src.startsWith("https://www.youtube.com") && !iframe.src.startsWith("https://player.vimeo.com") - ? iframe.src - : element.link + ? iframe.src + : element.link, + view.app ); }} icon={ICONS.Globe} diff --git a/src/menu/ToolsPanel.tsx b/src/menu/ToolsPanel.tsx index d726a4c..aa84166 100644 --- a/src/menu/ToolsPanel.tsx +++ b/src/menu/ToolsPanel.tsx @@ -14,6 +14,7 @@ import { ExcalidrawImperativeAPI } from "@zsviczian/excalidraw/types/excalidraw/ import { isWinALTorMacOPT, isWinCTRLorMacCMD, isSHIFT } from "src/utils/ModifierkeyHelper"; import { InsertPDFModal } from "src/dialogs/InsertPDFModal"; import { ExportDialog } from "src/dialogs/ExportDialog"; +import { openExternalLink } from "src/utils/ExcalidrawViewUtils"; declare const PLUGIN_VERSION:string; const dark = ' { title={t("INSERT_LATEX")} action={(e) => { if(isWinALTorMacOPT(e)) { - this.props.view.openExternalLink("https://youtu.be/r08wk-58DPk"); + openExternalLink("https://youtu.be/r08wk-58DPk", this.props.view.app); return; } this.props.centerPointer(); @@ -532,7 +533,7 @@ export class ToolsPanel extends React.Component { title={t("INSERT_LINK_TO_ELEMENT")} action={(e:React.MouseEvent) => { if(isWinALTorMacOPT(e)) { - this.props.view.openExternalLink("https://youtu.be/yZQoJg2RCKI"); + openExternalLink("https://youtu.be/yZQoJg2RCKI", this.props.view.app); return; } this.props.view.copyLinkToSelectedElementToClipboard( diff --git a/src/settings.ts b/src/settings.ts index 56b84ab..f3d9a09 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -33,9 +33,11 @@ import { EmbeddalbeMDFileCustomDataSettingsComponent } from "./dialogs/Embeddabl import { startupScript } from "./constants/starutpscript"; import { ModifierKeySet, ModifierSetType } from "./utils/ModifierkeyHelper"; import { ModifierKeySettingsComponent } from "./dialogs/ModifierKeySettings"; +import { CROPPED_PREFIX } from "./utils/CarveOut"; export interface ExcalidrawSettings { folder: string; + cropFolder: string; embedUseExcalidrawFolder: boolean; templateFilePath: string; scriptFolderPath: string; @@ -49,6 +51,7 @@ export interface ExcalidrawSettings { drawingFilnameEmbedPostfix: string; drawingFilenameDateTime: string; useExcalidrawExtension: boolean; + cropPrefix: string; displaySVGInPreview: boolean; //No longer used since 1.9.13 previewImageType: PreviewImageType; //Introduced with 1.9.13 allowImageCache: boolean; @@ -177,6 +180,7 @@ declare const PLUGIN_VERSION:string; export const DEFAULT_SETTINGS: ExcalidrawSettings = { folder: "Excalidraw", + cropFolder: "", embedUseExcalidrawFolder: false, templateFilePath: "Excalidraw/Template.excalidraw", scriptFolderPath: "Excalidraw/Scripts", @@ -190,6 +194,7 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = { drawingFilnameEmbedPostfix: " ", drawingFilenameDateTime: "YYYY-MM-DD HH.mm.ss", useExcalidrawExtension: true, + cropPrefix: CROPPED_PREFIX, displaySVGInPreview: undefined, previewImageType: undefined, allowImageCache: true, @@ -552,6 +557,19 @@ export class ExcalidrawSettingTab extends PluginSettingTab { }), ); + new Setting(detailsEl) + .setName(t("CROP_FOLDER_NAME")) + .setDesc(fragWithHTML(t("CROP_FOLDER_DESC"))) + .addText((text) => + text + .setPlaceholder("e.g.: Excalidraw/Cropped") + .setValue(this.plugin.settings.cropFolder) + .onChange(async (value) => { + this.plugin.settings.cropFolder = value; + this.applySettingsUpdate(); + }), + ); + new Setting(detailsEl) .setName(t("TEMPLATE_NAME")) .setDesc(fragWithHTML(t("TEMPLATE_DESC"))) @@ -749,6 +767,22 @@ export class ExcalidrawSettingTab extends PluginSettingTab { ); + new Setting(detailsEl) + .setName(t("CROP_PREFIX_NAME")) + .setDesc(fragWithHTML(t("CROP_PREFIX_DESC"))) + .addText((text) => + text + .setPlaceholder("e.g.: Cropped_ ") + .setValue(this.plugin.settings.cropPrefix) + .onChange(async (value) => { + this.plugin.settings.cropPrefix = value.replaceAll( + /[<>:"/\\|?*]/g, + "_", + ); + text.setValue(this.plugin.settings.cropPrefix); + this.applySettingsUpdate(); + }), + ); //------------------------------------------------ // AI Settings //------------------------------------------------ diff --git a/src/types.d.ts b/src/types.d.ts index 0b9cdf7..e3195b1 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -35,6 +35,9 @@ declare module "obsidian" { internalPlugins: any; isMobile(): boolean; getObsidianUrl(file:TFile): string; + metadataTypeManager: { + setType(name:string, type:string): void; + }; } interface Keymap { getRootScope(): Scope; @@ -60,5 +63,6 @@ declare module "obsidian" { } interface MetadataCache { getBacklinksForFile(file: TFile): any; + getLinks(): { [id: string]: Array<{ link: string; displayText: string; original: string; position: any }> }; } } \ No newline at end of file diff --git a/src/utils/CarveOut.ts b/src/utils/CarveOut.ts index ceb104a..36d7c44 100644 --- a/src/utils/CarveOut.ts +++ b/src/utils/CarveOut.ts @@ -1,8 +1,8 @@ -import { ExcalidrawFrameElement, ExcalidrawImageElement } from "@zsviczian/excalidraw/types/excalidraw/element/types"; +import { ExcalidrawEmbeddableElement, ExcalidrawFrameElement, ExcalidrawImageElement } from "@zsviczian/excalidraw/types/excalidraw/element/types"; import { Mutable } from "@zsviczian/excalidraw/types/excalidraw/utility-types"; import { getEA } from "src"; import { ExcalidrawAutomate } from "src/ExcalidrawAutomate"; -import { splitFolderAndFilename } from "./FileUtils"; +import { getCropFileNameAndFolder, splitFolderAndFilename } from "./FileUtils"; import { Notice, TFile } from "obsidian"; import ExcalidrawView from "src/ExcalidrawView"; import { ExcalidrawImperativeAPI } from "@zsviczian/excalidraw/types/excalidraw/types"; @@ -34,20 +34,20 @@ export const carveOutImage = async (sourceEA: ExcalidrawAutomate, viewImageEl: E let imageLink = ""; let fname = ""; if(ef.file) { - fname = CROPPED_PREFIX + ef.file.basename; - imageLink = `[[${ef.file.path}]]`; + fname = ef.file.basename; + const ref = ef.linkParts?.ref ? `#${ef.linkParts.ref}` : ``; + imageLink = `[[${ef.file.path}${ref}]]`; } else { const imagename = ef.hyperlink?.match(/^.*\/([^?]*)\??.*$/)?.[1]; imageLink = ef.hyperlink; fname = viewImageEl - ? CROPPED_PREFIX + imagename.substring(0,imagename.lastIndexOf(".")) - : CROPPED_PREFIX + "_image"; + ? imagename.substring(0,imagename.lastIndexOf(".")) + : "_image"; } - const attachmentPath = await sourceEA.getAttachmentFilepath(fname + ".md"); - const {folderpath: foldername, filename} = splitFolderAndFilename(attachmentPath); + const {folderpath, filename} = await getCropFileNameAndFolder(sourceEA.plugin,sourceEA.targetView.file.path,fname); - const file = await createImageCropperFile(targetEA, newImage.id, imageLink, foldername, filename); + const file = await createImageCropperFile(targetEA, newImage.id, imageLink, folderpath, filename); if(!file) return; //console.log(await app.vault.read(file)); @@ -65,13 +65,57 @@ export const carveOutImage = async (sourceEA: ExcalidrawAutomate, viewImageEl: E sourceEA.addElementsToView(false, true, true); } +export const carveOutPDF = async (sourceEA: ExcalidrawAutomate, embeddableEl: ExcalidrawEmbeddableElement, pdfPathWithPage: string, pdfFile: TFile) => { + if(!embeddableEl || !pdfPathWithPage || !sourceEA?.targetView) return; + + const targetEA = getEA(sourceEA.targetView) as ExcalidrawAutomate; + + const {height, width} = embeddableEl; + + if(!height || !width || height === 0 || width === 0) return; + + const imageId = await targetEA.addImage(0,0, pdfPathWithPage); + const newImage = targetEA.getElement(imageId) as Mutable; + newImage.x = 0; + newImage.y = 0; + newImage.width = width; + newImage.height = height; + const angle = embeddableEl.angle; + + const fname = pdfFile.basename; + const imageLink = `[[${pdfPathWithPage}]]`; + + const {folderpath, filename} = await getCropFileNameAndFolder(sourceEA.plugin,sourceEA.targetView.file.path,fname); + + const file = await createImageCropperFile(targetEA, newImage.id, imageLink, folderpath, filename); + if(!file) return; + + //console.log(await app.vault.read(file)); + sourceEA.clear(); + const replacingImageID = await sourceEA.addImage(embeddableEl.x + embeddableEl.width + 10, embeddableEl.y, file, true); + const replacingImage = sourceEA.getElement(replacingImageID) as Mutable; + const imageAspectRatio = replacingImage.width / replacingImage.height; + if(imageAspectRatio > 1) { + replacingImage.width = embeddableEl.width; + replacingImage.height = replacingImage.width / imageAspectRatio; + } else { + replacingImage.height = embeddableEl.height; + replacingImage.width = replacingImage.height * imageAspectRatio; + } + replacingImage.angle = angle; + sourceEA.addElementsToView(false, true, true); +} + + export const createImageCropperFile = async (targetEA: ExcalidrawAutomate, imageID: string, imageLink:string, foldername: string, filename: string): Promise => { - const workspace = targetEA.plugin.app.workspace; const vault = targetEA.plugin.app.vault; const newImage = targetEA.getElement(imageID) as Mutable; const { width, height } = newImage; + const isPDF = imageLink.match(/\[\[([^#]*)#.*]]/)?.[1]?.endsWith(".pdf"); + newImage.opacity = 100; newImage.locked = true; + newImage.link = imageLink; const frameID = targetEA.addFrame(0,0,width,height,"Adjust frame to crop image. Add elements for mask: White shows, Black hides."); const frame = targetEA.getElement(frameID) as Mutable; @@ -87,7 +131,7 @@ export const createImageCropperFile = async (targetEA: ExcalidrawAutomate, image targetEA.style.roughness = 0; targetEA.style.roundness = null; targetEA.canvas.theme = "light"; - targetEA.canvas.viewBackgroundColor = "#3d3d3d"; + targetEA.canvas.viewBackgroundColor = isPDF ? "#5d5d5d" : "#3d3d3d"; const templateFile = app.vault.getAbstractFileByPath(targetEA.plugin.settings.templateFilePath); if(templateFile && templateFile instanceof TFile) { @@ -107,6 +151,7 @@ export const createImageCropperFile = async (targetEA: ExcalidrawAutomate, image "excalidraw-export-dark": false, "excalidraw-export-padding": 0, "excalidraw-export-transparent": true, + ...isPDF ? {"cssclasses": "excalidraw-cropped-pdfpage"} : {}, } }); diff --git a/src/utils/DynamicStyling.ts b/src/utils/DynamicStyling.ts index 2f45be3..ae18e5f 100644 --- a/src/utils/DynamicStyling.ts +++ b/src/utils/DynamicStyling.ts @@ -98,7 +98,7 @@ export const setDynamicStyle = ( [`--color-gray-50`]: str(text), //frame [`--color-surface-highlight`]: str(gray1()), //[`--color-gray-30`]: str(gray1), - [`--color-gray-80`]: str(isDark?text.lighterBy(15):text.darkerBy(15)), //frame + [`--color-gray-80`]: str(isDark?text.darkerBy(40):text.lighterBy(40)), //frame [`--sidebar-border-color`]: str(gray1()), [`--color-primary-light`]: str(accent().lighterBy(step)), [`--button-hover-bg`]: str(gray1()), @@ -130,7 +130,7 @@ export const setDynamicStyle = ( const frameColor = { stroke: str(isDark?gray2().lighterBy(15):gray2().darkerBy(15)), fill: str((isDark?gray2().lighterBy(30):gray2().darkerBy(30)).alphaTo(0.2)), - nameColor: str(isDark?gray2().lighterBy(40):gray2().darkerBy(40)), + nameColor: str(isDark?gray2().lighterBy(50):gray2().darkerBy(50)), } const scene = api.getSceneElements(); scene.filter(el=>el.type==="frame").forEach((e:ExcalidrawFrameElement)=>{ diff --git a/src/utils/ExcalidrawViewUtils.ts b/src/utils/ExcalidrawViewUtils.ts index b4c804b..0e23d86 100644 --- a/src/utils/ExcalidrawViewUtils.ts +++ b/src/utils/ExcalidrawViewUtils.ts @@ -1,8 +1,11 @@ import { MAX_IMAGE_SIZE, IMAGE_TYPES, ANIMATED_IMAGE_TYPES } from "src/constants/constants"; -import { TFile } from "obsidian"; +import { App, TFile } from "obsidian"; import { ExcalidrawAutomate } from "src/ExcalidrawAutomate"; import { REGEX_LINK, REG_LINKINDEX_HYPERLINK } from "src/ExcalidrawData"; +import ExcalidrawView from "src/ExcalidrawView"; +import { ExcalidrawElement } from "@zsviczian/excalidraw/types/excalidraw/element/types"; +import { getLinkParts } from "./Utils"; export const insertImageToView = async ( ea: ExcalidrawAutomate, @@ -61,4 +64,56 @@ export const getLinkTextFromLink = (text: string): string => { if (linktext.match(REG_LINKINDEX_HYPERLINK)) return; return linktext; +} + +export const 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) { + return; + } + const search = app.workspace.getLeavesOfType("search"); + if (search.length == 0) { + return; + } + //@ts-ignore + search[0].view.setQuery(`tag:${tags.value[1]}`); + app.workspace.revealLeaf(search[0]); + + if (view && view.isFullscreen()) { + view.exitFullscreen(); + } + return; +} + +export const openExternalLink = (link:string, app: App, element?: ExcalidrawElement):boolean => { + if (link.match(/^cmd:\/\/.*/)) { + const cmd = link.replace("cmd://", ""); + //@ts-ignore + app.commands.executeCommandById(cmd); + return true; + } + if (link.match(REG_LINKINDEX_HYPERLINK)) { + window.open(link, "_blank"); + return true; + } + return false; +} + +export const getExcalidrawFileForwardLinks = (app: App, excalidrawFile: TFile):string => { + let secondOrderLinks = ""; + const forwardLinks = app.metadataCache.getLinks()[excalidrawFile.path]; + if(forwardLinks && forwardLinks.length > 0) { + const linkset = new Set(); + forwardLinks.forEach(link => { + const linkparts = getLinkParts(link.link); + const f = app.metadataCache.getFirstLinkpathDest(linkparts.path, excalidrawFile.path); + if(f && f.path !== excalidrawFile.path) { + linkset.add(`[[${f.path}${linkparts.ref?"#"+linkparts.ref:""}|Second Order Link: ${f.basename}]]`); + } + }); + secondOrderLinks = [...linkset].join(" "); + } + return secondOrderLinks; } \ No newline at end of file diff --git a/src/utils/FileUtils.ts b/src/utils/FileUtils.ts index fb3b71c..347d102 100644 --- a/src/utils/FileUtils.ts +++ b/src/utils/FileUtils.ts @@ -5,6 +5,9 @@ import { IMAGE_MIME_TYPES, MimeType } from "src/EmbeddedFileLoader"; import { ExcalidrawSettings } from "src/settings"; import { errorlog, getDataURL } from "./Utils"; import ExcalidrawPlugin from "src/main"; +import ExcalidrawView from "src/ExcalidrawView"; +import { CROPPED_PREFIX } from "./CarveOut"; +import { getAttachmentsFolderAndFilePath } from "./ObsidianUtils"; /** * Splits a full path including a folderpath and a filename into separate folderpath and filename components @@ -370,4 +373,17 @@ export const getLink = ( return plugin.settings.embedWikiLink ? `${embed ? "!" : ""}[[${path}${alias ? `|${alias}` : ""}]]` : `${embed ? "!" : ""}[${alias ?? ""}](${encodeURI(path)})` +} + +export const getCropFileNameAndFolder = async (plugin: ExcalidrawPlugin, hostPath: string, baseNewFileName: string):Promise<{folderpath: string, filename: string}> => { + let prefix = plugin.settings.cropPrefix; + if(!prefix || prefix.trim() === "") prefix = CROPPED_PREFIX; + const filename = prefix + baseNewFileName + ".md"; + if(!plugin.settings.cropFolder || plugin.settings.cropFolder.trim() === "") { + const folderpath = (await getAttachmentsFolderAndFilePath(plugin.app, hostPath, filename)).folder; + return {folderpath, filename}; + } + const folderpath = normalizePath(plugin.settings.cropFolder); + await checkAndCreateFolder(folderpath); + return {folderpath, filename}; } \ No newline at end of file diff --git a/src/utils/ObsidianUtils.ts b/src/utils/ObsidianUtils.ts index 8d7d550..43ae9ff 100644 --- a/src/utils/ObsidianUtils.ts +++ b/src/utils/ObsidianUtils.ts @@ -1,6 +1,6 @@ import { App, - normalizePath, parseFrontMatterEntry, TFile, Workspace, WorkspaceLeaf, WorkspaceSplit + normalizePath, parseFrontMatterEntry, TFile, View, Workspace, WorkspaceLeaf, WorkspaceSplit } from "obsidian"; import ExcalidrawPlugin from "../main"; import { checkAndCreateFolder, splitFolderAndFilename } from "./FileUtils"; @@ -254,4 +254,7 @@ export const getFileCSSClasses = ( return []; } return []; -} \ No newline at end of file +} + +//@ts-ignore +export const getActivePDFPageNumberFromPDFView = (view: View): number => view?.viewer?.child?.pdfViewer?.page; \ No newline at end of file diff --git a/src/utils/Utils.ts b/src/utils/Utils.ts index 35bfe6a..71e6a2f 100644 --- a/src/utils/Utils.ts +++ b/src/utils/Utils.ts @@ -13,15 +13,10 @@ import { VIRGIL_FONT, } from "src/constants/constFonts"; import { - FRONTMATTER_KEY_EXPORT_DARK, - FRONTMATTER_KEY_EXPORT_TRANSPARENT, - FRONTMATTER_KEY_EXPORT_SVGPADDING, - FRONTMATTER_KEY_EXPORT_PNGSCALE, - FRONTMATTER_KEY_EXPORT_PADDING, exportToSvg, exportToBlob, IMAGE_TYPES, - FRONTMATTER_KEY_MASK + FRONTMATTER_KEYS, } from "../constants/constants"; import ExcalidrawPlugin from "../main"; import { ExcalidrawElement } from "@zsviczian/excalidraw/types/excalidraw/element/types"; @@ -548,9 +543,9 @@ export const isMaskFile = ( const fileCache = plugin.app.metadataCache.getFileCache(file); if ( fileCache?.frontmatter && - fileCache.frontmatter[FRONTMATTER_KEY_MASK] != null + fileCache.frontmatter[FRONTMATTER_KEYS["mask"].name] != null ) { - return Boolean(fileCache.frontmatter[FRONTMATTER_KEY_MASK]); + return Boolean(fileCache.frontmatter[FRONTMATTER_KEYS["mask"].name]); } } return false; @@ -564,7 +559,7 @@ export const hasExportTheme = ( const fileCache = plugin.app.metadataCache.getFileCache(file); if ( fileCache?.frontmatter && - fileCache.frontmatter[FRONTMATTER_KEY_EXPORT_DARK] != null + fileCache.frontmatter[FRONTMATTER_KEYS["export-dark"].name] != null ) { return true; } @@ -581,9 +576,9 @@ export const getExportTheme = ( const fileCache = plugin.app.metadataCache.getFileCache(file); if ( fileCache?.frontmatter && - fileCache.frontmatter[FRONTMATTER_KEY_EXPORT_DARK] != null + fileCache.frontmatter[FRONTMATTER_KEYS["export-dark"].name] != null ) { - return fileCache.frontmatter[FRONTMATTER_KEY_EXPORT_DARK] + return fileCache.frontmatter[FRONTMATTER_KEYS["export-dark"].name] ? "dark" : "light"; } @@ -599,7 +594,7 @@ export const hasExportBackground = ( const fileCache = plugin.app.metadataCache.getFileCache(file); if ( fileCache?.frontmatter && - fileCache.frontmatter[FRONTMATTER_KEY_EXPORT_TRANSPARENT] != null + fileCache.frontmatter[FRONTMATTER_KEYS["export-transparent"].name] != null ) { return true; } @@ -615,9 +610,9 @@ export const getWithBackground = ( const fileCache = plugin.app.metadataCache.getFileCache(file); if ( fileCache?.frontmatter && - fileCache.frontmatter[FRONTMATTER_KEY_EXPORT_TRANSPARENT] != null + fileCache.frontmatter[FRONTMATTER_KEYS["export-transparent"].name] != null ) { - return !fileCache.frontmatter[FRONTMATTER_KEY_EXPORT_TRANSPARENT]; + return !fileCache.frontmatter[FRONTMATTER_KEYS["export-transparent"].name]; } } return plugin.settings.exportWithBackground; @@ -631,9 +626,9 @@ export const getExportPadding = ( const fileCache = plugin.app.metadataCache.getFileCache(file); if(!fileCache?.frontmatter) return plugin.settings.exportPaddingSVG; - if (fileCache.frontmatter[FRONTMATTER_KEY_EXPORT_PADDING] != null) { + if (fileCache.frontmatter[FRONTMATTER_KEYS["export-padding"].name] != null) { const val = parseInt( - fileCache.frontmatter[FRONTMATTER_KEY_EXPORT_PADDING], + fileCache.frontmatter[FRONTMATTER_KEYS["export-padding"].name], ); if (!isNaN(val)) { return val; @@ -641,9 +636,9 @@ export const getExportPadding = ( } //depricated. Retained for backward compatibility - if (fileCache.frontmatter[FRONTMATTER_KEY_EXPORT_SVGPADDING] != null) { + if (fileCache.frontmatter[FRONTMATTER_KEYS["export-svgpadding"].name] != null) { const val = parseInt( - fileCache.frontmatter[FRONTMATTER_KEY_EXPORT_SVGPADDING], + fileCache.frontmatter[FRONTMATTER_KEYS["export-svgpadding"].name], ); if (!isNaN(val)) { return val; @@ -659,10 +654,10 @@ export const getPNGScale = (plugin: ExcalidrawPlugin, file: TFile): number => { const fileCache = plugin.app.metadataCache.getFileCache(file); if ( fileCache?.frontmatter && - fileCache.frontmatter[FRONTMATTER_KEY_EXPORT_PNGSCALE] != null + fileCache.frontmatter[FRONTMATTER_KEYS["export-pngscale"].name] != null ) { const val = parseFloat( - fileCache.frontmatter[FRONTMATTER_KEY_EXPORT_PNGSCALE], + fileCache.frontmatter[FRONTMATTER_KEYS["export-pngscale"].name], ); if (!isNaN(val) && val > 0) { return val; diff --git a/styles.css b/styles.css index 2cd70b0..3ba9101 100644 --- a/styles.css +++ b/styles.css @@ -546,4 +546,14 @@ hr.excalidraw-setting-hr { .excalidraw__embeddable-container .canvas-node.is-selected.is-themed .canvas-node-container, .excalidraw__embeddable-container .canvas-node.is-focused.is-themed .canvas-node-container { border-color: var(--canvas-color); +} + +img.excalidraw-cropped-pdfpage, +.excalidraw-cropped-pdfpage svg { + background-color: white; +} + +.excalidraw .pdf-toolbar, +.excalidraw .pdf-container { + width: 100%; } \ No newline at end of file