Support for area, group, frame references in image embeds

This commit is contained in:
zsviczian
2024-07-17 20:31:36 +02:00
parent 53c27f2a59
commit fc1467b05b
4 changed files with 106 additions and 55 deletions

View File

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

View File

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

View File

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

View File

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