mirror of
https://github.com/zsviczian/obsidian-excalidraw-plugin.git
synced 2025-08-06 05:46:28 +00:00
Support for area, group, frame references in image embeds
This commit is contained in:
@@ -39,12 +39,13 @@ import {
|
||||
svgToBase64,
|
||||
isMaskFile,
|
||||
embedFontsInSVG,
|
||||
getEmbeddedFilenameParts,
|
||||
} from "./utils/Utils";
|
||||
import { ValueOf } from "./types";
|
||||
import { getMermaidImageElements, getMermaidText, shouldRenderMermaid } from "./utils/MermaidUtils";
|
||||
import { mermaidToExcalidraw } from "src/constants/constants";
|
||||
import { ImageKey, imageCache } from "./utils/ImageCache";
|
||||
import { PreviewImageType } from "./utils/UtilTypes";
|
||||
import { FILENAMEPARTS, PreviewImageType } from "./utils/UtilTypes";
|
||||
|
||||
//An ugly workaround for the following situation.
|
||||
//File A is a markdown file that has an embedded Excalidraw file B
|
||||
@@ -145,8 +146,6 @@ const replaceSVGColors = (svg: SVGSVGElement | string, colorMap: ColorMap | null
|
||||
return svg;
|
||||
}
|
||||
|
||||
|
||||
|
||||
export class EmbeddedFile {
|
||||
public file: TFile = null;
|
||||
public isSVGwithBitmap: boolean = false;
|
||||
@@ -157,6 +156,7 @@ export class EmbeddedFile {
|
||||
public mimeType: MimeType = "application/octet-stream";
|
||||
public size: Size = { height: 0, width: 0 };
|
||||
public linkParts: LinkParts;
|
||||
public filenameparts: FILENAMEPARTS
|
||||
private hostPath: string;
|
||||
public attemptCounter: number = 0;
|
||||
public isHyperLink: boolean = false;
|
||||
@@ -204,7 +204,7 @@ export class EmbeddedFile {
|
||||
if (!this.linkParts.height) {
|
||||
this.linkParts.height = this.plugin.settings.mdSVGmaxHeight;
|
||||
}
|
||||
this.file = app.metadataCache.getFirstLinkpathDest(
|
||||
this.file = this.plugin.app.metadataCache.getFirstLinkpathDest(
|
||||
this.linkParts.path,
|
||||
hostPath,
|
||||
);
|
||||
@@ -215,6 +215,9 @@ export class EmbeddedFile {
|
||||
5000,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
this.filenameparts = getEmbeddedFilenameParts(imgPath);
|
||||
this.filenameparts.filepath = this.file.path;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -364,22 +367,26 @@ export class EmbeddedFilesLoader {
|
||||
|
||||
const hasColorMap = Boolean(inFile instanceof EmbeddedFile ? inFile.colorMap : null);
|
||||
const shouldUseCache = !hasColorMap && this.plugin.settings.allowImageCacheInScene && file && imageCache.isReady();
|
||||
const hasFilenameParts = Boolean((inFile instanceof EmbeddedFile) && inFile.filenameparts);
|
||||
const filenameParts = hasFilenameParts ? (inFile as EmbeddedFile).filenameparts : null;
|
||||
const cacheKey:ImageKey = {
|
||||
filepath: file.path,
|
||||
blockref: null,
|
||||
sectionref: null,
|
||||
...hasFilenameParts? filenameParts : {
|
||||
filepath: file.path,
|
||||
hasBlockref: false,
|
||||
hasGroupref: false,
|
||||
hasTaskbone: false,
|
||||
hasArearef: false,
|
||||
hasFrameref: false,
|
||||
hasSectionref: false,
|
||||
blockref: null,
|
||||
sectionref: null,
|
||||
linkpartReference: null,
|
||||
linkpartAlias: null,
|
||||
},
|
||||
isDark,
|
||||
previewImageType: PreviewImageType.SVG,
|
||||
scale: 1,
|
||||
isTransparent: !exportSettings.withBackground,
|
||||
hasBlockref: false,
|
||||
hasGroupref: false,
|
||||
hasTaskbone: false,
|
||||
hasArearef: false,
|
||||
hasFrameref: false,
|
||||
hasSectionref: false,
|
||||
linkpartReference: null,
|
||||
linkpartAlias: null,
|
||||
}
|
||||
|
||||
const maybeSVG = shouldUseCache
|
||||
@@ -390,7 +397,12 @@ export class EmbeddedFilesLoader {
|
||||
? maybeSVG
|
||||
: replaceSVGColors(
|
||||
await createSVG(
|
||||
file?.path,
|
||||
hasFilenameParts
|
||||
? (filenameParts.hasGroupref || filenameParts.hasBlockref ||
|
||||
filenameParts.hasSectionref || filenameParts.hasFrameref
|
||||
? filenameParts.filepath + filenameParts.linkpartReference
|
||||
: file.path)
|
||||
: file?.path,
|
||||
false, //false
|
||||
exportSettings,
|
||||
this,
|
||||
|
||||
@@ -127,7 +127,7 @@ import { anyModifierKeysPressed, emulateKeysForLinkClick, webbrowserDragModifier
|
||||
import { setDynamicStyle } from "./utils/DynamicStyling";
|
||||
import { InsertPDFModal } from "./dialogs/InsertPDFModal";
|
||||
import { CustomEmbeddable, renderWebView } from "./customEmbeddable";
|
||||
import { addBackOfTheNoteCard, getExcalidrawFileForwardLinks, getFrameBasedOnFrameNameOrId, getLinkTextFromLink, insertEmbeddableToView, insertImageToView, openExternalLink, openTagSearch, parseObsidianLink, renderContextMenuAction, tmpBruteForceCleanup } from "./utils/ExcalidrawViewUtils";
|
||||
import { addBackOfTheNoteCard, getExcalidrawFileForwardLinks, getFrameBasedOnFrameNameOrId, getLinkTextFromLink, insertEmbeddableToView, insertImageToView, isTextImageTransclusion, openExternalLink, openTagSearch, parseObsidianLink, renderContextMenuAction, tmpBruteForceCleanup } from "./utils/ExcalidrawViewUtils";
|
||||
import { imageCache } from "./utils/ImageCache";
|
||||
import { CanvasNodeFactory, ObsidianCanvasNode } from "./utils/CanvasNodeFactory";
|
||||
import { EmbeddableMenu } from "./menu/EmbeddableActionsMenu";
|
||||
@@ -3731,6 +3731,23 @@ export default class ExcalidrawView extends TextFileView {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(isTextImageTransclusion(data.text,this, async (link, file)=>{
|
||||
const ea = getEA(this) as ExcalidrawAutomate;
|
||||
if(IMAGE_TYPES.contains(file.extension)) {
|
||||
ea.selectElementsInView([await insertImageToView (ea, this.currentPosition, file)]);
|
||||
ea.destroy();
|
||||
} else if(file.extension !== "pdf") {
|
||||
ea.selectElementsInView([await insertEmbeddableToView (ea, this.currentPosition, file, link)]);
|
||||
ea.destroy();
|
||||
} else {
|
||||
const modal = new UniversalInsertFileModal(this.plugin, this);
|
||||
modal.open(file, this.currentPosition);
|
||||
}
|
||||
this.setDirty(9);
|
||||
})) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const quoteWithRef = obsidianPDFQuoteWithRef(data.text);
|
||||
if(quoteWithRef) {
|
||||
const ea = getEA(this) as ExcalidrawAutomate;
|
||||
@@ -4308,42 +4325,33 @@ export default class ExcalidrawView extends TextFileView {
|
||||
// If the link is an image or a PDF file, replace the text element with the image or the PDF.
|
||||
// If the link is an embedded markdown file, then display a message, but otherwise transclude the text step 5.
|
||||
// 1 2
|
||||
const REG_TRANSCLUSION = /^!\[\[([^|\]]*)?.*?]]$|^!\[[^\]]*?]\((.*?)\)$/g;
|
||||
const match = nextOriginalText.trim().matchAll(REG_TRANSCLUSION).next(); //reset the iterator
|
||||
if(match?.value?.[0]) {
|
||||
const link = match.value[1] ?? match.value[2];
|
||||
const file = this.app.metadataCache.getFirstLinkpathDest(link, this.file.path);
|
||||
if(file && file instanceof TFile) {
|
||||
if (file.extension !== "md" || this.plugin.isExcalidrawFile(file)) {
|
||||
window.setTimeout(async ()=>{
|
||||
const elements = this.excalidrawAPI.getSceneElements();
|
||||
const el = elements.filter((el:ExcalidrawElement)=>el.id === textElement.id) as ExcalidrawTextElement[];
|
||||
if(el.length === 1) {
|
||||
const center = {x: el[0].x, y: el[0].y };
|
||||
const clone = cloneElement(el[0]);
|
||||
clone.isDeleted = true;
|
||||
this.excalidrawData.deleteTextElement(clone.id);
|
||||
elements[elements.indexOf(el[0])] = clone;
|
||||
this.updateScene({elements});
|
||||
const ea:ExcalidrawAutomate = getEA(this);
|
||||
if(IMAGE_TYPES.contains(file.extension)) {
|
||||
ea.selectElementsInView([await insertImageToView (ea, center, file)]);
|
||||
ea.destroy();
|
||||
} else if(file.extension !== "pdf") {
|
||||
ea.selectElementsInView([await insertEmbeddableToView (ea, center, file)]);
|
||||
ea.destroy();
|
||||
} else {
|
||||
const modal = new UniversalInsertFileModal(this.plugin, this);
|
||||
modal.open(file, center);
|
||||
}
|
||||
this.setDirty(9);
|
||||
}
|
||||
});
|
||||
return {updatedNextOriginalText: null, nextLink: textElement.link};
|
||||
} else {
|
||||
new Notice(t("USE_INSERT_FILE_MODAL"),5000);
|
||||
if(isTextImageTransclusion(nextOriginalText, this, (link, file)=>{
|
||||
window.setTimeout(async ()=>{
|
||||
const elements = this.excalidrawAPI.getSceneElements();
|
||||
const el = elements.filter((el:ExcalidrawElement)=>el.id === textElement.id) as ExcalidrawTextElement[];
|
||||
if(el.length === 1) {
|
||||
const center = {x: el[0].x, y: el[0].y };
|
||||
const clone = cloneElement(el[0]);
|
||||
clone.isDeleted = true;
|
||||
this.excalidrawData.deleteTextElement(clone.id);
|
||||
elements[elements.indexOf(el[0])] = clone;
|
||||
this.updateScene({elements});
|
||||
const ea:ExcalidrawAutomate = getEA(this);
|
||||
if(IMAGE_TYPES.contains(file.extension)) {
|
||||
ea.selectElementsInView([await insertImageToView (ea, center, file)]);
|
||||
ea.destroy();
|
||||
} else if(file.extension !== "pdf") {
|
||||
ea.selectElementsInView([await insertEmbeddableToView (ea, center, file, link)]);
|
||||
ea.destroy();
|
||||
} else {
|
||||
const modal = new UniversalInsertFileModal(this.plugin, this);
|
||||
modal.open(file, center);
|
||||
}
|
||||
this.setDirty(9);
|
||||
}
|
||||
}
|
||||
});
|
||||
})) {
|
||||
return {updatedNextOriginalText: null, nextLink: textElement.link};
|
||||
}
|
||||
|
||||
// 5. Check if the user made changes to the text, or
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
import { MAX_IMAGE_SIZE, IMAGE_TYPES, ANIMATED_IMAGE_TYPES, MD_EX_SECTIONS } from "src/constants/constants";
|
||||
import { App, TFile, WorkspaceLeaf } from "obsidian";
|
||||
import { App, Notice, TFile, WorkspaceLeaf } from "obsidian";
|
||||
import { ExcalidrawAutomate } from "src/ExcalidrawAutomate";
|
||||
import { REGEX_LINK, REG_LINKINDEX_HYPERLINK, getExcalidrawMarkdownHeaderSection } from "src/ExcalidrawData";
|
||||
import ExcalidrawView from "src/ExcalidrawView";
|
||||
@@ -11,6 +11,7 @@ import { getEA } from "src";
|
||||
import { ExcalidrawImperativeAPI } from "@zsviczian/excalidraw/types/excalidraw/types";
|
||||
import { EmbeddableMDCustomProps } from "src/dialogs/EmbeddableSettings";
|
||||
import { nanoid } from "nanoid";
|
||||
import { t } from "src/lang/helpers";
|
||||
|
||||
export async function insertImageToView(
|
||||
ea: ExcalidrawAutomate,
|
||||
@@ -43,7 +44,7 @@ export async function insertEmbeddableToView (
|
||||
ea.style.strokeColor = "transparent";
|
||||
ea.style.backgroundColor = "transparent";
|
||||
if(file && (IMAGE_TYPES.contains(file.extension) || ea.isExcalidrawFile(file)) && !ANIMATED_IMAGE_TYPES.contains(file.extension)) {
|
||||
return await insertImageToView(ea, position, file);
|
||||
return await insertImageToView(ea, position, link??file);
|
||||
} else {
|
||||
const id = ea.addEmbeddable(
|
||||
position.x,
|
||||
@@ -345,4 +346,34 @@ export function tmpBruteForceCleanup (view: ExcalidrawView) {
|
||||
delete view[key];
|
||||
});
|
||||
}, 500);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the text matches the transclusion pattern and if so,
|
||||
* check if the link in the transclusion can be resolved to a file in the vault.
|
||||
* if yes, call the callback function with the link and the file.
|
||||
* @param text
|
||||
* @param callback
|
||||
* @returns true if text is a transclusion and the link can be resolved to a file in the vault, false otherwise.
|
||||
*/
|
||||
export function isTextImageTransclusion (
|
||||
text: string,
|
||||
view: ExcalidrawView,
|
||||
callback: (link: string, file: TFile)=>void
|
||||
): boolean {
|
||||
const REG_TRANSCLUSION = /^!\[\[([^|\]]*)?.*?]]$|^!\[[^\]]*?]\((.*?)\)$/g;
|
||||
const match = text.trim().matchAll(REG_TRANSCLUSION).next(); //reset the iterator
|
||||
if(match?.value?.[0]) {
|
||||
const link = match.value[1] ?? match.value[2];
|
||||
const file = view.app.metadataCache.getFirstLinkpathDest(link?.split("#")[0], view.file.path);
|
||||
if(file && file instanceof TFile) {
|
||||
if (file.extension !== "md" || view.plugin.isExcalidrawFile(file)) {
|
||||
callback(link, file);
|
||||
return true;
|
||||
} else {
|
||||
new Notice(t("USE_INSERT_FILE_MODAL"),5000);
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -5,8 +5,8 @@ export type FILENAMEPARTS = {
|
||||
hasTaskbone: boolean,
|
||||
hasArearef: boolean,
|
||||
hasFrameref: boolean,
|
||||
blockref: string,
|
||||
hasSectionref: boolean,
|
||||
blockref: string,
|
||||
sectionref: string,
|
||||
linkpartReference: string,
|
||||
linkpartAlias: string
|
||||
|
||||
Reference in New Issue
Block a user