mirror of
https://github.com/zsviczian/obsidian-excalidraw-plugin.git
synced 2025-08-06 05:46:28 +00:00
Added comments to EA, moved non-class function to EAUtils
This commit is contained in:
@@ -39,7 +39,8 @@ import {
|
||||
setRootElementSize,
|
||||
} from "../constants/constants";
|
||||
import { ExcalidrawSettings, DEFAULT_SETTINGS, ExcalidrawSettingTab } from "./settings";
|
||||
import { initExcalidrawAutomate, ExcalidrawAutomate } from "../shared/ExcalidrawAutomate";
|
||||
import { ExcalidrawAutomate } from "../shared/ExcalidrawAutomate";
|
||||
import { initExcalidrawAutomate } from "src/utils/excalidrawAutomateUtils";
|
||||
import { around, dedupe } from "monkey-around";
|
||||
import { t } from "../lang/helpers";
|
||||
import {
|
||||
|
||||
@@ -29,11 +29,8 @@ import { InsertCommandDialog } from "../../shared/Dialogs/InsertCommandDialog";
|
||||
import { InsertImageDialog } from "../../shared/Dialogs/InsertImageDialog";
|
||||
import { ImportSVGDialog } from "../../shared/Dialogs/ImportSVGDialog";
|
||||
import { InsertMDDialog } from "../../shared/Dialogs/InsertMDDialog";
|
||||
import {
|
||||
ExcalidrawAutomate,
|
||||
insertLaTeXToView,
|
||||
search,
|
||||
} from "../../shared/ExcalidrawAutomate";
|
||||
import { ExcalidrawAutomate } from "../../shared/ExcalidrawAutomate";
|
||||
import { insertLaTeXToView, search } from "src/utils/excalidrawAutomateUtils";
|
||||
import { templatePromt } from "../../shared/Dialogs/Prompt";
|
||||
import { t } from "../../lang/helpers";
|
||||
import {
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
} from "obsidian";
|
||||
import { DEVICE, RERENDER_EVENT } from "../../constants/constants";
|
||||
import { EmbeddedFilesLoader } from "../../shared/EmbeddedFileLoader";
|
||||
import { createPNG, createSVG } from "../../shared/ExcalidrawAutomate";
|
||||
import { createPNG, createSVG } from "../../utils/excalidrawAutomateUtils";
|
||||
import { ExportSettings } from "../../view/ExcalidrawView";
|
||||
import ExcalidrawPlugin from "../main";
|
||||
import {getIMGFilename,} from "../../utils/fileUtils";
|
||||
|
||||
@@ -4,7 +4,8 @@ import { Mutable } from "@zsviczian/excalidraw/types/excalidraw/utility-types";
|
||||
import { Notice } from "obsidian";
|
||||
|
||||
import { getEA } from "src/core";
|
||||
import { ExcalidrawAutomate, cloneElement } from "src/shared/ExcalidrawAutomate";
|
||||
import { ExcalidrawAutomate } from "src/shared/ExcalidrawAutomate";
|
||||
import { cloneElement } from "src/utils/excalidrawAutomateUtils";
|
||||
import { ExportSettings } from "src/view/ExcalidrawView";
|
||||
import { nanoid } from "src/constants/constants";
|
||||
import { svgToBase64 } from "../utils/utils";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { App, FuzzySuggestModal, Notice, TFile } from "obsidian";
|
||||
import { App, FuzzySuggestModal, Notice } from "obsidian";
|
||||
import { t } from "../../lang/helpers";
|
||||
import ExcalidrawView from "src/view/ExcalidrawView";
|
||||
import { getEA } from "src/core";
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
FRONTMATTER_KEYS,
|
||||
getCSSFontDefinition,
|
||||
} from "../constants/constants";
|
||||
import { createSVG } from "./ExcalidrawAutomate";
|
||||
import { createSVG } from "src/utils/excalidrawAutomateUtils";
|
||||
import { ExcalidrawData, getTransclusion } from "./ExcalidrawData";
|
||||
import { ExportSettings } from "../view/ExcalidrawView";
|
||||
import { t } from "../lang/helpers";
|
||||
|
||||
@@ -14,10 +14,10 @@ import {
|
||||
ExcalidrawTextContainer,
|
||||
} from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
import { ColorMap, MimeType } from "./EmbeddedFileLoader";
|
||||
import { Editor, normalizePath, Notice, OpenViewState, RequestUrlResponse, TFile, TFolder, WorkspaceLeaf } from "obsidian";
|
||||
import { Editor, Notice, OpenViewState, RequestUrlResponse, TFile, TFolder, WorkspaceLeaf } from "obsidian";
|
||||
import * as obsidian_module from "obsidian";
|
||||
import ExcalidrawView, { ExportSettings, TextMode, getTextMode } from "src/view/ExcalidrawView";
|
||||
import { ExcalidrawData, getExcalidrawMarkdownHeaderSection, getMarkdownDrawingSection, REGEX_LINK } from "./ExcalidrawData";
|
||||
import ExcalidrawView, { ExportSettings, TextMode } from "src/view/ExcalidrawView";
|
||||
import { ExcalidrawData, getMarkdownDrawingSection } from "./ExcalidrawData";
|
||||
import {
|
||||
FRONTMATTER,
|
||||
nanoid,
|
||||
@@ -30,39 +30,24 @@ import {
|
||||
getLineHeight,
|
||||
getMaximumGroups,
|
||||
intersectElementWithLine,
|
||||
measureText,
|
||||
DEVICE,
|
||||
restore,
|
||||
REG_LINKINDEX_INVALIDCHARS,
|
||||
THEME_FILTER,
|
||||
mermaidToExcalidraw,
|
||||
refreshTextDimensions,
|
||||
getFontFamilyString,
|
||||
EXCALIDRAW_PLUGIN,
|
||||
} from "src/constants/constants";
|
||||
import { blobToBase64, checkAndCreateFolder, getDrawingFilename, getExcalidrawEmbeddedFilesFiletree, getListOfTemplateFiles, getNewUniqueFilepath, hasExcalidrawEmbeddedImagesTreeChanged, } from "src/utils/fileUtils";
|
||||
import { blobToBase64, checkAndCreateFolder, getDrawingFilename, getExcalidrawEmbeddedFilesFiletree, getListOfTemplateFiles, getNewUniqueFilepath } from "src/utils/fileUtils";
|
||||
import {
|
||||
//debug,
|
||||
errorlog,
|
||||
getEmbeddedFilenameParts,
|
||||
getImageSize,
|
||||
getLinkParts,
|
||||
getPNG,
|
||||
getSVG,
|
||||
isMaskFile,
|
||||
isVersionNewerThanOther,
|
||||
scaleLoadedImage,
|
||||
wrapTextAtCharLength,
|
||||
arrayToMap,
|
||||
addAppendUpdateCustomData,
|
||||
} from "src/utils/utils";
|
||||
import { getAttachmentsFolderAndFilePath, getExcalidrawViews, getLeaf, getNewOrAdjacentLeaf, isObsidianThemeDark, mergeMarkdownFiles, openLeaf } from "src/utils/obsidianUtils";
|
||||
import { AppState, BinaryFileData, DataURL, ExcalidrawImperativeAPI } from "@zsviczian/excalidraw/types/excalidraw/types";
|
||||
import { EmbeddedFile, EmbeddedFilesLoader, FileData } from "./EmbeddedFileLoader";
|
||||
import { EmbeddedFile, EmbeddedFilesLoader } from "./EmbeddedFileLoader";
|
||||
import { tex2dataURL } from "./LaTeX";
|
||||
import { GenericInputPrompt, NewFileActions } from "src/shared/Dialogs/Prompt";
|
||||
import { t } from "src/lang/helpers";
|
||||
import { ScriptEngine } from "./Scripts";
|
||||
import { NewFileActions } from "src/shared/Dialogs/Prompt";
|
||||
import { ConnectionPoint, DeviceType, Point } from "src/types/types";
|
||||
import CM, { ColorMaster, extendPlugins } from "@zsviczian/colormaster";
|
||||
import HarmonyPlugin from "@zsviczian/colormaster/plugins/harmony";
|
||||
@@ -92,12 +77,12 @@ import {
|
||||
extractCodeBlocks as _extractCodeBlocks,
|
||||
} from "../utils/AIUtils";
|
||||
import { EXCALIDRAW_AUTOMATE_INFO, EXCALIDRAW_SCRIPTENGINE_INFO } from "./Dialogs/SuggesterInfo";
|
||||
import { addBackOfTheNoteCard, getFrameBasedOnFrameNameOrId } from "../utils/excalidrawViewUtils";
|
||||
import { addBackOfTheNoteCard } from "../utils/excalidrawViewUtils";
|
||||
import { log } from "../utils/debugHelper";
|
||||
import { ExcalidrawLib } from "../types/excalidrawLib";
|
||||
import { GlobalPoint } from "@zsviczian/excalidraw/types/math/types";
|
||||
import { AddImageOptions, ImageInfo, SVGColorInfo } from "src/types/excalidrawAutomateTypes";
|
||||
import { errorMessage, filterColorMap, getEmbeddedFileForImageElment, isColorStringTransparent, isSVGColorInfo, mergeColorMapIntoSVGColorInfo, svgColorInfoToColorMap, updateOrAddSVGColorInfo } from "src/utils/excalidrawAutomateUtils";
|
||||
import { _measureText, cloneElement, createPNG, createSVG, errorMessage, filterColorMap, getEmbeddedFileForImageElment, getFontFamily, getLineBox, getTemplate, isColorStringTransparent, isSVGColorInfo, mergeColorMapIntoSVGColorInfo, normalizeLinePoints, repositionElementsToCursor, svgColorInfoToColorMap, updateOrAddSVGColorInfo, verifyMinimumPluginVersion } from "src/utils/excalidrawAutomateUtils";
|
||||
|
||||
extendPlugins([
|
||||
HarmonyPlugin,
|
||||
@@ -121,6 +106,33 @@ declare const excalidrawLib: typeof ExcalidrawLib;
|
||||
|
||||
const GAP = 4;
|
||||
|
||||
/**
|
||||
* ExcalidrawAutomate is a utility class that provides a simplified API to interact with Excalidraw elements and the Excalidraw canvas.
|
||||
* Elements in the Excalidraw Scene are immutable. You should never directly change element properties in the scene object.
|
||||
* ExcalidrawAutomate provides a "workbench" where you can create, modify, and delete elements before committing them to the Excalidraw Scene.
|
||||
* The basic workflow is to create elements in ExcalidrawAutomate and once ready commit them to the Excalidraw Scene using addElementsToView().
|
||||
* To modify elements in the scene, you should first copy them over to EA using copyViewElementsToEAforEditing, make the necessary modifications,
|
||||
* then commit them back to the scene using addElementsToView().
|
||||
* To delete an element from the view set element.isDeleted = true and commit the changes to the scene using addElementsToView().
|
||||
*
|
||||
* At a very high level, EA has 3 type of functions:
|
||||
* - functions that modify elements in the EA workbench
|
||||
* - functions that access elements and properties of the Scene
|
||||
* - these only work if targetView is set using setView()
|
||||
* - Scripts executed by the Excalidraw ScritpEngine will have the targetView set automatically
|
||||
* - These functions include the word view in their name e.g. getViewSelectedElements()
|
||||
* - utility functions that do not modify eleeemnts in the EA workbench or access the scene e.g.
|
||||
* - ea.obsidian is a utility function that returns the Obsidian Module object.
|
||||
* - eg.getCM() returns the ColorMaster object for manipulationg colors,
|
||||
* - ea.help() provides information about functions and properties in the ExcalidrawAutomate class intended for use in Developer Console
|
||||
* - checkAndCreateFolder (thought this has been superceeded by app.vault.createFolder in the Obsidian API)
|
||||
* - etc.
|
||||
*
|
||||
* Note that some actions are asynchronous and require await to complete. e.g.:
|
||||
* - addImage()
|
||||
* - convertStringToDataURL()
|
||||
* - etc.
|
||||
*/
|
||||
export class ExcalidrawAutomate {
|
||||
/**
|
||||
* Utility function that returns the Obsidian Module object.
|
||||
@@ -129,14 +141,23 @@ export class ExcalidrawAutomate {
|
||||
return obsidian_module;
|
||||
};
|
||||
|
||||
/**
|
||||
* settings for the laser pointer
|
||||
*/
|
||||
get LASERPOINTER() {
|
||||
return this.plugin.settings.laserSettings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Info about the device the script is running on.
|
||||
*/
|
||||
get DEVICE():DeviceType {
|
||||
return DEVICE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function to print detailed breakdown of startup time.
|
||||
*/
|
||||
public printStartupBreakdown() {
|
||||
this.plugin.printStarupBreakdown();
|
||||
}
|
||||
@@ -158,6 +179,9 @@ export class ExcalidrawAutomate {
|
||||
return addAppendUpdateCustomData(el,newData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function to get help on ExcalidrawAutomate functions and properties intended to be used in Obsidian developer console.
|
||||
*/
|
||||
public help(target: Function | string) {
|
||||
if (!target) {
|
||||
log("Usage: ea.help(ea.functionName) or ea.help('propertyName') or ea.help('utils.functionName') - notice property name and utils function name is in quotes");
|
||||
@@ -294,6 +318,10 @@ export class ExcalidrawAutomate {
|
||||
return getExcalidrawEmbeddedFilesFiletree(excalidrawFile, this.plugin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new unique attachment filepath for the filename provided based on Obsidian settings
|
||||
* @returns
|
||||
*/
|
||||
public async getAttachmentFilepath(filename: string): Promise<string> {
|
||||
if (!this.targetView || !this.targetView?.file) {
|
||||
errorMessage("targetView not set", "getAttachmentFolderAndFilePath()");
|
||||
@@ -303,10 +331,20 @@ export class ExcalidrawAutomate {
|
||||
return getNewUniqueFilepath(this.plugin.app.vault, filename, folderAndPath.folder);
|
||||
}
|
||||
|
||||
/**
|
||||
* compresses a string to base64 using LZString
|
||||
* @param str
|
||||
* @returns
|
||||
*/
|
||||
public compressToBase64(str:string): string {
|
||||
return LZString.compressToBase64(str);
|
||||
}
|
||||
|
||||
/**
|
||||
* decompresses a string from base64 using LZString
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
public decompressFromBase64(data:string): string {
|
||||
if (!data) throw new Error("No input string provided for decompression.");
|
||||
let cleanedData = '';
|
||||
@@ -397,6 +435,12 @@ export class ExcalidrawAutomate {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the Excalidraw File is a mask file.
|
||||
* @param file
|
||||
* @returns
|
||||
*/
|
||||
|
||||
public isExcalidrawMaskFile(file?:TFile): boolean {
|
||||
if(file) {
|
||||
return this.isExcalidrawFile(file) && isMaskFile(this.plugin, file);
|
||||
@@ -457,7 +501,7 @@ export class ExcalidrawAutomate {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Returns the Excalidraw API for the current view or the view provided.
|
||||
* @returns
|
||||
*/
|
||||
public getAPI(view?:ExcalidrawView):ExcalidrawAutomate {
|
||||
@@ -556,6 +600,7 @@ export class ExcalidrawAutomate {
|
||||
};
|
||||
|
||||
/**
|
||||
* Generates a groupID and adds the groupId to all the elements in the objectIds array. Essentially grouping the elements in the view.
|
||||
* @param objectIds
|
||||
* @returns
|
||||
*/
|
||||
@@ -568,6 +613,7 @@ export class ExcalidrawAutomate {
|
||||
};
|
||||
|
||||
/**
|
||||
* copies elements from ExcalidrawAutomate to the clipboard as a valid Excalidraw JSON string
|
||||
* @param templatePath
|
||||
*/
|
||||
async toClipboard(templatePath?: string) {
|
||||
@@ -591,6 +637,7 @@ export class ExcalidrawAutomate {
|
||||
};
|
||||
|
||||
/**
|
||||
* Extracts the Excalidraw Scene from an Excalidraw File
|
||||
* @param file: TFile
|
||||
* @returns ExcalidrawScene
|
||||
*/
|
||||
@@ -846,7 +893,7 @@ export class ExcalidrawAutomate {
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* Creates an SVG image from the ExcalidrawAutomate elements and the template provided.
|
||||
* @param templatePath
|
||||
* @param embedFont
|
||||
* @param exportSettings use ExcalidrawAutomate.getExportSettings(boolean,boolean)
|
||||
@@ -903,7 +950,7 @@ export class ExcalidrawAutomate {
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* Creates a PNG image from the ExcalidrawAutomate elements and the template provided.
|
||||
* @param templatePath
|
||||
* @param scale
|
||||
* @param exportSettings use ExcalidrawAutomate.getExportSettings(boolean,boolean)
|
||||
@@ -959,7 +1006,7 @@ export class ExcalidrawAutomate {
|
||||
};
|
||||
|
||||
/**
|
||||
* Wrapper for createPNG() that returns a base64 encoded string
|
||||
* Wrapper for createPNG() that returns a base64 encoded string designed to support LLM workflows.
|
||||
* @param templatePath
|
||||
* @param scale
|
||||
* @param exportSettings
|
||||
@@ -990,6 +1037,18 @@ export class ExcalidrawAutomate {
|
||||
return wrapTextAtCharLength(text, lineLen, this.plugin.settings.forceWrap);
|
||||
};
|
||||
|
||||
/**
|
||||
* Utility function. Returns an element object using style settings and provided parameters
|
||||
* @param id
|
||||
* @param eltype
|
||||
* @param x
|
||||
* @param y
|
||||
* @param w
|
||||
* @param h
|
||||
* @param link
|
||||
* @param scale
|
||||
* @returns
|
||||
*/
|
||||
private boxedElement(
|
||||
id: string,
|
||||
eltype: any,
|
||||
@@ -1033,12 +1092,23 @@ export class ExcalidrawAutomate {
|
||||
};
|
||||
}
|
||||
|
||||
//retained for backward compatibility
|
||||
/**
|
||||
* Deprecated. Use addEmbeddable() instead.
|
||||
* Retained for backward compatibility
|
||||
* @param topX
|
||||
* @param topY
|
||||
* @param width
|
||||
* @param height
|
||||
* @param url
|
||||
* @param file
|
||||
* @returns
|
||||
*/
|
||||
addIFrame(topX: number, topY: number, width: number, height: number, url?: string, file?: TFile): string {
|
||||
return this.addEmbeddable(topX, topY, width, height, url, file);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Adds an embeddable element to the ExcalidrawAutomate instance.
|
||||
* @param topX
|
||||
* @param topY
|
||||
* @param width
|
||||
@@ -1054,7 +1124,6 @@ export class ExcalidrawAutomate {
|
||||
file?: TFile,
|
||||
embeddableCustomData?: EmbeddableMDCustomProps,
|
||||
): string {
|
||||
//@ts-ignore
|
||||
if (!this.targetView || !this.targetView?._loaded) {
|
||||
errorMessage("targetView not set", "addEmbeddable()");
|
||||
return null;
|
||||
@@ -1953,8 +2022,8 @@ export class ExcalidrawAutomate {
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param elToDelete
|
||||
* Deletes elements in the view by removing them from the scene (ie. not by setting isDeleted to true)
|
||||
* @param elToDelete: Array of Excalidraw Elements
|
||||
* @returns
|
||||
*/
|
||||
deleteViewElements(elToDelete: ExcalidrawElement[]): boolean {
|
||||
@@ -1967,10 +2036,11 @@ export class ExcalidrawAutomate {
|
||||
if (!api) {
|
||||
return false;
|
||||
}
|
||||
const ids = elToDelete.map((e: ExcalidrawElement) => e.id);
|
||||
const el: ExcalidrawElement[] = api.getSceneElements() as ExcalidrawElement[];
|
||||
const st: AppState = api.getAppState();
|
||||
this.targetView.updateScene({
|
||||
elements: el.filter((e: ExcalidrawElement) => !elToDelete.includes(e)),
|
||||
elements: el.filter((e: ExcalidrawElement) => !ids.includes(e.id)),
|
||||
appState: st,
|
||||
storeAction: "capture",
|
||||
});
|
||||
@@ -3094,668 +3164,3 @@ export class ExcalidrawAutomate {
|
||||
this.colorPalette = {};
|
||||
}
|
||||
};
|
||||
|
||||
export function initExcalidrawAutomate(
|
||||
plugin: ExcalidrawPlugin,
|
||||
): ExcalidrawAutomate {
|
||||
const ea = new ExcalidrawAutomate(plugin);
|
||||
//@ts-ignore
|
||||
window.ExcalidrawAutomate = ea;
|
||||
return ea;
|
||||
}
|
||||
|
||||
function normalizeLinePoints(
|
||||
points: [x: number, y: number][],
|
||||
//box: { x: number; y: number; w: number; h: number },
|
||||
): number[][] {
|
||||
const p = [];
|
||||
const [x, y] = points[0];
|
||||
for (let i = 0; i < points.length; i++) {
|
||||
p.push([points[i][0] - x, points[i][1] - y]);
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
function getLineBox(
|
||||
points: [x: number, y: number][]
|
||||
):{x:number, y:number, w: number, h:number} {
|
||||
const [x1, y1, x2, y2] = estimateLineBound(points);
|
||||
return {
|
||||
x: x1,
|
||||
y: y1,
|
||||
w: x2 - x1, //Math.abs(points[points.length-1][0]-points[0][0]),
|
||||
h: y2 - y1, //Math.abs(points[points.length-1][1]-points[0][1])
|
||||
};
|
||||
}
|
||||
|
||||
function getFontFamily(id: number):string {
|
||||
return getFontFamilyString({fontFamily:id})
|
||||
}
|
||||
|
||||
export function _measureText(
|
||||
newText: string,
|
||||
fontSize: number,
|
||||
fontFamily: number,
|
||||
lineHeight: number,
|
||||
): {w: number, h:number} {
|
||||
//following odd error with mindmap on iPad while synchornizing with desktop.
|
||||
if (!fontSize) {
|
||||
fontSize = 20;
|
||||
}
|
||||
if (!fontFamily) {
|
||||
fontFamily = 1;
|
||||
lineHeight = getLineHeight(fontFamily);
|
||||
}
|
||||
const metrics = measureText(
|
||||
newText,
|
||||
`${fontSize.toString()}px ${getFontFamily(fontFamily)}` as any,
|
||||
lineHeight
|
||||
);
|
||||
return { w: metrics.width, h: metrics.height };
|
||||
}
|
||||
|
||||
async function getTemplate(
|
||||
plugin: ExcalidrawPlugin,
|
||||
fileWithPath: string,
|
||||
loadFiles: boolean = false,
|
||||
loader: EmbeddedFilesLoader,
|
||||
depth: number,
|
||||
convertMarkdownLinksToObsidianURLs: boolean = false,
|
||||
): Promise<{
|
||||
elements: any;
|
||||
appState: any;
|
||||
frontmatter: string;
|
||||
files: any;
|
||||
hasSVGwithBitmap: boolean;
|
||||
plaintext: string; //markdown data above Excalidraw data and below YAML frontmatter
|
||||
}> {
|
||||
const app = plugin.app;
|
||||
const vault = app.vault;
|
||||
const filenameParts = getEmbeddedFilenameParts(fileWithPath);
|
||||
const templatePath = normalizePath(filenameParts.filepath);
|
||||
const file = app.metadataCache.getFirstLinkpathDest(templatePath, "");
|
||||
let hasSVGwithBitmap = false;
|
||||
if (file && file instanceof TFile) {
|
||||
const data = (await vault.read(file))
|
||||
.replaceAll("\r\n", "\n")
|
||||
.replaceAll("\r", "\n");
|
||||
const excalidrawData: ExcalidrawData = new ExcalidrawData(plugin);
|
||||
|
||||
if (file.extension === "excalidraw") {
|
||||
await excalidrawData.loadLegacyData(data, file);
|
||||
return {
|
||||
elements: convertMarkdownLinksToObsidianURLs
|
||||
? updateElementLinksToObsidianLinks({
|
||||
elements: excalidrawData.scene.elements,
|
||||
hostFile: file,
|
||||
}) : excalidrawData.scene.elements,
|
||||
appState: excalidrawData.scene.appState,
|
||||
frontmatter: "",
|
||||
files: excalidrawData.scene.files,
|
||||
hasSVGwithBitmap,
|
||||
plaintext: "",
|
||||
};
|
||||
}
|
||||
|
||||
const textMode = getTextMode(data);
|
||||
await excalidrawData.loadData(
|
||||
data,
|
||||
file,
|
||||
textMode,
|
||||
);
|
||||
|
||||
let trimLocation = data.search(/^##? Text Elements$/m);
|
||||
if (trimLocation == -1) {
|
||||
trimLocation = data.search(/##? Drawing\n/);
|
||||
}
|
||||
|
||||
let scene = excalidrawData.scene;
|
||||
|
||||
let groupElements:ExcalidrawElement[] = scene.elements;
|
||||
if(filenameParts.hasGroupref) {
|
||||
const el = filenameParts.hasSectionref
|
||||
? getTextElementsMatchingQuery(scene.elements,["# "+filenameParts.sectionref],true)
|
||||
: scene.elements.filter((el: ExcalidrawElement)=>el.id===filenameParts.blockref);
|
||||
if(el.length > 0) {
|
||||
groupElements = plugin.ea.getElementsInTheSameGroupWithElement(el[0],scene.elements,true)
|
||||
}
|
||||
}
|
||||
if(filenameParts.hasFrameref || filenameParts.hasClippedFrameref) {
|
||||
const el = getFrameBasedOnFrameNameOrId(filenameParts.blockref,scene.elements);
|
||||
|
||||
if(el) {
|
||||
groupElements = plugin.ea.getElementsInFrame(el,scene.elements, filenameParts.hasClippedFrameref);
|
||||
}
|
||||
}
|
||||
|
||||
if(filenameParts.hasTaskbone) {
|
||||
groupElements = groupElements.filter( el =>
|
||||
el.type==="freedraw" ||
|
||||
( el.type==="image" &&
|
||||
!plugin.isExcalidrawFile(excalidrawData.getFile(el.fileId)?.file)
|
||||
));
|
||||
}
|
||||
|
||||
let fileIDWhiteList:Set<FileId>;
|
||||
|
||||
if(groupElements.length < scene.elements.length) {
|
||||
fileIDWhiteList = new Set<FileId>();
|
||||
groupElements.filter(el=>el.type==="image").forEach((el:ExcalidrawImageElement)=>fileIDWhiteList.add(el.fileId));
|
||||
}
|
||||
|
||||
if (loadFiles) {
|
||||
//debug({where:"getTemplate",template:file.name,loader:loader.uid});
|
||||
await loader.loadSceneFiles({
|
||||
excalidrawData,
|
||||
addFiles: (fileArray: FileData[]) => {
|
||||
//, isDark: boolean) => {
|
||||
if (!fileArray || fileArray.length === 0) {
|
||||
return;
|
||||
}
|
||||
for (const f of fileArray) {
|
||||
if (f.hasSVGwithBitmap) {
|
||||
hasSVGwithBitmap = true;
|
||||
}
|
||||
excalidrawData.scene.files[f.id] = {
|
||||
mimeType: f.mimeType,
|
||||
id: f.id,
|
||||
dataURL: f.dataURL,
|
||||
created: f.created,
|
||||
};
|
||||
}
|
||||
scene = scaleLoadedImage(excalidrawData.scene, fileArray).scene;
|
||||
},
|
||||
depth,
|
||||
fileIDWhiteList
|
||||
});
|
||||
}
|
||||
|
||||
excalidrawData.destroy();
|
||||
const filehead = getExcalidrawMarkdownHeaderSection(data); // data.substring(0, trimLocation);
|
||||
let files:any = {};
|
||||
const sceneFilesSize = Object.values(scene.files).length;
|
||||
if (sceneFilesSize > 0) {
|
||||
if(fileIDWhiteList && (sceneFilesSize > fileIDWhiteList.size)) {
|
||||
Object.values(scene.files).filter((f: any) => fileIDWhiteList.has(f.id)).forEach((f: any) => {
|
||||
files[f.id] = f;
|
||||
});
|
||||
} else {
|
||||
files = scene.files;
|
||||
}
|
||||
}
|
||||
|
||||
const frontmatter = filehead.match(/^---\n.*\n---\n/ms)?.[0] ?? filehead;
|
||||
return {
|
||||
elements: convertMarkdownLinksToObsidianURLs
|
||||
? updateElementLinksToObsidianLinks({
|
||||
elements: groupElements,
|
||||
hostFile: file,
|
||||
}) : groupElements,
|
||||
appState: scene.appState,
|
||||
frontmatter,
|
||||
plaintext: frontmatter !== filehead
|
||||
? (filehead.split(/^---\n.*\n---\n/ms)?.[1] ?? "")
|
||||
: "",
|
||||
files,
|
||||
hasSVGwithBitmap,
|
||||
};
|
||||
}
|
||||
return {
|
||||
elements: [],
|
||||
appState: {},
|
||||
frontmatter: null,
|
||||
files: [],
|
||||
hasSVGwithBitmap,
|
||||
plaintext: "",
|
||||
};
|
||||
}
|
||||
|
||||
export const generatePlaceholderDataURL = (width: number, height: number): DataURL => {
|
||||
const svgString = `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}"><rect width="100%" height="100%" fill="#E7E7E7" /><text x="${width / 2}" y="${height / 2}" dominant-baseline="middle" text-anchor="middle" font-family="Arial" font-size="${Math.min(width, height) / 5}" fill="#888">Placeholder</text></svg>`;
|
||||
return `data:image/svg+xml;base64,${btoa(svgString)}` as DataURL;
|
||||
};
|
||||
|
||||
export async function createPNG(
|
||||
templatePath: string = undefined,
|
||||
scale: number = 1,
|
||||
exportSettings: ExportSettings,
|
||||
loader: EmbeddedFilesLoader,
|
||||
forceTheme: string = undefined,
|
||||
canvasTheme: string = undefined,
|
||||
canvasBackgroundColor: string = undefined,
|
||||
automateElements: ExcalidrawElement[] = [],
|
||||
plugin: ExcalidrawPlugin,
|
||||
depth: number,
|
||||
padding?: number,
|
||||
imagesDict?: any,
|
||||
): Promise<Blob> {
|
||||
if (!loader) {
|
||||
loader = new EmbeddedFilesLoader(plugin);
|
||||
}
|
||||
padding = padding ?? plugin.settings.exportPaddingSVG;
|
||||
const template = templatePath
|
||||
? await getTemplate(plugin, templatePath, true, loader, depth)
|
||||
: null;
|
||||
let elements = template?.elements ?? [];
|
||||
elements = elements.concat(automateElements);
|
||||
const files = imagesDict ?? {};
|
||||
if(template?.files) {
|
||||
Object.values(template.files).forEach((f:any)=>{
|
||||
if(!f.dataURL.startsWith("http")) {
|
||||
files[f.id]=f;
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
return await getPNG(
|
||||
{
|
||||
type: "excalidraw",
|
||||
version: 2,
|
||||
source: GITHUB_RELEASES+PLUGIN_VERSION,
|
||||
elements,
|
||||
appState: {
|
||||
theme: forceTheme ?? template?.appState?.theme ?? canvasTheme,
|
||||
viewBackgroundColor:
|
||||
template?.appState?.viewBackgroundColor ?? canvasBackgroundColor,
|
||||
...template?.appState?.frameRendering ? {frameRendering: template.appState.frameRendering} : {},
|
||||
},
|
||||
files,
|
||||
},
|
||||
{
|
||||
withBackground:
|
||||
exportSettings?.withBackground ?? plugin.settings.exportWithBackground,
|
||||
withTheme: exportSettings?.withTheme ?? plugin.settings.exportWithTheme,
|
||||
isMask: exportSettings?.isMask ?? false,
|
||||
},
|
||||
padding,
|
||||
scale,
|
||||
);
|
||||
}
|
||||
|
||||
export const updateElementLinksToObsidianLinks = ({elements, hostFile}:{
|
||||
elements: ExcalidrawElement[];
|
||||
hostFile: TFile;
|
||||
}): ExcalidrawElement[] => {
|
||||
return elements.map((el)=>{
|
||||
if(el.link && el.link.startsWith("[")) {
|
||||
const partsArray = REGEX_LINK.getResList(el.link)[0];
|
||||
if(!partsArray?.value) return el;
|
||||
let linkText = REGEX_LINK.getLink(partsArray);
|
||||
if (linkText.search("#") > -1) {
|
||||
const linkParts = getLinkParts(linkText, hostFile);
|
||||
linkText = linkParts.path;
|
||||
}
|
||||
if (linkText.match(REG_LINKINDEX_INVALIDCHARS)) {
|
||||
return el;
|
||||
}
|
||||
const file = EXCALIDRAW_PLUGIN.app.metadataCache.getFirstLinkpathDest(
|
||||
linkText,
|
||||
hostFile.path,
|
||||
);
|
||||
if(!file) {
|
||||
return el;
|
||||
}
|
||||
let link = EXCALIDRAW_PLUGIN.app.getObsidianUrl(file);
|
||||
if(window.ExcalidrawAutomate?.onUpdateElementLinkForExportHook) {
|
||||
link = window.ExcalidrawAutomate.onUpdateElementLinkForExportHook({
|
||||
originalLink: el.link,
|
||||
obsidianLink: link,
|
||||
linkedFile: file,
|
||||
hostFile: hostFile
|
||||
});
|
||||
}
|
||||
const newElement: Mutable<ExcalidrawElement> = cloneElement(el);
|
||||
newElement.link = link;
|
||||
return newElement;
|
||||
}
|
||||
return el;
|
||||
})
|
||||
}
|
||||
|
||||
function addFilterToForeignObjects(svg:SVGSVGElement):void {
|
||||
const foreignObjects = svg.querySelectorAll("foreignObject");
|
||||
foreignObjects.forEach((foreignObject) => {
|
||||
foreignObject.setAttribute("filter", THEME_FILTER);
|
||||
});
|
||||
}
|
||||
|
||||
export async function createSVG(
|
||||
templatePath: string = undefined,
|
||||
embedFont: boolean = false,
|
||||
exportSettings: ExportSettings,
|
||||
loader: EmbeddedFilesLoader,
|
||||
forceTheme: string = undefined,
|
||||
canvasTheme: string = undefined,
|
||||
canvasBackgroundColor: string = undefined,
|
||||
automateElements: ExcalidrawElement[] = [],
|
||||
plugin: ExcalidrawPlugin,
|
||||
depth: number,
|
||||
padding?: number,
|
||||
imagesDict?: any,
|
||||
convertMarkdownLinksToObsidianURLs: boolean = false,
|
||||
): Promise<SVGSVGElement> {
|
||||
if (!loader) {
|
||||
loader = new EmbeddedFilesLoader(plugin);
|
||||
}
|
||||
if(typeof exportSettings.skipInliningFonts === "undefined") {
|
||||
exportSettings.skipInliningFonts = !embedFont;
|
||||
}
|
||||
const template = templatePath
|
||||
? await getTemplate(plugin, templatePath, true, loader, depth, convertMarkdownLinksToObsidianURLs)
|
||||
: null;
|
||||
let elements = template?.elements ?? [];
|
||||
elements = elements.concat(automateElements);
|
||||
padding = padding ?? plugin.settings.exportPaddingSVG;
|
||||
const files = imagesDict ?? {};
|
||||
if(template?.files) {
|
||||
Object.values(template.files).forEach((f:any)=>{
|
||||
files[f.id]=f;
|
||||
});
|
||||
}
|
||||
|
||||
const theme = forceTheme ?? template?.appState?.theme ?? canvasTheme;
|
||||
const withTheme = exportSettings?.withTheme ?? plugin.settings.exportWithTheme;
|
||||
|
||||
const filenameParts = getEmbeddedFilenameParts(templatePath);
|
||||
const svg = await getSVG(
|
||||
{
|
||||
//createAndOpenDrawing
|
||||
type: "excalidraw",
|
||||
version: 2,
|
||||
source: GITHUB_RELEASES+PLUGIN_VERSION,
|
||||
elements,
|
||||
appState: {
|
||||
theme,
|
||||
viewBackgroundColor:
|
||||
template?.appState?.viewBackgroundColor ?? canvasBackgroundColor,
|
||||
...template?.appState?.frameRendering ? {frameRendering: template.appState.frameRendering} : {},
|
||||
},
|
||||
files,
|
||||
},
|
||||
{
|
||||
withBackground:
|
||||
exportSettings?.withBackground ?? plugin.settings.exportWithBackground,
|
||||
withTheme,
|
||||
isMask: exportSettings?.isMask ?? false,
|
||||
...filenameParts?.hasClippedFrameref
|
||||
? {frameRendering: {enabled: true, name: false, outline: false, clip: true}}
|
||||
: {},
|
||||
},
|
||||
padding,
|
||||
null,
|
||||
);
|
||||
|
||||
if (withTheme && theme === "dark") addFilterToForeignObjects(svg);
|
||||
|
||||
if(
|
||||
!(filenameParts.hasGroupref || filenameParts.hasFrameref || filenameParts.hasClippedFrameref) &&
|
||||
(filenameParts.hasBlockref || filenameParts.hasSectionref)
|
||||
) {
|
||||
let el = filenameParts.hasSectionref
|
||||
? getTextElementsMatchingQuery(elements,["# "+filenameParts.sectionref],true)
|
||||
: elements.filter((el: ExcalidrawElement)=>el.id===filenameParts.blockref);
|
||||
if(el.length>0) {
|
||||
const containerId = el[0].containerId;
|
||||
if(containerId) {
|
||||
el = el.concat(elements.filter((el: ExcalidrawElement)=>el.id === containerId));
|
||||
}
|
||||
const elBB = plugin.ea.getBoundingBox(el);
|
||||
const drawingBB = plugin.ea.getBoundingBox(elements);
|
||||
svg.viewBox.baseVal.x = elBB.topX - drawingBB.topX;
|
||||
svg.viewBox.baseVal.y = elBB.topY - drawingBB.topY;
|
||||
const width = elBB.width + 2*padding;
|
||||
svg.viewBox.baseVal.width = width;
|
||||
const height = elBB.height + 2*padding;
|
||||
svg.viewBox.baseVal.height = height;
|
||||
svg.setAttribute("width", `${width}`);
|
||||
svg.setAttribute("height", `${height}`);
|
||||
}
|
||||
}
|
||||
if (template?.hasSVGwithBitmap) {
|
||||
svg.setAttribute("hasbitmap", "true");
|
||||
}
|
||||
return svg;
|
||||
}
|
||||
|
||||
function estimateLineBound(points: any): [number, number, number, number] {
|
||||
let minX = Infinity;
|
||||
let minY = Infinity;
|
||||
let maxX = -Infinity;
|
||||
let maxY = -Infinity;
|
||||
|
||||
for (const [x, y] of points) {
|
||||
minX = Math.min(minX, x);
|
||||
minY = Math.min(minY, y);
|
||||
maxX = Math.max(maxX, x);
|
||||
maxY = Math.max(maxY, y);
|
||||
}
|
||||
|
||||
return [minX, minY, maxX, maxY];
|
||||
}
|
||||
|
||||
export function estimateBounds(
|
||||
elements: ExcalidrawElement[],
|
||||
): [number, number, number, number] {
|
||||
const bb = getCommonBoundingBox(elements);
|
||||
return [bb.minX, bb.minY, bb.maxX, bb.maxY];
|
||||
}
|
||||
|
||||
export function repositionElementsToCursor(
|
||||
elements: ExcalidrawElement[],
|
||||
newPosition: { x: number; y: number },
|
||||
center: boolean = false,
|
||||
): ExcalidrawElement[] {
|
||||
const [x1, y1, x2, y2] = estimateBounds(elements);
|
||||
let [offsetX, offsetY] = [0, 0];
|
||||
if (center) {
|
||||
[offsetX, offsetY] = [
|
||||
newPosition.x - (x1 + x2) / 2,
|
||||
newPosition.y - (y1 + y2) / 2,
|
||||
];
|
||||
} else {
|
||||
[offsetX, offsetY] = [newPosition.x - x1, newPosition.y - y1];
|
||||
}
|
||||
|
||||
elements.forEach((element: any) => {
|
||||
//using any so I can write read-only propery x & y
|
||||
element.x = element.x + offsetX;
|
||||
element.y = element.y + offsetY;
|
||||
});
|
||||
|
||||
return restore({elements}, null, null).elements;
|
||||
}
|
||||
|
||||
export const insertLaTeXToView = (view: ExcalidrawView) => {
|
||||
const app = view.plugin.app;
|
||||
const ea = view.plugin.ea;
|
||||
GenericInputPrompt.Prompt(
|
||||
view,
|
||||
view.plugin,
|
||||
app,
|
||||
t("ENTER_LATEX"),
|
||||
"\\color{red}\\oint_S {E_n dA = \\frac{1}{{\\varepsilon _0 }}} Q_{inside}",
|
||||
view.plugin.settings.latexBoilerplate,
|
||||
undefined,
|
||||
3
|
||||
).then(async (formula: string) => {
|
||||
if (!formula) {
|
||||
return;
|
||||
}
|
||||
ea.reset();
|
||||
await ea.addLaTex(0, 0, formula);
|
||||
ea.setView(view);
|
||||
ea.addElementsToView(true, false, true);
|
||||
});
|
||||
};
|
||||
|
||||
export const search = async (view: ExcalidrawView) => {
|
||||
const ea = view.plugin.ea;
|
||||
ea.reset();
|
||||
ea.setView(view);
|
||||
const elements = ea.getViewElements().filter((el) => el.type === "text" || el.type === "frame" || el.link || el.type === "image");
|
||||
if (elements.length === 0) {
|
||||
return;
|
||||
}
|
||||
let text = await ScriptEngine.inputPrompt(
|
||||
view,
|
||||
view.plugin,
|
||||
view.plugin.app,
|
||||
"Search for",
|
||||
"use quotation marks for exact match",
|
||||
"",
|
||||
);
|
||||
if (!text) {
|
||||
return;
|
||||
}
|
||||
const res = text.matchAll(/"(.*?)"/g);
|
||||
let query: string[] = [];
|
||||
let parts;
|
||||
while (!(parts = res.next()).done) {
|
||||
query.push(parts.value[1]);
|
||||
}
|
||||
text = text.replaceAll(/"(.*?)"/g, "");
|
||||
query = query.concat(text.split(" ").filter((s:string) => s.length !== 0));
|
||||
|
||||
ea.targetView.selectElementsMatchingQuery(elements, query);
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param elements
|
||||
* @param query
|
||||
* @param exactMatch - when searching for section header exactMatch should be set to true
|
||||
* @returns the elements matching the query
|
||||
*/
|
||||
export const getTextElementsMatchingQuery = (
|
||||
elements: ExcalidrawElement[],
|
||||
query: string[],
|
||||
exactMatch: boolean = false, //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/530
|
||||
): ExcalidrawElement[] => {
|
||||
if (!elements || elements.length === 0 || !query || query.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return elements.filter((el: any) =>
|
||||
el.type === "text" &&
|
||||
query.some((q) => {
|
||||
if (exactMatch) {
|
||||
const text = el.rawText.toLowerCase().split("\n")[0].trim();
|
||||
const m = text.match(/^#*(# .*)/);
|
||||
if (!m || m.length !== 2) {
|
||||
return false;
|
||||
}
|
||||
return m[1] === q.toLowerCase();
|
||||
}
|
||||
const text = el.rawText.toLowerCase().replaceAll("\n", " ").trim();
|
||||
return text.match(q.toLowerCase()); //to distinguish between "# frame" and "# frame 1" https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/530
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param elements
|
||||
* @param query
|
||||
* @param exactMatch - when searching for section header exactMatch should be set to true
|
||||
* @returns the elements matching the query
|
||||
*/
|
||||
export const getFrameElementsMatchingQuery = (
|
||||
elements: ExcalidrawElement[],
|
||||
query: string[],
|
||||
exactMatch: boolean = false, //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/530
|
||||
): ExcalidrawElement[] => {
|
||||
if (!elements || elements.length === 0 || !query || query.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return elements.filter((el: any) =>
|
||||
el.type === "frame" &&
|
||||
query.some((q) => {
|
||||
if (exactMatch) {
|
||||
const text = el.name?.toLowerCase().split("\n")[0].trim() ?? "";
|
||||
const m = text.match(/^#*(# .*)/);
|
||||
if (!m || m.length !== 2) {
|
||||
return false;
|
||||
}
|
||||
return m[1] === q.toLowerCase();
|
||||
}
|
||||
const text = el.name
|
||||
? el.name.toLowerCase().replaceAll("\n", " ").trim()
|
||||
: "";
|
||||
|
||||
return text.match(q.toLowerCase()); //to distinguish between "# frame" and "# frame 1" https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/530
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param elements
|
||||
* @param query
|
||||
* @param exactMatch - when searching for section header exactMatch should be set to true
|
||||
* @returns the elements matching the query
|
||||
*/
|
||||
export const getElementsWithLinkMatchingQuery = (
|
||||
elements: ExcalidrawElement[],
|
||||
query: string[],
|
||||
exactMatch: boolean = false, //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/530
|
||||
): ExcalidrawElement[] => {
|
||||
if (!elements || elements.length === 0 || !query || query.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return elements.filter((el: any) =>
|
||||
el.link &&
|
||||
query.some((q) => {
|
||||
const text = el.link.toLowerCase().trim();
|
||||
return exactMatch
|
||||
? (text === q.toLowerCase())
|
||||
: text.match(q.toLowerCase());
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param elements
|
||||
* @param query
|
||||
* @param exactMatch - when searching for section header exactMatch should be set to true
|
||||
* @returns the elements matching the query
|
||||
*/
|
||||
export const getImagesMatchingQuery = (
|
||||
elements: ExcalidrawElement[],
|
||||
query: string[],
|
||||
excalidrawData: ExcalidrawData,
|
||||
exactMatch: boolean = false, //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/530
|
||||
): ExcalidrawElement[] => {
|
||||
if (!elements || elements.length === 0 || !query || query.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return elements.filter((el: ExcalidrawElement) =>
|
||||
el.type === "image" &&
|
||||
query.some((q) => {
|
||||
const filename = excalidrawData.getFile(el.fileId)?.file?.basename.toLowerCase().trim();
|
||||
const equation = excalidrawData.getEquation(el.fileId)?.latex?.toLocaleLowerCase().trim();
|
||||
const text = filename ?? equation;
|
||||
if(!text) return false;
|
||||
return exactMatch
|
||||
? (text === q.toLowerCase())
|
||||
: text.match(q.toLowerCase());
|
||||
}));
|
||||
}
|
||||
|
||||
export const cloneElement = (el: ExcalidrawElement):any => {
|
||||
const newEl = JSON.parse(JSON.stringify(el));
|
||||
newEl.version = el.version + 1;
|
||||
newEl.updated = Date.now();
|
||||
newEl.versionNonce = Math.floor(Math.random() * 1000000000);
|
||||
return newEl;
|
||||
}
|
||||
|
||||
export const verifyMinimumPluginVersion = (requiredVersion: string): boolean => {
|
||||
return PLUGIN_VERSION === requiredVersion || isVersionNewerThanOther(PLUGIN_VERSION,requiredVersion);
|
||||
}
|
||||
|
||||
export const getBoundTextElementId = (container: ExcalidrawElement | null) => {
|
||||
return container?.boundElements?.length
|
||||
? container?.boundElements?.find((ele) => ele.type === "text")?.id || null
|
||||
: null;
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Notice, RequestUrlResponse, requestUrl } from "obsidian";
|
||||
import { Notice, RequestUrlResponse } from "obsidian";
|
||||
import ExcalidrawPlugin from "src/core/main";
|
||||
|
||||
type MessageContent =
|
||||
|
||||
@@ -3,7 +3,7 @@ import { ColorMaster } from "@zsviczian/colormaster";
|
||||
import { ExcalidrawAutomate } from "src/shared/ExcalidrawAutomate";
|
||||
import ExcalidrawView from "src/view/ExcalidrawView";
|
||||
import { DynamicStyle } from "src/types/types";
|
||||
import { cloneElement } from "src/shared/ExcalidrawAutomate";
|
||||
import { cloneElement } from "./excalidrawAutomateUtils";
|
||||
import { ExcalidrawFrameElement } from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
import { addAppendUpdateCustomData } from "./utils";
|
||||
import { mutateElement } from "src/constants/constants";
|
||||
|
||||
@@ -1,8 +1,49 @@
|
||||
import { ExcalidrawElement } from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
import { ExcalidrawAutomate } from "src/shared/ExcalidrawAutomate";
|
||||
import { errorlog } from "./utils";
|
||||
import { ColorMap } from "src/shared/EmbeddedFileLoader";
|
||||
import { DataURL } from "@zsviczian/excalidraw/types/excalidraw/types";
|
||||
import ExcalidrawPlugin from "src/core/main";
|
||||
import {
|
||||
ExcalidrawElement,
|
||||
ExcalidrawImageElement,
|
||||
FileId,
|
||||
} from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
import { normalizePath, TFile } from "obsidian";
|
||||
|
||||
import ExcalidrawView, { ExportSettings, getTextMode } from "src/view/ExcalidrawView";
|
||||
import {
|
||||
GITHUB_RELEASES,
|
||||
getCommonBoundingBox,
|
||||
restore,
|
||||
REG_LINKINDEX_INVALIDCHARS,
|
||||
THEME_FILTER,
|
||||
EXCALIDRAW_PLUGIN,
|
||||
getFontFamilyString,
|
||||
getLineHeight,
|
||||
measureText,
|
||||
} from "src/constants/constants";
|
||||
import {
|
||||
//debug,
|
||||
errorlog,
|
||||
getEmbeddedFilenameParts,
|
||||
getLinkParts,
|
||||
getPNG,
|
||||
getSVG,
|
||||
isVersionNewerThanOther,
|
||||
scaleLoadedImage,
|
||||
} from "src/utils/utils";
|
||||
import { GenericInputPrompt, NewFileActions } from "src/shared/Dialogs/Prompt";
|
||||
import { t } from "src/lang/helpers";
|
||||
import { Mutable } from "@zsviczian/excalidraw/types/excalidraw/utility-types";
|
||||
import {
|
||||
postOpenAI as _postOpenAI,
|
||||
extractCodeBlocks as _extractCodeBlocks,
|
||||
} from "../utils/AIUtils";
|
||||
import { ColorMap, EmbeddedFilesLoader, FileData } from "src/shared/EmbeddedFileLoader";
|
||||
import { SVGColorInfo } from "src/types/excalidrawAutomateTypes";
|
||||
import { ExcalidrawData, getExcalidrawMarkdownHeaderSection, REGEX_LINK } from "src/shared/ExcalidrawData";
|
||||
import { getFrameBasedOnFrameNameOrId } from "./excalidrawViewUtils";
|
||||
import { ScriptEngine } from "src/shared/Scripts";
|
||||
|
||||
declare const PLUGIN_VERSION:string;
|
||||
|
||||
export function isSVGColorInfo(obj: ColorMap | SVGColorInfo): boolean {
|
||||
return (
|
||||
@@ -112,3 +153,668 @@ export function isColorStringTransparent(color: string): boolean {
|
||||
|
||||
return rgbaHslaTransparentRegex.test(color) || hexTransparentRegex.test(color);
|
||||
}
|
||||
|
||||
export function initExcalidrawAutomate(
|
||||
plugin: ExcalidrawPlugin,
|
||||
): ExcalidrawAutomate {
|
||||
const ea = new ExcalidrawAutomate(plugin);
|
||||
//@ts-ignore
|
||||
window.ExcalidrawAutomate = ea;
|
||||
return ea;
|
||||
}
|
||||
|
||||
export function normalizeLinePoints(
|
||||
points: [x: number, y: number][],
|
||||
//box: { x: number; y: number; w: number; h: number },
|
||||
): number[][] {
|
||||
const p = [];
|
||||
const [x, y] = points[0];
|
||||
for (let i = 0; i < points.length; i++) {
|
||||
p.push([points[i][0] - x, points[i][1] - y]);
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
export function getLineBox(
|
||||
points: [x: number, y: number][]
|
||||
):{x:number, y:number, w: number, h:number} {
|
||||
const [x1, y1, x2, y2] = estimateLineBound(points);
|
||||
return {
|
||||
x: x1,
|
||||
y: y1,
|
||||
w: x2 - x1, //Math.abs(points[points.length-1][0]-points[0][0]),
|
||||
h: y2 - y1, //Math.abs(points[points.length-1][1]-points[0][1])
|
||||
};
|
||||
}
|
||||
|
||||
export function getFontFamily(id: number):string {
|
||||
return getFontFamilyString({fontFamily:id})
|
||||
}
|
||||
|
||||
export function _measureText(
|
||||
newText: string,
|
||||
fontSize: number,
|
||||
fontFamily: number,
|
||||
lineHeight: number,
|
||||
): {w: number, h:number} {
|
||||
//following odd error with mindmap on iPad while synchornizing with desktop.
|
||||
if (!fontSize) {
|
||||
fontSize = 20;
|
||||
}
|
||||
if (!fontFamily) {
|
||||
fontFamily = 1;
|
||||
lineHeight = getLineHeight(fontFamily);
|
||||
}
|
||||
const metrics = measureText(
|
||||
newText,
|
||||
`${fontSize.toString()}px ${getFontFamily(fontFamily)}` as any,
|
||||
lineHeight
|
||||
);
|
||||
return { w: metrics.width, h: metrics.height };
|
||||
}
|
||||
|
||||
export async function getTemplate(
|
||||
plugin: ExcalidrawPlugin,
|
||||
fileWithPath: string,
|
||||
loadFiles: boolean = false,
|
||||
loader: EmbeddedFilesLoader,
|
||||
depth: number,
|
||||
convertMarkdownLinksToObsidianURLs: boolean = false,
|
||||
): Promise<{
|
||||
elements: any;
|
||||
appState: any;
|
||||
frontmatter: string;
|
||||
files: any;
|
||||
hasSVGwithBitmap: boolean;
|
||||
plaintext: string; //markdown data above Excalidraw data and below YAML frontmatter
|
||||
}> {
|
||||
const app = plugin.app;
|
||||
const vault = app.vault;
|
||||
const filenameParts = getEmbeddedFilenameParts(fileWithPath);
|
||||
const templatePath = normalizePath(filenameParts.filepath);
|
||||
const file = app.metadataCache.getFirstLinkpathDest(templatePath, "");
|
||||
let hasSVGwithBitmap = false;
|
||||
if (file && file instanceof TFile) {
|
||||
const data = (await vault.read(file))
|
||||
.replaceAll("\r\n", "\n")
|
||||
.replaceAll("\r", "\n");
|
||||
const excalidrawData: ExcalidrawData = new ExcalidrawData(plugin);
|
||||
|
||||
if (file.extension === "excalidraw") {
|
||||
await excalidrawData.loadLegacyData(data, file);
|
||||
return {
|
||||
elements: convertMarkdownLinksToObsidianURLs
|
||||
? updateElementLinksToObsidianLinks({
|
||||
elements: excalidrawData.scene.elements,
|
||||
hostFile: file,
|
||||
}) : excalidrawData.scene.elements,
|
||||
appState: excalidrawData.scene.appState,
|
||||
frontmatter: "",
|
||||
files: excalidrawData.scene.files,
|
||||
hasSVGwithBitmap,
|
||||
plaintext: "",
|
||||
};
|
||||
}
|
||||
|
||||
const textMode = getTextMode(data);
|
||||
await excalidrawData.loadData(
|
||||
data,
|
||||
file,
|
||||
textMode,
|
||||
);
|
||||
|
||||
let trimLocation = data.search(/^##? Text Elements$/m);
|
||||
if (trimLocation == -1) {
|
||||
trimLocation = data.search(/##? Drawing\n/);
|
||||
}
|
||||
|
||||
let scene = excalidrawData.scene;
|
||||
|
||||
let groupElements:ExcalidrawElement[] = scene.elements;
|
||||
if(filenameParts.hasGroupref) {
|
||||
const el = filenameParts.hasSectionref
|
||||
? getTextElementsMatchingQuery(scene.elements,["# "+filenameParts.sectionref],true)
|
||||
: scene.elements.filter((el: ExcalidrawElement)=>el.id===filenameParts.blockref);
|
||||
if(el.length > 0) {
|
||||
groupElements = plugin.ea.getElementsInTheSameGroupWithElement(el[0],scene.elements,true)
|
||||
}
|
||||
}
|
||||
if(filenameParts.hasFrameref || filenameParts.hasClippedFrameref) {
|
||||
const el = getFrameBasedOnFrameNameOrId(filenameParts.blockref,scene.elements);
|
||||
|
||||
if(el) {
|
||||
groupElements = plugin.ea.getElementsInFrame(el,scene.elements, filenameParts.hasClippedFrameref);
|
||||
}
|
||||
}
|
||||
|
||||
if(filenameParts.hasTaskbone) {
|
||||
groupElements = groupElements.filter( el =>
|
||||
el.type==="freedraw" ||
|
||||
( el.type==="image" &&
|
||||
!plugin.isExcalidrawFile(excalidrawData.getFile(el.fileId)?.file)
|
||||
));
|
||||
}
|
||||
|
||||
let fileIDWhiteList:Set<FileId>;
|
||||
|
||||
if(groupElements.length < scene.elements.length) {
|
||||
fileIDWhiteList = new Set<FileId>();
|
||||
groupElements.filter(el=>el.type==="image").forEach((el:ExcalidrawImageElement)=>fileIDWhiteList.add(el.fileId));
|
||||
}
|
||||
|
||||
if (loadFiles) {
|
||||
//debug({where:"getTemplate",template:file.name,loader:loader.uid});
|
||||
await loader.loadSceneFiles({
|
||||
excalidrawData,
|
||||
addFiles: (fileArray: FileData[]) => {
|
||||
//, isDark: boolean) => {
|
||||
if (!fileArray || fileArray.length === 0) {
|
||||
return;
|
||||
}
|
||||
for (const f of fileArray) {
|
||||
if (f.hasSVGwithBitmap) {
|
||||
hasSVGwithBitmap = true;
|
||||
}
|
||||
excalidrawData.scene.files[f.id] = {
|
||||
mimeType: f.mimeType,
|
||||
id: f.id,
|
||||
dataURL: f.dataURL,
|
||||
created: f.created,
|
||||
};
|
||||
}
|
||||
scene = scaleLoadedImage(excalidrawData.scene, fileArray).scene;
|
||||
},
|
||||
depth,
|
||||
fileIDWhiteList
|
||||
});
|
||||
}
|
||||
|
||||
excalidrawData.destroy();
|
||||
const filehead = getExcalidrawMarkdownHeaderSection(data); // data.substring(0, trimLocation);
|
||||
let files:any = {};
|
||||
const sceneFilesSize = Object.values(scene.files).length;
|
||||
if (sceneFilesSize > 0) {
|
||||
if(fileIDWhiteList && (sceneFilesSize > fileIDWhiteList.size)) {
|
||||
Object.values(scene.files).filter((f: any) => fileIDWhiteList.has(f.id)).forEach((f: any) => {
|
||||
files[f.id] = f;
|
||||
});
|
||||
} else {
|
||||
files = scene.files;
|
||||
}
|
||||
}
|
||||
|
||||
const frontmatter = filehead.match(/^---\n.*\n---\n/ms)?.[0] ?? filehead;
|
||||
return {
|
||||
elements: convertMarkdownLinksToObsidianURLs
|
||||
? updateElementLinksToObsidianLinks({
|
||||
elements: groupElements,
|
||||
hostFile: file,
|
||||
}) : groupElements,
|
||||
appState: scene.appState,
|
||||
frontmatter,
|
||||
plaintext: frontmatter !== filehead
|
||||
? (filehead.split(/^---\n.*\n---\n/ms)?.[1] ?? "")
|
||||
: "",
|
||||
files,
|
||||
hasSVGwithBitmap,
|
||||
};
|
||||
}
|
||||
return {
|
||||
elements: [],
|
||||
appState: {},
|
||||
frontmatter: null,
|
||||
files: [],
|
||||
hasSVGwithBitmap,
|
||||
plaintext: "",
|
||||
};
|
||||
}
|
||||
|
||||
export const generatePlaceholderDataURL = (width: number, height: number): DataURL => {
|
||||
const svgString = `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}"><rect width="100%" height="100%" fill="#E7E7E7" /><text x="${width / 2}" y="${height / 2}" dominant-baseline="middle" text-anchor="middle" font-family="Arial" font-size="${Math.min(width, height) / 5}" fill="#888">Placeholder</text></svg>`;
|
||||
return `data:image/svg+xml;base64,${btoa(svgString)}` as DataURL;
|
||||
};
|
||||
|
||||
export async function createPNG(
|
||||
templatePath: string = undefined,
|
||||
scale: number = 1,
|
||||
exportSettings: ExportSettings,
|
||||
loader: EmbeddedFilesLoader,
|
||||
forceTheme: string = undefined,
|
||||
canvasTheme: string = undefined,
|
||||
canvasBackgroundColor: string = undefined,
|
||||
automateElements: ExcalidrawElement[] = [],
|
||||
plugin: ExcalidrawPlugin,
|
||||
depth: number,
|
||||
padding?: number,
|
||||
imagesDict?: any,
|
||||
): Promise<Blob> {
|
||||
if (!loader) {
|
||||
loader = new EmbeddedFilesLoader(plugin);
|
||||
}
|
||||
padding = padding ?? plugin.settings.exportPaddingSVG;
|
||||
const template = templatePath
|
||||
? await getTemplate(plugin, templatePath, true, loader, depth)
|
||||
: null;
|
||||
let elements = template?.elements ?? [];
|
||||
elements = elements.concat(automateElements);
|
||||
const files = imagesDict ?? {};
|
||||
if(template?.files) {
|
||||
Object.values(template.files).forEach((f:any)=>{
|
||||
if(!f.dataURL.startsWith("http")) {
|
||||
files[f.id]=f;
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
return await getPNG(
|
||||
{
|
||||
type: "excalidraw",
|
||||
version: 2,
|
||||
source: GITHUB_RELEASES+PLUGIN_VERSION,
|
||||
elements,
|
||||
appState: {
|
||||
theme: forceTheme ?? template?.appState?.theme ?? canvasTheme,
|
||||
viewBackgroundColor:
|
||||
template?.appState?.viewBackgroundColor ?? canvasBackgroundColor,
|
||||
...template?.appState?.frameRendering ? {frameRendering: template.appState.frameRendering} : {},
|
||||
},
|
||||
files,
|
||||
},
|
||||
{
|
||||
withBackground:
|
||||
exportSettings?.withBackground ?? plugin.settings.exportWithBackground,
|
||||
withTheme: exportSettings?.withTheme ?? plugin.settings.exportWithTheme,
|
||||
isMask: exportSettings?.isMask ?? false,
|
||||
},
|
||||
padding,
|
||||
scale,
|
||||
);
|
||||
}
|
||||
|
||||
export const updateElementLinksToObsidianLinks = ({elements, hostFile}:{
|
||||
elements: ExcalidrawElement[];
|
||||
hostFile: TFile;
|
||||
}): ExcalidrawElement[] => {
|
||||
return elements.map((el)=>{
|
||||
if(el.link && el.link.startsWith("[")) {
|
||||
const partsArray = REGEX_LINK.getResList(el.link)[0];
|
||||
if(!partsArray?.value) return el;
|
||||
let linkText = REGEX_LINK.getLink(partsArray);
|
||||
if (linkText.search("#") > -1) {
|
||||
const linkParts = getLinkParts(linkText, hostFile);
|
||||
linkText = linkParts.path;
|
||||
}
|
||||
if (linkText.match(REG_LINKINDEX_INVALIDCHARS)) {
|
||||
return el;
|
||||
}
|
||||
const file = EXCALIDRAW_PLUGIN.app.metadataCache.getFirstLinkpathDest(
|
||||
linkText,
|
||||
hostFile.path,
|
||||
);
|
||||
if(!file) {
|
||||
return el;
|
||||
}
|
||||
let link = EXCALIDRAW_PLUGIN.app.getObsidianUrl(file);
|
||||
if(window.ExcalidrawAutomate?.onUpdateElementLinkForExportHook) {
|
||||
link = window.ExcalidrawAutomate.onUpdateElementLinkForExportHook({
|
||||
originalLink: el.link,
|
||||
obsidianLink: link,
|
||||
linkedFile: file,
|
||||
hostFile: hostFile
|
||||
});
|
||||
}
|
||||
const newElement: Mutable<ExcalidrawElement> = cloneElement(el);
|
||||
newElement.link = link;
|
||||
return newElement;
|
||||
}
|
||||
return el;
|
||||
})
|
||||
}
|
||||
|
||||
function addFilterToForeignObjects(svg:SVGSVGElement):void {
|
||||
const foreignObjects = svg.querySelectorAll("foreignObject");
|
||||
foreignObjects.forEach((foreignObject) => {
|
||||
foreignObject.setAttribute("filter", THEME_FILTER);
|
||||
});
|
||||
}
|
||||
|
||||
export async function createSVG(
|
||||
templatePath: string = undefined,
|
||||
embedFont: boolean = false,
|
||||
exportSettings: ExportSettings,
|
||||
loader: EmbeddedFilesLoader,
|
||||
forceTheme: string = undefined,
|
||||
canvasTheme: string = undefined,
|
||||
canvasBackgroundColor: string = undefined,
|
||||
automateElements: ExcalidrawElement[] = [],
|
||||
plugin: ExcalidrawPlugin,
|
||||
depth: number,
|
||||
padding?: number,
|
||||
imagesDict?: any,
|
||||
convertMarkdownLinksToObsidianURLs: boolean = false,
|
||||
): Promise<SVGSVGElement> {
|
||||
if (!loader) {
|
||||
loader = new EmbeddedFilesLoader(plugin);
|
||||
}
|
||||
if(typeof exportSettings.skipInliningFonts === "undefined") {
|
||||
exportSettings.skipInliningFonts = !embedFont;
|
||||
}
|
||||
const template = templatePath
|
||||
? await getTemplate(plugin, templatePath, true, loader, depth, convertMarkdownLinksToObsidianURLs)
|
||||
: null;
|
||||
let elements = template?.elements ?? [];
|
||||
elements = elements.concat(automateElements);
|
||||
padding = padding ?? plugin.settings.exportPaddingSVG;
|
||||
const files = imagesDict ?? {};
|
||||
if(template?.files) {
|
||||
Object.values(template.files).forEach((f:any)=>{
|
||||
files[f.id]=f;
|
||||
});
|
||||
}
|
||||
|
||||
const theme = forceTheme ?? template?.appState?.theme ?? canvasTheme;
|
||||
const withTheme = exportSettings?.withTheme ?? plugin.settings.exportWithTheme;
|
||||
|
||||
const filenameParts = getEmbeddedFilenameParts(templatePath);
|
||||
const svg = await getSVG(
|
||||
{
|
||||
//createAndOpenDrawing
|
||||
type: "excalidraw",
|
||||
version: 2,
|
||||
source: GITHUB_RELEASES+PLUGIN_VERSION,
|
||||
elements,
|
||||
appState: {
|
||||
theme,
|
||||
viewBackgroundColor:
|
||||
template?.appState?.viewBackgroundColor ?? canvasBackgroundColor,
|
||||
...template?.appState?.frameRendering ? {frameRendering: template.appState.frameRendering} : {},
|
||||
},
|
||||
files,
|
||||
},
|
||||
{
|
||||
withBackground:
|
||||
exportSettings?.withBackground ?? plugin.settings.exportWithBackground,
|
||||
withTheme,
|
||||
isMask: exportSettings?.isMask ?? false,
|
||||
...filenameParts?.hasClippedFrameref
|
||||
? {frameRendering: {enabled: true, name: false, outline: false, clip: true}}
|
||||
: {},
|
||||
},
|
||||
padding,
|
||||
null,
|
||||
);
|
||||
|
||||
if (withTheme && theme === "dark") addFilterToForeignObjects(svg);
|
||||
|
||||
if(
|
||||
!(filenameParts.hasGroupref || filenameParts.hasFrameref || filenameParts.hasClippedFrameref) &&
|
||||
(filenameParts.hasBlockref || filenameParts.hasSectionref)
|
||||
) {
|
||||
let el = filenameParts.hasSectionref
|
||||
? getTextElementsMatchingQuery(elements,["# "+filenameParts.sectionref],true)
|
||||
: elements.filter((el: ExcalidrawElement)=>el.id===filenameParts.blockref);
|
||||
if(el.length>0) {
|
||||
const containerId = el[0].containerId;
|
||||
if(containerId) {
|
||||
el = el.concat(elements.filter((el: ExcalidrawElement)=>el.id === containerId));
|
||||
}
|
||||
const elBB = plugin.ea.getBoundingBox(el);
|
||||
const drawingBB = plugin.ea.getBoundingBox(elements);
|
||||
svg.viewBox.baseVal.x = elBB.topX - drawingBB.topX;
|
||||
svg.viewBox.baseVal.y = elBB.topY - drawingBB.topY;
|
||||
const width = elBB.width + 2*padding;
|
||||
svg.viewBox.baseVal.width = width;
|
||||
const height = elBB.height + 2*padding;
|
||||
svg.viewBox.baseVal.height = height;
|
||||
svg.setAttribute("width", `${width}`);
|
||||
svg.setAttribute("height", `${height}`);
|
||||
}
|
||||
}
|
||||
if (template?.hasSVGwithBitmap) {
|
||||
svg.setAttribute("hasbitmap", "true");
|
||||
}
|
||||
return svg;
|
||||
}
|
||||
|
||||
function estimateLineBound(points: any): [number, number, number, number] {
|
||||
let minX = Infinity;
|
||||
let minY = Infinity;
|
||||
let maxX = -Infinity;
|
||||
let maxY = -Infinity;
|
||||
|
||||
for (const [x, y] of points) {
|
||||
minX = Math.min(minX, x);
|
||||
minY = Math.min(minY, y);
|
||||
maxX = Math.max(maxX, x);
|
||||
maxY = Math.max(maxY, y);
|
||||
}
|
||||
|
||||
return [minX, minY, maxX, maxY];
|
||||
}
|
||||
|
||||
export function estimateBounds(
|
||||
elements: ExcalidrawElement[],
|
||||
): [number, number, number, number] {
|
||||
const bb = getCommonBoundingBox(elements);
|
||||
return [bb.minX, bb.minY, bb.maxX, bb.maxY];
|
||||
}
|
||||
|
||||
export function repositionElementsToCursor(
|
||||
elements: ExcalidrawElement[],
|
||||
newPosition: { x: number; y: number },
|
||||
center: boolean = false,
|
||||
): ExcalidrawElement[] {
|
||||
const [x1, y1, x2, y2] = estimateBounds(elements);
|
||||
let [offsetX, offsetY] = [0, 0];
|
||||
if (center) {
|
||||
[offsetX, offsetY] = [
|
||||
newPosition.x - (x1 + x2) / 2,
|
||||
newPosition.y - (y1 + y2) / 2,
|
||||
];
|
||||
} else {
|
||||
[offsetX, offsetY] = [newPosition.x - x1, newPosition.y - y1];
|
||||
}
|
||||
|
||||
elements.forEach((element: any) => {
|
||||
//using any so I can write read-only propery x & y
|
||||
element.x = element.x + offsetX;
|
||||
element.y = element.y + offsetY;
|
||||
});
|
||||
|
||||
return restore({elements}, null, null).elements;
|
||||
}
|
||||
|
||||
export const insertLaTeXToView = (view: ExcalidrawView) => {
|
||||
const app = view.plugin.app;
|
||||
const ea = view.plugin.ea;
|
||||
GenericInputPrompt.Prompt(
|
||||
view,
|
||||
view.plugin,
|
||||
app,
|
||||
t("ENTER_LATEX"),
|
||||
"\\color{red}\\oint_S {E_n dA = \\frac{1}{{\\varepsilon _0 }}} Q_{inside}",
|
||||
view.plugin.settings.latexBoilerplate,
|
||||
undefined,
|
||||
3
|
||||
).then(async (formula: string) => {
|
||||
if (!formula) {
|
||||
return;
|
||||
}
|
||||
ea.reset();
|
||||
await ea.addLaTex(0, 0, formula);
|
||||
ea.setView(view);
|
||||
ea.addElementsToView(true, false, true);
|
||||
});
|
||||
};
|
||||
|
||||
export const search = async (view: ExcalidrawView) => {
|
||||
const ea = view.plugin.ea;
|
||||
ea.reset();
|
||||
ea.setView(view);
|
||||
const elements = ea.getViewElements().filter((el) => el.type === "text" || el.type === "frame" || el.link || el.type === "image");
|
||||
if (elements.length === 0) {
|
||||
return;
|
||||
}
|
||||
let text = await ScriptEngine.inputPrompt(
|
||||
view,
|
||||
view.plugin,
|
||||
view.plugin.app,
|
||||
"Search for",
|
||||
"use quotation marks for exact match",
|
||||
"",
|
||||
);
|
||||
if (!text) {
|
||||
return;
|
||||
}
|
||||
const res = text.matchAll(/"(.*?)"/g);
|
||||
let query: string[] = [];
|
||||
let parts;
|
||||
while (!(parts = res.next()).done) {
|
||||
query.push(parts.value[1]);
|
||||
}
|
||||
text = text.replaceAll(/"(.*?)"/g, "");
|
||||
query = query.concat(text.split(" ").filter((s:string) => s.length !== 0));
|
||||
|
||||
ea.targetView.selectElementsMatchingQuery(elements, query);
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param elements
|
||||
* @param query
|
||||
* @param exactMatch - when searching for section header exactMatch should be set to true
|
||||
* @returns the elements matching the query
|
||||
*/
|
||||
export const getTextElementsMatchingQuery = (
|
||||
elements: ExcalidrawElement[],
|
||||
query: string[],
|
||||
exactMatch: boolean = false, //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/530
|
||||
): ExcalidrawElement[] => {
|
||||
if (!elements || elements.length === 0 || !query || query.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return elements.filter((el: any) =>
|
||||
el.type === "text" &&
|
||||
query.some((q) => {
|
||||
if (exactMatch) {
|
||||
const text = el.rawText.toLowerCase().split("\n")[0].trim();
|
||||
const m = text.match(/^#*(# .*)/);
|
||||
if (!m || m.length !== 2) {
|
||||
return false;
|
||||
}
|
||||
return m[1] === q.toLowerCase();
|
||||
}
|
||||
const text = el.rawText.toLowerCase().replaceAll("\n", " ").trim();
|
||||
return text.match(q.toLowerCase()); //to distinguish between "# frame" and "# frame 1" https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/530
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param elements
|
||||
* @param query
|
||||
* @param exactMatch - when searching for section header exactMatch should be set to true
|
||||
* @returns the elements matching the query
|
||||
*/
|
||||
export const getFrameElementsMatchingQuery = (
|
||||
elements: ExcalidrawElement[],
|
||||
query: string[],
|
||||
exactMatch: boolean = false, //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/530
|
||||
): ExcalidrawElement[] => {
|
||||
if (!elements || elements.length === 0 || !query || query.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return elements.filter((el: any) =>
|
||||
el.type === "frame" &&
|
||||
query.some((q) => {
|
||||
if (exactMatch) {
|
||||
const text = el.name?.toLowerCase().split("\n")[0].trim() ?? "";
|
||||
const m = text.match(/^#*(# .*)/);
|
||||
if (!m || m.length !== 2) {
|
||||
return false;
|
||||
}
|
||||
return m[1] === q.toLowerCase();
|
||||
}
|
||||
const text = el.name
|
||||
? el.name.toLowerCase().replaceAll("\n", " ").trim()
|
||||
: "";
|
||||
|
||||
return text.match(q.toLowerCase()); //to distinguish between "# frame" and "# frame 1" https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/530
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param elements
|
||||
* @param query
|
||||
* @param exactMatch - when searching for section header exactMatch should be set to true
|
||||
* @returns the elements matching the query
|
||||
*/
|
||||
export const getElementsWithLinkMatchingQuery = (
|
||||
elements: ExcalidrawElement[],
|
||||
query: string[],
|
||||
exactMatch: boolean = false, //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/530
|
||||
): ExcalidrawElement[] => {
|
||||
if (!elements || elements.length === 0 || !query || query.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return elements.filter((el: any) =>
|
||||
el.link &&
|
||||
query.some((q) => {
|
||||
const text = el.link.toLowerCase().trim();
|
||||
return exactMatch
|
||||
? (text === q.toLowerCase())
|
||||
: text.match(q.toLowerCase());
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param elements
|
||||
* @param query
|
||||
* @param exactMatch - when searching for section header exactMatch should be set to true
|
||||
* @returns the elements matching the query
|
||||
*/
|
||||
export const getImagesMatchingQuery = (
|
||||
elements: ExcalidrawElement[],
|
||||
query: string[],
|
||||
excalidrawData: ExcalidrawData,
|
||||
exactMatch: boolean = false, //https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/530
|
||||
): ExcalidrawElement[] => {
|
||||
if (!elements || elements.length === 0 || !query || query.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return elements.filter((el: ExcalidrawElement) =>
|
||||
el.type === "image" &&
|
||||
query.some((q) => {
|
||||
const filename = excalidrawData.getFile(el.fileId)?.file?.basename.toLowerCase().trim();
|
||||
const equation = excalidrawData.getEquation(el.fileId)?.latex?.toLocaleLowerCase().trim();
|
||||
const text = filename ?? equation;
|
||||
if(!text) return false;
|
||||
return exactMatch
|
||||
? (text === q.toLowerCase())
|
||||
: text.match(q.toLowerCase());
|
||||
}));
|
||||
}
|
||||
|
||||
export const cloneElement = (el: ExcalidrawElement):any => {
|
||||
const newEl = JSON.parse(JSON.stringify(el));
|
||||
newEl.version = el.version + 1;
|
||||
newEl.updated = Date.now();
|
||||
newEl.versionNonce = Math.floor(Math.random() * 1000000000);
|
||||
return newEl;
|
||||
}
|
||||
|
||||
export const verifyMinimumPluginVersion = (requiredVersion: string): boolean => {
|
||||
return PLUGIN_VERSION === requiredVersion || isVersionNewerThanOther(PLUGIN_VERSION,requiredVersion);
|
||||
}
|
||||
|
||||
export const getBoundTextElementId = (container: ExcalidrawElement | null) => {
|
||||
return container?.boundElements?.length
|
||||
? container?.boundElements?.find((ele) => ele.type === "text")?.id || null
|
||||
: null;
|
||||
};
|
||||
@@ -2,7 +2,7 @@ import { ExcalidrawElement, ExcalidrawImageElement, ExcalidrawTextElement } from
|
||||
import { REGEX_LINK, REG_LINKINDEX_HYPERLINK } from "../shared/ExcalidrawData";
|
||||
import ExcalidrawView, { TextMode } from "src/view/ExcalidrawView";
|
||||
import { rotatedDimensions } from "./utils";
|
||||
import { getBoundTextElementId } from "src/shared/ExcalidrawAutomate";
|
||||
import { getBoundTextElementId } from "src/utils/excalidrawAutomateUtils";
|
||||
|
||||
export const getElementsAtPointer = (
|
||||
pointer: any,
|
||||
|
||||
@@ -25,7 +25,7 @@ import { generateEmbeddableLink } from "./customEmbeddableUtils";
|
||||
import { FILENAMEPARTS } from "../types/utilTypes";
|
||||
import { Mutable } from "@zsviczian/excalidraw/types/excalidraw/utility-types";
|
||||
import { cleanBlockRef, cleanSectionHeading, getFileCSSClasses } from "./obsidianUtils";
|
||||
import { updateElementLinksToObsidianLinks } from "src/shared/ExcalidrawAutomate";
|
||||
import { updateElementLinksToObsidianLinks } from "./excalidrawAutomateUtils";
|
||||
import { CropImage } from "../shared/CropImage";
|
||||
import opentype from 'opentype.js';
|
||||
import { runCompressionWorker } from "src/shared/Workers/compression-worker";
|
||||
|
||||
@@ -57,16 +57,16 @@ import {
|
||||
getContainerElement,
|
||||
} from "../constants/constants";
|
||||
import ExcalidrawPlugin from "../core/main";
|
||||
import { ExcalidrawAutomate } from "../shared/ExcalidrawAutomate";
|
||||
import {
|
||||
repositionElementsToCursor,
|
||||
ExcalidrawAutomate,
|
||||
getTextElementsMatchingQuery,
|
||||
cloneElement,
|
||||
getFrameElementsMatchingQuery,
|
||||
getElementsWithLinkMatchingQuery,
|
||||
getImagesMatchingQuery,
|
||||
getBoundTextElementId
|
||||
} from "../shared/ExcalidrawAutomate";
|
||||
} from "../utils/excalidrawAutomateUtils";
|
||||
import { t } from "../lang/helpers";
|
||||
import {
|
||||
ExcalidrawData,
|
||||
|
||||
@@ -4,7 +4,7 @@ import * as React from "react";
|
||||
import { ActionButton } from "./ActionButton";
|
||||
import { ICONS, saveIcon, stringToSVG } from "../../../constants/actionIcons";
|
||||
import { DEVICE, SCRIPT_INSTALL_FOLDER } from "../../../constants/constants";
|
||||
import { insertLaTeXToView, search } from "../../../shared/ExcalidrawAutomate";
|
||||
import { insertLaTeXToView, search } from "../../../utils/excalidrawAutomateUtils";
|
||||
import ExcalidrawView, { TextMode } from "../../ExcalidrawView";
|
||||
import { t } from "../../../lang/helpers";
|
||||
import { ReleaseNotes } from "../../../shared/Dialogs/ReleaseNotes";
|
||||
|
||||
Reference in New Issue
Block a user