mirror of
https://github.com/zsviczian/obsidian-excalidraw-plugin.git
synced 2025-08-06 05:46:28 +00:00
Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9bfbf47963 | ||
|
|
252bf411b1 | ||
|
|
5622c019dd | ||
|
|
b32fab7865 | ||
|
|
cafdad1f7a | ||
|
|
9da40944ab | ||
|
|
f678203a64 | ||
|
|
a9572e08e9 | ||
|
|
ee9b042cdf | ||
|
|
bc138fa78a | ||
|
|
67dbe256f7 | ||
|
|
8b066d46e2 | ||
|
|
1769a65a82 | ||
|
|
941eb56769 | ||
|
|
a317613ef4 | ||
|
|
173571846f | ||
|
|
ab1078d393 | ||
|
|
681321a595 | ||
|
|
fcd50d4bc2 | ||
|
|
7e214e5aaa | ||
|
|
de39053857 | ||
|
|
f543e3218e | ||
|
|
e668aea214 | ||
|
|
d810daa735 | ||
|
|
b94b3118eb | ||
|
|
06cbd0c92d | ||
|
|
96ebbbf11d | ||
|
|
fc1467b05b | ||
|
|
d3aeedb9f6 |
4
docs/API/ExcalidrawAutomate.d.ts
vendored
4
docs/API/ExcalidrawAutomate.d.ts
vendored
@@ -6,7 +6,7 @@ import * as obsidian_module from "obsidian";
|
||||
import ExcalidrawView, { ExportSettings } from "src/ExcalidrawView";
|
||||
import { AppState, BinaryFileData, DataURL, ExcalidrawImperativeAPI, Point } from "@zsviczian/excalidraw/types/excalidraw/types";
|
||||
import { EmbeddedFilesLoader } from "src/EmbeddedFileLoader";
|
||||
import { ConnectionPoint, DeviceType } from "src/types";
|
||||
import { ConnectionPoint, DeviceType } from "src/types/types";
|
||||
import { ColorMaster } from "colormaster";
|
||||
import { TInput } from "colormaster/types";
|
||||
import { ClipboardData } from "@zsviczian/excalidraw/types/excalidraw/clipboard";
|
||||
@@ -123,7 +123,7 @@ export declare class ExcalidrawAutomate {
|
||||
* @param val //1: Virgil, 2:Helvetica, 3:Cascadia
|
||||
* @returns
|
||||
*/
|
||||
setFontFamily(val: number): "Virgil, Segoe UI Emoji" | "Helvetica, Segoe UI Emoji" | "Cascadia, Segoe UI Emoji" | "LocalFont";
|
||||
setFontFamily(val: number): "Virgil, Segoe UI Emoji" | "Helvetica, Segoe UI Emoji" | "Cascadia, Segoe UI Emoji" | "Local Font";
|
||||
/**
|
||||
* @param val //0:"light", 1:"dark"
|
||||
* @returns
|
||||
|
||||
@@ -427,7 +427,7 @@ const run = async (text) => {
|
||||
|
||||
const requestObject = isImageEditRequest
|
||||
? {
|
||||
...imageDataURL ? {image: imageDataURL} : {},
|
||||
...imageDataURL ? {image: {url: imageDataURL}} : {},
|
||||
...(text && text.trim() !== "") ? {text} : {},
|
||||
imageGenerationProperties: {
|
||||
size: imageSize,
|
||||
@@ -437,7 +437,7 @@ const run = async (text) => {
|
||||
},
|
||||
}
|
||||
: {
|
||||
...imageDataURL ? {image: imageDataURL} : {},
|
||||
...imageDataURL ? {image: {url: imageDataURL}} : {},
|
||||
...(text && text.trim() !== "") ? {text} : {},
|
||||
systemPrompt: systemPrompt.prompt,
|
||||
instruction: outputType.instruction,
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -20,7 +20,7 @@ If you want to modify scripts, I recommend moving them to the `Excalidraw Automa
|
||||
I would love to include your contribution in the script library. If you have a script of your own that you would like to share with the community, please open a [PR](https://github.com/zsviczian/obsidian-excalidraw-plugin/pulls) on GitHub. Be sure to include the following in your pull request
|
||||
- The [script file](https://github.com/zsviczian/obsidian-excalidraw-plugin/tree/master/ea-scripts) with a self explanetory name. The name of the file will be the name of the script in the Command Palette.
|
||||
- An [image](https://github.com/zsviczian/obsidian-excalidraw-plugin/tree/master/images) explaining the scripts purpose. Remember a picture speaks thousand words!
|
||||
- An update to this file [ea-scripts/index.md](https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/index.md)
|
||||
- An update to this file [ea-scripts/index-new.md](https://github.com/zsviczian/obsidian-excalidraw-plugin/blob/master/ea-scripts/index-new.md)
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "obsidian-excalidraw-plugin",
|
||||
"name": "Excalidraw",
|
||||
"version": "2.2.7-5",
|
||||
"version": "2.3.0",
|
||||
"minAppVersion": "1.1.6",
|
||||
"description": "An Obsidian plugin to edit and view Excalidraw drawings",
|
||||
"author": "Zsolt Viczian",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "obsidian-excalidraw-plugin",
|
||||
"name": "Excalidraw",
|
||||
"version": "2.2.9",
|
||||
"version": "2.3.0",
|
||||
"minAppVersion": "1.1.6",
|
||||
"description": "An Obsidian plugin to edit and view Excalidraw drawings",
|
||||
"author": "Zsolt Viczian",
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@zsviczian/excalidraw": "0.17.1-obsidian-30",
|
||||
"@zsviczian/excalidraw": "0.17.1-obsidian-36",
|
||||
"chroma-js": "^2.4.2",
|
||||
"clsx": "^2.0.0",
|
||||
"colormaster": "^1.2.1",
|
||||
@@ -32,7 +32,9 @@
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"roughjs": "^4.5.2",
|
||||
"js-yaml": "^4.1.0"
|
||||
"js-yaml": "^4.1.0",
|
||||
"opentype.js": "^1.3.4",
|
||||
"woff2sfnt-sfnt2woff": "^1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"dotenv": "^16.4.5",
|
||||
@@ -52,6 +54,7 @@
|
||||
"@types/react": "^18.2.45",
|
||||
"@types/react-dom": "^18.2.18",
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"@types/opentype.js": "^1.3.8",
|
||||
"@zerollup/ts-transform-paths": "^1.7.18",
|
||||
"cross-env": "^7.0.3",
|
||||
"cssnano": "^6.0.2",
|
||||
|
||||
@@ -107,7 +107,7 @@ const BUILD_CONFIG = {
|
||||
babel({
|
||||
presets: [['@babel/preset-env', {
|
||||
targets: {
|
||||
esmodules: true,
|
||||
ios: "15", // ios Compatibility //esmodules: true,
|
||||
},
|
||||
}]],
|
||||
exclude: "node_modules/**",
|
||||
|
||||
@@ -4,11 +4,6 @@
|
||||
import { ExcalidrawElement, FileId } from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
import { BinaryFileData, DataURL } from "@zsviczian/excalidraw/types/excalidraw/types";
|
||||
import { App, MarkdownRenderer, Notice, TFile } from "obsidian";
|
||||
import {
|
||||
ASSISTANT_FONT,
|
||||
CASCADIA_FONT,
|
||||
VIRGIL_FONT,
|
||||
} from "./constants/constFonts";
|
||||
import {
|
||||
DEFAULT_MD_EMBED_CSS,
|
||||
fileid,
|
||||
@@ -16,6 +11,7 @@ import {
|
||||
nanoid,
|
||||
THEME_FILTER,
|
||||
FRONTMATTER_KEYS,
|
||||
getFontDefinition,
|
||||
} from "./constants/constants";
|
||||
import { createSVG } from "./ExcalidrawAutomate";
|
||||
import { ExcalidrawData, getTransclusion } from "./ExcalidrawData";
|
||||
@@ -38,13 +34,13 @@ import {
|
||||
LinkParts,
|
||||
svgToBase64,
|
||||
isMaskFile,
|
||||
embedFontsInSVG,
|
||||
getEmbeddedFilenameParts,
|
||||
} from "./utils/Utils";
|
||||
import { ValueOf } from "./types";
|
||||
import { ValueOf } from "./types/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 +141,6 @@ const replaceSVGColors = (svg: SVGSVGElement | string, colorMap: ColorMap | null
|
||||
return svg;
|
||||
}
|
||||
|
||||
|
||||
|
||||
export class EmbeddedFile {
|
||||
public file: TFile = null;
|
||||
public isSVGwithBitmap: boolean = false;
|
||||
@@ -157,6 +151,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 +199,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 +210,9 @@ export class EmbeddedFile {
|
||||
5000,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
this.filenameparts = getEmbeddedFilenameParts(imgPath);
|
||||
this.filenameparts.filepath = this.file.path;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -360,26 +358,36 @@ export class EmbeddedFilesLoader {
|
||||
: false,
|
||||
withTheme: !!forceTheme,
|
||||
isMask,
|
||||
skipInliningFonts: false,
|
||||
};
|
||||
|
||||
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,
|
||||
inlineFonts: !exportSettings.skipInliningFonts,
|
||||
}: {
|
||||
filepath: file.path,
|
||||
hasBlockref: false,
|
||||
hasGroupref: false,
|
||||
hasTaskbone: false,
|
||||
hasArearef: false,
|
||||
hasFrameref: false,
|
||||
hasClippedFrameref: false,
|
||||
hasSectionref: false,
|
||||
inlineFonts: !exportSettings.skipInliningFonts,
|
||||
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,9 +398,17 @@ export class EmbeddedFilesLoader {
|
||||
? maybeSVG
|
||||
: replaceSVGColors(
|
||||
await createSVG(
|
||||
file?.path,
|
||||
hasFilenameParts
|
||||
? (filenameParts.hasGroupref || filenameParts.hasBlockref ||
|
||||
filenameParts.hasSectionref || filenameParts.hasFrameref ||
|
||||
filenameParts.hasClippedFrameref
|
||||
? filenameParts.filepath + filenameParts.linkpartReference
|
||||
: file.path)
|
||||
: file?.path,
|
||||
false, //false
|
||||
exportSettings,
|
||||
hasFilenameParts && filenameParts.hasClippedFrameref
|
||||
? {...exportSettings, frameRendering: {enabled: true, name: false, outline: false, clip: true}}
|
||||
: exportSettings,
|
||||
this,
|
||||
forceTheme,
|
||||
null,
|
||||
@@ -429,16 +445,16 @@ export class EmbeddedFilesLoader {
|
||||
//see svgWithFont below
|
||||
imageCache.addImageToCache(cacheKey,"", svg);
|
||||
}
|
||||
const svgWithFont = embedFontsInSVG(svg, this.plugin);
|
||||
if(!svgWithFont.hasAttribute("width") && svgWithFont.hasAttribute("viewBox")){
|
||||
|
||||
if(!svg.hasAttribute("width") && svg.hasAttribute("viewBox")){
|
||||
//2024.06.09
|
||||
//this addresses backward compatibility issues where the cache does not have the width and height attributes
|
||||
//this should be removed in the future
|
||||
const vb = svgWithFont.getAttr("viewBox").split(" ");
|
||||
Boolean(vb[2]) && svgWithFont.setAttribute("width", vb[2]);
|
||||
Boolean(vb[3]) && svgWithFont.setAttribute("height", vb[3]);
|
||||
const vb = svg.getAttr("viewBox").split(" ");
|
||||
Boolean(vb[2]) && svg.setAttribute("width", vb[2]);
|
||||
Boolean(vb[3]) && svg.setAttribute("height", vb[3]);
|
||||
}
|
||||
const dURL = svgToBase64(svgWithFont.outerHTML) as DataURL;
|
||||
const dURL = svgToBase64(svg.outerHTML) as DataURL;
|
||||
return {dataURL: dURL as DataURL, hasSVGwithBitmap};
|
||||
};
|
||||
|
||||
@@ -556,7 +572,8 @@ export class EmbeddedFilesLoader {
|
||||
return {
|
||||
mimeType,
|
||||
fileId: await generateIdFromFile(
|
||||
isHyperLink || isPDF ? (new TextEncoder()).encode(dataURL as string) : ab
|
||||
isHyperLink || isPDF ? (new TextEncoder()).encode(dataURL as string) : ab,
|
||||
inFile instanceof EmbeddedFile ? inFile.filenameparts?.linkpartReference : undefined
|
||||
),
|
||||
dataURL,
|
||||
created: isHyperLink || isLocalLink ? 0 : file.stat.mtime,
|
||||
@@ -799,13 +816,29 @@ export class EmbeddedFilesLoader {
|
||||
}
|
||||
switch (fontName) {
|
||||
case "Virgil":
|
||||
fontDef = VIRGIL_FONT;
|
||||
fontDef = await getFontDefinition(1);
|
||||
break;
|
||||
case "Cascadia":
|
||||
fontDef = CASCADIA_FONT;
|
||||
fontDef = await getFontDefinition(3);
|
||||
break;
|
||||
case "Assistant":
|
||||
fontDef = ASSISTANT_FONT;
|
||||
case "Assistant":
|
||||
case "Helvetica":
|
||||
fontDef = await getFontDefinition(2);
|
||||
break;
|
||||
case "Excalifont":
|
||||
fontDef = await getFontDefinition(5);
|
||||
break;
|
||||
case "Nunito":
|
||||
fontDef = await getFontDefinition(6);
|
||||
break;
|
||||
case "Lilita One":
|
||||
fontDef = await getFontDefinition(7);
|
||||
break;
|
||||
case "Comic Shanns":
|
||||
fontDef = await getFontDefinition(8);
|
||||
break;
|
||||
case "Liberation Sans":
|
||||
fontDef = await getFontDefinition(9);
|
||||
break;
|
||||
case "":
|
||||
fontDef = "";
|
||||
@@ -1005,7 +1038,7 @@ const getSVGData = async (app: App, file: TFile, colorMap: ColorMap | null): Pro
|
||||
return svgToBase64(svg) as DataURL;
|
||||
};
|
||||
|
||||
export const generateIdFromFile = async (file: ArrayBuffer): Promise<FileId> => {
|
||||
/*export const generateIdFromFile = async (file: ArrayBuffer): Promise<FileId> => {
|
||||
let id: FileId;
|
||||
try {
|
||||
const hashBuffer = await window.crypto.subtle.digest("SHA-1", file);
|
||||
@@ -1020,4 +1053,37 @@ export const generateIdFromFile = async (file: ArrayBuffer): Promise<FileId> =>
|
||||
id = fileid() as FileId;
|
||||
}
|
||||
return id;
|
||||
};*/
|
||||
|
||||
export const generateIdFromFile = async (file: ArrayBuffer, key?: string): Promise<FileId> => {
|
||||
let id: FileId;
|
||||
try {
|
||||
// Convert the file ArrayBuffer to a Uint8Array
|
||||
const fileArray = new Uint8Array(file);
|
||||
|
||||
// If a key is provided, concatenate it to the file data
|
||||
let dataToHash: Uint8Array;
|
||||
if (key) {
|
||||
const encoder = new TextEncoder();
|
||||
const keyArray = encoder.encode(key);
|
||||
dataToHash = new Uint8Array(fileArray.length + keyArray.length);
|
||||
dataToHash.set(fileArray);
|
||||
dataToHash.set(keyArray, fileArray.length);
|
||||
} else {
|
||||
dataToHash = fileArray;
|
||||
}
|
||||
|
||||
// Hash the combined data (file and key, if provided)
|
||||
const hashBuffer = await window.crypto.subtle.digest("SHA-1", dataToHash);
|
||||
id =
|
||||
// Convert buffer to byte array
|
||||
Array.from(new Uint8Array(hashBuffer))
|
||||
// Convert to hex string
|
||||
.map((byte) => byte.toString(16).padStart(2, "0"))
|
||||
.join("") as FileId;
|
||||
} catch (error) {
|
||||
errorlog({ where: "EmbeddedFileLoader.generateIdFromFile", error });
|
||||
id = fileid() as FileId;
|
||||
}
|
||||
return id;
|
||||
};
|
||||
|
||||
@@ -27,7 +27,7 @@ import {
|
||||
GITHUB_RELEASES,
|
||||
determineFocusDistance,
|
||||
getCommonBoundingBox,
|
||||
getDefaultLineHeight,
|
||||
getLineHeight,
|
||||
getMaximumGroups,
|
||||
intersectElementWithLine,
|
||||
measureText,
|
||||
@@ -37,11 +37,11 @@ import {
|
||||
THEME_FILTER,
|
||||
mermaidToExcalidraw,
|
||||
refreshTextDimensions,
|
||||
getFontFamilyString,
|
||||
} from "src/constants/constants";
|
||||
import { blobToBase64, checkAndCreateFolder, getDrawingFilename, getExcalidrawEmbeddedFilesFiletree, getListOfTemplateFiles, getNewUniqueFilepath, hasExcalidrawEmbeddedImagesTreeChanged, } from "src/utils/FileUtils";
|
||||
import {
|
||||
//debug,
|
||||
embedFontsInSVG,
|
||||
errorlog,
|
||||
getEmbeddedFilenameParts,
|
||||
getImageSize,
|
||||
@@ -61,7 +61,7 @@ import { tex2dataURL } from "src/LaTeX";
|
||||
import { GenericInputPrompt, NewFileActions } from "src/dialogs/Prompt";
|
||||
import { t } from "src/lang/helpers";
|
||||
import { ScriptEngine } from "src/Scripts";
|
||||
import { ConnectionPoint, DeviceType } from "src/types";
|
||||
import { ConnectionPoint, DeviceType } from "src/types/types";
|
||||
import CM, { ColorMaster, extendPlugins } from "colormaster";
|
||||
import HarmonyPlugin from "colormaster/plugins/harmony";
|
||||
import MixPlugin from "colormaster/plugins/mix"
|
||||
@@ -92,6 +92,7 @@ import {
|
||||
import { EXCALIDRAW_AUTOMATE_INFO, EXCALIDRAW_SCRIPTENGINE_INFO } from "./dialogs/SuggesterInfo";
|
||||
import { addBackOfTheNoteCard, getFrameBasedOnFrameNameOrId } from "./utils/ExcalidrawViewUtils";
|
||||
import { log } from "./utils/DebugHelper";
|
||||
import { ExcalidrawLib } from "./ExcalidrawLib";
|
||||
|
||||
extendPlugins([
|
||||
HarmonyPlugin,
|
||||
@@ -111,6 +112,7 @@ extendPlugins([
|
||||
|
||||
declare const PLUGIN_VERSION:string;
|
||||
declare var LZString: any;
|
||||
declare const excalidrawLib: typeof ExcalidrawLib;
|
||||
|
||||
const GAP = 4;
|
||||
|
||||
@@ -386,7 +388,7 @@ export class ExcalidrawAutomate {
|
||||
opacity: number;
|
||||
strokeSharpness?: StrokeRoundness; //defaults to undefined, use strokeRoundess and roundess instead. Only kept for legacy script compatibility type StrokeRoundness = "round" | "sharp"
|
||||
roundness: null | { type: RoundnessType; value?: number };
|
||||
fontFamily: number; //1: Virgil, 2:Helvetica, 3:Cascadia, 4:LocalFont
|
||||
fontFamily: number; //1: Virgil, 2:Helvetica, 3:Cascadia, 4:Local Font
|
||||
fontSize: number;
|
||||
textAlign: string; //"left"|"right"|"center"
|
||||
verticalAlign: string; //"top"|"bottom"|"middle" :for future use, has no effect currently
|
||||
@@ -1191,7 +1193,7 @@ export class ExcalidrawAutomate {
|
||||
element.text,
|
||||
element.fontSize,
|
||||
element.fontFamily,
|
||||
getDefaultLineHeight(element.fontFamily)
|
||||
getLineHeight(element.fontFamily)
|
||||
);
|
||||
element.width = w;
|
||||
element.height = h;
|
||||
@@ -1236,7 +1238,7 @@ export class ExcalidrawAutomate {
|
||||
text,
|
||||
this.style.fontSize,
|
||||
this.style.fontFamily,
|
||||
getDefaultLineHeight(this.style.fontFamily)
|
||||
getLineHeight(this.style.fontFamily)
|
||||
);
|
||||
const width = formatting?.width ? formatting.width : w;
|
||||
const height = formatting?.height ? formatting.height : h;
|
||||
@@ -1294,7 +1296,7 @@ export class ExcalidrawAutomate {
|
||||
containerId: isContainerBound ? boxId : null,
|
||||
originalText: isContainerBound ? originalText : text,
|
||||
rawText: isContainerBound ? originalText : text,
|
||||
lineHeight: getDefaultLineHeight(this.style.fontFamily),
|
||||
lineHeight: getLineHeight(this.style.fontFamily),
|
||||
autoResize: formatting?.box ? true : (formatting?.autoResize ?? true),
|
||||
};
|
||||
if (boxId && formatting?.box === "blob") {
|
||||
@@ -1995,7 +1997,7 @@ export class ExcalidrawAutomate {
|
||||
appState: {
|
||||
viewModeEnabled: !isFullscreen,
|
||||
},
|
||||
storeAction: "none",
|
||||
storeAction: "update",
|
||||
});
|
||||
this.targetView.toolsPanelRef?.current?.setExcalidrawViewMode(!isFullscreen);
|
||||
}
|
||||
@@ -2014,7 +2016,7 @@ export class ExcalidrawAutomate {
|
||||
return;
|
||||
}
|
||||
const view = this.targetView as ExcalidrawView;
|
||||
view.updateScene({appState:{viewModeEnabled: enabled}});
|
||||
view.updateScene({appState:{viewModeEnabled: enabled}, storeAction: "update"});
|
||||
view.toolsPanelRef?.current?.setExcalidrawViewMode(enabled);
|
||||
}
|
||||
|
||||
@@ -2040,7 +2042,7 @@ export class ExcalidrawAutomate {
|
||||
return;
|
||||
}
|
||||
if (!Boolean(scene.storeAction)) {
|
||||
scene.storeAction = scene.commitToHistory ? "capture" : "none";
|
||||
scene.storeAction = scene.commitToHistory ? "capture" : "update";
|
||||
}
|
||||
|
||||
this.targetView.updateScene({
|
||||
@@ -2416,13 +2418,19 @@ export class ExcalidrawAutomate {
|
||||
|
||||
/**
|
||||
* Gets all the elements from elements[] that are contained in the frame.
|
||||
* @param element
|
||||
* @param elements - typically all the non-deleted elements in the scene
|
||||
* @param frameElement - the frame element for which to get the elements
|
||||
* @param elements - typically all the non-deleted elements in the scene
|
||||
* @param shouldIncludeFrame - if true, the frame element will be included in the returned array
|
||||
* this is useful when generating an image in which you want the frame to be clipped
|
||||
* @returns
|
||||
*/
|
||||
getElementsInFrame(frameElement: ExcalidrawElement, elements: ExcalidrawElement[]): ExcalidrawElement[] {
|
||||
getElementsInFrame(
|
||||
frameElement: ExcalidrawElement,
|
||||
elements: ExcalidrawElement[],
|
||||
shouldIncludeFrame: boolean = false,
|
||||
): ExcalidrawElement[] {
|
||||
if(!frameElement || !elements || frameElement.type !== "frame") return [];
|
||||
return elements.filter(el=>el.frameId === frameElement.id);
|
||||
return elements.filter(el=>(el.frameId === frameElement.id) || (shouldIncludeFrame && el.id === frameElement.id));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2488,7 +2496,7 @@ export class ExcalidrawAutomate {
|
||||
text,
|
||||
this.style.fontSize,
|
||||
this.style.fontFamily,
|
||||
getDefaultLineHeight(this.style.fontFamily),
|
||||
getLineHeight(this.style.fontFamily),
|
||||
);
|
||||
return { width: size.w ?? 0, height: size.h ?? 0 };
|
||||
};
|
||||
@@ -2750,26 +2758,15 @@ function getLineBox(points: [x: number, y: number][]) {
|
||||
}
|
||||
|
||||
function getFontFamily(id: number) {
|
||||
switch (id) {
|
||||
case 1:
|
||||
return "Virgil, Segoe UI Emoji";
|
||||
case 2:
|
||||
return "Helvetica, Segoe UI Emoji";
|
||||
case 3:
|
||||
return "Cascadia, Segoe UI Emoji";
|
||||
case 4:
|
||||
return "LocalFont";
|
||||
}
|
||||
getFontFamilyString({fontFamily:id})
|
||||
}
|
||||
|
||||
export async function initFonts(doc: Document = document) {
|
||||
for (let i = 1; i <= 3; i++) {
|
||||
await (doc as any).fonts.load(`20px ${getFontFamily(i)}`);
|
||||
}
|
||||
await (doc as any).fonts.load("400 20px Assistant");
|
||||
await (doc as any).fonts.load("500 20px Assistant");
|
||||
await (doc as any).fonts.load("600 20px Assistant");
|
||||
await (doc as any).fonts.load("700 20px Assistant");
|
||||
export async function initFonts() {
|
||||
await excalidrawLib.registerFontsInCSS();
|
||||
const fonts = excalidrawLib.getFontFamilies();
|
||||
for(let i=0;i<fonts.length;i++) {
|
||||
if(fonts[i] !== "Local Font") await (document as any).fonts.load(`16px ${fonts[i]}`);
|
||||
};
|
||||
}
|
||||
|
||||
export function _measureText(
|
||||
@@ -2784,7 +2781,7 @@ export function _measureText(
|
||||
}
|
||||
if (!fontFamily) {
|
||||
fontFamily = 1;
|
||||
lineHeight = getDefaultLineHeight(fontFamily);
|
||||
lineHeight = getLineHeight(fontFamily);
|
||||
}
|
||||
const metrics = measureText(
|
||||
newText,
|
||||
@@ -2879,15 +2876,14 @@ async function getTemplate(
|
||||
groupElements = plugin.ea.getElementsInTheSameGroupWithElement(el[0],scene.elements)
|
||||
}
|
||||
}
|
||||
if(filenameParts.hasFrameref) {
|
||||
if(filenameParts.hasFrameref || filenameParts.hasClippedFrameref) {
|
||||
const el = getFrameBasedOnFrameNameOrId(filenameParts.blockref,scene.elements);
|
||||
|
||||
if(el) {
|
||||
groupElements = plugin.ea.getElementsInFrame(el,scene.elements)
|
||||
groupElements = plugin.ea.getElementsInFrame(el,scene.elements, filenameParts.hasClippedFrameref);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if(filenameParts.hasTaskbone) {
|
||||
groupElements = groupElements.filter( el =>
|
||||
el.type==="freedraw" ||
|
||||
@@ -2965,6 +2961,7 @@ export async function createPNG(
|
||||
theme: forceTheme ?? template?.appState?.theme ?? canvasTheme,
|
||||
viewBackgroundColor:
|
||||
template?.appState?.viewBackgroundColor ?? canvasBackgroundColor,
|
||||
...template?.appState?.frameRendering ? {frameRendering: template.appState.frameRendering} : {},
|
||||
},
|
||||
files,
|
||||
},
|
||||
@@ -3044,6 +3041,9 @@ export async function createSVG(
|
||||
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;
|
||||
@@ -3060,6 +3060,7 @@ export async function createSVG(
|
||||
const theme = forceTheme ?? template?.appState?.theme ?? canvasTheme;
|
||||
const withTheme = exportSettings?.withTheme ?? plugin.settings.exportWithTheme;
|
||||
|
||||
const filenameParts = getEmbeddedFilenameParts(templatePath);
|
||||
const svg = await getSVG(
|
||||
{
|
||||
//createAndOpenDrawing
|
||||
@@ -3071,6 +3072,7 @@ export async function createSVG(
|
||||
theme,
|
||||
viewBackgroundColor:
|
||||
template?.appState?.viewBackgroundColor ?? canvasBackgroundColor,
|
||||
...template?.appState?.frameRendering ? {frameRendering: template.appState.frameRendering} : {},
|
||||
},
|
||||
files,
|
||||
},
|
||||
@@ -3079,6 +3081,9 @@ export async function createSVG(
|
||||
exportSettings?.withBackground ?? plugin.settings.exportWithBackground,
|
||||
withTheme,
|
||||
isMask: exportSettings?.isMask ?? false,
|
||||
...filenameParts?.hasClippedFrameref
|
||||
? {frameRendering: {enabled: true, name: false, outline: false, clip: true}}
|
||||
: {},
|
||||
},
|
||||
padding,
|
||||
null,
|
||||
@@ -3086,9 +3091,8 @@ export async function createSVG(
|
||||
|
||||
if (withTheme && theme === "dark") addFilterToForeignObjects(svg);
|
||||
|
||||
const filenameParts = getEmbeddedFilenameParts(templatePath);
|
||||
if(
|
||||
!(filenameParts.hasGroupref || filenameParts.hasFrameref) &&
|
||||
!(filenameParts.hasGroupref || filenameParts.hasFrameref || filenameParts.hasClippedFrameref) &&
|
||||
(filenameParts.hasBlockref || filenameParts.hasSectionref)
|
||||
) {
|
||||
let el = filenameParts.hasSectionref
|
||||
@@ -3110,7 +3114,7 @@ export async function createSVG(
|
||||
if (template?.hasSVGwithBitmap) {
|
||||
svg.setAttribute("hasbitmap", "true");
|
||||
}
|
||||
return embedFont ? embedFontsInSVG(svg, plugin) : svg;
|
||||
return svg;
|
||||
}
|
||||
|
||||
function estimateLineBound(points: any): [number, number, number, number] {
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
fileid,
|
||||
DEVICE,
|
||||
EMBEDDABLE_THEME_FRONTMATTER_VALUES,
|
||||
getDefaultLineHeight,
|
||||
getLineHeight,
|
||||
ERROR_IFRAME_CONVERSION_CANCELED,
|
||||
JSON_parse,
|
||||
FRONTMATTER_KEYS,
|
||||
@@ -549,7 +549,7 @@ export class ExcalidrawData {
|
||||
}
|
||||
|
||||
if (el.type === "text" && !el.hasOwnProperty("lineHeight")) {
|
||||
el.lineHeight = getDefaultLineHeight(el.fontFamily);
|
||||
el.lineHeight = getLineHeight(el.fontFamily);
|
||||
}
|
||||
|
||||
if (el.type === "image" && !el.hasOwnProperty("roundness")) {
|
||||
@@ -887,7 +887,7 @@ export class ExcalidrawData {
|
||||
? data.substring(indexOfNewEmbeddedFiles + embeddedFilesNewLength)
|
||||
: data.substring(indexOfOldEmbeddedFiles + embeddedFilesOldLength);
|
||||
//Load Embedded files
|
||||
const REG_FILEID_FILEPATH = /([\w\d]*):\s*\[\[([^\]]*)]]\s*(\{[^}]*})?\n/gm;
|
||||
const REG_FILEID_FILEPATH = /([\w\d]*):\s*\!?\[\[([^\]]*)]]\s*(\{[^}]*})?\n/gm;
|
||||
res = data.matchAll(REG_FILEID_FILEPATH);
|
||||
while (!(parts = res.next()).done) {
|
||||
const embeddedFile = new EmbeddedFile(
|
||||
|
||||
19
src/ExcalidrawLib.d.ts
vendored
19
src/ExcalidrawLib.d.ts
vendored
@@ -2,6 +2,7 @@ import { RestoredDataState } from "@zsviczian/excalidraw/types/excalidraw/data/r
|
||||
import { ImportedDataState } from "@zsviczian/excalidraw/types/excalidraw/data/types";
|
||||
import { BoundingBox } from "@zsviczian/excalidraw/types/excalidraw/element/bounds";
|
||||
import { ElementsMap, ExcalidrawBindableElement, ExcalidrawElement, ExcalidrawFrameElement, ExcalidrawTextContainer, ExcalidrawTextElement, FontFamilyValues, FontString, NonDeleted, NonDeletedExcalidrawElement, Theme } from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
import { FontMetadata } from "@zsviczian/excalidraw/types/excalidraw/fonts/metadata";
|
||||
import { AppState, BinaryFiles, Point, Zoom } from "@zsviczian/excalidraw/types/excalidraw/types";
|
||||
import { Mutable } from "@zsviczian/excalidraw/types/excalidraw/utility-types";
|
||||
|
||||
@@ -46,6 +47,7 @@ declare namespace ExcalidrawLib {
|
||||
exportPadding?: number;
|
||||
exportingFrame: ExcalidrawFrameElement | null | undefined;
|
||||
renderEmbeddables?: boolean;
|
||||
skipInliningFonts?: boolean;
|
||||
}): Promise<SVGSVGElement>;
|
||||
|
||||
function sceneCoordsToViewportCoords(
|
||||
@@ -116,8 +118,7 @@ declare namespace ExcalidrawLib {
|
||||
lineHeight: number,
|
||||
): { width: number; height: number; };
|
||||
|
||||
function getDefaultLineHeight(fontFamily: FontFamilyValues): number;
|
||||
|
||||
function getLineHeight (fontFamily: FontFamilyValues):number;
|
||||
function wrapText(text: string, font: FontString, maxWidth: number): string;
|
||||
|
||||
function getFontString({
|
||||
@@ -128,6 +129,13 @@ declare namespace ExcalidrawLib {
|
||||
fontFamily: FontFamilyValues;
|
||||
}): FontString;
|
||||
|
||||
|
||||
function getFontFamilyString ({
|
||||
fontFamily,
|
||||
}: {
|
||||
fontFamily: number;
|
||||
}): string;
|
||||
|
||||
function getBoundTextMaxWidth(container: ExcalidrawElement): number;
|
||||
|
||||
function exportToBlob(
|
||||
@@ -164,4 +172,9 @@ declare namespace ExcalidrawLib {
|
||||
var TTDDialog: any;
|
||||
|
||||
function destroyObsidianUtils(): void;
|
||||
}
|
||||
function registerLocalFont(fontMetrics: FontMetadata, uri: string): void;
|
||||
function getFontFamilies(): string[];
|
||||
function registerFontsInCSS(): Promise<void>;
|
||||
function getFontDefinition(fontFamily: number): Promise<string>;
|
||||
}
|
||||
|
||||
|
||||
@@ -82,7 +82,6 @@ import {
|
||||
} from "./utils/FileUtils";
|
||||
import {
|
||||
checkExcalidrawVersion,
|
||||
embedFontsInSVG,
|
||||
errorlog,
|
||||
getEmbeddedFilenameParts,
|
||||
getExportTheme,
|
||||
@@ -104,7 +103,7 @@ import {
|
||||
_getContainerElement,
|
||||
arrayToMap,
|
||||
} from "./utils/Utils";
|
||||
import { cleanBlockRef, cleanSectionHeading, getAttachmentsFolderAndFilePath, getLeaf, getParentOfClass, obsidianPDFQuoteWithRef, openLeaf } from "./utils/ObsidianUtils";
|
||||
import { cleanBlockRef, cleanSectionHeading, closeLeafView, getAttachmentsFolderAndFilePath, getLeaf, getParentOfClass, obsidianPDFQuoteWithRef, openLeaf, setExcalidrawView } from "./utils/ObsidianUtils";
|
||||
import { splitFolderAndFilename } from "./utils/FileUtils";
|
||||
import { ConfirmationPrompt, GenericInputPrompt, NewFileActions, Prompt, linkPrompt } from "./dialogs/Prompt";
|
||||
import { ClipboardData } from "@zsviczian/excalidraw/types/excalidraw/clipboard";
|
||||
@@ -120,14 +119,14 @@ import { ObsidianMenu } from "./menu/ObsidianMenu";
|
||||
import { ToolsPanel } from "./menu/ToolsPanel";
|
||||
import { ScriptEngine } from "./Scripts";
|
||||
import { getTextElementAtPointer, getImageElementAtPointer, getElementWithLinkAtPointer } from "./utils/GetElementAtPointer";
|
||||
import { ICONS, LogoWrapper, saveIcon } from "./menu/ActionIcons";
|
||||
import { excalidrawSword, ICONS, LogoWrapper, Rank, saveIcon, SwordColors } from "./menu/ActionIcons";
|
||||
import { ExportDialog } from "./dialogs/ExportDialog";
|
||||
import { getEA } from "src"
|
||||
import { anyModifierKeysPressed, emulateKeysForLinkClick, webbrowserDragModifierType, internalDragModifierType, isWinALTorMacOPT, isWinCTRLorMacCMD, isWinMETAorMacCTRL, isSHIFT, linkClickModifierType, localFileDragModifierType, ModifierKeys, modifierKeyTooltipMessages } from "./utils/ModifierkeyHelper";
|
||||
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";
|
||||
@@ -139,7 +138,7 @@ import { CustomMutationObserver, DEBUGGING, debug, log} from "./utils/DebugHelpe
|
||||
import { extractCodeBlocks, postOpenAI } from "./utils/AIUtils";
|
||||
import { Mutable } from "@zsviczian/excalidraw/types/excalidraw/utility-types";
|
||||
import { SelectCard } from "./dialogs/SelectCard";
|
||||
import { Packages } from "./types";
|
||||
import { Packages } from "./types/types";
|
||||
import React from "react";
|
||||
|
||||
const EMBEDDABLE_SEMAPHORE_TIMEOUT = 2000;
|
||||
@@ -174,6 +173,13 @@ export interface ExportSettings {
|
||||
withBackground: boolean;
|
||||
withTheme: boolean;
|
||||
isMask: boolean;
|
||||
frameRendering?: { //optional, overrides relevant appState settings for rendering the frame
|
||||
enabled: boolean;
|
||||
name: boolean;
|
||||
outline: boolean;
|
||||
clip: boolean;
|
||||
};
|
||||
skipInliningFonts?: boolean;
|
||||
}
|
||||
|
||||
const HIDE = "excalidraw-hidden";
|
||||
@@ -209,7 +215,7 @@ export const addFiles = async (
|
||||
view.updateScene({
|
||||
elements: s.scene.elements,
|
||||
appState: s.scene.appState,
|
||||
storeAction: "none",
|
||||
storeAction: "update",
|
||||
});
|
||||
}
|
||||
for (const f of files) {
|
||||
@@ -238,6 +244,8 @@ const warningUnknowSeriousError = () => {
|
||||
|
||||
type ActionButtons = "save" | "isParsed" | "isRaw" | "link" | "scriptInstall";
|
||||
|
||||
let windowMigratedDisableZoomOnce = false;
|
||||
|
||||
export default class ExcalidrawView extends TextFileView {
|
||||
public exportDialog: ExportDialog;
|
||||
public excalidrawData: ExcalidrawData;
|
||||
@@ -453,7 +461,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
);
|
||||
}
|
||||
|
||||
public async svg(scene: any, theme?:string, embedScene?: boolean): Promise<SVGSVGElement> {
|
||||
public async svg(scene: any, theme?:string, embedScene?: boolean, embedFont: boolean = false): Promise<SVGSVGElement> {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.svg, "ExcalidrawView.svg", scene, theme, embedScene);
|
||||
const ed = this.exportDialog;
|
||||
|
||||
@@ -461,6 +469,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
withBackground: ed ? !ed.transparent : getWithBackground(this.plugin, this.file),
|
||||
withTheme: true,
|
||||
isMask: isMaskFile(this.plugin, this.file),
|
||||
skipInliningFonts: !embedFont,
|
||||
};
|
||||
|
||||
if(typeof embedScene === "undefined") {
|
||||
@@ -498,14 +507,12 @@ export default class ExcalidrawView extends TextFileView {
|
||||
const exportImage = async (filepath:string, theme?:string) => {
|
||||
const file = this.app.vault.getAbstractFileByPath(normalizePath(filepath));
|
||||
|
||||
const svg = await this.svg(scene,theme, embedScene);
|
||||
const svg = await this.svg(scene,theme, embedScene, true);
|
||||
if (!svg) {
|
||||
return;
|
||||
}
|
||||
const serializer = new XMLSerializer();
|
||||
const svgString = serializer.serializeToString(
|
||||
embedFontsInSVG(svg, this.plugin),
|
||||
);
|
||||
const svgString = serializer.serializeToString(svg);
|
||||
if (file && file instanceof TFile) {
|
||||
await this.app.vault.modify(file, svgString);
|
||||
} else {
|
||||
@@ -527,11 +534,10 @@ export default class ExcalidrawView extends TextFileView {
|
||||
return;
|
||||
}
|
||||
|
||||
let svg = await this.svg(this.getScene(selectedOnly),undefined,embedScene);
|
||||
const svg = await this.svg(this.getScene(selectedOnly),undefined,embedScene, true);
|
||||
if (!svg) {
|
||||
return;
|
||||
}
|
||||
svg = embedFontsInSVG(svg, this.plugin);
|
||||
download(
|
||||
null,
|
||||
svgToBase64(svg.outerHTML),
|
||||
@@ -897,21 +903,21 @@ export default class ExcalidrawView extends TextFileView {
|
||||
toggleDisableBinding() {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.toggleDisableBinding, "ExcalidrawView.toggleDisableBinding");
|
||||
const newState = !this.excalidrawAPI.getAppState().invertBindingBehaviour;
|
||||
this.updateScene({appState: {invertBindingBehaviour:newState}});
|
||||
this.updateScene({appState: {invertBindingBehaviour:newState}, storeAction: "update"});
|
||||
new Notice(newState ? t("ARROW_BINDING_INVERSE_MODE") : t("ARROW_BINDING_NORMAL_MODE"));
|
||||
}
|
||||
|
||||
toggleFrameRendering() {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.toggleFrameRendering, "ExcalidrawView.toggleFrameRendering");
|
||||
const frameRenderingSt = (this.excalidrawAPI as ExcalidrawImperativeAPI).getAppState().frameRendering;
|
||||
this.updateScene({appState: {frameRendering: {...frameRenderingSt, enabled: !frameRenderingSt.enabled}}});
|
||||
this.updateScene({appState: {frameRendering: {...frameRenderingSt, enabled: !frameRenderingSt.enabled}}, storeAction: "update"});
|
||||
new Notice(frameRenderingSt.enabled ? t("FRAME_CLIPPING_ENABLED") : t("FRAME_CLIPPING_DISABLED"));
|
||||
}
|
||||
|
||||
toggleFrameClipping() {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.toggleFrameClipping, "ExcalidrawView.toggleFrameClipping");
|
||||
const frameRenderingSt = (this.excalidrawAPI as ExcalidrawImperativeAPI).getAppState().frameRendering;
|
||||
this.updateScene({appState: {frameRendering: {...frameRenderingSt, clip: !frameRenderingSt.clip}}});
|
||||
this.updateScene({appState: {frameRendering: {...frameRenderingSt, clip: !frameRenderingSt.clip}}, storeAction: "update"});
|
||||
new Notice(frameRenderingSt.clip ? "Frame Clipping: Enabled" : "Frame Clipping: Disabled");
|
||||
}
|
||||
|
||||
@@ -1131,7 +1137,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
if (this.excalidrawData.hasMermaid(selectedImage.fileId) || getMermaidText(imageElement)) {
|
||||
if(shouldRenderMermaid) {
|
||||
const api = this.excalidrawAPI as ExcalidrawImperativeAPI;
|
||||
api.updateScene({appState: {openDialog: { name: "ttd", tab: "mermaid" }}})
|
||||
api.updateScene({appState: {openDialog: { name: "ttd", tab: "mermaid" }}, storeAction: "update"})
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -1348,12 +1354,25 @@ export default class ExcalidrawView extends TextFileView {
|
||||
const apiMissing = Boolean(typeof this.containerEl.onWindowMigrated === "undefined")
|
||||
this.packages = this.plugin.getPackage(this.ownerWindow);
|
||||
|
||||
/*if(!DEVICE.isMobile && !apiMissing) {
|
||||
if(DEVICE.isDesktop && !apiMissing) {
|
||||
this.destroyers.push(
|
||||
//@ts-ignore
|
||||
this.containerEl.onWindowMigrated(this.leaf.rebuildView.bind(this))
|
||||
//this.containerEl.onWindowMigrated(this.leaf.rebuildView.bind(this))
|
||||
this.containerEl.onWindowMigrated(async() => {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.onload, "ExcalidrawView.onWindowMigrated");
|
||||
const f = this.file;
|
||||
const l = this.leaf;
|
||||
await closeLeafView(l);
|
||||
windowMigratedDisableZoomOnce = true;
|
||||
l.setViewState({
|
||||
type: VIEW_TYPE_EXCALIDRAW,
|
||||
state: {
|
||||
file: f.path,
|
||||
}
|
||||
});
|
||||
})
|
||||
);
|
||||
}*/
|
||||
}
|
||||
|
||||
this.semaphores.scriptsReady = true;
|
||||
|
||||
@@ -1550,7 +1569,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
...st,
|
||||
theme,
|
||||
},
|
||||
storeAction: "none",
|
||||
storeAction: "update",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1712,6 +1731,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
|
||||
this.clearPreventReloadTimer();
|
||||
this.clearEmbeddableIsEditingSelfTimer();
|
||||
this.plugin.scriptEngine?.removeViewEAs(this);
|
||||
this.excalidrawAPI = null;
|
||||
if(this.draginfoDiv) {
|
||||
this.ownerDocument.body.removeChild(this.draginfoDiv);
|
||||
@@ -1896,6 +1916,12 @@ export default class ExcalidrawView extends TextFileView {
|
||||
return;
|
||||
}
|
||||
|
||||
if (state.rename === "all") {
|
||||
//@ts-ignore
|
||||
this.app.fileManager.promptForFileRename(this.file);
|
||||
return;
|
||||
}
|
||||
|
||||
let query: string[] = null;
|
||||
|
||||
if (
|
||||
@@ -2066,7 +2092,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
return;
|
||||
}
|
||||
let counter = 0;
|
||||
while (!this.file && counter++<50) await sleep(50);
|
||||
while ((!this.file || !this.plugin.fourthFontLoaded) && counter++<50) await sleep(50);
|
||||
if(!this.file) return;
|
||||
this.compatibilityMode = this.file.extension === "excalidraw";
|
||||
await this.plugin.loadSettings();
|
||||
@@ -2144,7 +2170,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
await this.app.vault.modify(file, drawingBAK);
|
||||
//@ts-ignore
|
||||
plugin.excalidrawFileModes[leaf.id || file.path] = VIEW_TYPE_EXCALIDRAW;
|
||||
plugin.setExcalidrawView(leaf);
|
||||
setExcalidrawView(leaf);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2191,8 +2217,8 @@ export default class ExcalidrawView extends TextFileView {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.getGridColor, "ExcalidrawView.getGridColor", bgColor, st);
|
||||
const cm = this.plugin.ea.getCM(bgColor);
|
||||
const isDark = cm.isDark();
|
||||
const Regular = (isDark ? cm.lighterBy(7) : cm.darkerBy(7)).stringHEX();
|
||||
const Bold = (isDark ? cm.lighterBy(14) : cm.darkerBy(14)).stringHEX();
|
||||
const Regular = (isDark ? cm.lighterBy(7) : cm.darkerBy(7)).stringHEX({alpha: false});
|
||||
const Bold = (isDark ? cm.lighterBy(14) : cm.darkerBy(14)).stringHEX({alpha: false});
|
||||
return {Bold, Regular, MajorGridFrequency:st.gridColor.MajorGridFrequency};
|
||||
}
|
||||
|
||||
@@ -2373,7 +2399,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
if(this.getSceneVersion(inData.scene.elements) !== this.previousSceneVersion) {
|
||||
this.setDirty(3);
|
||||
}
|
||||
this.updateScene({elements: sceneElements});
|
||||
this.updateScene({elements: sceneElements, storeAction: "capture"});
|
||||
if(reloadFiles) this.loadSceneFiles();
|
||||
} catch(e) {
|
||||
errorlog({
|
||||
@@ -2410,13 +2436,13 @@ export default class ExcalidrawView extends TextFileView {
|
||||
? om.zenModeEnabled
|
||||
: api.getAppState().zenModeEnabled;
|
||||
//debug({where:"ExcalidrawView.loadDrawing",file:this.file.name,dataTheme:excalidrawData.appState.theme,before:"updateScene"})
|
||||
api.setLocalFont(this.plugin.settings.experimentalEnableFourthFont);
|
||||
//api.setLocalFont(this.plugin.settings.experimentalEnableFourthFont);
|
||||
|
||||
this.updateScene(
|
||||
{
|
||||
elements: excalidrawData.elements.concat(deletedElements??[]), //need to preserve deleted elements during autosave if images, links, etc. are updated
|
||||
files: excalidrawData.files,
|
||||
storeAction: justloaded ? "update" : "none",
|
||||
storeAction: justloaded ? "update" : "update", //was none, but I think based on a false understanding of none
|
||||
},
|
||||
justloaded
|
||||
);
|
||||
@@ -2439,7 +2465,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
pinnedScripts: this.plugin.settings.pinnedScripts,
|
||||
customPens: this.plugin.settings.customPens.slice(0,this.plugin.settings.numberOfCustomPens),
|
||||
},
|
||||
storeAction: justloaded ? "update" : "none",
|
||||
storeAction: justloaded ? "update" : "update", //was none, but I think based on a false understanding of none
|
||||
},
|
||||
);
|
||||
if (
|
||||
@@ -3601,7 +3627,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
private canvasColorChangeHook(st: AppState) {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.canvasColorChangeHook, "ExcalidrawView.canvasColorChangeHook", st);
|
||||
const canvasColor = st.viewBackgroundColor === "transparent" ? "white" : st.viewBackgroundColor;
|
||||
window.setTimeout(()=>this.updateScene({appState:{gridColor: this.getGridColor(canvasColor, st)}}));
|
||||
window.setTimeout(()=>this.updateScene({appState:{gridColor: this.getGridColor(canvasColor, st)}, storeAction: "update"}));
|
||||
setDynamicStyle(this.plugin.ea,this,canvasColor,this.plugin.settings.dynamicStyling);
|
||||
if(this.plugin.ea.onCanvasColorChangeHook) {
|
||||
try {
|
||||
@@ -3731,6 +3757,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;
|
||||
@@ -4294,7 +4337,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
clone.rawText = WARNING;
|
||||
elements[elements.indexOf(el[0])] = clone;
|
||||
this.excalidrawData.setTextElement(clone.id,WARNING,()=>{});
|
||||
this.updateScene({elements});
|
||||
this.updateScene({elements, storeAction: "update"});
|
||||
api.history.clear();
|
||||
}
|
||||
});
|
||||
@@ -4308,42 +4351,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, storeAction: "update"});
|
||||
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
|
||||
@@ -4380,7 +4414,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
}
|
||||
|
||||
elements[elements.indexOf(el[0])] = clone;
|
||||
this.updateScene({elements});
|
||||
this.updateScene({elements, storeAction: "update"});
|
||||
if(clone.containerId) this.updateContainerSize(clone.containerId);
|
||||
this.setDirty(8.1);
|
||||
}
|
||||
@@ -4803,6 +4837,17 @@ export default class ExcalidrawView extends TextFileView {
|
||||
onClose
|
||||
),
|
||||
]);
|
||||
} else {
|
||||
contextMenuActions.push([
|
||||
renderContextMenuAction(
|
||||
React,
|
||||
t("COPY_DRAWING_LINK"),
|
||||
() => {
|
||||
navigator.clipboard.writeText(`![[${this.file.path}]]`);
|
||||
},
|
||||
onClose
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
if(this.getViewSelectedElements().filter(el=>el.type==="embeddable").length === 1) {
|
||||
@@ -4899,7 +4944,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
|
||||
private setExcalidrawAPI (api: ExcalidrawImperativeAPI) {
|
||||
this.excalidrawAPI = api;
|
||||
api.setLocalFont(this.plugin.settings.experimentalEnableFourthFont);
|
||||
//api.setLocalFont(this.plugin.settings.experimentalEnableFourthFont);
|
||||
window.setTimeout(() => {
|
||||
this.onAfterLoadScene(true);
|
||||
this.excalidrawContainer?.focus();
|
||||
@@ -4973,7 +5018,10 @@ export default class ExcalidrawView extends TextFileView {
|
||||
private renderWelcomeScreen () {
|
||||
const React = this.packages.react;
|
||||
const {WelcomeScreen} = this.packages.excalidrawLib;
|
||||
|
||||
const filecount = this.app.vault.getFiles().filter(f=>this.plugin.isExcalidrawFile(f)).length;
|
||||
const rank = filecount < 200 ? "Bronze" : filecount < 750 ? "Silver" : filecount < 2000 ? "Gold" : "Platinum";
|
||||
const nextRankDelta = filecount < 200 ? 200 - filecount : filecount < 750 ? 750 - filecount : filecount < 2000 ? 2000 - filecount : 0;
|
||||
const {decoration, title} = SwordColors[rank as Rank];
|
||||
return React.createElement(
|
||||
WelcomeScreen,
|
||||
{},
|
||||
@@ -4986,9 +5034,17 @@ export default class ExcalidrawView extends TextFileView {
|
||||
React.createElement(
|
||||
LogoWrapper,
|
||||
{},
|
||||
ICONS.ExcalidrawSword,
|
||||
excalidrawSword(rank as Rank),
|
||||
),
|
||||
),
|
||||
React.createElement(
|
||||
WelcomeScreen.Center.Heading,
|
||||
{
|
||||
color: decoration,
|
||||
message: nextRankDelta > 0 ? `${rank}: ${nextRankDelta} more drawings until the next rank!` : `${rank}: You're at the top. Keep on being legendary!`,
|
||||
},
|
||||
title,
|
||||
),
|
||||
React.createElement(
|
||||
WelcomeScreen.Center.Heading,
|
||||
{},
|
||||
@@ -5019,9 +5075,9 @@ export default class ExcalidrawView extends TextFileView {
|
||||
icon: ICONS.Discord,
|
||||
href: "https://discord.gg/DyfAXFwUHc",
|
||||
shortcut: null,
|
||||
"aria-label": "Join the Visual Thinking Discord Server",
|
||||
"aria-label": "Join the Discord Server",
|
||||
},
|
||||
" Join the Visual Thinking Discord Server"
|
||||
" Join the Discord Server"
|
||||
),
|
||||
React.createElement(
|
||||
WelcomeScreen.Center.MenuItemLink,
|
||||
@@ -5033,6 +5089,16 @@ export default class ExcalidrawView extends TextFileView {
|
||||
},
|
||||
" Follow me on Twitter"
|
||||
),
|
||||
React.createElement(
|
||||
WelcomeScreen.Center.MenuItemLink,
|
||||
{
|
||||
icon: ICONS.Learn,
|
||||
href: "https://visual-thinking-workshop.com",
|
||||
shortcut: null,
|
||||
"aria-label": "Learn Visual PKM",
|
||||
},
|
||||
" Sign up for the Visual Thinking Workshop"
|
||||
),
|
||||
React.createElement(
|
||||
WelcomeScreen.Center.MenuItemLink,
|
||||
{
|
||||
@@ -5383,6 +5449,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
renderEmbeddable: this.renderEmbeddable.bind(this),
|
||||
renderMermaid: shouldRenderMermaid,
|
||||
obsidianHostPlugin: new WeakRef(this.plugin),
|
||||
showDeprecatedFonts: true,
|
||||
},
|
||||
this.renderCustomActionsMenu(),
|
||||
this.renderWelcomeScreen(),
|
||||
@@ -5457,6 +5524,10 @@ export default class ExcalidrawView extends TextFileView {
|
||||
if (!api || this.semaphores.isEditingText || this.semaphores.preventAutozoom) {
|
||||
return;
|
||||
}
|
||||
if (windowMigratedDisableZoomOnce) {
|
||||
windowMigratedDisableZoomOnce = false;
|
||||
return;
|
||||
}
|
||||
const maxZoom = this.plugin.settings.zoomToFitMaxLevel;
|
||||
const elements = api.getSceneElements().filter((el:ExcalidrawElement)=>el.width<10000 && el.height<10000);
|
||||
if((DEVICE.isMobile && elements.length>1000) || elements.length>2500) {
|
||||
@@ -5482,6 +5553,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
}
|
||||
api.updateScene({
|
||||
appState: { pinnedScripts: this.plugin.settings.pinnedScripts },
|
||||
storeAction: "update",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -5493,8 +5565,9 @@ export default class ExcalidrawView extends TextFileView {
|
||||
}
|
||||
api.updateScene({
|
||||
appState: {
|
||||
customPens: this.plugin.settings.customPens.slice(0,this.plugin.settings.numberOfCustomPens)
|
||||
customPens: this.plugin.settings.customPens.slice(0,this.plugin.settings.numberOfCustomPens),
|
||||
},
|
||||
storeAction: "update",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -5506,6 +5579,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
}
|
||||
api.updateScene({
|
||||
appState: { allowPinchZoom: this.plugin.settings.allowPinchZoom },
|
||||
storeAction: "update",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -5517,6 +5591,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
}
|
||||
api.updateScene({
|
||||
appState: { allowWheelZoom: this.plugin.settings.allowWheelZoom },
|
||||
storeAction: "update",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -5529,6 +5604,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
const st = api.getAppState();
|
||||
api.updateScene({
|
||||
appState: { trayModeEnabled: !st.trayModeEnabled },
|
||||
storeAction: "update",
|
||||
});
|
||||
|
||||
//just in case settings were updated via Obsidian sync
|
||||
@@ -5676,11 +5752,19 @@ export default class ExcalidrawView extends TextFileView {
|
||||
let buttons = [];
|
||||
if(isFrame) {
|
||||
switch(prefix) {
|
||||
case "clippedframe=":
|
||||
buttons = [
|
||||
{caption: "Clipped Frame", action:()=>{prefix="clippedframe="; return;}},
|
||||
{caption: "Frame", action:()=>{prefix="frame="; return;}},
|
||||
{caption: "Link", action:()=>{prefix="";return}},
|
||||
];
|
||||
break;
|
||||
case "area=":
|
||||
case "group=":
|
||||
case "frame=":
|
||||
buttons = [
|
||||
{caption: "Frame", action:()=>{prefix="frame="; return;}},
|
||||
{caption: "Clipped Frame", action:()=>{prefix="clippedframe="; return;}},
|
||||
{caption: "Link", action:()=>{prefix="";return}},
|
||||
];
|
||||
break;
|
||||
@@ -5688,6 +5772,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
buttons = [
|
||||
{caption: "Link", action:()=>{prefix="";return}},
|
||||
{caption: "Frame", action:()=>{prefix="frame="; return;}},
|
||||
{caption: "Clipped Frame", action:()=>{prefix="clippedframe="; return;}},
|
||||
]
|
||||
}
|
||||
|
||||
@@ -5719,7 +5804,7 @@ export default class ExcalidrawView extends TextFileView {
|
||||
const alias = await ScriptEngine.inputPrompt(
|
||||
this,
|
||||
this.plugin,
|
||||
app,
|
||||
this.app,
|
||||
"Set link alias",
|
||||
"Leave empty if you do not want to set an alias",
|
||||
"",
|
||||
|
||||
@@ -12,7 +12,6 @@ import { ExportSettings } from "./ExcalidrawView";
|
||||
import ExcalidrawPlugin from "./main";
|
||||
import {getIMGFilename,} from "./utils/FileUtils";
|
||||
import {
|
||||
embedFontsInSVG,
|
||||
getEmbeddedFilenameParts,
|
||||
getExportTheme,
|
||||
getQuickImagePreview,
|
||||
@@ -86,7 +85,14 @@ const _getPNG = async ({imgAttributes,filenameParts,theme,cacheReady,img,file,ex
|
||||
? 2
|
||||
: 1;
|
||||
|
||||
const cacheKey = {...filenameParts, isDark: theme==="dark", previewImageType: PreviewImageType.PNG, scale, isTransparent: !exportSettings.withBackground};
|
||||
const cacheKey = {
|
||||
...filenameParts,
|
||||
isDark: theme==="dark",
|
||||
previewImageType: PreviewImageType.PNG,
|
||||
scale,
|
||||
isTransparent: !exportSettings.withBackground,
|
||||
inlineFonts: true, //though for PNG this makes no difference, but the key requires it
|
||||
};
|
||||
|
||||
if(cacheReady) {
|
||||
const src = await imageCache.getImageFromCache(cacheKey);
|
||||
@@ -105,11 +111,13 @@ const _getPNG = async ({imgAttributes,filenameParts,theme,cacheReady,img,file,ex
|
||||
const png =
|
||||
quickPNG ??
|
||||
(await createPNG(
|
||||
(filenameParts.hasGroupref || filenameParts.hasFrameref)
|
||||
(filenameParts.hasGroupref || filenameParts.hasFrameref || filenameParts.hasClippedFrameref)
|
||||
? filenameParts.filepath + filenameParts.linkpartReference
|
||||
: file.path,
|
||||
scale,
|
||||
exportSettings,
|
||||
filenameParts.hasClippedFrameref
|
||||
? { ...exportSettings, frameRendering: { enabled: true, name: false, outline: false, clip: true}}
|
||||
: exportSettings,
|
||||
loader,
|
||||
theme,
|
||||
null,
|
||||
@@ -163,7 +171,16 @@ const _getSVGIMG = async ({filenameParts,theme,cacheReady,img,file,exportSetting
|
||||
exportSettings: ExportSettings,
|
||||
loader: EmbeddedFilesLoader,
|
||||
}):Promise<HTMLImageElement> => {
|
||||
const cacheKey = {...filenameParts, isDark: theme==="dark", previewImageType: PreviewImageType.SVGIMG, scale:1, isTransparent: !exportSettings.withBackground};
|
||||
exportSettings.skipInliningFonts = false;
|
||||
const cacheKey = {
|
||||
...filenameParts,
|
||||
isDark: theme==="dark",
|
||||
previewImageType: PreviewImageType.SVGIMG,
|
||||
scale:1,
|
||||
isTransparent: !exportSettings.withBackground,
|
||||
inlineFonts: !exportSettings.skipInliningFonts,
|
||||
};
|
||||
|
||||
if(cacheReady) {
|
||||
const src = await imageCache.getImageFromCache(cacheKey);
|
||||
if(src && typeof src === "string") {
|
||||
@@ -182,13 +199,15 @@ const _getSVGIMG = async ({filenameParts,theme,cacheReady,img,file,exportSetting
|
||||
}
|
||||
}
|
||||
|
||||
let svg = convertSVGStringToElement((
|
||||
const svg = convertSVGStringToElement((
|
||||
await createSVG(
|
||||
filenameParts.hasGroupref || filenameParts.hasBlockref || filenameParts.hasSectionref || filenameParts.hasFrameref
|
||||
filenameParts.hasGroupref || filenameParts.hasBlockref || filenameParts.hasSectionref || filenameParts.hasFrameref || filenameParts.hasClippedFrameref
|
||||
? filenameParts.filepath + filenameParts.linkpartReference
|
||||
: file.path,
|
||||
true,
|
||||
exportSettings,
|
||||
filenameParts?.hasClippedFrameref
|
||||
? { ...exportSettings, frameRendering: { enabled: true, name: false, outline: false, clip: true}}
|
||||
: exportSettings,
|
||||
loader,
|
||||
theme,
|
||||
null,
|
||||
@@ -204,7 +223,6 @@ const _getSVGIMG = async ({filenameParts,theme,cacheReady,img,file,exportSetting
|
||||
return null;
|
||||
}
|
||||
|
||||
svg = embedFontsInSVG(svg, plugin, false);
|
||||
//need to remove width and height attributes to support area= embeds
|
||||
svg.removeAttribute("width");
|
||||
svg.removeAttribute("height");
|
||||
@@ -220,20 +238,30 @@ const _getSVGNative = async ({filenameParts,theme,cacheReady,containerElement,fi
|
||||
exportSettings: ExportSettings,
|
||||
loader: EmbeddedFilesLoader,
|
||||
}):Promise<HTMLDivElement> => {
|
||||
const cacheKey = {...filenameParts, isDark: theme==="dark", previewImageType: PreviewImageType.SVG, scale:1, isTransparent: !exportSettings.withBackground};
|
||||
exportSettings.skipInliningFonts = false;
|
||||
const cacheKey = {
|
||||
...filenameParts,
|
||||
isDark: theme==="dark",
|
||||
previewImageType: PreviewImageType.SVG,
|
||||
scale:1,
|
||||
isTransparent: !exportSettings.withBackground,
|
||||
inlineFonts: !exportSettings.skipInliningFonts,
|
||||
};
|
||||
let maybeSVG;
|
||||
if(cacheReady) {
|
||||
maybeSVG = await imageCache.getImageFromCache(cacheKey);
|
||||
}
|
||||
|
||||
let svg = (maybeSVG && (maybeSVG instanceof SVGSVGElement))
|
||||
const svg = (maybeSVG && (maybeSVG instanceof SVGSVGElement))
|
||||
? maybeSVG
|
||||
: convertSVGStringToElement((await createSVG(
|
||||
filenameParts.hasGroupref || filenameParts.hasBlockref || filenameParts.hasSectionref || filenameParts.hasFrameref
|
||||
filenameParts.hasGroupref || filenameParts.hasBlockref || filenameParts.hasSectionref || filenameParts.hasFrameref || filenameParts.hasClippedFrameref
|
||||
? filenameParts.filepath + filenameParts.linkpartReference
|
||||
: file.path,
|
||||
false,
|
||||
exportSettings,
|
||||
filenameParts.hasClippedFrameref
|
||||
? { ...exportSettings, frameRendering: { enabled: true, name: false, outline: false, clip: true}}
|
||||
: exportSettings,
|
||||
loader,
|
||||
theme,
|
||||
null,
|
||||
@@ -254,7 +282,7 @@ const _getSVGNative = async ({filenameParts,theme,cacheReady,containerElement,fi
|
||||
if(!Boolean(maybeSVG)) {
|
||||
cacheReady && imageCache.addImageToCache(cacheKey,"", svg);
|
||||
}
|
||||
svg = embedFontsInSVG(svg, plugin, true);
|
||||
|
||||
svg.removeAttribute("width");
|
||||
svg.removeAttribute("height");
|
||||
containerElement.append(svg);
|
||||
@@ -571,7 +599,7 @@ const isTextOnlyEmbed = (internalEmbedEl: Element):boolean => {
|
||||
const src = internalEmbedEl.getAttribute("src");
|
||||
if(!src) return true; //technically this does not mean this is a text only embed, but still should abort further processing
|
||||
const fnameParts = getEmbeddedFilenameParts(src);
|
||||
return !(fnameParts.hasArearef || fnameParts.hasGroupref || fnameParts.hasFrameref) &&
|
||||
return !(fnameParts.hasArearef || fnameParts.hasGroupref || fnameParts.hasFrameref || fnameParts.hasClippedFrameref) &&
|
||||
(fnameParts.hasBlockref || fnameParts.hasSectionref)
|
||||
}
|
||||
|
||||
@@ -661,7 +689,7 @@ const tmpObsidianWYSIWYG = async (
|
||||
} else {
|
||||
const warningEl = el.querySelector("div>h3[data-heading^='Unable to find section #^");
|
||||
if(warningEl) {
|
||||
const ref = warningEl.getAttr("data-heading").match(/Unable to find section (#\^(?:group=|area=|frame=)[^ ]*)/)?.[1];
|
||||
const ref = warningEl.getAttr("data-heading").match(/Unable to find section (#\^(?:group=|area=|frame=|clippedframe=)[^ ]*)/)?.[1];
|
||||
if(ref) {
|
||||
attr.fname = file.path + ref;
|
||||
areaPreview = true;
|
||||
|
||||
@@ -33,8 +33,20 @@ export class ScriptEngine {
|
||||
this.registerEventHandlers();
|
||||
}
|
||||
|
||||
public removeViewEAs(view: ExcalidrawView) {
|
||||
const eas = new Set<ExcalidrawAutomate>();
|
||||
this.eaInstances.forEach((ea) => {
|
||||
if (ea.targetView === view) {
|
||||
eas.add(ea);
|
||||
ea.destroy();
|
||||
}
|
||||
});
|
||||
this.eaInstances.removeObjects(eas);
|
||||
}
|
||||
|
||||
public destroy() {
|
||||
this.eaInstances.forEach((ea) => ea.destroy());
|
||||
this.eaInstances.clear();
|
||||
this.eaInstances = null;
|
||||
this.scriptIconMap = null;
|
||||
this.plugin = null;
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,5 +1,5 @@
|
||||
import { customAlphabet } from "nanoid";
|
||||
import { DeviceType } from "../types";
|
||||
import { DeviceType } from "../types/types";
|
||||
import { ExcalidrawLib } from "../ExcalidrawLib";
|
||||
import { moment } from "obsidian";
|
||||
import ExcalidrawPlugin from "src/main";
|
||||
@@ -85,7 +85,7 @@ export const {
|
||||
getCommonBoundingBox,
|
||||
getMaximumGroups,
|
||||
measureText,
|
||||
getDefaultLineHeight,
|
||||
getLineHeight,
|
||||
wrapText,
|
||||
getFontString,
|
||||
getBoundTextMaxWidth,
|
||||
@@ -94,10 +94,14 @@ export const {
|
||||
mutateElement,
|
||||
restore,
|
||||
mermaidToExcalidraw,
|
||||
getFontFamilyString,
|
||||
getContainerElement,
|
||||
refreshTextDimensions,
|
||||
getFontDefinition,
|
||||
} = excalidrawLib;
|
||||
|
||||
export const FONTS_STYLE_ID = "excalidraw-custom-fonts";
|
||||
|
||||
export function JSON_parse(x: string): any {
|
||||
return JSON.parse(x.replaceAll("[", "["));
|
||||
}
|
||||
|
||||
@@ -223,6 +223,7 @@ function RenderObsidianView(
|
||||
} else {
|
||||
const workspaceLeaf:HTMLDivElement = rootSplit.containerEl.querySelector("div.workspace-leaf");
|
||||
if(workspaceLeaf) workspaceLeaf.style.borderRadius = "var(--embeddable-radius)";
|
||||
rootSplit.containerEl.addClass("mod-visible");
|
||||
containerRef.current.appendChild(rootSplit.containerEl);
|
||||
setColors(containerRef.current, element, mdProps, canvasColor);
|
||||
}
|
||||
|
||||
@@ -184,7 +184,7 @@ export class EmbeddableSettings extends Modal {
|
||||
new Notice("File rename failed. A file with this name already exists.\n"+newPath,10000);
|
||||
} else {
|
||||
try {
|
||||
await this.app.vault.rename(this.file,newPath);
|
||||
await this.app.fileManager.renameFile(this.file,newPath);
|
||||
el.link = this.element.link.replace(
|
||||
/(\[\[)([^#\]]*)([^\]]*]])/,`$1${
|
||||
this.plugin.app.metadataCache.fileToLinktext(
|
||||
@@ -226,7 +226,7 @@ export class EmbeddableSettings extends Modal {
|
||||
(async() => {
|
||||
await this.ea.addElementsToView();
|
||||
//@ts-ignore
|
||||
this.ea.viewUpdateScene({appState: {}});
|
||||
this.ea.viewUpdateScene({appState: {}, storeAction: "update"});
|
||||
this.close(); //close should only run once update scene is done
|
||||
})();
|
||||
} else {
|
||||
|
||||
83
src/dialogs/FrameSettings.ts
Normal file
83
src/dialogs/FrameSettings.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import { ExcalidrawAutomate } from "src/ExcalidrawAutomate";
|
||||
import { t } from "src/lang/helpers";
|
||||
|
||||
export const showFrameSettings = (ea: ExcalidrawAutomate) => {
|
||||
const {enabled, clip, name, outline} = ea.getExcalidrawAPI().getAppState().frameRendering;
|
||||
|
||||
// Create modal dialog
|
||||
const frameSettingsModal = new ea.obsidian.Modal(app);
|
||||
|
||||
frameSettingsModal.onOpen = () => {
|
||||
const {contentEl} = frameSettingsModal;
|
||||
|
||||
contentEl.createEl("h1", {text: t("FRAME_SETTINGS_TITLE")});
|
||||
|
||||
const settings = { enabled, clip, name, outline };
|
||||
|
||||
// Add toggles
|
||||
const enableFramesSetting = new ea.obsidian.Setting(contentEl)
|
||||
.setName(t("FRAME_SETTINGS_ENABLE"))
|
||||
.addToggle(toggle => toggle
|
||||
.setValue(settings.enabled)
|
||||
.onChange(value => {
|
||||
settings.enabled = value;
|
||||
hideComponent(displayFrameNameSetting, !value);
|
||||
hideComponent(displayFrameOutlineSetting, !value);
|
||||
hideComponent(enableFrameClippingSetting, !value);
|
||||
})
|
||||
);
|
||||
|
||||
const displayFrameNameSetting = new ea.obsidian.Setting(contentEl)
|
||||
.setName(t("FRAME_SETTIGNS_NAME"))
|
||||
.addToggle(toggle => toggle
|
||||
.setValue(settings.name)
|
||||
.onChange(value => settings.name = value)
|
||||
);
|
||||
|
||||
const displayFrameOutlineSetting = new ea.obsidian.Setting(contentEl)
|
||||
.setName(t("FRAME_SETTINGS_OUTLINE"))
|
||||
.addToggle(toggle => toggle
|
||||
.setValue(settings.outline)
|
||||
.onChange(value => settings.outline = value)
|
||||
);
|
||||
|
||||
const enableFrameClippingSetting = new ea.obsidian.Setting(contentEl)
|
||||
.setName(t("FRAME_SETTINGS_CLIP"))
|
||||
.addToggle(toggle => toggle
|
||||
.setValue(settings.clip)
|
||||
.onChange(value => settings.clip = value)
|
||||
);
|
||||
|
||||
// Hide or show components based on initial state
|
||||
hideComponent(displayFrameNameSetting, !settings.enabled);
|
||||
hideComponent(displayFrameOutlineSetting, !settings.enabled);
|
||||
hideComponent(enableFrameClippingSetting, !settings.enabled);
|
||||
|
||||
// Add OK button
|
||||
new ea.obsidian.Setting(contentEl)
|
||||
.addButton(button => button
|
||||
.setButtonText("OK")
|
||||
.onClick(() => {
|
||||
// Update appState with new settings
|
||||
ea.viewUpdateScene({
|
||||
// @ts-ignore
|
||||
appState: {
|
||||
frameRendering: settings
|
||||
},
|
||||
storeAction: "update",
|
||||
});
|
||||
frameSettingsModal.close();
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
frameSettingsModal.onClose = () => {
|
||||
ea.destroy();
|
||||
}
|
||||
frameSettingsModal.open();
|
||||
};
|
||||
|
||||
// Function to hide or show a component
|
||||
function hideComponent(comp:any, value:any) {
|
||||
comp.settingEl.style.display = value ? "none" : "";
|
||||
}
|
||||
@@ -17,6 +17,60 @@ I develop this plugin as a hobby, spending my free time doing this. If you find
|
||||
|
||||
<div class="ex-coffee-div"><a href="https://ko-fi.com/zsolt"><img src="https://cdn.ko-fi.com/cdn/kofi3.png?v=3" height=45></a></div>
|
||||
`,
|
||||
"2.3.0": `
|
||||
I am moving to a new release approach aiming to publish one update per month to the Obsidian script store. If you want to continue to receive more frequent updates with new features and minor bug fixes, then join the beta testing team. [#1912](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1912)
|
||||
|
||||
<div class="excalidraw-videoWrapper"><div>
|
||||
<iframe src="https://www.youtube.com/embed/2poSS-Z91lY" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
</div></div>
|
||||
|
||||
## New
|
||||
- Elbow connectors: https://x.com/excalidraw/status/1819084086222393554
|
||||
|
||||
## Fixed
|
||||
- Convert Markdown to Excalidraw did not work correctly when there was ${String.fromCharCode(96)}---${String.fromCharCode(96)} anywhere in the file, but no frontmatter (e.g. a table)
|
||||
- Fixed Obsidian move tab to new window
|
||||
- Fixed duplicating bound arrows without its bound elements throwing error [#8315](https://github.com/excalidraw/excalidraw/issues/8315)
|
||||
`,
|
||||
"2.2.13": `
|
||||
## Fixed
|
||||
- Could not undo element after pasting [#1906](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1906)
|
||||
- Links broke after renaming an Excalidraw file using the F2 shortcut [#1907](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1907)
|
||||
- Unable to open or convert very large ${String.fromCharCode(96)}.excalidraw${String.fromCharCode(96)} file, e.g. BoaPs you can download from [here](https://ko-fi.com/zsolt/shop)
|
||||
`,
|
||||
"2.2.12": `
|
||||
## Fixed
|
||||
- Rename moved files to root folder [#1905](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1905)
|
||||
- Fonts not displaying correctly in cached image previews
|
||||
`,
|
||||
"2.2.11": `
|
||||
<img alt="badges" src="https://github.com/user-attachments/assets/7591b523-6bc6-46ff-b552-5c3492139e4c" referrerpolicy="no-referrer" style="width: 100%;">
|
||||
|
||||
## New
|
||||
- Font picker with additional fonts (not yet fully configurable, but that will come in due time) [#8012](https://github.com/excalidraw/excalidraw/pull/8012)
|
||||
- Introducing Visual Thinking Badges. The more you use Excalidraw the higher your rank will be. Levels are: Bronze, Silver, Gold and Platinum.
|
||||
|
||||
## Fixed
|
||||
- Embedded PDF was not visible on phones [#1904](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1904)
|
||||
- F2 does not rename files in Excalidraw View [#1900](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1900)
|
||||
- Wireframe to Code now honors the GPT model settings in plugin settings. [#1901](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1901)
|
||||
- Updated ExcaliAI to support gpt-4o for vision. [#1859](https://github.com/zsviczian/obsidian-excalidraw-plugin/pull/1859) 🙏@Saik0s
|
||||
- Minor fixes from excalidraw.com [#8287](https://github.com/excalidraw/excalidraw/pull/8287), [#8285](https://github.com/excalidraw/excalidraw/pull/8285)
|
||||
`,
|
||||
"2.2.10": `
|
||||
<div class="excalidraw-videoWrapper"><div>
|
||||
<iframe src="https://www.youtube.com/embed/sjZfdqpxqsg" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||
</div></div>
|
||||
|
||||
## Fixed
|
||||
- Drastically degraded rendering performance when zoomed in and when arrows with labels are used. [#8267](https://github.com/excalidraw/excalidraw/pull/8267), [#8266](https://github.com/excalidraw/excalidraw/pull/8266), [#1893](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/1893)
|
||||
- Frame title font in exports.
|
||||
|
||||
## New
|
||||
- Area, Group, Frame, and Clipped-Frame references to images now also work when pasting images to Excalidraw.
|
||||
- The new reference type ${String.fromCharCode(96)}clippedframe=${String.fromCharCode(96)} works in the same way as ${String.fromCharCode(96)}frame=${String.fromCharCode(96)} but will display the elements clipped by the frame. ${String.fromCharCode(96)}clippedframe=${String.fromCharCode(96)} will always display the image with zero padding.
|
||||
- New command palette action: ${String.fromCharCode(96)}Frame Settings${String.fromCharCode(96)} gives you fine-grained control over how frames are rendered. Frame settings will also be reflected in image exports. For example, if you hide the frame name or outline, then in exports they will not be visible.
|
||||
`,
|
||||
"2.2.9": `
|
||||
## New
|
||||
- Improved the "Open the back-of-the-note of the selected Excalidraw image" action. It now works with grouped elements and keeps the popout window within the visible screen area when elements are close to the top of the canvas. Note: Due to an Obsidian bug, I do not recommend using this feature with versions 1.6.0 - 1.6.6, if you have Obsidian Sync enabled, because Obsidian may freeze when closing the popout window. It functions properly in Obsidian versions before 1.6.0 and from 1.6.7 onwards.
|
||||
|
||||
44
src/dialogs/RankMessage.ts
Normal file
44
src/dialogs/RankMessage.ts
Normal file
File diff suppressed because one or more lines are too long
@@ -100,7 +100,7 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
|
||||
{
|
||||
field: "style.fontFamily",
|
||||
code: "[number]",
|
||||
desc: "1: Virgil, 2:Helvetica, 3:Cascadia, 4:LocalFont",
|
||||
desc: "1: Virgil, 2:Helvetica, 3:Cascadia, 4:Local Font, 5: Excalifont, 6: Nunito, 7: Lilita One, 8: Comic Shanns, 9: Liberation Sans",
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
@@ -442,7 +442,19 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
|
||||
{
|
||||
field: "getExportSettings",
|
||||
code: "getExportSettings(withBackground: boolean, withTheme: boolean,): ExportSettings;",
|
||||
desc: "Utility function to generate ExportSettings object",
|
||||
desc: "Utility function to generate ExportSettings object\n" +
|
||||
"export interface ExportSettings {\n" +
|
||||
" withBackground: boolean;\n" +
|
||||
" withTheme: boolean;\n" +
|
||||
" isMask: boolean; //if true elements will be processed as mask, clipping, etc.\n" +
|
||||
" frameRendering?: { //optional, overrides relevant appState settings for rendering the frame\n" +
|
||||
" enabled: boolean;\n" +
|
||||
" name: boolean;\n" +
|
||||
" outline: boolean;\n" +
|
||||
" clip: boolean;\n" +
|
||||
" };\n" +
|
||||
" skipInliningFonts?: boolean;\n" +
|
||||
"}",
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
@@ -481,6 +493,12 @@ export const EXCALIDRAW_AUTOMATE_INFO: SuggesterInfo[] = [
|
||||
desc: "Gets all the elements from elements[] that share one or more groupIds with element.",
|
||||
after: ""
|
||||
},
|
||||
{
|
||||
field: "getElementsInFrame",
|
||||
code: " getElementsInFrame(frameElement: ExcalidrawElement,elements: ExcalidrawElement[],shouldIncludeFrame: boolean = false,): ExcalidrawElement[];",
|
||||
desc: "Gets all the elements from elements[] that are inside the frameElement. If shouldIncludeFrame is true, the frameElement will also be included in the result.",
|
||||
after: "",
|
||||
},
|
||||
{
|
||||
field: "activeScript",
|
||||
code: "activeScript: string;",
|
||||
|
||||
@@ -50,17 +50,20 @@ export default {
|
||||
NEW_IN_POPOUT_WINDOW_EMBED: "Create new drawing - IN A POPOUT WINDOW - and embed into active document",
|
||||
TOGGLE_LOCK: "Toggle Text Element between edit RAW and PREVIEW",
|
||||
DELETE_FILE: "Delete selected image or Markdown file from Obsidian Vault",
|
||||
COPY_ELEMENT_LINK: "Copy markdown link for selected element(s)",
|
||||
COPY_ELEMENT_LINK: "Copy [[link]] for selected element(s)",
|
||||
COPY_DRAWING_LINK: "Copy ![[embed link]] for this drawing",
|
||||
INSERT_LINK_TO_ELEMENT:
|
||||
`Copy markdown link for selected element to clipboard. ${labelCTRL()}+CLICK to copy 'group=' link. ${labelSHIFT()}+CLICK to copy an 'area=' link. ${labelALT()}+CLICK to watch a help video.`,
|
||||
`Copy [[link]] for selected element to clipboard. ${labelCTRL()}+CLICK to copy 'group=' link. ${labelSHIFT()}+CLICK to copy an 'area=' link. ${labelALT()}+CLICK to watch a help video.`,
|
||||
INSERT_LINK_TO_ELEMENT_GROUP:
|
||||
"Copy 'group=' markdown link for selected element to clipboard.",
|
||||
"Copy 'group=' ![[link]] for selected element to clipboard.",
|
||||
INSERT_LINK_TO_ELEMENT_AREA:
|
||||
"Copy 'area=' markdown link for selected element to clipboard.",
|
||||
"Copy 'area=' ![[link]] for selected element to clipboard.",
|
||||
INSERT_LINK_TO_ELEMENT_FRAME:
|
||||
"Copy 'frame=' markdown link for selected element to clipboard.",
|
||||
"Copy 'frame=' ![[link]] for selected element to clipboard.",
|
||||
INSERT_LINK_TO_ELEMENT_FRAME_CLIPPED:
|
||||
"Copy 'clippedframe=' ![[link]] for selected element to clipboard.",
|
||||
INSERT_LINK_TO_ELEMENT_NORMAL:
|
||||
"Copy markdown link for selected element to clipboard.",
|
||||
"Copy [[link]] for selected element to clipboard.",
|
||||
INSERT_LINK_TO_ELEMENT_ERROR: "Select a single element in the scene",
|
||||
INSERT_LINK_TO_ELEMENT_READY: "Link is READY and available on the clipboard",
|
||||
INSERT_LINK: "Insert link to file",
|
||||
@@ -136,6 +139,7 @@ export default {
|
||||
ERROR_SAVING_IMAGE: "Unknown error occurred while fetching the image. It could be that for some reason the image is not available or rejected the fetch request from Obsidian",
|
||||
WARNING_PASTING_ELEMENT_AS_TEXT: "PASTING EXCALIDRAW ELEMENTS AS A TEXT ELEMENT IS NOT ALLOWED",
|
||||
USE_INSERT_FILE_MODAL: "Use 'Insert Any File' to embed a markdown note",
|
||||
RECURSIVE_INSERT_ERROR: "You may not recursively insert part of an image into the same image as it would create an infinite loop",
|
||||
CONVERT_TO_MARKDOWN: "Convert to file...",
|
||||
SELECT_TEXTELEMENT_ONLY: "Select text element only (not container)",
|
||||
REMOVE_LINK: "Remove text element link",
|
||||
@@ -226,7 +230,7 @@ export default {
|
||||
"The default OpenAI API URL. This is a freetext field, so you can enter any valid OpenAI API compatible URL. " +
|
||||
"Excalidraw will use this URL when posting API requests to OpenAI. I am not doing any error handling on this field, so make sure you enter a valid URL and only change this if you know what you are doing. ",
|
||||
AI_OPENAI_DEFAULT_IMAGE_API_URL_NAME: "OpenAI Image Generation API URL",
|
||||
AI_OPENAI_DEFAULT_VISION_MODEL_PLACEHOLDER: "Enter your default AI vision model here. e.g.: gpt-4-vision-preview",
|
||||
AI_OPENAI_DEFAULT_VISION_MODEL_PLACEHOLDER: "Enter your default AI vision model here. e.g.: gpt-4o",
|
||||
SAVING_HEAD: "Saving",
|
||||
SAVING_DESC: "In the 'Saving' section of Excalidraw Settings, you can configure how your drawings are saved. This includes options for compressing Excalidraw JSON in Markdown, setting autosave intervals for both desktop and mobile, defining filename formats, and choosing whether to use the .excalidraw.md or .md file extension. ",
|
||||
COMPRESS_NAME: "Compress Excalidraw JSON in Markdown",
|
||||
@@ -571,7 +575,7 @@ FILENAME_HEAD: "Filename",
|
||||
"If turned off, the exported image will be transparent.",
|
||||
EXPORT_PADDING_NAME: "Image Padding",
|
||||
EXPORT_PADDING_DESC:
|
||||
"The padding (in pixels) around the exported SVG or PNG image. " +
|
||||
"The padding (in pixels) around the exported SVG or PNG image. Padding is set to 0 for clippedFrame references." +
|
||||
"If you have curved lines close to the edge of the image they might get cropped during image export. You can increase this value to avoid cropping. " +
|
||||
"You can also override this setting at a file level by adding the <code>excalidraw-export-padding: 5<code> frontmatter key.",
|
||||
EXPORT_THEME_NAME: "Export image with theme",
|
||||
@@ -643,7 +647,7 @@ FILENAME_HEAD: "Filename",
|
||||
LATEX_DEFAULT_DESC: "Leave empty if you don't want a default formula. You can add default formatting here such as <code>\\color{white}</code>.",
|
||||
NONSTANDARD_HEAD: "Non-Excalidraw.com supported features",
|
||||
NONSTANDARD_DESC: `These settings in the "Non-Excalidraw.com Supported Features" section provide customization options beyond the default Excalidraw.com features. These features are not available on excalidraw.com. When exporting the drawing to Excalidraw.com these features will appear different.
|
||||
You can configure the number of custom pens displayed next to the Obsidian Menu on the canvas, allowing you to choose from a range of options. Additionally, you can enable a fourth font option, which adds a fourth font button to the properties panel for text elements. `,
|
||||
You can configure the number of custom pens displayed next to the Obsidian Menu on the canvas, allowing you to choose from a range of options. Additionally, you can enable a local font option, which adds a local font to the list of fonts on the element properties panel for text elements. `,
|
||||
RENDER_TWEAK_HEAD: "Rendering tweaks",
|
||||
MAX_IMAGE_ZOOM_IN_NAME: "Maximum image zoom in resolution",
|
||||
MAX_IMAGE_ZOOM_IN_DESC: "To save on memory and because Apple Safari (Obsidian on iOS) has some hard-coded limitations, Excalidraw.com limits the max resolution of images and large objects when zooming in. You can override this limitation using a multiplicator. " +
|
||||
@@ -693,16 +697,16 @@ FILENAME_HEAD: "Filename",
|
||||
"Enabling this feature simplifies the use of Excalidraw front matter properties, allowing you to leverage many powerful settings. If you prefer not to load these properties automatically, " +
|
||||
"you can disable this feature, but you will need to manually remove any unwanted properties from the suggester. " +
|
||||
"Note that turning on this setting requires restarting the plugin as properties are loaded at startup.",
|
||||
CUSTOM_FONT_HEAD: "Fourth font",
|
||||
ENABLE_FOURTH_FONT_NAME: "Enable fourth font option",
|
||||
CUSTOM_FONT_HEAD: "Local font",
|
||||
ENABLE_FOURTH_FONT_NAME: "Enable local font option",
|
||||
ENABLE_FOURTH_FONT_DESC:
|
||||
"By turning this on, you will see a fourth font button on the properties panel for text elements. " +
|
||||
"Files that use this fourth font will (partly) lose their platform independence. " +
|
||||
"By turning this on, you will see a local font in the font list on the properties panel for text elements. " +
|
||||
"Files that use this local font will (partly) lose their platform independence. " +
|
||||
"Depending on the custom font set in settings, they will look differently when loaded in another vault, or at a later time. " +
|
||||
"Also the 4th font will display as system default font on excalidraw.com, or other Excalidraw versions.",
|
||||
FOURTH_FONT_NAME: "Fourth font file",
|
||||
FOURTH_FONT_NAME: "Local font file",
|
||||
FOURTH_FONT_DESC:
|
||||
"Select a .ttf, .woff or .woff2 font file from your vault to use as the fourth font. " +
|
||||
"Select a .ttf, .woff or .woff2 font file from your vault to use as the local font. " +
|
||||
"If no file is selected, Excalidraw will use the Virgil font by default.",
|
||||
SCRIPT_SETTINGS_HEAD: "Settings for installed Scripts",
|
||||
SCRIPT_SETTINGS_DESC: "Some of the Excalidraw Automate Scripts include settings. Settings are organized by script. Settings will only become visible in this list after you have executed the newly downloaded script once.",
|
||||
@@ -824,4 +828,11 @@ FILENAME_HEAD: "Filename",
|
||||
INTERNAL_DRAG_ACTION: "Obsidian Internal Drag Action",
|
||||
PANE_TARGET: "Link click behavior",
|
||||
DEFAULT_ACTION_DESC: "In case none of the combinations apply the default action for this group is: ",
|
||||
|
||||
//FrameSettings.ts
|
||||
FRAME_SETTINGS_TITLE: "Frame Settings",
|
||||
FRAME_SETTINGS_ENABLE: "Enable Frames",
|
||||
FRAME_SETTIGNS_NAME: "Display Frame Name",
|
||||
FRAME_SETTINGS_OUTLINE: "Display Frame Outline",
|
||||
FRAME_SETTINGS_CLIP: "Enable Frame Clipping",
|
||||
};
|
||||
|
||||
@@ -50,15 +50,18 @@ export default {
|
||||
NEW_IN_POPOUT_WINDOW_EMBED: "新建绘图 - 于新窗口 - 并将其嵌入(形如 ![[drawing]])到当前 Markdown 文档中",
|
||||
TOGGLE_LOCK: "文本元素:原文模式(RAW)⟺ 预览模式(PREVIEW)",
|
||||
DELETE_FILE: "从库中删除所选图像(或以图像形式嵌入绘图中的 Markdown)的源文件",
|
||||
COPY_ELEMENT_LINK: "复制所选元素的 Markdown 链接",
|
||||
COPY_ELEMENT_LINK: "复制所选元素的链接(形如 [[file#^id]]])",
|
||||
COPY_DRAWING_LINK: "复制绘图的嵌入链接(形如 ![[darwing]])",
|
||||
INSERT_LINK_TO_ELEMENT:
|
||||
`复制所选元素为内部链接(形如 [[file#^id]] )。\n按住 ${labelCTRL()} 可复制元素所在分组为内部链接(形如 [[file#^group=id]] )。\n按住 ${labelSHIFT()} 可复制所选元素所在区域为内部链接(形如 [[file#^area=id]] )。\n按住 ${labelALT()} 可观看视频演示。`,
|
||||
INSERT_LINK_TO_ELEMENT_GROUP:
|
||||
"复制所选元素所在分组为内部链接(形如 [[file#^group=id]] )",
|
||||
"复制所选元素所在分组为嵌入链接(形如 ![[file#^group=id]] )",
|
||||
INSERT_LINK_TO_ELEMENT_AREA:
|
||||
"复制所选元素所在区域为内部链接(形如 [[file#^area=id]] )",
|
||||
"复制所选元素所在区域为嵌入链接(形如 ![[file#^area=id]] )",
|
||||
INSERT_LINK_TO_ELEMENT_FRAME:
|
||||
"复制所选框架为内部链接(形如 [[file#^frame=id]] )",
|
||||
"复制所选框架为嵌入链接(形如 ![[file#^frame=id]] )",
|
||||
INSERT_LINK_TO_ELEMENT_FRAME_CLIPPED:
|
||||
"复制所选框架(内容)为嵌入链接(形如 ![[file#^clippedframe=id]] )",
|
||||
INSERT_LINK_TO_ELEMENT_NORMAL:
|
||||
"复制所选元素为内部链接(形如 [[file#^id]] )",
|
||||
INSERT_LINK_TO_ELEMENT_ERROR: "未选择画布里的单个元素",
|
||||
@@ -136,6 +139,7 @@ export default {
|
||||
ERROR_SAVING_IMAGE: "获取图像时发生未知错误。可能是由于某种原因,图像不可用或拒绝了 Obsidian 的获取请求。",
|
||||
WARNING_PASTING_ELEMENT_AS_TEXT: "你不能将 Excalidraw 元素粘贴为文本元素!",
|
||||
USE_INSERT_FILE_MODAL: "使用“插入任意文件”功能来嵌入 Markdown 文档",
|
||||
RECURSIVE_INSERT_ERROR: "你不能将图像的一部分嵌入到此图像中,因为这可能导致无限循环。",
|
||||
CONVERT_TO_MARKDOWN: "转存为 Markdown 文档(并嵌入为 MD-Embeddable)",
|
||||
SELECT_TEXTELEMENT_ONLY: "只选择文本元素(非容器)",
|
||||
REMOVE_LINK: "移除文字元素链接",
|
||||
@@ -226,7 +230,7 @@ export default {
|
||||
"默认的 OpenAI API URL。请填写有效的 OpenAI API URL。" +
|
||||
"Excalidraw 会通过该 URL 发送 API 请求给 OpenAI。我没有对此选项做任何错误处理,请谨慎修改。",
|
||||
AI_OPENAI_DEFAULT_IMAGE_API_URL_NAME: "OpenAI 图像生成 API URL",
|
||||
AI_OPENAI_DEFAULT_VISION_MODEL_PLACEHOLDER: "gpt-4-vision-preview",
|
||||
AI_OPENAI_DEFAULT_VISION_MODEL_PLACEHOLDER: "输入你的默认 AI 模型名称,例如:gpt-4o",
|
||||
SAVING_HEAD: "保存",
|
||||
SAVING_DESC: "包括:压缩,自动保存的时间间隔,文件的命名格式和扩展名等的设置。",
|
||||
COMPRESS_NAME: "压缩 Excalidraw JSON",
|
||||
@@ -571,7 +575,7 @@ FILENAME_HEAD: "文件名",
|
||||
"如果关闭,将导出透明背景的图像。",
|
||||
EXPORT_PADDING_NAME: "导出的图像的空白边距",
|
||||
EXPORT_PADDING_DESC:
|
||||
"导出的 SVG/PNG 图像四周的空白边距(单位:像素)。<br>" +
|
||||
"导出的 SVG/PNG 图像四周的空白边距(单位:像素)。对于裁剪框架引用,间距被设置为 0。<br>" +
|
||||
"增加该值,可以避免在导出图像时,靠近图像边缘的图形被裁掉。<br>" +
|
||||
"您可为某个绘图单独设置此项,方法是在其 frontmatter 中添加形如 <code>excalidraw-export-padding: 5<code> 的键值对。",
|
||||
EXPORT_THEME_NAME: "导出的图像匹配主题",
|
||||
@@ -642,7 +646,7 @@ FILENAME_HEAD: "文件名",
|
||||
LATEX_DEFAULT_NAME: "插入 LaTeX 时的默认表达式",
|
||||
LATEX_DEFAULT_DESC: "允许留空。允许使用类似 <code>\\color{white}</code> 的格式化表达式。",
|
||||
NONSTANDARD_HEAD: "非 Excalidraw.com 官方支持的特性",
|
||||
NONSTANDARD_DESC: `这些特性不受 Excalidraw.com 官方支持。如果以 Excalidraw.com 格式导出绘图,这些特性将会发生不可预知的变化。
|
||||
NONSTANDARD_DESC: `这些特性不受 Excalidraw.com 官方支持。如果在 Excalidraw.com 导入绘图,这些特性将会发生不可预知的变化。
|
||||
包括:自定义画笔工具的数量,自定义字体等。`,
|
||||
RENDER_TWEAK_HEAD: "渲染优化",
|
||||
MAX_IMAGE_ZOOM_IN_NAME: "最大图像放大倍数",
|
||||
@@ -693,7 +697,7 @@ FILENAME_HEAD: "文件名",
|
||||
"启用此功能简化了 Excalidraw 前置属性的使用,使您能够利用许多强大的设置。如果您不希望自动加载这些属性," +
|
||||
"您可以禁用此功能,但您将需要手动从自动提示中移除任何不需要的属性。" +
|
||||
"请注意,启用此设置需要重启插件,因为属性是在启动时加载的。",
|
||||
CUSTOM_FONT_HEAD: "自定义字体",
|
||||
CUSTOM_FONT_HEAD: "本地字体",
|
||||
ENABLE_FOURTH_FONT_NAME: "为文本元素启用本地字体",
|
||||
ENABLE_FOURTH_FONT_DESC:
|
||||
"开启此项后,文本元素的属性面板里会多出一个本地字体按钮。<br>" +
|
||||
@@ -824,4 +828,11 @@ FILENAME_HEAD: "文件名",
|
||||
INTERNAL_DRAG_ACTION: "在 Obsidian 内部拖放时",
|
||||
PANE_TARGET: "点击链接时",
|
||||
DEFAULT_ACTION_DESC: "无修饰键时的行为:",
|
||||
|
||||
//FrameSettings.ts
|
||||
FRAME_SETTINGS_TITLE: "框架设置",
|
||||
FRAME_SETTINGS_ENABLE: "启用框架",
|
||||
FRAME_SETTIGNS_NAME: "显示框架名称",
|
||||
FRAME_SETTINGS_OUTLINE: "显示框架外边框",
|
||||
FRAME_SETTINGS_CLIP: "启用框架裁剪",
|
||||
};
|
||||
|
||||
182
src/main.ts
182
src/main.ts
@@ -43,13 +43,9 @@ import {
|
||||
IMAGE_TYPES,
|
||||
setExcalidrawPlugin,
|
||||
DEVICE,
|
||||
sceneCoordsToViewportCoords
|
||||
} from "./constants/constants";
|
||||
import {
|
||||
VIRGIL_FONT,
|
||||
VIRGIL_DATAURL,
|
||||
sceneCoordsToViewportCoords,
|
||||
FONTS_STYLE_ID,
|
||||
} from "./constants/constFonts";
|
||||
} from "./constants/constants";
|
||||
import ExcalidrawView, { TextMode, getTextMode } from "./ExcalidrawView";
|
||||
import {
|
||||
changeThemeOfExcalidrawMD,
|
||||
@@ -103,8 +99,9 @@ import {
|
||||
decompress,
|
||||
getImageSize,
|
||||
versionUpdateCheckTimer,
|
||||
getFontMetrics,
|
||||
} from "./utils/Utils";
|
||||
import { editorInsertText, extractSVGPNGFileName, foldExcalidrawSection, getActivePDFPageNumberFromPDFView, getAttachmentsFolderAndFilePath, getNewOrAdjacentLeaf, getParentOfClass, isObsidianThemeDark, mergeMarkdownFiles, openLeaf } from "./utils/ObsidianUtils";
|
||||
import { editorInsertText, extractSVGPNGFileName, foldExcalidrawSection, getActivePDFPageNumberFromPDFView, getAttachmentsFolderAndFilePath, getNewOrAdjacentLeaf, getParentOfClass, isObsidianThemeDark, mergeMarkdownFiles, openLeaf, setExcalidrawView } from "./utils/ObsidianUtils";
|
||||
import { ExcalidrawElement, ExcalidrawEmbeddableElement, ExcalidrawImageElement, ExcalidrawTextElement, FileId } from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
import { ScriptEngine } from "./Scripts";
|
||||
import {
|
||||
@@ -116,7 +113,7 @@ import {
|
||||
|
||||
import { FieldSuggester } from "./dialogs/FieldSuggester";
|
||||
import { ReleaseNotes } from "./dialogs/ReleaseNotes";
|
||||
import { Packages } from "./types";
|
||||
import { Packages } from "./types/types";
|
||||
import { PreviewImageType } from "./utils/UtilTypes";
|
||||
import { ScriptInstallPrompt } from "./dialogs/ScriptInstallPrompt";
|
||||
import Taskbone from "./ocr/Taskbone";
|
||||
@@ -137,14 +134,19 @@ import { carveOutImage, carveOutPDF, createImageCropperFile } from "./utils/Carv
|
||||
import { ExcalidrawConfig } from "./utils/ExcalidrawConfig";
|
||||
import { EditorHandler } from "./CodeMirrorExtension/EditorHandler";
|
||||
import { clearMathJaxVariables } from "./LaTeX";
|
||||
import { showFrameSettings } from "./dialogs/FrameSettings";
|
||||
import { ExcalidrawLib } from "./ExcalidrawLib";
|
||||
import { Rank, SwordColors } from "./menu/ActionIcons";
|
||||
import { RankMessage } from "./dialogs/RankMessage";
|
||||
|
||||
declare let EXCALIDRAW_PACKAGES:string;
|
||||
declare let react:any;
|
||||
declare let reactDOM:any;
|
||||
declare let excalidrawLib: any;
|
||||
declare let excalidrawLib: typeof ExcalidrawLib;
|
||||
declare let PLUGIN_VERSION:string;
|
||||
|
||||
export default class ExcalidrawPlugin extends Plugin {
|
||||
public fourthFontLoaded: boolean = false;
|
||||
public excalidrawConfig: ExcalidrawConfig;
|
||||
public taskbone: Taskbone;
|
||||
private excalidrawFiles: Set<TFile> = new Set<TFile>();
|
||||
@@ -177,7 +179,6 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
public equationsMaster: Map<FileId, string> = null; //fileId, formula
|
||||
public mermaidsMaster: Map<FileId, string> = null; //fileId, mermaidText
|
||||
public scriptEngine: ScriptEngine;
|
||||
public fourthFontDef: string = VIRGIL_FONT;
|
||||
private packageMap: Map<Window,Packages> = new Map<Window,Packages>();
|
||||
public leafChangeTimeout: number = null;
|
||||
private forceSaveCommand:Command;
|
||||
@@ -186,8 +187,9 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
public editorHandler: EditorHandler;
|
||||
//if set, the next time this file is opened it will be opened as markdown
|
||||
public forceToOpenInMarkdownFilepath: string = null;
|
||||
private slob:string;
|
||||
//private slob:string;
|
||||
private ribbonIcon:HTMLElement;
|
||||
public loadTimestamp:number;
|
||||
|
||||
constructor(app: App, manifest: PluginManifest) {
|
||||
super(app, manifest);
|
||||
@@ -199,9 +201,9 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
this.equationsMaster = new Map<FileId, string>();
|
||||
this.mermaidsMaster = new Map<FileId, string>();
|
||||
setExcalidrawPlugin(this);
|
||||
if((process.env.NODE_ENV === 'development')) {
|
||||
/*if((process.env.NODE_ENV === 'development')) {
|
||||
this.slob = new Array(200 * 1024 * 1024 + 1).join('A'); // Create a 200MB blob
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
get locale() {
|
||||
@@ -309,14 +311,23 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
}*/
|
||||
|
||||
async onload() {
|
||||
this.loadTimestamp = Date.now();
|
||||
addIcon(ICON_NAME, EXCALIDRAW_ICON);
|
||||
addIcon(SCRIPTENGINE_ICON_NAME, SCRIPTENGINE_ICON);
|
||||
addIcon(EXPORT_IMG_ICON_NAME, EXPORT_IMG_ICON);
|
||||
|
||||
await this.loadSettings({reEnableAutosave:true});
|
||||
const updateSettings = !this.settings.onceOffCompressFlagReset || !this.settings.onceOffGPTVersionReset;
|
||||
if(!this.settings.onceOffCompressFlagReset) {
|
||||
this.settings.compress = true;
|
||||
this.settings.onceOffCompressFlagReset = true;
|
||||
}
|
||||
if(!this.settings.onceOffGPTVersionReset) {
|
||||
if(this.settings.openAIDefaultVisionModel === "gpt-4-vision-preview") {
|
||||
this.settings.openAIDefaultVisionModel = "gpt-4o";
|
||||
}
|
||||
}
|
||||
if(updateSettings) {
|
||||
await this.saveSettings();
|
||||
}
|
||||
this.excalidrawConfig = new ExcalidrawConfig(this);
|
||||
@@ -391,26 +402,50 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
|
||||
public initializeFonts() {
|
||||
this.app.workspace.onLayoutReady(async () => {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.initializeFonts,`ExcalidrawPlugin.initializeFonts > app.workspace.onLayoutReady`);
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.initializeFonts, `ExcalidrawPlugin.initializeFonts > app.workspace.onLayoutReady`);
|
||||
|
||||
const font = await getFontDataURL(
|
||||
this.app,
|
||||
this.settings.experimantalFourthFont,
|
||||
"",
|
||||
"LocalFont",
|
||||
"Local Font",
|
||||
);
|
||||
const fourthFontDataURL =
|
||||
font.dataURL === "" ? VIRGIL_DATAURL : font.dataURL;
|
||||
this.fourthFontDef = font.fontDef;
|
||||
|
||||
this.getOpenObsidianDocuments().forEach((ownerDocument) => {
|
||||
this.addFonts([
|
||||
`@font-face{font-family:'LocalFont';src:url("${fourthFontDataURL}");font-display: swap;`,
|
||||
],ownerDocument);
|
||||
})
|
||||
if(font.dataURL === "") {
|
||||
this.fourthFontLoaded = true;
|
||||
return;
|
||||
}
|
||||
|
||||
const fourthFontDataURL = font.dataURL;
|
||||
|
||||
const f = this.app.metadataCache.getFirstLinkpathDest(this.settings.experimantalFourthFont, "");
|
||||
// Call getFontMetrics with the fourthFontDataURL
|
||||
let fontMetrics = f.extension.startsWith("woff") ? undefined : await getFontMetrics(fourthFontDataURL, "Local Font");
|
||||
|
||||
if (!fontMetrics) {
|
||||
console.log("Font Metrics not found, using default");
|
||||
fontMetrics = {
|
||||
unitsPerEm: 1000,
|
||||
ascender: 750,
|
||||
descender: -250,
|
||||
lineHeight: 1.2,
|
||||
fontName: "Local Font",
|
||||
}
|
||||
}
|
||||
this.packageMap.forEach(({excalidrawLib}) => {
|
||||
(excalidrawLib as typeof ExcalidrawLib).registerLocalFont({metrics: fontMetrics as any, icon: null}, fourthFontDataURL);
|
||||
});
|
||||
// Add fonts to open Obsidian documents
|
||||
for(const ownerDocument of this.getOpenObsidianDocuments()) {
|
||||
await this.addFonts([
|
||||
`@font-face{font-family:'Local Font';src:url("${fourthFontDataURL}");font-display: swap;font-weight: 400;`,
|
||||
], ownerDocument);
|
||||
};
|
||||
if(!this.fourthFontLoaded) setTimeout(()=>{this.fourthFontLoaded = true},100);
|
||||
});
|
||||
}
|
||||
|
||||
public addFonts(declarations: string[],ownerDocument:Document = document) {
|
||||
public async addFonts(declarations: string[],ownerDocument:Document = document) {
|
||||
// replace the old local font <style> element with the one we just created
|
||||
const newStylesheet = ownerDocument.createElement("style");
|
||||
newStylesheet.id = FONTS_STYLE_ID;
|
||||
@@ -420,7 +455,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
if (oldStylesheet) {
|
||||
ownerDocument.head.removeChild(oldStylesheet);
|
||||
}
|
||||
ownerDocument.fonts.load('20px LocalFont');
|
||||
await ownerDocument.fonts.load('20px Local Font');
|
||||
}
|
||||
|
||||
public removeFonts() {
|
||||
@@ -453,7 +488,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
if (fileShouldDefaultAsExcalidraw(leaf.view.file?.path, this.app)) {
|
||||
this.excalidrawFileModes[(leaf as any).id || leaf.view.file.path] =
|
||||
VIEW_TYPE_EXCALIDRAW;
|
||||
this.setExcalidrawView(leaf);
|
||||
setExcalidrawView(leaf);
|
||||
} else {
|
||||
foldExcalidrawSection(leaf.view);
|
||||
}
|
||||
@@ -1370,6 +1405,42 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
},
|
||||
});
|
||||
|
||||
this.addCommand({
|
||||
id: "frame-settings",
|
||||
name: t("FRAME_SETTINGS_TITLE"),
|
||||
checkCallback: (checking: boolean) => {
|
||||
if (checking) {
|
||||
return (
|
||||
Boolean(this.app.workspace.getActiveViewOfType(ExcalidrawView))
|
||||
);
|
||||
}
|
||||
const view = this.app.workspace.getActiveViewOfType(ExcalidrawView);
|
||||
if (view) {
|
||||
showFrameSettings(getEA(view));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
});
|
||||
|
||||
this.addCommand({
|
||||
id: "copy-link-to-drawing",
|
||||
name: t("COPY_DRAWING_LINK"),
|
||||
checkCallback: (checking: boolean) => {
|
||||
if (checking) {
|
||||
return (
|
||||
Boolean(this.app.workspace.getActiveViewOfType(ExcalidrawView))
|
||||
);
|
||||
}
|
||||
const view = this.app.workspace.getActiveViewOfType(ExcalidrawView);
|
||||
if (view) {
|
||||
navigator.clipboard.writeText(`![[${view.file.path}]]`);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
});
|
||||
|
||||
this.addCommand({
|
||||
id: "disable-frameclipping",
|
||||
name: t("TOGGLE_FRAME_CLIPPING"),
|
||||
@@ -1579,7 +1650,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
name: t("INSERT_LINK_TO_ELEMENT_FRAME"),
|
||||
checkCallback: (checking: boolean) => {
|
||||
if (checking) {
|
||||
return Boolean(this.app.workspace.getActiveViewOfType(ExcalidrawView))
|
||||
return Boolean(this.app.workspace.getActiveViewOfType(ExcalidrawView));
|
||||
}
|
||||
const view = this.app.workspace.getActiveViewOfType(ExcalidrawView);
|
||||
if (view) {
|
||||
@@ -1590,6 +1661,22 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
},
|
||||
});
|
||||
|
||||
this.addCommand({
|
||||
id: "insert-link-to-element-frame-clipped",
|
||||
name: t("INSERT_LINK_TO_ELEMENT_FRAME_CLIPPED"),
|
||||
checkCallback: (checking: boolean) => {
|
||||
if (checking) {
|
||||
return Boolean(this.app.workspace.getActiveViewOfType(ExcalidrawView));
|
||||
}
|
||||
const view = this.app.workspace.getActiveViewOfType(ExcalidrawView);
|
||||
if (view) {
|
||||
view.copyLinkToSelectedElementToClipboard("clippedframe=");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
});
|
||||
|
||||
this.addCommand({
|
||||
id: "insert-link-to-element-area",
|
||||
name: t("INSERT_LINK_TO_ELEMENT_AREA"),
|
||||
@@ -1640,6 +1727,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
id: "flip-image",
|
||||
name: t("FLIP_IMAGE"),
|
||||
checkCallback: (checking:boolean) => {
|
||||
if (!DEVICE.isDesktop) return;
|
||||
const view = this.app.workspace.getActiveViewOfType(ExcalidrawView);
|
||||
if(!view) return false;
|
||||
if(!view.excalidrawAPI) return false;
|
||||
@@ -2289,7 +2377,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
const activeLeaf = markdownView.leaf;
|
||||
this.excalidrawFileModes[(activeLeaf as any).id || activeFile.path] =
|
||||
VIEW_TYPE_EXCALIDRAW;
|
||||
this.setExcalidrawView(activeLeaf);
|
||||
setExcalidrawView(activeLeaf);
|
||||
})()
|
||||
return;
|
||||
}
|
||||
@@ -2324,7 +2412,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
activeFile,
|
||||
mergedTarget,
|
||||
);
|
||||
this.setExcalidrawView(activeView.leaf);
|
||||
setExcalidrawView(activeView.leaf);
|
||||
})();
|
||||
},
|
||||
});
|
||||
@@ -2362,7 +2450,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
log(fname);
|
||||
const result = await this.app.vault.create(
|
||||
fname,
|
||||
FRONTMATTER + (await this.exportSceneToMD(data)),
|
||||
FRONTMATTER + (await this.exportSceneToMD(data, false)),
|
||||
);
|
||||
if (this.settings.keepInSync) {
|
||||
EXPORT_TYPES.forEach((ext: string) => {
|
||||
@@ -2450,7 +2538,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
await view.save();
|
||||
//@ts-ignore
|
||||
this.excalidrawFileModes[leaf.id || file.path] = VIEW_TYPE_EXCALIDRAW;
|
||||
this.setExcalidrawView(leaf);
|
||||
setExcalidrawView(leaf);
|
||||
}));
|
||||
},
|
||||
),
|
||||
@@ -2475,7 +2563,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
await view.save();
|
||||
//@ts-ignore
|
||||
this.excalidrawFileModes[leaf.id || file.path] = VIEW_TYPE_EXCALIDRAW;
|
||||
this.setExcalidrawView(leaf);
|
||||
setExcalidrawView(leaf);
|
||||
})});
|
||||
//@ts-ignore
|
||||
menu.items.unshift(menu.items.pop());
|
||||
@@ -3398,7 +3486,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
* @param {string} data - Excalidraw scene JSON string
|
||||
* @returns {string} - Text starting with the "# Text Elements" header and followed by each "## id-value" and text
|
||||
*/
|
||||
public async exportSceneToMD(data: string): Promise<string> {
|
||||
public async exportSceneToMD(data: string, compressOverride?: boolean): Promise<string> {
|
||||
if (!data) {
|
||||
return "";
|
||||
}
|
||||
@@ -3423,7 +3511,9 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
outString +
|
||||
getMarkdownDrawingSection(
|
||||
JSON.stringify(JSON_parse(data), null, "\t"),
|
||||
this.settings.compress,
|
||||
typeof compressOverride === "undefined"
|
||||
? this.settings.compress
|
||||
: compressOverride,
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -3453,6 +3543,21 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
errorlog({file, error: "new drawing not recognized as an excalidraw file", fn: this.createDrawing});
|
||||
}
|
||||
|
||||
if(Date.now() - this.loadTimestamp > 1){//2000) {
|
||||
const filecount = this.app.vault.getFiles().filter(f=>this.isExcalidrawFile(f)).length;
|
||||
const rank:Rank = filecount < 200 ? "Bronze" : filecount < 750 ? "Silver" : filecount < 2000 ? "Gold" : "Platinum";
|
||||
const {grip, decoration, blade} = SwordColors[rank];
|
||||
if(this.settings.rank !== rank) {
|
||||
//in case the message was already displayed on another device and it was synced in the mean time
|
||||
await this.loadSettings();
|
||||
if(this.settings.rank !== rank) {
|
||||
this.settings.rank = rank;
|
||||
await this.saveSettings();
|
||||
new RankMessage(this.app, this, filecount, rank, decoration, blade, grip).open();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
@@ -3493,14 +3598,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
|
||||
}
|
||||
|
||||
public async setExcalidrawView(leaf: WorkspaceLeaf) {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.setExcalidrawView,`ExcalidrawPlugin.setExcalidrawView`, leaf);
|
||||
await leaf.setViewState({
|
||||
type: VIEW_TYPE_EXCALIDRAW,
|
||||
state: leaf.view.getState(),
|
||||
popstate: true,
|
||||
} as ViewState);
|
||||
}
|
||||
|
||||
|
||||
public isExcalidrawFile(f: TFile) {
|
||||
if(!f) return false;
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -70,7 +70,7 @@ export class EmbeddableMenu {
|
||||
};
|
||||
|
||||
private async actionMarkdownSelection (file: TFile, isExcalidrawFile: boolean, subpath: string, element: ExcalidrawEmbeddableElement) {
|
||||
this.view.updateScene({appState: {activeEmbeddable: null}});
|
||||
this.view.updateScene({appState: {activeEmbeddable: null}, storeAction: "update"});
|
||||
const sections = (await app.metadataCache.blockCache
|
||||
.getForFile({ isCancelled: () => false },file))
|
||||
.blocks.filter((b: any) => b.display && b.node?.type === "heading")
|
||||
@@ -98,7 +98,7 @@ export class EmbeddableMenu {
|
||||
|
||||
private async actionMarkdownBlock (file: TFile, subpath: string, element: ExcalidrawEmbeddableElement) {
|
||||
if(!file) return;
|
||||
this.view.updateScene({appState: {activeEmbeddable: null}});
|
||||
this.view.updateScene({appState: {activeEmbeddable: null}, storeAction: "update"});
|
||||
const paragraphs = (await app.metadataCache.blockCache
|
||||
.getForFile({ isCancelled: () => false },file))
|
||||
.blocks.filter((b: any) => b.display && b.node &&
|
||||
|
||||
@@ -34,7 +34,8 @@ export function setPen (pen: PenStyle, api: any) {
|
||||
currentItemRoughness: st.currentItemRoughness,
|
||||
}}
|
||||
: null,
|
||||
}
|
||||
},
|
||||
storeAction: "update",
|
||||
})
|
||||
}
|
||||
|
||||
@@ -50,7 +51,8 @@ export function resetStrokeOptions (resetCustomPen:any, api: ExcalidrawImperativ
|
||||
}: null,
|
||||
resetCustomPen: null,
|
||||
...clearCurrentStrokeOptions ? {currentStrokeOptions: null} : null,
|
||||
}
|
||||
},
|
||||
storeAction: "update",
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import {
|
||||
App,
|
||||
ButtonComponent,
|
||||
DropdownComponent,
|
||||
getIcon,
|
||||
normalizePath,
|
||||
PluginSettingTab,
|
||||
Setting,
|
||||
@@ -13,7 +14,7 @@ import ExcalidrawView from "./ExcalidrawView";
|
||||
import { t } from "./lang/helpers";
|
||||
import type ExcalidrawPlugin from "./main";
|
||||
import { PenStyle } from "./PenTypes";
|
||||
import { DynamicStyle } from "./types";
|
||||
import { DynamicStyle } from "./types/types";
|
||||
import { PreviewImageType } from "./utils/UtilTypes";
|
||||
import { setDynamicStyle } from "./utils/DynamicStyling";
|
||||
import {
|
||||
@@ -36,6 +37,8 @@ import { ModifierKeySettingsComponent } from "./dialogs/ModifierKeySettings";
|
||||
import { ANNOTATED_PREFIX, CROPPED_PREFIX } from "./utils/CarveOut";
|
||||
import { EDITOR_FADEOUT } from "./CodeMirrorExtension/EditorHandler";
|
||||
import { setDebugging } from "./utils/DebugHelper";
|
||||
import { link } from "fs";
|
||||
import { Rank } from "./menu/ActionIcons";
|
||||
|
||||
export interface ExcalidrawSettings {
|
||||
folder: string;
|
||||
@@ -47,6 +50,7 @@ export interface ExcalidrawSettings {
|
||||
compress: boolean;
|
||||
decompressForMDView: boolean;
|
||||
onceOffCompressFlagReset: boolean; //used to reset compress to true in 2.2.0
|
||||
onceOffGPTVersionReset: boolean; //used to reset GPT version in 2.2.11
|
||||
autosave: boolean;
|
||||
autosaveIntervalDesktop: number;
|
||||
autosaveIntervalMobile: number;
|
||||
@@ -196,6 +200,7 @@ export interface ExcalidrawSettings {
|
||||
longPressDesktop: number;
|
||||
longPressMobile: number;
|
||||
isDebugMode: boolean;
|
||||
rank: Rank;
|
||||
}
|
||||
|
||||
declare const PLUGIN_VERSION:string;
|
||||
@@ -210,6 +215,7 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
|
||||
compress: true,
|
||||
decompressForMDView: false,
|
||||
onceOffCompressFlagReset: false,
|
||||
onceOffGPTVersionReset: false,
|
||||
autosave: true,
|
||||
autosaveIntervalDesktop: 15000,
|
||||
autosaveIntervalMobile: 10000,
|
||||
@@ -360,7 +366,7 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
|
||||
startupScriptPath: "",
|
||||
openAIAPIToken: "",
|
||||
openAIDefaultTextModel: "gpt-3.5-turbo-1106",
|
||||
openAIDefaultVisionModel: "gpt-4-vision-preview",
|
||||
openAIDefaultVisionModel: "gpt-4o",
|
||||
openAIDefaultImageGenerationModel: "dall-e-3",
|
||||
openAIURL: "https://api.openai.com/v1/chat/completions",
|
||||
openAIImageGenerationURL: "https://api.openai.com/v1/images/generations",
|
||||
@@ -451,6 +457,7 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
|
||||
longPressDesktop: 500,
|
||||
longPressMobile: 500,
|
||||
isDebugMode: false,
|
||||
rank: "Bronze",
|
||||
};
|
||||
|
||||
export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
@@ -539,6 +546,46 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
},
|
||||
});
|
||||
coffeeImg.height = 45;
|
||||
|
||||
const iconLinks = [
|
||||
{
|
||||
icon: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 2 5 2 5 2c-.3 1.15-.3 2.35 0 3.5A5.403 5.403 0 0 0 4 9c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4"></path><path d="M9 18c-4.51 2-5-2-7-2"></path></svg>`,
|
||||
href: "https://github.com/zsviczian/obsidian-excalidraw-plugin/issues",
|
||||
aria: "Report bugs and raise feature requsts on the plugin's GitHub page",
|
||||
text: "Bugs and Feature Requests",
|
||||
},
|
||||
{
|
||||
icon: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 19c-2.3 0-6.4-.2-8.1-.6-.7-.2-1.2-.7-1.4-1.4-.3-1.1-.5-3.4-.5-5s.2-3.9.5-5c.2-.7.7-1.2 1.4-1.4C5.6 5.2 9.7 5 12 5s6.4.2 8.1.6c.7.2 1.2.7 1.4 1.4.3 1.1.5 3.4.5 5s-.2 3.9-.5 5c-.2.7-.7 1.2-1.4 1.4-1.7.4-5.8.6-8.1.6 0 0 0 0 0 0z"></path><polygon points="10 15 15 12 10 9"></polygon></svg>`,
|
||||
href: "https://www.youtube.com/@VisualPKM",
|
||||
aria: "Check out my YouTube channel to learn about Visual Thinking and Excalidraw",
|
||||
text: "Visual PKM on YouTube",
|
||||
},
|
||||
{
|
||||
icon: `<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" stroke="none" strokeWidth="2" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 640 512"><path d="M524.531,69.836a1.5,1.5,0,0,0-.764-.7A485.065,485.065,0,0,0,404.081,32.03a1.816,1.816,0,0,0-1.923.91,337.461,337.461,0,0,0-14.9,30.6,447.848,447.848,0,0,0-134.426,0,309.541,309.541,0,0,0-15.135-30.6,1.89,1.89,0,0,0-1.924-.91A483.689,483.689,0,0,0,116.085,69.137a1.712,1.712,0,0,0-.788.676C39.068,183.651,18.186,294.69,28.43,404.354a2.016,2.016,0,0,0,.765,1.375A487.666,487.666,0,0,0,176.02,479.918a1.9,1.9,0,0,0,2.063-.676A348.2,348.2,0,0,0,208.12,430.4a1.86,1.86,0,0,0-1.019-2.588,321.173,321.173,0,0,1-45.868-21.853,1.885,1.885,0,0,1-.185-3.126c3.082-2.309,6.166-4.711,9.109-7.137a1.819,1.819,0,0,1,1.9-.256c96.229,43.917,200.41,43.917,295.5,0a1.812,1.812,0,0,1,1.924.233c2.944,2.426,6.027,4.851,9.132,7.16a1.884,1.884,0,0,1-.162,3.126,301.407,301.407,0,0,1-45.89,21.83,1.875,1.875,0,0,0-1,2.611,391.055,391.055,0,0,0,30.014,48.815,1.864,1.864,0,0,0,2.063.7A486.048,486.048,0,0,0,610.7,405.729a1.882,1.882,0,0,0,.765-1.352C623.729,277.594,590.933,167.465,524.531,69.836ZM222.491,337.58c-28.972,0-52.844-26.587-52.844-59.239S193.056,219.1,222.491,219.1c29.665,0,53.306,26.82,52.843,59.239C275.334,310.993,251.924,337.58,222.491,337.58Zm195.38,0c-28.971,0-52.843-26.587-52.843-59.239S388.437,219.1,417.871,219.1c29.667,0,53.307,26.82,52.844,59.239C470.715,310.993,447.538,337.58,417.871,337.58Z"/></svg>`,
|
||||
href: "https://discord.gg/DyfAXFwUHc",
|
||||
aria: "Join the Visual Thinking Workshop Discord Server",
|
||||
text: "Community on Discord",
|
||||
},
|
||||
{
|
||||
icon: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 4s-.7 2.1-2 3.4c1.6 10-9.4 17.3-18 11.6 2.2.1 4.4-.6 6-2C3 15.5.5 9.6 3 5c2.2 2.6 5.6 4.1 9 4-.9-4.2 4-6.6 7-3.8 1.1 0 3-1.2 3-1.2z"></path></svg>`,
|
||||
href: "https://twitter.com/zsviczian",
|
||||
aria: "Follow me on Twitter",
|
||||
text: "Follow me on Twitter",
|
||||
},
|
||||
{
|
||||
icon: getIcon("graduation-cap").outerHTML,
|
||||
href: "https://visual-thinking-workshop.com",
|
||||
aria: "Learn about Visual PKM, Excalidraw, Obsidian, ExcaliBrain and more",
|
||||
text: "Join the Visual Thinking Workshop",
|
||||
}
|
||||
];
|
||||
|
||||
const linksEl = containerEl.createDiv("setting-item-description excalidraw-settings-links-container");
|
||||
iconLinks.forEach(({ icon, href, aria, text }) => {
|
||||
linksEl.createEl("a",{href, attr: { "aria-label": aria }}, (a)=> {
|
||||
a.innerHTML = icon + text;
|
||||
});
|
||||
});
|
||||
|
||||
// ------------------------------------------------
|
||||
// Saving
|
||||
@@ -2137,7 +2184,7 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
d.addOption("Assistant", "Assistant");
|
||||
this.app.vault
|
||||
.getFiles()
|
||||
.filter((f) => ["ttf", "woff", "woff2"].contains(f.extension))
|
||||
.filter((f) => ["ttf", "woff", "woff2", "otf"].contains(f.extension))
|
||||
.forEach((f: TFile) => {
|
||||
d.addOption(f.path, f.name);
|
||||
});
|
||||
|
||||
164
src/types.d.ts → src/types/types.d.ts
vendored
164
src/types.d.ts → src/types/types.d.ts
vendored
@@ -1,83 +1,83 @@
|
||||
import { ExcalidrawAutomate } from "./ExcalidrawAutomate";
|
||||
import { ExcalidrawLib } from "./ExcalidrawLib";
|
||||
|
||||
export type ConnectionPoint = "top" | "bottom" | "left" | "right" | null;
|
||||
|
||||
export type Packages = {
|
||||
react: any,
|
||||
reactDOM: any,
|
||||
excalidrawLib: typeof ExcalidrawLib,
|
||||
}
|
||||
|
||||
export type ValueOf<T> = T[keyof T];
|
||||
|
||||
export type DynamicStyle = "none" | "gray" | "colorful";
|
||||
|
||||
export type DeviceType = {
|
||||
isDesktop: boolean,
|
||||
isPhone: boolean,
|
||||
isTablet: boolean,
|
||||
isMobile: boolean,
|
||||
isLinux: boolean,
|
||||
isMacOS: boolean,
|
||||
isWindows: boolean,
|
||||
isIOS: boolean,
|
||||
isAndroid: boolean
|
||||
};
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
ExcalidrawAutomate: ExcalidrawAutomate;
|
||||
}
|
||||
}
|
||||
|
||||
declare module "obsidian" {
|
||||
interface App {
|
||||
internalPlugins: any;
|
||||
isMobile(): boolean;
|
||||
getObsidianUrl(file:TFile): string;
|
||||
metadataTypeManager: {
|
||||
setType(name:string, type:string): void;
|
||||
};
|
||||
}
|
||||
interface Keymap {
|
||||
getRootScope(): Scope;
|
||||
}
|
||||
interface Scope {
|
||||
keys: any[];
|
||||
}
|
||||
interface Workspace {
|
||||
on(
|
||||
name: "hover-link",
|
||||
callback: (e: MouseEvent) => any,
|
||||
ctx?: any,
|
||||
): EventRef;
|
||||
}
|
||||
interface DataAdapter {
|
||||
url: {
|
||||
pathToFileURL(path: string): URL;
|
||||
},
|
||||
basePath: string;
|
||||
}
|
||||
interface FoldPosition {
|
||||
from: number;
|
||||
to: number;
|
||||
}
|
||||
|
||||
interface FoldInfo {
|
||||
folds: FoldPosition[];
|
||||
lines: number;
|
||||
}
|
||||
|
||||
interface MarkdownSubView {
|
||||
applyFoldInfo(foldInfo: FoldInfo): void;
|
||||
getFoldInfo(): FoldInfo | null;
|
||||
}
|
||||
/*interface Editor {
|
||||
insertText(data: string): void;
|
||||
}*/
|
||||
interface MetadataCache {
|
||||
getBacklinksForFile(file: TFile): any;
|
||||
getLinks(): { [id: string]: Array<{ link: string; displayText: string; original: string; position: any }> };
|
||||
}
|
||||
import { ExcalidrawAutomate } from "../ExcalidrawAutomate";
|
||||
import { ExcalidrawLib } from "../ExcalidrawLib";
|
||||
|
||||
export type ConnectionPoint = "top" | "bottom" | "left" | "right" | null;
|
||||
|
||||
export type Packages = {
|
||||
react: any,
|
||||
reactDOM: any,
|
||||
excalidrawLib: typeof ExcalidrawLib,
|
||||
}
|
||||
|
||||
export type ValueOf<T> = T[keyof T];
|
||||
|
||||
export type DynamicStyle = "none" | "gray" | "colorful";
|
||||
|
||||
export type DeviceType = {
|
||||
isDesktop: boolean,
|
||||
isPhone: boolean,
|
||||
isTablet: boolean,
|
||||
isMobile: boolean,
|
||||
isLinux: boolean,
|
||||
isMacOS: boolean,
|
||||
isWindows: boolean,
|
||||
isIOS: boolean,
|
||||
isAndroid: boolean
|
||||
};
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
ExcalidrawAutomate: ExcalidrawAutomate;
|
||||
}
|
||||
}
|
||||
|
||||
declare module "obsidian" {
|
||||
interface App {
|
||||
internalPlugins: any;
|
||||
isMobile(): boolean;
|
||||
getObsidianUrl(file:TFile): string;
|
||||
metadataTypeManager: {
|
||||
setType(name:string, type:string): void;
|
||||
};
|
||||
}
|
||||
interface Keymap {
|
||||
getRootScope(): Scope;
|
||||
}
|
||||
interface Scope {
|
||||
keys: any[];
|
||||
}
|
||||
interface Workspace {
|
||||
on(
|
||||
name: "hover-link",
|
||||
callback: (e: MouseEvent) => any,
|
||||
ctx?: any,
|
||||
): EventRef;
|
||||
}
|
||||
interface DataAdapter {
|
||||
url: {
|
||||
pathToFileURL(path: string): URL;
|
||||
},
|
||||
basePath: string;
|
||||
}
|
||||
interface FoldPosition {
|
||||
from: number;
|
||||
to: number;
|
||||
}
|
||||
|
||||
interface FoldInfo {
|
||||
folds: FoldPosition[];
|
||||
lines: number;
|
||||
}
|
||||
|
||||
interface MarkdownSubView {
|
||||
applyFoldInfo(foldInfo: FoldInfo): void;
|
||||
getFoldInfo(): FoldInfo | null;
|
||||
}
|
||||
/*interface Editor {
|
||||
insertText(data: string): void;
|
||||
}*/
|
||||
interface MetadataCache {
|
||||
getBacklinksForFile(file: TFile): any;
|
||||
getLinks(): { [id: string]: Array<{ link: string; displayText: string; original: string; position: any }> };
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@ import { Notice } from "obsidian";
|
||||
import { getEA } from "src";
|
||||
import { ExcalidrawAutomate, cloneElement } from "src/ExcalidrawAutomate";
|
||||
import { ExportSettings } from "src/ExcalidrawView";
|
||||
import { embedFontsInSVG } from "./Utils";
|
||||
import { nanoid } from "src/constants/constants";
|
||||
|
||||
export class CropImage {
|
||||
@@ -92,7 +91,7 @@ export class CropImage {
|
||||
isMask: false,
|
||||
}
|
||||
|
||||
const maskSVG = await this.maskEA.createSVG(null,false,exportSettings,null,null,0);
|
||||
const maskSVG = await this.maskEA.createSVG(null,true,exportSettings,null,null,0);
|
||||
const defs = maskSVG.querySelector("defs");
|
||||
const styleEl = maskSVG.querySelector("style");
|
||||
const style = styleEl ? styleEl.outerHTML : "";
|
||||
@@ -138,7 +137,7 @@ export class CropImage {
|
||||
async getCroppedPNG(): Promise<Blob> {
|
||||
//@ts-ignore
|
||||
const PLUGIN = app.plugins.plugins["obsidian-excalidraw-plugin"];
|
||||
const svg = embedFontsInSVG(await this.buildSVG(), PLUGIN);
|
||||
const svg = await this.buildSVG();
|
||||
return new Promise((resolve, reject) => {
|
||||
const svgData = new XMLSerializer().serializeToString(svg);
|
||||
const canvas = document.createElement('canvas');
|
||||
|
||||
@@ -2,7 +2,7 @@ import { ExcalidrawImperativeAPI } from "@zsviczian/excalidraw/types/excalidraw/
|
||||
import { ColorMaster } from "colormaster";
|
||||
import { ExcalidrawAutomate } from "src/ExcalidrawAutomate";
|
||||
import ExcalidrawView from "src/ExcalidrawView";
|
||||
import { DynamicStyle } from "src/types";
|
||||
import { DynamicStyle } from "src/types/types";
|
||||
import { cloneElement } from "src/ExcalidrawAutomate";
|
||||
import { ExcalidrawFrameElement } from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||
import { addAppendUpdateCustomData } from "./Utils";
|
||||
@@ -16,7 +16,7 @@ export const setDynamicStyle = (
|
||||
) => {
|
||||
if(dynamicStyle === "none") {
|
||||
view.excalidrawContainer?.removeAttribute("style");
|
||||
setTimeout(()=>view.updateScene({appState:{dynamicStyle: ""}}));
|
||||
setTimeout(()=>view.updateScene({appState:{dynamicStyle: ""}, storeAction: "update"}));
|
||||
const toolspanel = view.toolsPanelRef?.current?.containerRef?.current;
|
||||
if(toolspanel) {
|
||||
let toolsStyle = toolspanel.getAttribute("style");
|
||||
@@ -167,7 +167,8 @@ export const setDynamicStyle = (
|
||||
appState:{
|
||||
frameColor,
|
||||
dynamicStyle: styleObject
|
||||
}
|
||||
},
|
||||
storeAction: "update",
|
||||
});
|
||||
view = null;
|
||||
ea = null;
|
||||
|
||||
@@ -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,
|
||||
@@ -235,8 +236,8 @@ export async function addBackOfTheNoteCard(
|
||||
const el = ea.getViewElements().find(el=>el.id === id);
|
||||
api.selectElements([el]);
|
||||
if(activate) {
|
||||
setTimeout(()=>{
|
||||
api.updateScene({appState: {activeEmbeddable: {element: el, state: "active"}}});
|
||||
window.setTimeout(()=>{
|
||||
api.updateScene({appState: {activeEmbeddable: {element: el, state: "active"}}, storeAction: "update"});
|
||||
if(found) view.getEmbeddableLeafElementById(el.id)?.editNode?.();
|
||||
});
|
||||
}
|
||||
@@ -345,4 +346,38 @@ 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(view.file === file) {
|
||||
new Notice(t("RECURSIVE_INSERT_ERROR"));
|
||||
return false;
|
||||
}
|
||||
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;
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import { getAttachmentsFolderAndFilePath } from "./ObsidianUtils";
|
||||
/**
|
||||
* Splits a full path including a folderpath and a filename into separate folderpath and filename components
|
||||
* @param filepath
|
||||
* @returns folderpath will be normalized. This means "/" for root folder and no trailing "/" for other folders
|
||||
*/
|
||||
type ImageExtension = keyof typeof IMAGE_MIME_TYPES;
|
||||
|
||||
|
||||
@@ -21,11 +21,13 @@ export type ImageKey = {
|
||||
previewImageType: PreviewImageType;
|
||||
scale: number;
|
||||
isTransparent: boolean;
|
||||
inlineFonts: boolean;
|
||||
} & FILENAMEPARTS;
|
||||
|
||||
const getKey = (key: ImageKey): string =>
|
||||
`${key.filepath}#${key.blockref??""}#${key.sectionref??""}#${key.isDark ? 1 : 0}#${
|
||||
key.hasGroupref}#${key.hasArearef}#${key.hasFrameref}#${key.hasSectionref}#${
|
||||
key.hasGroupref}#${key.hasArearef}#${key.hasFrameref}#${key.hasClippedFrameref}#${
|
||||
key.hasSectionref}#${key.inlineFonts}#${
|
||||
key.previewImageType === PreviewImageType.SVGIMG
|
||||
? 1
|
||||
: key.previewImageType === PreviewImageType.PNG
|
||||
@@ -172,7 +174,7 @@ class ImageCache {
|
||||
const cursor = (event.target as IDBRequest<IDBCursorWithValue | null>).result;
|
||||
if(cursor) {
|
||||
const key = cursor.key as string;
|
||||
const isLegacyKey = key.replaceAll(/[^#]/g,"").length < 9; // introduced hasGroupref, etc. in 1.9.28
|
||||
const isLegacyKey = key.split("#").length-1 < 12; // introduced hasGroupref, etc. in 1.9.28 // introduced hasClippedFrameref in 2.2.10 //introduced inlineFonts 2.2.11
|
||||
const filepath = key.split("#")[0];
|
||||
const fileExists = files.some((f: TFile) => f.path === filepath);
|
||||
const file = fileExists ? files.find((f: TFile) => f.path === filepath) : null;
|
||||
|
||||
@@ -3,13 +3,14 @@ import {
|
||||
Editor,
|
||||
FrontMatterCache,
|
||||
MarkdownView,
|
||||
normalizePath, OpenViewState, parseFrontMatterEntry, TFile, View, Workspace, WorkspaceLeaf, WorkspaceSplit
|
||||
normalizePath, OpenViewState, parseFrontMatterEntry, TFile, View, ViewState, Workspace, WorkspaceLeaf, WorkspaceSplit
|
||||
} from "obsidian";
|
||||
import ExcalidrawPlugin from "../main";
|
||||
import { checkAndCreateFolder, splitFolderAndFilename } from "./FileUtils";
|
||||
import { linkClickModifierType, ModifierKeys } from "./ModifierkeyHelper";
|
||||
import { REG_BLOCK_REF_CLEAN, REG_SECTION_REF_CLEAN } from "src/constants/constants";
|
||||
import { REG_BLOCK_REF_CLEAN, REG_SECTION_REF_CLEAN, VIEW_TYPE_EXCALIDRAW } from "src/constants/constants";
|
||||
import yaml, { Mark } from "js-yaml";
|
||||
import { debug, DEBUGGING } from "./DebugHelper";
|
||||
|
||||
export const getParentOfClass = (element: Element, cssClass: string):HTMLElement | null => {
|
||||
let parent = element.parentElement;
|
||||
@@ -300,7 +301,7 @@ export const openLeaf = ({
|
||||
return {leaf, promise};
|
||||
}
|
||||
|
||||
export const mergeMarkdownFiles = (template: string, target: string): string => {
|
||||
export function mergeMarkdownFiles (template: string, target: string): string {
|
||||
// Extract frontmatter from the template
|
||||
const templateFrontmatterEnd = template.indexOf('---', 4); // Find end of frontmatter
|
||||
const templateFrontmatter = template.substring(4, templateFrontmatterEnd).trim();
|
||||
@@ -312,8 +313,8 @@ export const mergeMarkdownFiles = (template: string, target: string): string =>
|
||||
// Extract frontmatter from the target if it exists
|
||||
let targetFrontmatterObj: FrontMatterCache = {};
|
||||
let targetContent = '';
|
||||
if (target.includes('---')) {
|
||||
const targetFrontmatterEnd = target.indexOf('---', 4); // Find end of frontmatter
|
||||
if (target.startsWith('---\n') && target.indexOf('---\n', 4) > 0) {
|
||||
const targetFrontmatterEnd = target.indexOf('---\n', 4); // Find end of frontmatter
|
||||
const targetFrontmatter = target.substring(4, targetFrontmatterEnd).trim();
|
||||
targetContent = target.substring(targetFrontmatterEnd + 3); // Skip frontmatter and ---
|
||||
|
||||
@@ -392,3 +393,20 @@ export const foldExcalidrawSection = (view: MarkdownView) => {
|
||||
view.currentMode.applyFoldInfo({ folds: foldPositions, lines: lineCount });
|
||||
}
|
||||
};
|
||||
|
||||
export async function setExcalidrawView(leaf: WorkspaceLeaf) {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(setExcalidrawView,`setExcalidrawView`, leaf);
|
||||
await leaf.setViewState({
|
||||
type: VIEW_TYPE_EXCALIDRAW,
|
||||
state: leaf.view.getState(),
|
||||
popstate: true,
|
||||
} as ViewState);
|
||||
}
|
||||
|
||||
export async function closeLeafView(leaf: WorkspaceLeaf) {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(setExcalidrawView,`setExcalidrawView`, leaf);
|
||||
await leaf.setViewState({
|
||||
type: "empty",
|
||||
state: {},
|
||||
});
|
||||
}
|
||||
@@ -5,8 +5,9 @@ export type FILENAMEPARTS = {
|
||||
hasTaskbone: boolean,
|
||||
hasArearef: boolean,
|
||||
hasFrameref: boolean,
|
||||
blockref: string,
|
||||
hasClippedFrameref: boolean,
|
||||
hasSectionref: boolean,
|
||||
blockref: string,
|
||||
sectionref: string,
|
||||
linkpartReference: string,
|
||||
linkpartAlias: string
|
||||
|
||||
@@ -1,18 +1,12 @@
|
||||
import {
|
||||
App,
|
||||
Notice,
|
||||
request,
|
||||
requestUrl,
|
||||
request,requestUrl,
|
||||
TFile,
|
||||
TFolder,
|
||||
} from "obsidian";
|
||||
import { Random } from "roughjs/bin/math";
|
||||
import { BinaryFileData, DataURL} from "@zsviczian/excalidraw/types/excalidraw/types";
|
||||
import {
|
||||
ASSISTANT_FONT,
|
||||
CASCADIA_FONT,
|
||||
VIRGIL_FONT,
|
||||
} from "src/constants/constFonts";
|
||||
import {
|
||||
exportToSvg,
|
||||
exportToBlob,
|
||||
@@ -33,7 +27,7 @@ import { Mutable } from "@zsviczian/excalidraw/types/excalidraw/utility-types";
|
||||
import { cleanBlockRef, cleanSectionHeading, getFileCSSClasses } from "./ObsidianUtils";
|
||||
import { updateElementLinksToObsidianLinks } from "src/ExcalidrawAutomate";
|
||||
import { CropImage } from "./CropImage";
|
||||
|
||||
import opentype from 'opentype.js';
|
||||
|
||||
declare const PLUGIN_VERSION:string;
|
||||
declare var LZString: any;
|
||||
@@ -195,7 +189,35 @@ export async function getFontDataURL (
|
||||
const f = app.metadataCache.getFirstLinkpathDest(fontFileName, sourcePath);
|
||||
if (f) {
|
||||
const ab = await app.vault.readBinary(f);
|
||||
const mimeType = f.extension.startsWith("woff")
|
||||
let mimeType = "";
|
||||
let format = "";
|
||||
|
||||
switch (f.extension) {
|
||||
case "woff":
|
||||
mimeType = "application/font-woff";
|
||||
format = "woff";
|
||||
break;
|
||||
case "woff2":
|
||||
mimeType = "font/woff2";
|
||||
format = "woff2";
|
||||
break;
|
||||
case "ttf":
|
||||
mimeType = "font/ttf";
|
||||
format = "truetype";
|
||||
break;
|
||||
case "otf":
|
||||
mimeType = "font/otf";
|
||||
format = "opentype";
|
||||
break;
|
||||
default:
|
||||
mimeType = "application/octet-stream"; // Fallback if file type is unexpected
|
||||
}
|
||||
fontName = name ?? f.basename;
|
||||
dataURL = await getDataURL(ab, mimeType);
|
||||
const split = dataURL.split(";base64,", 2);
|
||||
dataURL = `${split[0]};charset=utf-8;base64,${split[1]}`;
|
||||
fontDef = ` @font-face {font-family: "${fontName}";src: url("${dataURL}") format("${format}")}`;
|
||||
/* const mimeType = f.extension.startsWith("woff")
|
||||
? "application/font-woff"
|
||||
: "font/truetype";
|
||||
fontName = name ?? f.basename;
|
||||
@@ -203,7 +225,7 @@ export async function getFontDataURL (
|
||||
fontDef = ` @font-face {font-family: "${fontName}";src: url("${dataURL}")}`;
|
||||
//format("${f.extension === "ttf" ? "truetype" : f.extension}");}`;
|
||||
const split = fontDef.split(";base64,", 2);
|
||||
fontDef = `${split[0]};charset=utf-8;base64,${split[1]}`;
|
||||
fontDef = `${split[0]};charset=utf-8;base64,${split[1]}`;*/
|
||||
}
|
||||
return { fontDef, fontName, dataURL };
|
||||
};
|
||||
@@ -273,16 +295,20 @@ export async function getSVG (
|
||||
svg = await exportToSvg({
|
||||
elements: elements.filter((el:ExcalidrawElement)=>el.isDeleted !== true),
|
||||
appState: {
|
||||
...scene.appState,
|
||||
exportBackground: exportSettings.withBackground,
|
||||
exportWithDarkMode: exportSettings.withTheme
|
||||
? scene.appState?.theme !== "light"
|
||||
: false,
|
||||
...scene.appState,
|
||||
...exportSettings.frameRendering
|
||||
? {frameRendering: exportSettings.frameRendering}
|
||||
: {},
|
||||
},
|
||||
files: scene.files,
|
||||
exportPadding: padding,
|
||||
exportPadding: exportSettings.frameRendering ? 0 : padding,
|
||||
exportingFrame: null,
|
||||
renderEmbeddables: true,
|
||||
skipInliningFonts: exportSettings.skipInliningFonts,
|
||||
});
|
||||
}
|
||||
if(svg) {
|
||||
@@ -327,14 +353,17 @@ export async function getPNG (
|
||||
return await exportToBlob({
|
||||
elements: scene.elements.filter((el:ExcalidrawElement)=>el.isDeleted !== true),
|
||||
appState: {
|
||||
...scene.appState,
|
||||
exportBackground: exportSettings.withBackground,
|
||||
exportWithDarkMode: exportSettings.withTheme
|
||||
? scene.appState?.theme !== "light"
|
||||
: false,
|
||||
...scene.appState,
|
||||
...exportSettings.frameRendering
|
||||
? {frameRendering: exportSettings.frameRendering}
|
||||
: {},
|
||||
},
|
||||
files: filterFiles(scene.files),
|
||||
exportPadding: padding,
|
||||
exportPadding: exportSettings.frameRendering ? 0 : padding,
|
||||
mimeType: "image/png",
|
||||
getDimensions: (width: number, height: number) => ({
|
||||
width: width * scale,
|
||||
@@ -343,6 +372,7 @@ export async function getPNG (
|
||||
}),
|
||||
});
|
||||
} catch (error) {
|
||||
new Notice("Error exporting PNG - PNG too large, try a smaller resolution");
|
||||
errorlog({ where: "Utils.getPNG", error });
|
||||
return null;
|
||||
}
|
||||
@@ -369,34 +399,6 @@ export async function getQuickImagePreview (
|
||||
}
|
||||
};
|
||||
|
||||
export function embedFontsInSVG(
|
||||
svg: SVGSVGElement,
|
||||
plugin: ExcalidrawPlugin,
|
||||
localOnly: boolean = false,
|
||||
): SVGSVGElement {
|
||||
//replace font references with base64 fonts)
|
||||
const includesVirgil = !localOnly &&
|
||||
svg.querySelector("text[font-family^='Virgil']") !== null;
|
||||
const includesCascadia = !localOnly &&
|
||||
svg.querySelector("text[font-family^='Cascadia']") !== null;
|
||||
const includesAssistant = !localOnly &&
|
||||
svg.querySelector("text[font-family^='Assistant']") !== null;
|
||||
const includesLocalFont =
|
||||
svg.querySelector("text[font-family^='LocalFont']") !== null;
|
||||
const defs = svg.querySelector("defs");
|
||||
if (defs && (includesCascadia || includesVirgil || includesLocalFont || includesAssistant)) {
|
||||
let style = defs.querySelector("style");
|
||||
if (!style) {
|
||||
style = document.createElement("style");
|
||||
defs.appendChild(style);
|
||||
}
|
||||
style.innerHTML = `${includesVirgil ? VIRGIL_FONT : ""}${
|
||||
includesCascadia ? CASCADIA_FONT : ""}${
|
||||
includesAssistant ? ASSISTANT_FONT : ""
|
||||
}${includesLocalFont ? plugin.fourthFontDef : ""}`;
|
||||
}
|
||||
return svg;
|
||||
};
|
||||
|
||||
export async function getImageSize (
|
||||
src: string,
|
||||
@@ -712,7 +714,7 @@ export function isVersionNewerThanOther (version: string, otherVersion: string):
|
||||
|
||||
export function getEmbeddedFilenameParts (fname:string): FILENAMEPARTS {
|
||||
// 0 1 23 4 5 6 7 8 9
|
||||
const parts = fname?.match(/([^#\^]*)((#\^)(group=|area=|frame=|taskbone)?([^\|]*)|(#)(group=|area=|frame=|taskbone)?([^\^\|]*))(.*)/);
|
||||
const parts = fname?.match(/([^#\^]*)((#\^)(group=|area=|frame=|clippedframe=|taskbone)?([^\|]*)|(#)(group=|area=|frame=|clippedframe=|taskbone)?([^\^\|]*))(.*)/);
|
||||
if(!parts) {
|
||||
return {
|
||||
filepath: fname,
|
||||
@@ -721,6 +723,7 @@ export function getEmbeddedFilenameParts (fname:string): FILENAMEPARTS {
|
||||
hasTaskbone: false,
|
||||
hasArearef: false,
|
||||
hasFrameref: false,
|
||||
hasClippedFrameref: false,
|
||||
blockref: "",
|
||||
hasSectionref: false,
|
||||
sectionref: "",
|
||||
@@ -735,6 +738,7 @@ export function getEmbeddedFilenameParts (fname:string): FILENAMEPARTS {
|
||||
hasTaskbone: (parts[4]==="taskbone") || (parts[7]==="taskbone"),
|
||||
hasArearef: (parts[4]==="area=") || (parts[7]==="area="),
|
||||
hasFrameref: (parts[4]==="frame=") || (parts[7]==="frame="),
|
||||
hasClippedFrameref: (parts[4]==="clippedframe=") || (parts[7]==="clippedframe="),
|
||||
blockref: parts[5],
|
||||
hasSectionref: Boolean(parts[6]),
|
||||
sectionref: parts[8],
|
||||
@@ -887,4 +891,34 @@ export function addIframe (containerEl: HTMLElement, link:string, startAt?: numb
|
||||
sandbox: "allow-forms allow-presentation allow-same-origin allow-scripts allow-modals",
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export interface FontMetrics {
|
||||
unitsPerEm: number;
|
||||
ascender: number;
|
||||
descender: number;
|
||||
lineHeight: number;
|
||||
fontName: string;
|
||||
}
|
||||
|
||||
export async function getFontMetrics(fontUrl: string, name: string): Promise<FontMetrics | null> {
|
||||
try {
|
||||
const font = await opentype.load(fontUrl);
|
||||
const unitsPerEm = font.unitsPerEm;
|
||||
const ascender = font.ascender;
|
||||
const descender = font.descender;
|
||||
const lineHeight = (ascender - descender) / unitsPerEm;
|
||||
const fontName = font.names.fontFamily.en ?? name;
|
||||
|
||||
return {
|
||||
unitsPerEm,
|
||||
ascender,
|
||||
descender,
|
||||
lineHeight,
|
||||
fontName,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error loading font:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,4 +15,15 @@ export class WeakArray<T extends object> {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
removeObjects(objectsToRemove: Set<T>) {
|
||||
this.weakArray = this.weakArray.filter((ref) => {
|
||||
const obj = ref.deref();
|
||||
return obj && !objectsToRemove.has(obj);
|
||||
});
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.weakArray = [];
|
||||
}
|
||||
}
|
||||
31
styles.css
31
styles.css
@@ -594,4 +594,35 @@ root {
|
||||
|
||||
textarea.excalidraw-wysiwyg, .excalidraw input {
|
||||
caret-color: var(--excalidraw-caret-color);
|
||||
}
|
||||
|
||||
.excalidraw-settings-links-container {
|
||||
display: flex; /* Align SVG and text horizontally */
|
||||
align-items: center; /* Center SVG and text vertically */
|
||||
text-decoration: none; /* Remove underline from links */
|
||||
color: inherit; /* Inherit text color */
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.excalidraw-settings-links-container a {
|
||||
display: flex; /* Align children horizontally */
|
||||
align-items: center; /* Center items vertically */
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.excalidraw-settings-links-container svg {
|
||||
margin-right: 8px;
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
}
|
||||
|
||||
.excalidraw-rank {
|
||||
text-align: center;
|
||||
margin-top: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.excalidraw-rank svg {
|
||||
height: 8rem;
|
||||
width: 8rem;
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
"baseUrl": ".",
|
||||
"sourceMap": false,
|
||||
"module": "ES2015",
|
||||
"target": "es2017", //es2017 because script engine requires for async execution
|
||||
"target": "es2018", //es2017 because script engine requires for async execution
|
||||
"allowJs": true,
|
||||
"noImplicitAny": true,
|
||||
"moduleResolution": "node",
|
||||
@@ -18,11 +18,11 @@
|
||||
"DOM.Iterable"
|
||||
],
|
||||
"jsx": "react",
|
||||
"inlineSourceMap": true
|
||||
"inlineSourceMap": true,
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
"**/*.tsx", "src/Dialogs/OpenDrawing.ts",
|
||||
"src/types.d.ts"
|
||||
"src/types/types.d.ts",
|
||||
]
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
"baseUrl": ".",
|
||||
"sourceMap": false,
|
||||
"module": "es2015",
|
||||
"target": "es2017", //es2017 because script engine requires for async execution
|
||||
"target": "es2018", //es2017 because script engine requires for async execution //es2018 for named capture groups
|
||||
"allowJs": true,
|
||||
"noImplicitAny": true,
|
||||
"moduleResolution": "node",
|
||||
@@ -14,14 +14,14 @@
|
||||
"dom",
|
||||
"scripthost",
|
||||
"es2015",
|
||||
"esnext",
|
||||
"ESNext",
|
||||
"DOM.Iterable"
|
||||
],
|
||||
"jsx": "react"
|
||||
"jsx": "react",
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
"**/*.tsx", "src/Dialogs/OpenDrawing.ts",
|
||||
"src/types.d.ts"
|
||||
"src/types/types.d.ts",
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user