Compare commits

...

9 Commits

Author SHA1 Message Date
zsviczian
5bbe66900e 2.6.0-beta-3 2024-10-26 13:53:08 +02:00
zsviczian
a775a858c7 2.6.0-beta-2 2024-10-26 08:39:28 +02:00
zsviczian
2dab801ff5 Merge pull request #2078 from dmscode/master
Update zh-cn.ts to 91be6e2
2024-10-26 08:11:17 +02:00
dmscode
07f8a87580 Update zh-cn.ts to 91be6e2 2024-10-26 07:41:49 +08:00
zsviczian
91be6e2a2f local cjk fonts 2024-10-25 23:54:01 +02:00
zsviczian
5c709588dd 2.6.0-beta-1, 0.17.6-6, embedded file loader batching 2024-10-23 22:23:24 +02:00
zsviczian
19a46e5b11 2.3.5-beta-5 2024-10-23 06:43:56 +02:00
zsviczian
e132d4a9fc 2.5.3-beta-4 improved loading speeds, image cropping 2024-10-22 20:44:17 +02:00
zsviczian
cf2d9bea24 2.5.3-beta-3 2024-10-21 21:17:44 +02:00
16 changed files with 1937 additions and 240 deletions

Binary file not shown.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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") {

View File

@@ -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");

View File

@@ -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("&#91;", "["));

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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