This commit is contained in:
zsviczian
2024-02-02 20:34:23 +01:00
parent 5db4f8dd95
commit e8db9cbff6
26 changed files with 584 additions and 280 deletions

View File

@@ -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",

View File

@@ -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",

View File

@@ -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;

View File

@@ -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<string> {
@@ -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<string> {
@@ -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,
} : {},

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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

View File

@@ -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]",
"",
"---",

View File

@@ -162,6 +162,19 @@ export class InsertPDFModal extends Modal {
numPagesMessage.innerHTML = `There are <b>${numPages}</b> 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 <b>${pages.length}</b> pages. ⚠️ This may take a while. ⚠️`;
} else {
importPagesMessage.innerHTML = `You are importing <b>${pages.length}</b> 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 <b>${pages.length}</b> pages. ⚠️ This may take a while. ⚠️`;
} else {
importPagesMessage.innerHTML = `You are importing <b>${pages.length}</b> pages.`;
}
importButtonMessages();
})
.setValue("")
.onChange((value) => rangeOnChange(value))
text.inputEl.style.width = "100%";
})
importPagesMessage = ce.createEl("p", {text: ""});

View File

@@ -17,6 +17,28 @@ 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.0.19":`
<div class="excalidraw-videoWrapper"><div>
<iframe src="https://www.youtube.com/embed/4wp6vLiIdGM" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</div></div>
## 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

View File

@@ -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];
}

View File

@@ -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<string>;',
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<string>;',
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<string>;",
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<string>;",
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: "",
},
{

View File

@@ -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 <code>"
}${FRONTMATTER_KEY_CUSTOM_LINK_BRACKETS}: true/false</code> to the file's frontmatter.`,
}${FRONTMATTER_KEYS["link-brackets"].name}: true/false</code> 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 <code>"
}${FRONTMATTER_KEY_CUSTOM_PREFIX}: "📍 "</code> to the file's frontmatter.`,
}${FRONTMATTER_KEYS["link-prefix"].name}: "📍 "</code> 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 <code>"
}${FRONTMATTER_KEY_CUSTOM_URL_PREFIX}: "🌐 "</code> to the file's frontmatter.`,
}${FRONTMATTER_KEYS["url-prefix"].name}: "🌐 "</code> 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",

View File

@@ -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模式时在内部链接的两侧显示中括号。<br>" +
"您可为某个绘图单独设置此项,方法是在其 frontmatter 中添加形如 <code>"
}${FRONTMATTER_KEY_CUSTOM_LINK_BRACKETS}: true/false</code> 的键值对。`,
}${FRONTMATTER_KEYS["link-brackets"].name}: true/false</code> 的键值对。`,
LINK_PREFIX_NAME: "内部链接的前缀",
LINK_PREFIX_DESC: `${
"文本元素处于预览PREVIEW模式时如果其中包含链接则添加此前缀。<br>" +
"您可为某个绘图单独设置此项,方法是在其 frontmatter 中添加形如 <code>"
}${FRONTMATTER_KEY_CUSTOM_PREFIX}: "📍 "</code> 的键值对。`,
}${FRONTMATTER_KEYS["link-prefix"].name}: "📍 "</code> 的键值对。`,
URL_PREFIX_NAME: "外部链接的前缀",
URL_PREFIX_DESC: `${
"文本元素处于预览PREVIEW模式时如果其中包含外部链接则添加此前缀。<br>" +
"您可为某个绘图单独设置此项,方法是在其 frontmatter 中添加形如 <code>"
}${FRONTMATTER_KEY_CUSTOM_URL_PREFIX}: "🌐 "</code> 的键值对。`,
}${FRONTMATTER_KEYS["url-prefix"].name}: "🌐 "</code> 的键值对。`,
PARSE_TODO_NAME: "待办任务Todo",
PARSE_TODO_DESC: "将文本元素中的 <code>- [ ]</code> 和 <code>- [x]</code> 前缀显示为方框。",
TODO_NAME: "未完成项目",

View File

@@ -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<ExcalidrawImageElement>;
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() {

View File

@@ -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}

View File

@@ -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 = '<svg style="stroke:#ced4da;#212529;color:#ced4da;fill:#ced4da" ';
@@ -505,7 +506,7 @@ export class ToolsPanel extends React.Component<PanelProps, PanelState> {
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<PanelProps, PanelState> {
title={t("INSERT_LINK_TO_ELEMENT")}
action={(e:React.MouseEvent<HTMLButtonElement, 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(

View File

@@ -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
//------------------------------------------------

4
src/types.d.ts vendored
View File

@@ -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 }> };
}
}

View File

@@ -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<ExcalidrawImageElement>;
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<ExcalidrawImageElement>;
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<TFile> => {
const workspace = targetEA.plugin.app.workspace;
const vault = targetEA.plugin.app.vault;
const newImage = targetEA.getElement(imageID) as Mutable<ExcalidrawImageElement>;
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<ExcalidrawFrameElement>;
@@ -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"} : {},
}
});

View File

@@ -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)=>{

View File

@@ -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<string>();
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;
}

View File

@@ -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};
}

View File

@@ -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 [];
}
}
//@ts-ignore
export const getActivePDFPageNumberFromPDFView = (view: View): number => view?.viewer?.child?.pdfViewer?.page;

View File

@@ -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;

View File

@@ -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%;
}