mirror of
https://github.com/zsviczian/obsidian-excalidraw-plugin.git
synced 2025-08-06 05:46:28 +00:00
Compare commits
9 Commits
2.5.3-beta
...
2.6.0-beta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5bbe66900e | ||
|
|
a775a858c7 | ||
|
|
2dab801ff5 | ||
|
|
07f8a87580 | ||
|
|
91be6e2a2f | ||
|
|
5c709588dd | ||
|
|
19a46e5b11 | ||
|
|
e132d4a9fc | ||
|
|
cf2d9bea24 |
Binary file not shown.
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "obsidian-excalidraw-plugin",
|
||||
"name": "Excalidraw",
|
||||
"version": "2.5.3-beta-2",
|
||||
"version": "2.6.0-beta-3",
|
||||
"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.6-3",
|
||||
"@zsviczian/excalidraw": "0.17.6-6",
|
||||
"chroma-js": "^2.4.2",
|
||||
"clsx": "^2.0.0",
|
||||
"@zsviczian/colormaster": "^1.2.2",
|
||||
@@ -34,7 +34,8 @@
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"roughjs": "^4.5.2",
|
||||
"woff2sfnt-sfnt2woff": "^1.0.0"
|
||||
"woff2sfnt-sfnt2woff": "^1.0.0",
|
||||
"es6-promise-pool": "2.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.22.9",
|
||||
|
||||
@@ -8,6 +8,7 @@ import fs from 'fs';
|
||||
import LZString from 'lz-string';
|
||||
import postprocess from '@zsviczian/rollup-plugin-postprocess';
|
||||
import cssnano from 'cssnano';
|
||||
import jsesc from 'jsesc';
|
||||
|
||||
// Load environment variables
|
||||
import dotenv from 'dotenv';
|
||||
@@ -52,11 +53,13 @@ if (!isLib) console.log(manifest.version);
|
||||
const packageString = isLib
|
||||
? ""
|
||||
: ';' + lzstring_pkg +
|
||||
'\nlet EXCALIDRAW_PACKAGES = LZString.decompressFromBase64("' + LZString.compressToBase64(react_pkg + reactdom_pkg + excalidraw_pkg) + '");\n' +
|
||||
'let {react, reactDOM, excalidrawLib} = window.eval.call(window, `(function() {' +
|
||||
'${EXCALIDRAW_PACKAGES};' +
|
||||
'return {react: React, reactDOM: ReactDOM, excalidrawLib: ExcalidrawLib};})();`);\n' +
|
||||
'let PLUGIN_VERSION="' + manifest.version + '";';
|
||||
'\nlet REACT_PACKAGES = `' +
|
||||
jsesc(react_pkg + reactdom_pkg, { quotes: 'backtick' }) +
|
||||
'`;\n' +
|
||||
'let EXCALIDRAW_PACKAGE = ""; const unpackExcalidraw = () => {EXCALIDRAW_PACKAGE = LZString.decompressFromBase64("' + LZString.compressToBase64(excalidraw_pkg) + '");};\n' +
|
||||
'let {react, reactDOM } = window.eval.call(window, `(function() {' + '${REACT_PACKAGES};' + 'return {react: React, reactDOM: ReactDOM};})();`);\n' +
|
||||
`let excalidrawLib = {};\n` +
|
||||
'let PLUGIN_VERSION="' + manifest.version + '";';
|
||||
|
||||
const BASE_CONFIG = {
|
||||
input: 'src/main.ts',
|
||||
|
||||
@@ -36,6 +36,8 @@ import {
|
||||
isMaskFile,
|
||||
getEmbeddedFilenameParts,
|
||||
cropCanvas,
|
||||
promiseTry,
|
||||
PromisePool,
|
||||
} from "./utils/Utils";
|
||||
import { ValueOf } from "./types/types";
|
||||
import { getMermaidImageElements, getMermaidText, shouldRenderMermaid } from "./utils/MermaidUtils";
|
||||
@@ -606,126 +608,169 @@ export class EmbeddedFilesLoader {
|
||||
this.isDark = excalidrawData?.scene?.appState?.theme === "dark";
|
||||
}
|
||||
let entry: IteratorResult<[FileId, EmbeddedFile]>;
|
||||
const files: FileData[] = [];
|
||||
while (!this.terminate && !(entry = entries.next()).done) {
|
||||
if(fileIDWhiteList && !fileIDWhiteList.has(entry.value[0])) continue;
|
||||
const embeddedFile: EmbeddedFile = entry.value[1];
|
||||
if (!embeddedFile.isLoaded(this.isDark)) {
|
||||
//debug({where:"EmbeddedFileLoader.loadSceneFiles",uid:this.uid,status:"embedded Files are not loaded"});
|
||||
const data = await this._getObsidianImage(embeddedFile, depth);
|
||||
if (data) {
|
||||
const fileData: FileData = {
|
||||
mimeType: data.mimeType,
|
||||
id: entry.value[0],
|
||||
dataURL: data.dataURL,
|
||||
created: data.created,
|
||||
size: data.size,
|
||||
hasSVGwithBitmap: data.hasSVGwithBitmap,
|
||||
shouldScale: embeddedFile.shouldScale()
|
||||
};
|
||||
try {
|
||||
addFiles([fileData], this.isDark, false);
|
||||
}
|
||||
catch(e) {
|
||||
errorlog({ where: "EmbeddedFileLoader.loadSceneFiles", error: e });
|
||||
}
|
||||
//files.push(fileData);
|
||||
}
|
||||
} else if (embeddedFile.isSVGwithBitmap && (depth !== 0 || isThemeChange)) {
|
||||
//this will reload the image in light/dark mode when switching themes
|
||||
const fileData = {
|
||||
mimeType: embeddedFile.mimeType,
|
||||
id: entry.value[0],
|
||||
dataURL: embeddedFile.getImage(this.isDark) as DataURL,
|
||||
created: embeddedFile.mtime,
|
||||
size: embeddedFile.size,
|
||||
hasSVGwithBitmap: embeddedFile.isSVGwithBitmap,
|
||||
shouldScale: embeddedFile.shouldScale()
|
||||
};
|
||||
//files.push(fileData);
|
||||
try {
|
||||
addFiles([fileData], this.isDark, false);
|
||||
}
|
||||
catch(e) {
|
||||
errorlog({ where: "EmbeddedFileLoader.loadSceneFiles", error: e });
|
||||
}
|
||||
}
|
||||
}
|
||||
const files: FileData[][] = [];
|
||||
files.push([]);
|
||||
let batch = 0;
|
||||
|
||||
let equation;
|
||||
const equations = excalidrawData.getEquationEntries();
|
||||
while (!this.terminate && !(equation = equations.next()).done) {
|
||||
if(fileIDWhiteList && !fileIDWhiteList.has(equation.value[0])) continue;
|
||||
if (!excalidrawData.getEquation(equation.value[0]).isLoaded) {
|
||||
const latex = equation.value[1].latex;
|
||||
const data = await tex2dataURL(latex);
|
||||
if (data) {
|
||||
const fileData = {
|
||||
mimeType: data.mimeType,
|
||||
id: equation.value[0],
|
||||
dataURL: data.dataURL,
|
||||
created: data.created,
|
||||
size: data.size,
|
||||
hasSVGwithBitmap: false,
|
||||
shouldScale: true
|
||||
};
|
||||
files.push(fileData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(shouldRenderMermaid()) {
|
||||
const mermaidElements = getMermaidImageElements(excalidrawData.scene.elements);
|
||||
for(const element of mermaidElements) {
|
||||
if(this.terminate) {
|
||||
continue;
|
||||
}
|
||||
const data = getMermaidText(element);
|
||||
const result = await mermaidToExcalidraw(data, {fontSize: 20}, true);
|
||||
if(!result) {
|
||||
continue;
|
||||
}
|
||||
if(result?.files) {
|
||||
for (const key in result.files) {
|
||||
function* loadIterator():Generator<Promise<void>> {
|
||||
while (!(entry = entries.next()).done) {
|
||||
if(fileIDWhiteList && !fileIDWhiteList.has(entry.value[0])) continue;
|
||||
const embeddedFile: EmbeddedFile = entry.value[1];
|
||||
const id = entry.value[0];
|
||||
yield promiseTry(async () => {
|
||||
if(this.terminate) {
|
||||
return;
|
||||
}
|
||||
if (!embeddedFile.isLoaded(this.isDark)) {
|
||||
//debug({where:"EmbeddedFileLoader.loadSceneFiles",uid:this.uid,status:"embedded Files are not loaded"});
|
||||
const data = await this._getObsidianImage(embeddedFile, depth);
|
||||
if (data) {
|
||||
const fileData: FileData = {
|
||||
mimeType: data.mimeType,
|
||||
id: id,
|
||||
dataURL: data.dataURL,
|
||||
created: data.created,
|
||||
size: data.size,
|
||||
hasSVGwithBitmap: data.hasSVGwithBitmap,
|
||||
shouldScale: embeddedFile.shouldScale()
|
||||
};
|
||||
files[batch].push(fileData);
|
||||
/* try {
|
||||
addFiles([fileData], this.isDark, false);
|
||||
}
|
||||
catch(e) {
|
||||
errorlog({ where: "EmbeddedFileLoader.loadSceneFiles", error: e });
|
||||
}*/
|
||||
}
|
||||
} else if (embeddedFile.isSVGwithBitmap && (depth !== 0 || isThemeChange)) {
|
||||
//this will reload the image in light/dark mode when switching themes
|
||||
const fileData = {
|
||||
...result.files[key],
|
||||
id: element.fileId,
|
||||
created: Date.now(),
|
||||
hasSVGwithBitmap: false,
|
||||
shouldScale: true,
|
||||
size: await getImageSize(result.files[key].dataURL),
|
||||
mimeType: embeddedFile.mimeType,
|
||||
id: id,
|
||||
dataURL: embeddedFile.getImage(this.isDark) as DataURL,
|
||||
created: embeddedFile.mtime,
|
||||
size: embeddedFile.size,
|
||||
hasSVGwithBitmap: embeddedFile.isSVGwithBitmap,
|
||||
shouldScale: embeddedFile.shouldScale()
|
||||
};
|
||||
files.push(fileData);
|
||||
files[batch].push(fileData);
|
||||
/* try {
|
||||
addFiles([fileData], this.isDark, false);
|
||||
}
|
||||
catch(e) {
|
||||
errorlog({ where: "EmbeddedFileLoader.loadSceneFiles", error: e });
|
||||
}*/
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if(result?.elements) {
|
||||
//handle case that mermaidToExcalidraw has implemented this type of diagram in the mean time
|
||||
const res = await this.getExcalidrawSVG({
|
||||
isDark: this.isDark,
|
||||
file: null,
|
||||
depth,
|
||||
inFile: null,
|
||||
hasSVGwithBitmap: false,
|
||||
elements: result.elements
|
||||
});
|
||||
if(res?.dataURL) {
|
||||
const size = await getImageSize(res.dataURL);
|
||||
const fileData:FileData = {
|
||||
mimeType: "image/svg+xml",
|
||||
id: element.fileId,
|
||||
dataURL: res.dataURL,
|
||||
created: Date.now(),
|
||||
hasSVGwithBitmap: res.hasSVGwithBitmap,
|
||||
size,
|
||||
shouldScale: true,
|
||||
};
|
||||
files.push(fileData);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
let equationItem;
|
||||
const equations = excalidrawData.getEquationEntries();
|
||||
while (!(equationItem = equations.next()).done) {
|
||||
if(fileIDWhiteList && !fileIDWhiteList.has(equationItem.value[0])) continue;
|
||||
const equation = equationItem.value[1];
|
||||
const id = equationItem.value[0];
|
||||
yield promiseTry(async () => {
|
||||
if (this.terminate) {
|
||||
return;
|
||||
}
|
||||
if (!excalidrawData.getEquation(id).isLoaded) {
|
||||
const latex = equation.latex;
|
||||
const data = await tex2dataURL(latex);
|
||||
if (data) {
|
||||
const fileData = {
|
||||
mimeType: data.mimeType,
|
||||
id: id,
|
||||
dataURL: data.dataURL,
|
||||
created: data.created,
|
||||
size: data.size,
|
||||
hasSVGwithBitmap: false,
|
||||
shouldScale: true
|
||||
};
|
||||
files[batch].push(fileData);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if(shouldRenderMermaid()) {
|
||||
const mermaidElements = getMermaidImageElements(excalidrawData.scene.elements);
|
||||
for(const element of mermaidElements) {
|
||||
yield promiseTry(async () => {
|
||||
if(this.terminate) {
|
||||
return;
|
||||
}
|
||||
const data = getMermaidText(element);
|
||||
const result = await mermaidToExcalidraw(data, {fontSize: 20}, true);
|
||||
if(!result) {
|
||||
return;
|
||||
}
|
||||
if(result?.files) {
|
||||
for (const key in result.files) {
|
||||
const fileData = {
|
||||
...result.files[key],
|
||||
id: element.fileId,
|
||||
created: Date.now(),
|
||||
hasSVGwithBitmap: false,
|
||||
shouldScale: true,
|
||||
size: await getImageSize(result.files[key].dataURL),
|
||||
};
|
||||
files[batch].push(fileData);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if(result?.elements) {
|
||||
//handle case that mermaidToExcalidraw has implemented this type of diagram in the mean time
|
||||
if (this.terminate) {
|
||||
return;
|
||||
}
|
||||
const res = await this.getExcalidrawSVG({
|
||||
isDark: this.isDark,
|
||||
file: null,
|
||||
depth,
|
||||
inFile: null,
|
||||
hasSVGwithBitmap: false,
|
||||
elements: result.elements
|
||||
});
|
||||
if(res?.dataURL) {
|
||||
const size = await getImageSize(res.dataURL);
|
||||
const fileData:FileData = {
|
||||
mimeType: "image/svg+xml",
|
||||
id: element.fileId,
|
||||
dataURL: res.dataURL,
|
||||
created: Date.now(),
|
||||
hasSVGwithBitmap: res.hasSVGwithBitmap,
|
||||
size,
|
||||
shouldScale: true,
|
||||
};
|
||||
files[batch].push(fileData);
|
||||
}
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const addFilesTimer = setInterval(() => {
|
||||
if(files[batch].length === 0) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
addFiles(files[batch], this.isDark, false);
|
||||
}
|
||||
catch(e) {
|
||||
errorlog({ where: "EmbeddedFileLoader.loadSceneFiles", error: e });
|
||||
}
|
||||
files.push([]);
|
||||
batch++;
|
||||
}, 1200);
|
||||
|
||||
const iterator = loadIterator.bind(this)();
|
||||
const concurency = 5;
|
||||
await new PromisePool(iterator, concurency).all();
|
||||
|
||||
clearInterval(addFilesTimer);
|
||||
|
||||
this.emptyPDFDocsMap();
|
||||
if (this.terminate) {
|
||||
@@ -734,7 +779,7 @@ export class EmbeddedFilesLoader {
|
||||
//debug({where:"EmbeddedFileLoader.loadSceneFiles",uid:this.uid,status:"add Files"});
|
||||
try {
|
||||
//in try block because by the time files are loaded the user may have closed the view
|
||||
addFiles(files, this.isDark, true);
|
||||
addFiles(files[batch], this.isDark, true);
|
||||
} catch (e) {
|
||||
errorlog({ where: "EmbeddedFileLoader.loadSceneFiles", error: e });
|
||||
}
|
||||
|
||||
1
src/ExcalidrawLib.d.ts
vendored
1
src/ExcalidrawLib.d.ts
vendored
@@ -189,6 +189,5 @@ declare namespace ExcalidrawLib {
|
||||
): string;
|
||||
function safelyParseJSON (json: string): Record<string, any> | null;
|
||||
function loadSceneFonts(elements: NonDeletedExcalidrawElement[]): Promise<void>;
|
||||
function initializeObsidianUtils(obsidianPlugin: ExcalidrawPlugin): void;
|
||||
}
|
||||
|
||||
|
||||
@@ -1575,6 +1575,7 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
if(!this.plugin) {
|
||||
return;
|
||||
}
|
||||
await this.plugin.awaitInit();
|
||||
//implemented to overcome issue that activeLeafChangeEventHandler is not called when view is initialized from a saved workspace, since Obsidian 1.6.0
|
||||
let counter = 0;
|
||||
while(counter++<50 && (!Boolean(this?.plugin?.activeLeafChangeEventHandler) || !Boolean(this.canvasNodeFactory))) {
|
||||
@@ -2245,6 +2246,7 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
//I am using last loaded file to control when the view reloads.
|
||||
//It seems text file view gets the modified file event after sync before the modifyEventHandler in main.ts
|
||||
//reload can only be triggered via reload()
|
||||
await this.plugin.awaitInit();
|
||||
if(this.lastLoadedFile === this.file) return;
|
||||
this.isLoaded = false;
|
||||
if(!this.file) return;
|
||||
@@ -2275,6 +2277,7 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
if(!this?.app) {
|
||||
return;
|
||||
}
|
||||
await this.plugin.awaitInit();
|
||||
let counter = 0;
|
||||
while ((!this.file || !this.plugin.fourthFontLoaded) && counter++<50) await sleep(50);
|
||||
if(!this.file) return;
|
||||
@@ -3851,12 +3854,14 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
||||
return;
|
||||
}
|
||||
|
||||
//dobule click
|
||||
const now = Date.now();
|
||||
if ((now - this.doubleClickTimestamp) < 600 && (now - this.doubleClickTimestamp) > 40) {
|
||||
this.identifyElementClicked();
|
||||
if(this.plugin.settings.doubleClickLinkOpenViewMode) {
|
||||
//dobule click
|
||||
const now = Date.now();
|
||||
if ((now - this.doubleClickTimestamp) < 600 && (now - this.doubleClickTimestamp) > 40) {
|
||||
this.identifyElementClicked();
|
||||
}
|
||||
this.doubleClickTimestamp = now;
|
||||
}
|
||||
this.doubleClickTimestamp = now;
|
||||
return;
|
||||
}
|
||||
if (p.button === "up") {
|
||||
|
||||
@@ -353,7 +353,8 @@ const getIMG = async (
|
||||
);
|
||||
|
||||
const cacheReady = imageCache.isReady();
|
||||
|
||||
|
||||
await plugin.awaitInit();
|
||||
switch (plugin.settings.previewImageType) {
|
||||
case PreviewImageType.PNG: {
|
||||
const img = createEl("img");
|
||||
|
||||
@@ -82,7 +82,7 @@ export const obsidianToExcalidrawMap: { [key: string]: string } = {
|
||||
};
|
||||
|
||||
|
||||
export const {
|
||||
export let {
|
||||
sceneCoordsToViewportCoords,
|
||||
viewportCoordsToSceneCoords,
|
||||
determineFocusDistance,
|
||||
@@ -104,10 +104,36 @@ export const {
|
||||
refreshTextDimensions,
|
||||
getCSSFontDefinition,
|
||||
loadSceneFonts,
|
||||
initializeObsidianUtils,
|
||||
} = excalidrawLib;
|
||||
|
||||
export function updateExcalidrawLib() {
|
||||
({
|
||||
sceneCoordsToViewportCoords,
|
||||
viewportCoordsToSceneCoords,
|
||||
determineFocusDistance,
|
||||
intersectElementWithLine,
|
||||
getCommonBoundingBox,
|
||||
getMaximumGroups,
|
||||
measureText,
|
||||
getLineHeight,
|
||||
wrapText,
|
||||
getFontString,
|
||||
getBoundTextMaxWidth,
|
||||
exportToSvg,
|
||||
exportToBlob,
|
||||
mutateElement,
|
||||
restore,
|
||||
mermaidToExcalidraw,
|
||||
getFontFamilyString,
|
||||
getContainerElement,
|
||||
refreshTextDimensions,
|
||||
getCSSFontDefinition,
|
||||
loadSceneFonts,
|
||||
} = excalidrawLib);
|
||||
}
|
||||
|
||||
export const FONTS_STYLE_ID = "excalidraw-custom-fonts";
|
||||
export const CJK_STYLE_ID = "excalidraw-cjk-fonts";
|
||||
|
||||
export function JSON_parse(x: string): any {
|
||||
return JSON.parse(x.replaceAll("[", "["));
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
import { TAG_AUTOEXPORT, TAG_MDREADINGMODE, TAG_PDFEXPORT } from "src/constants/constSettingsTags";
|
||||
import { labelALT, labelCTRL, labelMETA, labelSHIFT } from "src/utils/ModifierkeyHelper";
|
||||
|
||||
const CJK_FONTS = "CJK Fonts";
|
||||
// English
|
||||
export default {
|
||||
// main.ts
|
||||
@@ -97,6 +98,8 @@ export default {
|
||||
RESET_IMG_ASPECT_RATIO: "Reset selected image element aspect ratio",
|
||||
TEMPORARY_DISABLE_AUTOSAVE: "Disable autosave until next time Obsidian starts (only set this if you know what you are doing)",
|
||||
TEMPORARY_ENABLE_AUTOSAVE: "Enable autosave",
|
||||
FONTS_LOADED: "Excalidraw: CJK Fonts loaded",
|
||||
FONTS_LOAD_ERROR: "Excalidraw: Could not find CJK Fonts in the assets folder\n",
|
||||
|
||||
//ExcalidrawView.ts
|
||||
NO_SEARCH_RESULT: "Didn't find a matching element in the drawing",
|
||||
@@ -229,15 +232,6 @@ export default {
|
||||
"You can access your scripts from Excalidraw via the Obsidian Command Palette. Assign " +
|
||||
"hotkeys to your favorite scripts just like to any other Obsidian command. " +
|
||||
"The folder may not be the root folder of your Vault. ",
|
||||
ASSETS_FOLDER_NAME: "Local Font Assets Folder (cAsE sENsiTIvE!)",
|
||||
ASSETS_FOLDER_DESC: `Since version 2.5.3, following the implementation of CJK font support, Excalidraw downloads fonts from the internet.
|
||||
If you prefer to keep Excalidraw fully local, allowing it to work without internet access, or if your internet connection is slow
|
||||
and you want to improve performance, you can download the necessary
|
||||
<a href="https://github.com/zsviczian/obsidian-excalidraw-plugin/raw/refs/heads/master/assets/excalidraw-fonts.zip" target="_blank">font assets from GitHub</a>.
|
||||
After downloading, unzip the contents into a folder within your Vault.<br>
|
||||
You can specify the location of that folder here. For example, you may choose to place it under <code>Excalidraw/FontAssets</code>.<br><br>
|
||||
<strong>Important:</strong> Do not set this to the Vault root! Ensure that no other files are placed in this folder.<br><br>
|
||||
<strong>Note:</strong> If you're using Obsidian Sync and want to synchronize these font files across your devices, ensure that Obsidian Sync is set to synchronize "All other file types".`,
|
||||
AI_HEAD: "AI Settings - Experimental",
|
||||
AI_DESC: `In the "AI" settings, you can configure options for using OpenAI's GPT API. ` +
|
||||
`While the OpenAI API is in beta, its use is strictly limited — as such we require you use your own API key. ` +
|
||||
@@ -440,6 +434,7 @@ FILENAME_HEAD: "Filename",
|
||||
LONG_PRESS_DESKTOP_DESC: "Long press delay in milliseconds to open an Excalidraw Drawing embedded in a Markdown file. ",
|
||||
LONG_PRESS_MOBILE_NAME: "Long press to open mobile",
|
||||
LONG_PRESS_MOBILE_DESC: "Long press delay in milliseconds to open an Excalidraw Drawing embedded in a Markdown file. ",
|
||||
DOUBLE_CLICK_LINK_OPEN_VIEW_MODE: "Allow double-click to open links in view mode",
|
||||
|
||||
FOCUS_ON_EXISTING_TAB_NAME: "Focus on Existing Tab",
|
||||
FOCUS_ON_EXISTING_TAB_DESC: "When opening a link, Excalidraw will focus on the existing tab if the file is already open. " +
|
||||
@@ -756,6 +751,8 @@ 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.",
|
||||
FONTS_HEAD: "Fonts",
|
||||
FONTS_DESC: "Configure local fontfaces and downloaded CJK fonts for Excalidraw.",
|
||||
CUSTOM_FONT_HEAD: "Local font",
|
||||
ENABLE_FOURTH_FONT_NAME: "Enable local font option",
|
||||
ENABLE_FOURTH_FONT_DESC:
|
||||
@@ -769,6 +766,20 @@ FILENAME_HEAD: "Filename",
|
||||
"If no file is selected, Excalidraw will default to the Virgil font. " +
|
||||
"For optimal performance, it is recommended to use a .woff2 file, as Excalidraw will encode only the necessary glyphs when exporting images to SVG. " +
|
||||
"Other font formats will embed the entire font in the exported file, potentially resulting in significantly larger file sizes.",
|
||||
OFFLINE_CJK_NAME: "Offline CJK font support",
|
||||
OFFLINE_CJK_DESC:
|
||||
`<strong>Changes you make here will only take effect after restarting Obsidian.</strong><br>
|
||||
Excalidraw.com offers handwritten CJK fonts. By default these fonts are not included in the plugin locally, but are served from the Internet.
|
||||
If you prefer to keep Excalidraw fully local, allowing it to work without Internet access you can download the necessary <a href="https://github.com/zsviczian/obsidian-excalidraw-plugin/raw/refs/heads/master/assets/excalidraw-fonts.zip" target="_blank">font files from GitHub</a>.
|
||||
After downloading, unzip the contents into a folder within your Vault.<br>
|
||||
Pre-loading fonts will impact startup performance. For this reason you can select which fonts to load.`,
|
||||
CJK_ASSETS_FOLDER_NAME: "CJK Font Folder (cAsE sENsiTIvE!)",
|
||||
CJK_ASSETS_FOLDER_DESC: `You can set the location of the CJK fonts folder here. For example, you may choose to place it under <code>Excalidraw/CJK Fonts</code>.<br><br>
|
||||
<strong>Important:</strong> Do not set this folder to the Vault root! Do not put other fonts in this folder.<br><br>
|
||||
<strong>Note:</strong> If you're using Obsidian Sync and want to synchronize these font files across your devices, ensure that Obsidian Sync is set to synchronize "All other file types".`,
|
||||
LOAD_CHINESE_FONTS_NAME: "Load Chinese fonts from file at startup",
|
||||
LOAD_JAPANESE_FONTS_NAME: "Load Japanese fonts from file at startup",
|
||||
LOAD_KOREAN_FONTS_NAME: "Load Korean fonts frome file at startup",
|
||||
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.",
|
||||
TASKBONE_HEAD: "Taskbone Optical Character Recogntion",
|
||||
@@ -831,9 +842,8 @@ FILENAME_HEAD: "Filename",
|
||||
FONT_INFO_DETAILED: `
|
||||
<p>
|
||||
To improve Obsidian's startup time and manage the large <strong>CJK font family</strong>,
|
||||
I've moved the fonts out of the plugin's <code>main.js</code>. Starting with version 2.5.3,
|
||||
fonts will be loaded from the internet. This typically shouldn't cause issues as Obsidian caches
|
||||
these files after first use.
|
||||
I've moved the CJK fonts out of the plugin's <code>main.js</code>. CJK fonts will be loaded from the internet by default.
|
||||
This typically shouldn't cause issues as Obsidian caches these files after first use.
|
||||
</p>
|
||||
<p>
|
||||
If you prefer to keep Obsidian 100% local or experience performance issues, you can download the font assets.
|
||||
@@ -841,7 +851,7 @@ FILENAME_HEAD: "Filename",
|
||||
<h3>Instructions:</h3>
|
||||
<ol>
|
||||
<li>Download the fonts from <a href="https://github.com/zsviczian/obsidian-excalidraw-plugin/raw/refs/heads/master/assets/excalidraw-fonts.zip">GitHub</a>.</li>
|
||||
<li>Unzip and copy files into a Vault folder (default: <code>Excalidraw/FontAssets</code>; folder names are cAse-senSITive).</li>
|
||||
<li>Unzip and copy files into a Vault folder (default: <code>Excalidraw/${CJK_FONTS}</code>; folder names are cAse-senSITive).</li>
|
||||
<li><mark>DO NOT</mark> set this folder to the Vault root or mix with other local fonts.</li>
|
||||
</ol>
|
||||
<h3>For Obsidian Sync Users:</h3>
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
import { TAG_AUTOEXPORT, TAG_MDREADINGMODE, TAG_PDFEXPORT } from "src/constants/constSettingsTags";
|
||||
import { labelALT, labelCTRL, labelMETA, labelSHIFT } from "src/utils/ModifierkeyHelper";
|
||||
|
||||
const CJK_FONTS = "CJK Fonts";
|
||||
// 简体中文
|
||||
export default {
|
||||
// main.ts
|
||||
@@ -229,14 +230,6 @@ export default {
|
||||
"您可以在 Obsidian 命令面板中执行这些脚本," +
|
||||
"还可以为喜欢的脚本分配快捷键,就像为其他 Obsidian 命令分配快捷键一样。<br>" +
|
||||
"该项不能设为库的根目录。",
|
||||
ASSETS_FOLDER_NAME: "本地字体资源文件夹(區分大小寫!)",
|
||||
ASSETS_FOLDER_DESC: `自 2.5.3 版本以来,随着 CJK 字体支持的实现,Excalidraw 将从互联网下载字体。
|
||||
如果您希望 Excalidraw 完全离线工作,避免依赖互联网,或者您的网络连接较慢,希望提高性能,您可以从
|
||||
<a href="https://github.com/zsviczian/obsidian-excalidraw-plugin/raw/refs/heads/master/assets/excalidraw-fonts.zip" target="_blank">GitHub 下载所需的字体资资源</a>。
|
||||
下载后,将内容解压到您的 Vault 中的一个文件夹内。<br>
|
||||
您可以在此处指定该文件夹的位置。例如,您可以选择将其放置在 <code>Excalidraw/FontAssets</code> 下。<br><br>
|
||||
<strong>重要:</strong> 请勿将其设置为 Vault 根目录!确保该文件夹中不放置其他文件。<br><br>
|
||||
<strong>注意:</strong> 如果您使用 Obsidian Sync 并希望在设备间同步这些字体文件,请确保 Obsidian Sync 设置为同步“所有其他文件类型”。`,
|
||||
AI_HEAD: "AI(实验性)",
|
||||
AI_DESC: `OpenAI GPT API 的设置。 ` +
|
||||
`目前 OpenAI API 还处于测试中,您需要在自己的。` +
|
||||
@@ -755,6 +748,8 @@ FILENAME_HEAD: "文件名",
|
||||
"启用此功能简化了 Excalidraw 前置属性的使用,使您能够利用许多强大的设置。如果您不希望自动加载这些属性," +
|
||||
"您可以禁用此功能,但您将需要手动从自动提示中移除任何不需要的属性。" +
|
||||
"请注意,启用此设置需要重启插件,因为属性是在启动时加载的。",
|
||||
FONTS_HEAD: "字体",
|
||||
FONTS_DESC: "配置本地字体并下载的 CJK 字体以供 Excalidraw 使用。",
|
||||
CUSTOM_FONT_HEAD: "本地字体",
|
||||
ENABLE_FOURTH_FONT_NAME: "为文本元素启用本地字体",
|
||||
ENABLE_FOURTH_FONT_DESC:
|
||||
@@ -768,6 +763,20 @@ FILENAME_HEAD: "文件名",
|
||||
"如果没有选择文件,Excalidraw 将默认使用 Virgil 字体。"+
|
||||
"为了获得最佳性能,建议使用 .woff2 文件,因为当导出到 SVG 格式的图像时,Excalidraw 只会编码必要的字形。"+
|
||||
"其他字体格式将在导出文件中嵌入整个字体,可能会导致文件大小显著增加。<mark>译者注:</mark>您可以在<a href='https://wangchujiang.com/free-font/' target='_blank'>Free Font</a>获取免费商用中文手写字体。",
|
||||
OFFLINE_CJK_NAME: "离线 CJK 字体支持",
|
||||
OFFLINE_CJK_DESC:
|
||||
`<strong>您在这里所做的更改将在重启 Obsidian 后生效。</strong><br>
|
||||
Excalidraw.com 提供手写风格的 CJK 字体。默认情况下,这些字体并未在插件中本地包含,而是从互联网获取。
|
||||
如果您希望 Excalidraw 完全本地化,以便在没有互联网连接的情况下使用,可以从 <a href="https://github.com/zsviczian/obsidian-excalidraw-plugin/raw/refs/heads/master/assets/excalidraw-fonts.zip" target="_blank">GitHub 下载所需的字体文件</a>。
|
||||
下载后,将内容解压到您的 Vault 中的一个文件夹内。<br>
|
||||
预加载字体会影响启动性能。因此,您可以选择加载哪些字体。`,
|
||||
CJK_ASSETS_FOLDER_NAME: "CJK 字体文件夹(區分大小寫!)",
|
||||
CJK_ASSETS_FOLDER_DESC: `您可以在此设置 CJK 字体文件夹的位置。例如,您可以选择将其放置在 <code>Excalidraw/CJK Fonts</code> 下。<br><br>
|
||||
<strong>重要:</strong> 请勿将此文件夹设置为 Vault 根目录!请勿在此文件夹中放置其他字体。<br><br>
|
||||
<strong>注意:</strong> 如果您使用 Obsidian Sync 并希望在设备之间同步这些字体文件,请确保 Obsidian Sync 设置为同步“所有其他文件类型”。`,
|
||||
LOAD_CHINESE_FONTS_NAME: "启动时从文件加载中文字体",
|
||||
LOAD_JAPANESE_FONTS_NAME: "启动时从文件加载日文字体",
|
||||
LOAD_KOREAN_FONTS_NAME: "启动时从文件加载韩文字体",
|
||||
SCRIPT_SETTINGS_HEAD: "已安装脚本的设置",
|
||||
SCRIPT_SETTINGS_DESC: "有些 Excalidraw 自动化脚本包含设置项,当执行这些脚本时,它们会在该列表下添加设置项。",
|
||||
TASKBONE_HEAD: "Taskbone OCR(光学符号识别)",
|
||||
@@ -824,16 +833,14 @@ FILENAME_HEAD: "文件名",
|
||||
|
||||
//ExcalidrawData.ts
|
||||
LOAD_FROM_BACKUP: "Excalidraw 文件已损坏。尝试从备份文件中加载。",
|
||||
|
||||
FONT_LOAD_SLOW: "正在加载字体...\n\n 这比预期花费的时间更长。如果这种延迟经常发生,您可以将字体下载到您的 Vault 中。\n\n" +
|
||||
"(点击=忽略提示,右键=更多信息)",
|
||||
FONT_INFO_TITLE: "从互联网加载 v2.5.3 字体",
|
||||
FONT_INFO_DETAILED: `
|
||||
<p>
|
||||
为了提高 Obsidian 的启动时间并管理大型 <strong>CJK 字体系列</strong>,
|
||||
我已将字体移出插件的 <code>main.js</code>。从 2.5.3 版本开始,
|
||||
字体将从互联网加载。这通常不会导致问题,因为 Obsidian 在首次使用后会缓存
|
||||
这些文件。
|
||||
我已将 CJK 字体移出插件的 <code>main.js</code>。默认情况下,CJK 字体将从互联网加载。
|
||||
这通常不会造成问题,因为 Obsidian 在首次使用后会缓存这些文件。
|
||||
</p>
|
||||
<p>
|
||||
如果您希望 Obsidian 完全离线或遇到性能问题,可以下载字体资源。
|
||||
@@ -841,7 +848,7 @@ FILENAME_HEAD: "文件名",
|
||||
<h3>说明:</h3>
|
||||
<ol>
|
||||
<li>从 <a href="https://github.com/zsviczian/obsidian-excalidraw-plugin/raw/refs/heads/master/assets/excalidraw-fonts.zip">GitHub</a> 下载字体。</li>
|
||||
<li>解压并将文件复制到 Vault 文件夹中(默认:<code>Excalidraw/FontAssets</code>; 文件夹名称區分大小寫!)。</li>
|
||||
<li>解压并将文件复制到 Vault 文件夹中(默认:<code>Excalidraw/${CJK_FONTS}</code>; 文件夹名称區分大小寫!)。</li>
|
||||
<li><mark>请勿</mark>将此文件夹设置为 Vault 根目录或与其他本地字体混合。</li>
|
||||
</ol>
|
||||
<h3>对于 Obsidian Sync 用户:</h3>
|
||||
|
||||
195
src/main.ts
195
src/main.ts
@@ -45,7 +45,8 @@ import {
|
||||
DEVICE,
|
||||
sceneCoordsToViewportCoords,
|
||||
FONTS_STYLE_ID,
|
||||
initializeObsidianUtils,
|
||||
CJK_STYLE_ID,
|
||||
updateExcalidrawLib,
|
||||
} from "./constants/constants";
|
||||
import ExcalidrawView, { TextMode, getTextMode } from "./ExcalidrawView";
|
||||
import {
|
||||
@@ -141,8 +142,11 @@ import { Rank, SwordColors } from "./menu/ActionIcons";
|
||||
import { RankMessage } from "./dialogs/RankMessage";
|
||||
import { initCompressionWorker, terminateCompressionWorker } from "./workers/compression-worker";
|
||||
import { WeakArray } from "./utils/WeakArray";
|
||||
import { getCJKDataURLs } from "./utils/CJKLoader";
|
||||
|
||||
declare let EXCALIDRAW_PACKAGES:string;
|
||||
declare let EXCALIDRAW_PACKAGE:string;
|
||||
declare let REACT_PACKAGES:string;
|
||||
declare const unpackExcalidraw: Function;
|
||||
declare let react:any;
|
||||
declare let reactDOM:any;
|
||||
declare let excalidrawLib: typeof ExcalidrawLib;
|
||||
@@ -194,6 +198,8 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
//private slob:string;
|
||||
private ribbonIcon:HTMLElement;
|
||||
public loadTimestamp:number;
|
||||
private isLocalCJKFontAvailabe:boolean = undefined
|
||||
public isReady = false;
|
||||
|
||||
constructor(app: App, manifest: PluginManifest) {
|
||||
super(app, manifest);
|
||||
@@ -205,7 +211,6 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
this.equationsMaster = new Map<FileId, string>();
|
||||
this.mermaidsMaster = new Map<FileId, string>();
|
||||
setExcalidrawPlugin(this);
|
||||
initializeObsidianUtils(this);
|
||||
/*if((process.env.NODE_ENV === 'development')) {
|
||||
this.slob = new Array(200 * 1024 * 1024 + 1).join('A'); // Create a 200MB blob
|
||||
}*/
|
||||
@@ -269,7 +274,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
//@ts-ignore
|
||||
const {react:r, reactDOM:rd, excalidrawLib:e} = win.eval.call(win,
|
||||
`(function() {
|
||||
${EXCALIDRAW_PACKAGES};
|
||||
${REACT_PACKAGES + EXCALIDRAW_PACKAGE};
|
||||
return {react:React,reactDOM:ReactDOM,excalidrawLib:ExcalidrawLib};
|
||||
})()`);
|
||||
this.packageMap.set(win,{react:r, reactDOM:rd, excalidrawLib:e});
|
||||
@@ -315,8 +320,27 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
};
|
||||
}*/
|
||||
|
||||
public async loadFontFromFile(fontName: string): Promise<ArrayBuffer> {
|
||||
public getCJKFontSettings() {
|
||||
const assetsFoler = this.settings.fontAssetsPath;
|
||||
if(typeof this.isLocalCJKFontAvailabe === "undefined") {
|
||||
this.isLocalCJKFontAvailabe = this.app.vault.getFiles().some(f=>f.path.startsWith(assetsFoler));
|
||||
}
|
||||
if(!this.isLocalCJKFontAvailabe) {
|
||||
return { c: false, j: false, k: false };
|
||||
}
|
||||
return {
|
||||
c: this.settings.loadChineseFonts,
|
||||
j: this.settings.loadJapaneseFonts,
|
||||
k: this.settings.loadKoreanFonts,
|
||||
}
|
||||
}
|
||||
|
||||
public async loadFontFromFile(fontName: string): Promise<ArrayBuffer|undefined> {
|
||||
const assetsFoler = this.settings.fontAssetsPath;
|
||||
|
||||
if(!this.isLocalCJKFontAvailabe) {
|
||||
return;
|
||||
}
|
||||
const file = this.app.vault.getAbstractFileByPath(normalizePath(assetsFoler + "/" + fontName));
|
||||
if(!file || !(file instanceof TFile)) {
|
||||
return;
|
||||
@@ -325,12 +349,10 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
}
|
||||
|
||||
async onload() {
|
||||
initCompressionWorker();
|
||||
this.loadTimestamp = Date.now();
|
||||
addIcon(ICON_NAME, EXCALIDRAW_ICON);
|
||||
addIcon(SCRIPTENGINE_ICON_NAME, SCRIPTENGINE_ICON);
|
||||
addIcon(EXPORT_IMG_ICON_NAME, EXPORT_IMG_ICON);
|
||||
|
||||
this.registerView(
|
||||
VIEW_TYPE_EXCALIDRAW,
|
||||
(leaf: WorkspaceLeaf) => new ExcalidrawView(leaf, this),
|
||||
);
|
||||
await this.loadSettings({reEnableAutosave:true});
|
||||
const updateSettings = !this.settings.onceOffCompressFlagReset || !this.settings.onceOffGPTVersionReset;
|
||||
if(!this.settings.onceOffCompressFlagReset) {
|
||||
@@ -345,68 +367,85 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
if(updateSettings) {
|
||||
await this.saveSettings();
|
||||
}
|
||||
this.excalidrawConfig = new ExcalidrawConfig(this);
|
||||
await loadMermaid();
|
||||
this.editorHandler = new EditorHandler(this);
|
||||
this.editorHandler.setup();
|
||||
|
||||
this.addSettingTab(new ExcalidrawSettingTab(this.app, this));
|
||||
this.ea = await initExcalidrawAutomate(this);
|
||||
|
||||
this.registerView(
|
||||
VIEW_TYPE_EXCALIDRAW,
|
||||
(leaf: WorkspaceLeaf) => new ExcalidrawView(leaf, this),
|
||||
);
|
||||
|
||||
//Compatibility mode with .excalidraw files
|
||||
this.registerExtensions(["excalidraw"], VIEW_TYPE_EXCALIDRAW);
|
||||
|
||||
this.addMarkdownPostProcessor();
|
||||
this.registerInstallCodeblockProcessor();
|
||||
this.addThemeObserver();
|
||||
this.experimentalFileTypeDisplayToggle(this.settings.experimentalFileType);
|
||||
this.registerCommands();
|
||||
this.registerEventListeners();
|
||||
this.runStartupScript();
|
||||
this.initializeFonts();
|
||||
this.registerEditorSuggest(new FieldSuggester(this));
|
||||
this.setPropertyTypes();
|
||||
|
||||
this.app.workspace.onLayoutReady(async () => {
|
||||
unpackExcalidraw();
|
||||
excalidrawLib = window.eval.call(window,`(function() {${EXCALIDRAW_PACKAGE};return ExcalidrawLib;})()`);
|
||||
this.packageMap.set(window,{react, reactDOM, excalidrawLib});
|
||||
updateExcalidrawLib();
|
||||
initCompressionWorker();
|
||||
this.loadTimestamp = Date.now();
|
||||
addIcon(ICON_NAME, EXCALIDRAW_ICON);
|
||||
addIcon(SCRIPTENGINE_ICON_NAME, SCRIPTENGINE_ICON);
|
||||
addIcon(EXPORT_IMG_ICON_NAME, EXPORT_IMG_ICON);
|
||||
|
||||
//inspiration taken from kanban:
|
||||
//https://github.com/mgmeyers/obsidian-kanban/blob/44118e25661bff9ebfe54f71ae33805dc88ffa53/src/main.ts#L267
|
||||
this.registerMonkeyPatches();
|
||||
this.excalidrawConfig = new ExcalidrawConfig(this);
|
||||
await loadMermaid();
|
||||
this.editorHandler = new EditorHandler(this);
|
||||
this.editorHandler.setup();
|
||||
|
||||
this.registerInstallCodeblockProcessor();
|
||||
this.addThemeObserver();
|
||||
this.experimentalFileTypeDisplayToggle(this.settings.experimentalFileType);
|
||||
this.registerCommands();
|
||||
this.registerEventListeners();
|
||||
this.runStartupScript();
|
||||
this.initializeFonts();
|
||||
this.registerEditorSuggest(new FieldSuggester(this));
|
||||
this.setPropertyTypes();
|
||||
|
||||
this.stylesManager = new StylesManager(this);
|
||||
//inspiration taken from kanban:
|
||||
//https://github.com/mgmeyers/obsidian-kanban/blob/44118e25661bff9ebfe54f71ae33805dc88ffa53/src/main.ts#L267
|
||||
this.registerMonkeyPatches();
|
||||
|
||||
// const patches = new OneOffs(this);
|
||||
if (this.settings.showReleaseNotes) {
|
||||
//I am repurposing imageElementNotice, if the value is true, this means the plugin was just newly installed to Obsidian.
|
||||
const obsidianJustInstalled = this.settings.previousRelease === "0.0.0"
|
||||
this.stylesManager = new StylesManager(this);
|
||||
|
||||
if (isVersionNewerThanOther(PLUGIN_VERSION, this.settings.previousRelease)) {
|
||||
new ReleaseNotes(
|
||||
this.app,
|
||||
this,
|
||||
obsidianJustInstalled ? null : PLUGIN_VERSION,
|
||||
).open();
|
||||
// const patches = new OneOffs(this);
|
||||
if (this.settings.showReleaseNotes) {
|
||||
//I am repurposing imageElementNotice, if the value is true, this means the plugin was just newly installed to Obsidian.
|
||||
const obsidianJustInstalled = this.settings.previousRelease === "0.0.0"
|
||||
|
||||
if (isVersionNewerThanOther(PLUGIN_VERSION, this.settings.previousRelease)) {
|
||||
new ReleaseNotes(
|
||||
this.app,
|
||||
this,
|
||||
obsidianJustInstalled ? null : PLUGIN_VERSION,
|
||||
).open();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.switchToExcalidarwAfterLoad();
|
||||
this.switchToExcalidarwAfterLoad();
|
||||
|
||||
this.app.workspace.onLayoutReady(() => {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.onload,"ExcalidrawPlugin.onload > app.workspace.onLayoutReady");
|
||||
this.scriptEngine = new ScriptEngine(this);
|
||||
imageCache.initializeDB(this);
|
||||
this.taskbone = new Taskbone(this);
|
||||
this.isReady = true;
|
||||
|
||||
this.app.workspace.onLayoutReady(() => {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.onload,"ExcalidrawPlugin.onload > app.workspace.onLayoutReady");
|
||||
this.scriptEngine = new ScriptEngine(this);
|
||||
imageCache.initializeDB(this);
|
||||
});
|
||||
});
|
||||
this.taskbone = new Taskbone(this);
|
||||
}
|
||||
|
||||
public async awaitInit() {
|
||||
let counter = 0;
|
||||
while(!this.isReady && counter < 150) {
|
||||
await sleep(50);
|
||||
}
|
||||
}
|
||||
|
||||
private setPropertyTypes() {
|
||||
if(!this.settings.loadPropertySuggestions) return;
|
||||
const app = this.app;
|
||||
this.app.workspace.onLayoutReady(() => {
|
||||
this.app.workspace.onLayoutReady(async () => {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.setPropertyTypes, `ExcalidrawPlugin.setPropertyTypes > app.workspace.onLayoutReady`);
|
||||
await this.awaitInit();
|
||||
Object.keys(FRONTMATTER_KEYS).forEach((key) => {
|
||||
if(FRONTMATTER_KEYS[key].depricated === true) return;
|
||||
const {name, type} = FRONTMATTER_KEYS[key];
|
||||
@@ -418,7 +457,22 @@ 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`);
|
||||
|
||||
await this.awaitInit();
|
||||
const cjkFontDataURLs = await getCJKDataURLs(this);
|
||||
if(typeof cjkFontDataURLs === "boolean" && !cjkFontDataURLs) {
|
||||
new Notice(t("FONTS_LOAD_ERROR") + this.settings.fontAssetsPath,6000);
|
||||
}
|
||||
|
||||
if(typeof cjkFontDataURLs === "object") {
|
||||
const fontDeclarations = cjkFontDataURLs.map(dataURL =>
|
||||
`@font-face { font-family: 'Xiaolai'; src: url("${dataURL}"); font-display: swap; font-weight: 400; }`
|
||||
);
|
||||
for(const ownerDocument of this.getOpenObsidianDocuments()) {
|
||||
await this.addFonts(fontDeclarations, ownerDocument, CJK_STYLE_ID);
|
||||
};
|
||||
new Notice(t("FONTS_LOADED"));
|
||||
}
|
||||
|
||||
const font = await getFontDataURL(
|
||||
this.app,
|
||||
this.settings.experimantalFourthFont,
|
||||
@@ -460,12 +514,12 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
});
|
||||
}
|
||||
|
||||
public async addFonts(declarations: string[],ownerDocument:Document = document) {
|
||||
public async addFonts(declarations: string[],ownerDocument:Document = document, styleId:string = FONTS_STYLE_ID) {
|
||||
// replace the old local font <style> element with the one we just created
|
||||
const newStylesheet = ownerDocument.createElement("style");
|
||||
newStylesheet.id = FONTS_STYLE_ID;
|
||||
newStylesheet.id = styleId;
|
||||
newStylesheet.textContent = declarations.join("");
|
||||
const oldStylesheet = ownerDocument.getElementById(FONTS_STYLE_ID);
|
||||
const oldStylesheet = ownerDocument.getElementById(styleId);
|
||||
ownerDocument.head.appendChild(newStylesheet);
|
||||
if (oldStylesheet) {
|
||||
ownerDocument.head.removeChild(oldStylesheet);
|
||||
@@ -475,11 +529,15 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
|
||||
public removeFonts() {
|
||||
this.getOpenObsidianDocuments().forEach((ownerDocument) => {
|
||||
const oldStylesheet = ownerDocument.getElementById(FONTS_STYLE_ID);
|
||||
if (oldStylesheet) {
|
||||
ownerDocument.head.removeChild(oldStylesheet);
|
||||
const oldCustomFontStylesheet = ownerDocument.getElementById(FONTS_STYLE_ID);
|
||||
if (oldCustomFontStylesheet) {
|
||||
ownerDocument.head.removeChild(oldCustomFontStylesheet);
|
||||
}
|
||||
})
|
||||
const oldCJKFontStylesheet = ownerDocument.getElementById(CJK_STYLE_ID);
|
||||
if (oldCJKFontStylesheet) {
|
||||
ownerDocument.head.removeChild(oldCJKFontStylesheet);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private getOpenObsidianDocuments(): Document[] {
|
||||
@@ -495,8 +553,9 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
}
|
||||
|
||||
private switchToExcalidarwAfterLoad() {
|
||||
this.app.workspace.onLayoutReady(() => {
|
||||
this.app.workspace.onLayoutReady(async () => {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.switchToExcalidarwAfterLoad, `ExcalidrawPlugin.switchToExcalidarwAfterLoad > app.workspace.onLayoutReady`);
|
||||
await this.awaitInit();
|
||||
let leaf: WorkspaceLeaf;
|
||||
for (leaf of this.app.workspace.getLeavesOfType("markdown")) {
|
||||
if ( leaf.view instanceof MarkdownView && this.isExcalidrawFile(leaf.view.file)) {
|
||||
@@ -728,9 +787,9 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
initializeMarkdownPostProcessor(this);
|
||||
this.registerMarkdownPostProcessor(markdownPostProcessor);
|
||||
|
||||
this.app.workspace.onLayoutReady(() => {
|
||||
this.app.workspace.onLayoutReady(async () => {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.addMarkdownPostProcessor, `ExcalidrawPlugin.addMarkdownPostProcessor > app.workspace.onLayoutReady`);
|
||||
|
||||
await this.awaitInit();
|
||||
// internal-link quick preview
|
||||
this.registerEvent(this.app.workspace.on("hover-link", hoverEvent));
|
||||
|
||||
@@ -850,8 +909,9 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
? new CustomMutationObserver(fileExplorerObserverFn, "fileExplorerObserver")
|
||||
: new MutationObserver(fileExplorerObserverFn);
|
||||
|
||||
this.app.workspace.onLayoutReady(() => {
|
||||
this.app.workspace.onLayoutReady(async () => {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.experimentalFileTypeDisplay, `ExcalidrawPlugin.experimentalFileTypeDisplay > app.workspace.onLayoutReady`);
|
||||
await this.awaitInit();
|
||||
document.querySelectorAll(".nav-file-title").forEach(insertFiletype); //apply filetype to files already displayed
|
||||
const container = document.querySelector(".nav-files-container");
|
||||
if (container) {
|
||||
@@ -2723,6 +2783,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
}
|
||||
this.app.workspace.onLayoutReady(async () => {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.runStartupScript, `ExcalidrawPlugin.runStartupScript > app.workspace.onLayoutReady, scriptPath:${this.settings?.startupScriptPath}`);
|
||||
await this.awaitInit();
|
||||
const path = this.settings.startupScriptPath.endsWith(".md")
|
||||
? this.settings.startupScriptPath
|
||||
: `${this.settings.startupScriptPath}.md`;
|
||||
@@ -2883,6 +2944,7 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
private registerEventListeners() {
|
||||
this.app.workspace.onLayoutReady(async () => {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.registerEventListeners,`ExcalidrawPlugin.registerEventListeners > app.workspace.onLayoutReady`);
|
||||
await this.awaitInit();
|
||||
const onPasteHandler = (
|
||||
evt: ClipboardEvent,
|
||||
editor: Editor,
|
||||
@@ -3331,7 +3393,8 @@ export default class ExcalidrawPlugin extends Plugin {
|
||||
|
||||
this.settings = null;
|
||||
clearMathJaxVariables();
|
||||
EXCALIDRAW_PACKAGES = "";
|
||||
EXCALIDRAW_PACKAGE = "";
|
||||
REACT_PACKAGES = "";
|
||||
//pluginPackages = null;
|
||||
PLUGIN_VERSION = null;
|
||||
//@ts-ignore
|
||||
|
||||
103
src/settings.ts
103
src/settings.ts
@@ -50,6 +50,9 @@ export interface ExcalidrawSettings {
|
||||
templateFilePath: string;
|
||||
scriptFolderPath: string;
|
||||
fontAssetsPath: string;
|
||||
loadChineseFonts: boolean;
|
||||
loadJapaneseFonts: boolean;
|
||||
loadKoreanFonts: boolean;
|
||||
compress: boolean;
|
||||
decompressForMDView: boolean;
|
||||
onceOffCompressFlagReset: boolean; //used to reset compress to true in 2.2.0
|
||||
@@ -206,6 +209,7 @@ export interface ExcalidrawSettings {
|
||||
areaZoomLimit: number;
|
||||
longPressDesktop: number;
|
||||
longPressMobile: number;
|
||||
doubleClickLinkOpenViewMode: boolean;
|
||||
isDebugMode: boolean;
|
||||
rank: Rank;
|
||||
modifierKeyOverrides: {modifiers: Modifier[], key: string}[];
|
||||
@@ -221,7 +225,10 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
|
||||
embedUseExcalidrawFolder: false,
|
||||
templateFilePath: "Excalidraw/Template.excalidraw",
|
||||
scriptFolderPath: "Excalidraw/Scripts",
|
||||
fontAssetsPath: "Excalidraw/FontAssets",
|
||||
fontAssetsPath: "Excalidraw/CJK Fonts",
|
||||
loadChineseFonts: false,
|
||||
loadJapaneseFonts: false,
|
||||
loadKoreanFonts: false,
|
||||
compress: true,
|
||||
decompressForMDView: false,
|
||||
onceOffCompressFlagReset: false,
|
||||
@@ -474,6 +481,7 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
|
||||
areaZoomLimit: 1,
|
||||
longPressDesktop: 500,
|
||||
longPressMobile: 500,
|
||||
doubleClickLinkOpenViewMode: true,
|
||||
isDebugMode: false,
|
||||
rank: "Bronze",
|
||||
modifierKeyOverrides: [
|
||||
@@ -722,19 +730,6 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
}),
|
||||
);
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("ASSETS_FOLDER_NAME"))
|
||||
.setDesc(fragWithHTML(t("ASSETS_FOLDER_DESC")))
|
||||
.addText((text) =>
|
||||
text
|
||||
.setPlaceholder("e.g.: Excalidraw/FontAssets")
|
||||
.setValue(this.plugin.settings.fontAssetsPath)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.fontAssetsPath = value;
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
|
||||
// ------------------------------------------------
|
||||
// Saving
|
||||
// ------------------------------------------------
|
||||
@@ -1500,6 +1495,18 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
el.innerText = ` ${this.plugin.settings.longPressMobile.toString()}`;
|
||||
});
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("DOUBLE_CLICK_LINK_OPEN_VIEW_MODE"))
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings.doubleClickLinkOpenViewMode)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.doubleClickLinkOpenViewMode = value;
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
new ModifierKeySettingsComponent(
|
||||
detailsEl,
|
||||
this.plugin.settings.modifierKeyConfig,
|
||||
@@ -2470,8 +2477,20 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
this.applySettingsUpdate(false);
|
||||
})
|
||||
)
|
||||
|
||||
// ------------------------------------------------
|
||||
// Fonts supported features
|
||||
// ------------------------------------------------
|
||||
containerEl.createEl("hr", { cls: "excalidraw-setting-hr" });
|
||||
containerEl.createDiv({ text: t("FONTS_DESC"), cls: "setting-item-description" });
|
||||
detailsEl = this.containerEl.createEl("details");
|
||||
const fontsDetailsEl = detailsEl;
|
||||
detailsEl.createEl("summary", {
|
||||
text: t("FONTS_HEAD"),
|
||||
cls: "excalidraw-setting-h1",
|
||||
});
|
||||
|
||||
detailsEl = nonstandardDetailsEl.createEl("details");
|
||||
detailsEl = fontsDetailsEl.createEl("details");
|
||||
detailsEl.createEl("summary", {
|
||||
text: t("CUSTOM_FONT_HEAD"),
|
||||
cls: "excalidraw-setting-h3",
|
||||
@@ -2512,7 +2531,61 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
||||
);
|
||||
});
|
||||
|
||||
detailsEl = fontsDetailsEl.createEl("details");
|
||||
detailsEl.createEl("summary", {
|
||||
text: t("OFFLINE_CJK_NAME"),
|
||||
cls: "excalidraw-setting-h3",
|
||||
});
|
||||
|
||||
const cjkdescdiv = detailsEl.createDiv({ cls: "setting-item-description" });
|
||||
cjkdescdiv.innerHTML = t("OFFLINE_CJK_DESC");
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("CJK_ASSETS_FOLDER_NAME"))
|
||||
.setDesc(fragWithHTML(t("CJK_ASSETS_FOLDER_DESC")))
|
||||
.addText((text) =>
|
||||
text
|
||||
.setPlaceholder("e.g.: Excalidraw/FontAssets")
|
||||
.setValue(this.plugin.settings.fontAssetsPath)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.fontAssetsPath = value;
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("LOAD_CHINESE_FONTS_NAME"))
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings.loadChineseFonts)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.loadChineseFonts = value;
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("LOAD_JAPANESE_FONTS_NAME"))
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings.loadJapaneseFonts)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.loadJapaneseFonts = value;
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
|
||||
new Setting(detailsEl)
|
||||
.setName(t("LOAD_KOREAN_FONTS_NAME"))
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings.loadKoreanFonts)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.loadKoreanFonts = value;
|
||||
this.applySettingsUpdate();
|
||||
}),
|
||||
);
|
||||
|
||||
// ------------------------------------------------
|
||||
// Experimental features
|
||||
// ------------------------------------------------
|
||||
|
||||
1403
src/utils/CJKLoader.ts
Normal file
1403
src/utils/CJKLoader.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -41,6 +41,7 @@ export class StylesManager {
|
||||
this.plugin = plugin;
|
||||
plugin.app.workspace.onLayoutReady(async () => {
|
||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(undefined, "StylesManager.constructor > app.workspace.onLayoutReady", this);
|
||||
await plugin.awaitInit();
|
||||
await this.harvestStyles();
|
||||
getAllWindowDocuments(plugin.app).forEach(doc => this.copyPropertiesToTheme(doc));
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ import { updateElementLinksToObsidianLinks } from "src/ExcalidrawAutomate";
|
||||
import { CropImage } from "./CropImage";
|
||||
import opentype from 'opentype.js';
|
||||
import { runCompressionWorker } from "src/workers/compression-worker";
|
||||
import Pool from "es6-promise-pool";
|
||||
|
||||
declare const PLUGIN_VERSION:string;
|
||||
declare var LZString: any;
|
||||
@@ -968,4 +969,63 @@ export function cropCanvas(
|
||||
0, 0, output.width, output.height
|
||||
);
|
||||
return dstCanvas;
|
||||
}
|
||||
|
||||
// Promise.try, adapted from https://github.com/sindresorhus/p-try
|
||||
export async function promiseTry <TValue, TArgs extends unknown[]>(
|
||||
fn: (...args: TArgs) => PromiseLike<TValue> | TValue,
|
||||
...args: TArgs
|
||||
): Promise<TValue> {
|
||||
return new Promise((resolve) => {
|
||||
resolve(fn(...args));
|
||||
});
|
||||
};
|
||||
|
||||
// extending the missing types
|
||||
// relying on the [Index, T] to keep a correct order
|
||||
type TPromisePool<T, Index = number> = Pool<[Index, T][]> & {
|
||||
addEventListener: (
|
||||
type: "fulfilled",
|
||||
listener: (event: { data: { result: [Index, T] } }) => void,
|
||||
) => (event: { data: { result: [Index, T] } }) => void;
|
||||
removeEventListener: (
|
||||
type: "fulfilled",
|
||||
listener: (event: { data: { result: [Index, T] } }) => void,
|
||||
) => void;
|
||||
};
|
||||
|
||||
export class PromisePool<T> {
|
||||
private readonly pool: TPromisePool<T>;
|
||||
private readonly entries: Record<number, T> = {};
|
||||
|
||||
constructor(
|
||||
source: IterableIterator<Promise<void | readonly [number, T]>>,
|
||||
concurrency: number,
|
||||
) {
|
||||
this.pool = new Pool(
|
||||
source as unknown as () => void | PromiseLike<[number, T][]>,
|
||||
concurrency,
|
||||
) as TPromisePool<T>;
|
||||
}
|
||||
|
||||
public all() {
|
||||
const listener = (event: { data: { result: void | [number, T] } }) => {
|
||||
if (event.data.result) {
|
||||
// by default pool does not return the results, so we are gathering them manually
|
||||
// with the correct call order (represented by the index in the tuple)
|
||||
const [index, value] = event.data.result;
|
||||
this.entries[index] = value;
|
||||
}
|
||||
};
|
||||
|
||||
this.pool.addEventListener("fulfilled", listener);
|
||||
|
||||
return this.pool.start().then(() => {
|
||||
setTimeout(() => {
|
||||
this.pool.removeEventListener("fulfilled", listener);
|
||||
});
|
||||
|
||||
return Object.values(this.entries);
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user