mirror of
https://github.com/zsviczian/obsidian-excalidraw-plugin.git
synced 2025-08-06 05:46:28 +00:00
Compare commits
17 Commits
2.6.0
...
fix-textwr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bb83523c0f | ||
|
|
f83c0a8458 | ||
|
|
7411d51477 | ||
|
|
55ce6456d8 | ||
|
|
da6619d55e | ||
|
|
6033c057c2 | ||
|
|
0efda1d6a6 | ||
|
|
59107f0c2a | ||
|
|
f7cd05f6c4 | ||
|
|
5cbd98e543 | ||
|
|
e2d5966ca3 | ||
|
|
dec2909db0 | ||
|
|
7233d1e037 | ||
|
|
5972f83369 | ||
|
|
0edfd7622c | ||
|
|
8f14f97007 | ||
|
|
758585a4c2 |
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"id": "obsidian-excalidraw-plugin",
|
"id": "obsidian-excalidraw-plugin",
|
||||||
"name": "Excalidraw",
|
"name": "Excalidraw",
|
||||||
"version": "2.6.0",
|
"version": "2.6.4",
|
||||||
"minAppVersion": "1.1.6",
|
"minAppVersion": "1.1.6",
|
||||||
"description": "An Obsidian plugin to edit and view Excalidraw drawings",
|
"description": "An Obsidian plugin to edit and view Excalidraw drawings",
|
||||||
"author": "Zsolt Viczian",
|
"author": "Zsolt Viczian",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"id": "obsidian-excalidraw-plugin",
|
"id": "obsidian-excalidraw-plugin",
|
||||||
"name": "Excalidraw",
|
"name": "Excalidraw",
|
||||||
"version": "2.6.0",
|
"version": "2.6.4",
|
||||||
"minAppVersion": "1.1.6",
|
"minAppVersion": "1.1.6",
|
||||||
"description": "An Obsidian plugin to edit and view Excalidraw drawings",
|
"description": "An Obsidian plugin to edit and view Excalidraw drawings",
|
||||||
"author": "Zsolt Viczian",
|
"author": "Zsolt Viczian",
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@popperjs/core": "^2.11.8",
|
"@popperjs/core": "^2.11.8",
|
||||||
"@zsviczian/excalidraw": "0.17.6-7",
|
"@zsviczian/excalidraw": "0.17.6-11",
|
||||||
"chroma-js": "^2.4.2",
|
"chroma-js": "^2.4.2",
|
||||||
"clsx": "^2.0.0",
|
"clsx": "^2.0.0",
|
||||||
"@zsviczian/colormaster": "^1.2.2",
|
"@zsviczian/colormaster": "^1.2.2",
|
||||||
@@ -38,6 +38,7 @@
|
|||||||
"es6-promise-pool": "2.5.0"
|
"es6-promise-pool": "2.5.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"jsesc": "^3.0.2",
|
||||||
"@babel/core": "^7.22.9",
|
"@babel/core": "^7.22.9",
|
||||||
"@babel/preset-env": "^7.22.10",
|
"@babel/preset-env": "^7.22.10",
|
||||||
"@babel/preset-react": "^7.22.5",
|
"@babel/preset-react": "^7.22.5",
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Extension } from "@codemirror/state";
|
import { Extension } from "@codemirror/state";
|
||||||
import ExcalidrawPlugin from "src/main";
|
import ExcalidrawPlugin from "src/main";
|
||||||
import { HideTextBetweenCommentsExtension } from "./Fadeout";
|
import { HideTextBetweenCommentsExtension } from "./Fadeout";
|
||||||
|
import { debug, DEBUGGING } from "src/utils/DebugHelper";
|
||||||
export const EDITOR_FADEOUT = "fadeOutExcalidrawMarkup";
|
export const EDITOR_FADEOUT = "fadeOutExcalidrawMarkup";
|
||||||
|
|
||||||
const editorExtensions: {[key:string]:Extension}= {
|
const editorExtensions: {[key:string]:Extension}= {
|
||||||
@@ -10,13 +11,16 @@ const editorExtensions: {[key:string]:Extension}= {
|
|||||||
export class EditorHandler {
|
export class EditorHandler {
|
||||||
private activeEditorExtensions: Extension[] = [];
|
private activeEditorExtensions: Extension[] = [];
|
||||||
|
|
||||||
constructor(private plugin: ExcalidrawPlugin) {}
|
constructor(private plugin: ExcalidrawPlugin) {
|
||||||
|
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(EditorHandler, `ExcalidrawPlugin.construct EditorHandler`);
|
||||||
|
}
|
||||||
|
|
||||||
destroy(): void {
|
destroy(): void {
|
||||||
this.plugin = null;
|
this.plugin = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
setup(): void {
|
setup(): void {
|
||||||
|
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.setup, `ExcalidrawPlugin.construct EditorHandler.setup`);
|
||||||
this.plugin.registerEditorExtension(this.activeEditorExtensions);
|
this.plugin.registerEditorExtension(this.activeEditorExtensions);
|
||||||
this.updateCMExtensionState(EDITOR_FADEOUT, this.plugin.settings.fadeOutExcalidrawMarkup);
|
this.updateCMExtensionState(EDITOR_FADEOUT, this.plugin.settings.fadeOutExcalidrawMarkup);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1106,27 +1106,10 @@ export class EmbeddedFilesLoader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getSVGData = async (app: App, file: TFile, colorMap: ColorMap | null): Promise<DataURL> => {
|
const getSVGData = async (app: App, file: TFile, colorMap: ColorMap | null): Promise<DataURL> => {
|
||||||
const svg = replaceSVGColors(await app.vault.read(file), colorMap) as string;
|
const svgString = replaceSVGColors(await app.vault.read(file), colorMap) as string;
|
||||||
return svgToBase64(svg) as DataURL;
|
return svgToBase64(svgString) as DataURL;
|
||||||
};
|
};
|
||||||
|
|
||||||
/*export const generateIdFromFile = async (file: ArrayBuffer): Promise<FileId> => {
|
|
||||||
let id: FileId;
|
|
||||||
try {
|
|
||||||
const hashBuffer = await window.crypto.subtle.digest("SHA-1", file);
|
|
||||||
id =
|
|
||||||
// convert buffer to byte array
|
|
||||||
Array.from(new Uint8Array(hashBuffer))
|
|
||||||
// convert to hex string
|
|
||||||
.map((byte) => byte.toString(16).padStart(2, "0"))
|
|
||||||
.join("") as FileId;
|
|
||||||
} catch (error) {
|
|
||||||
errorlog({ where: "EmbeddedFileLoader.generateIdFromFile", error });
|
|
||||||
id = fileid() as FileId;
|
|
||||||
}
|
|
||||||
return id;
|
|
||||||
};*/
|
|
||||||
|
|
||||||
export const generateIdFromFile = async (file: ArrayBuffer, key?: string): Promise<FileId> => {
|
export const generateIdFromFile = async (file: ArrayBuffer, key?: string): Promise<FileId> => {
|
||||||
let id: FileId;
|
let id: FileId;
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1547,6 +1547,10 @@ export class ExcalidrawAutomate {
|
|||||||
: imageFile.path + (scale || !anchor ? "":"|100%"),
|
: imageFile.path + (scale || !anchor ? "":"|100%"),
|
||||||
hasSVGwithBitmap: image.hasSVGwithBitmap,
|
hasSVGwithBitmap: image.hasSVGwithBitmap,
|
||||||
latex: null,
|
latex: null,
|
||||||
|
size: { //must have the natural size here (e.g. for PDF cropping)
|
||||||
|
height: image.size.height,
|
||||||
|
width: image.size.width,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
if (scale && (Math.max(image.size.width, image.size.height) > MAX_IMAGE_SIZE)) {
|
if (scale && (Math.max(image.size.width, image.size.height) > MAX_IMAGE_SIZE)) {
|
||||||
const scale =
|
const scale =
|
||||||
@@ -2621,26 +2625,31 @@ export class ExcalidrawAutomate {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const size = await this.getOriginalImageSize(imgEl, true);
|
let originalArea, originalAspectRatio;
|
||||||
if (size) {
|
if(imgEl.crop) {
|
||||||
const originalArea = imgEl.width * imgEl.height;
|
originalArea = imgEl.width * imgEl.height;
|
||||||
const originalAspectRatio = size.width / size.height;
|
originalAspectRatio = imgEl.crop.width / imgEl.crop.height;
|
||||||
let newWidth = Math.sqrt(originalArea * originalAspectRatio);
|
} else {
|
||||||
let newHeight = Math.sqrt(originalArea / originalAspectRatio);
|
const size = await this.getOriginalImageSize(imgEl, true);
|
||||||
const centerX = imgEl.x + imgEl.width / 2;
|
if (!size) { return false; }
|
||||||
const centerY = imgEl.y + imgEl.height / 2;
|
originalArea = imgEl.width * imgEl.height;
|
||||||
|
originalAspectRatio = size.width / size.height;
|
||||||
|
}
|
||||||
|
let newWidth = Math.sqrt(originalArea * originalAspectRatio);
|
||||||
|
let newHeight = Math.sqrt(originalArea / originalAspectRatio);
|
||||||
|
const centerX = imgEl.x + imgEl.width / 2;
|
||||||
|
const centerY = imgEl.y + imgEl.height / 2;
|
||||||
|
|
||||||
if (newWidth !== imgEl.width || newHeight !== imgEl.height) {
|
if (newWidth !== imgEl.width || newHeight !== imgEl.height) {
|
||||||
if(!this.getElement(imgEl.id)) {
|
if(!this.getElement(imgEl.id)) {
|
||||||
this.copyViewElementsToEAforEditing([imgEl]);
|
this.copyViewElementsToEAforEditing([imgEl]);
|
||||||
}
|
|
||||||
const eaEl = this.getElement(imgEl.id);
|
|
||||||
eaEl.width = newWidth;
|
|
||||||
eaEl.height = newHeight;
|
|
||||||
eaEl.x = centerX - newWidth / 2;
|
|
||||||
eaEl.y = centerY - newHeight / 2;
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
const eaEl = this.getElement(imgEl.id);
|
||||||
|
eaEl.width = newWidth;
|
||||||
|
eaEl.height = newHeight;
|
||||||
|
eaEl.x = centerX - newWidth / 2;
|
||||||
|
eaEl.y = centerY - newHeight / 2;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -2845,10 +2854,9 @@ export class ExcalidrawAutomate {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function initExcalidrawAutomate(
|
export function initExcalidrawAutomate(
|
||||||
plugin: ExcalidrawPlugin,
|
plugin: ExcalidrawPlugin,
|
||||||
): Promise<ExcalidrawAutomate> {
|
): ExcalidrawAutomate {
|
||||||
await initFonts();
|
|
||||||
const ea = new ExcalidrawAutomate(plugin);
|
const ea = new ExcalidrawAutomate(plugin);
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
window.ExcalidrawAutomate = ea;
|
window.ExcalidrawAutomate = ea;
|
||||||
@@ -2883,14 +2891,6 @@ function getFontFamily(id: number):string {
|
|||||||
return getFontFamilyString({fontFamily:id})
|
return getFontFamilyString({fontFamily:id})
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function initFonts():Promise<void> {
|
|
||||||
/*await excalidrawLib.registerFontsInCSS();
|
|
||||||
const fonts = excalidrawLib.getFontFamilies();
|
|
||||||
for(let i=0;i<fonts.length;i++) {
|
|
||||||
if(fonts[i] !== "Local Font") await (document as any).fonts.load(`16px ${fonts[i]}`);
|
|
||||||
};*/
|
|
||||||
}
|
|
||||||
|
|
||||||
export function _measureText(
|
export function _measureText(
|
||||||
newText: string,
|
newText: string,
|
||||||
fontSize: number,
|
fontSize: number,
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ import { updateElementIdsInScene } from "./utils/ExcalidrawSceneUtils";
|
|||||||
import { getNewUniqueFilepath } from "./utils/FileUtils";
|
import { getNewUniqueFilepath } from "./utils/FileUtils";
|
||||||
import { t } from "./lang/helpers";
|
import { t } from "./lang/helpers";
|
||||||
import { displayFontMessage } from "./utils/ExcalidrawViewUtils";
|
import { displayFontMessage } from "./utils/ExcalidrawViewUtils";
|
||||||
|
import { getPDFRect } from "./utils/PDFUtils";
|
||||||
|
|
||||||
type SceneDataWithFiles = SceneData & { files: BinaryFiles };
|
type SceneDataWithFiles = SceneData & { files: BinaryFiles };
|
||||||
|
|
||||||
@@ -1579,6 +1580,26 @@ export class ExcalidrawData {
|
|||||||
return file;
|
return file;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private syncCroppedPDFs() {
|
||||||
|
let dirty = false;
|
||||||
|
const scene = this.scene as SceneDataWithFiles;
|
||||||
|
const pdfScale = this.plugin.settings.pdfScale;
|
||||||
|
scene.elements
|
||||||
|
.filter(el=>el.type === "image" && el.crop && !el.isDeleted)
|
||||||
|
.forEach((el: Mutable<ExcalidrawImageElement>)=>{
|
||||||
|
const ef = this.getFile(el.fileId);
|
||||||
|
if(!ef.file) return;
|
||||||
|
if(ef.file.extension !== "pdf") return;
|
||||||
|
const pageRef = ef.linkParts.original.split("#")?.[1];
|
||||||
|
if(!pageRef || !pageRef.startsWith("page=") || pageRef.includes("rect")) return;
|
||||||
|
const restOfLink = el.link ? el.link.match(/&rect=\d*,\d*,\d*,\d*(.*)/)?.[1] : "";
|
||||||
|
const link = ef.linkParts.original + getPDFRect(el.crop, pdfScale) + (restOfLink ? restOfLink : "]]");
|
||||||
|
el.link = `[[${link}`;
|
||||||
|
this.elementLinks.set(el.id, el.link);
|
||||||
|
dirty = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* deletes fileIds from Excalidraw data for files no longer in the scene
|
* deletes fileIds from Excalidraw data for files no longer in the scene
|
||||||
* @returns
|
* @returns
|
||||||
@@ -1699,6 +1720,7 @@ export class ExcalidrawData {
|
|||||||
this.updateElementLinksFromScene();
|
this.updateElementLinksFromScene();
|
||||||
result =
|
result =
|
||||||
result ||
|
result ||
|
||||||
|
this.syncCroppedPDFs() ||
|
||||||
this.setLinkPrefix() ||
|
this.setLinkPrefix() ||
|
||||||
this.setUrlPrefix() ||
|
this.setUrlPrefix() ||
|
||||||
this.setShowLinkBrackets() ||
|
this.setShowLinkBrackets() ||
|
||||||
|
|||||||
@@ -148,6 +148,7 @@ import { Packages } from "./types/types";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { diagramToHTML } from "./utils/matic";
|
import { diagramToHTML } from "./utils/matic";
|
||||||
import { IS_WORKER_SUPPORTED } from "./workers/compression-worker";
|
import { IS_WORKER_SUPPORTED } from "./workers/compression-worker";
|
||||||
|
import { getPDFCropRect } from "./utils/PDFUtils";
|
||||||
|
|
||||||
const EMBEDDABLE_SEMAPHORE_TIMEOUT = 2000;
|
const EMBEDDABLE_SEMAPHORE_TIMEOUT = 2000;
|
||||||
const PREVENT_RELOAD_TIMEOUT = 2000;
|
const PREVENT_RELOAD_TIMEOUT = 2000;
|
||||||
@@ -218,6 +219,27 @@ export const addFiles = async (
|
|||||||
if (isDark === undefined) {
|
if (isDark === undefined) {
|
||||||
isDark = s.scene.appState.theme;
|
isDark = s.scene.appState.theme;
|
||||||
}
|
}
|
||||||
|
// update element.crop naturalWidth and naturalHeight in case scale of PDF loading has changed
|
||||||
|
// update crop.x crop.y, crop.width, crop.height according to the new scale
|
||||||
|
files
|
||||||
|
.filter((f:FileData) => view.excalidrawData.getFile(f.id)?.file?.extension === "pdf")
|
||||||
|
.forEach((f:FileData) => {
|
||||||
|
s.scene.elements
|
||||||
|
.filter((el:ExcalidrawElement)=>el.type === "image" && el.fileId === f.id && el.crop && el.crop.naturalWidth !== f.size.width)
|
||||||
|
.forEach((el:Mutable<ExcalidrawImageElement>) => {
|
||||||
|
s.dirty = true;
|
||||||
|
const scale = f.size.width / el.crop.naturalWidth;
|
||||||
|
el.crop = {
|
||||||
|
x: el.crop.x * scale,
|
||||||
|
y: el.crop.y * scale,
|
||||||
|
width: el.crop.width * scale,
|
||||||
|
height: el.crop.height * scale,
|
||||||
|
naturalWidth: f.size.width,
|
||||||
|
naturalHeight: f.size.height,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
if (s.dirty) {
|
if (s.dirty) {
|
||||||
//debug({where:"ExcalidrawView.addFiles",file:view.file.name,dataTheme:view.excalidrawData.scene.appState.theme,before:"updateScene",state:scene.appState})
|
//debug({where:"ExcalidrawView.addFiles",file:view.file.name,dataTheme:view.excalidrawData.scene.appState.theme,before:"updateScene",state:scene.appState})
|
||||||
view.updateScene({
|
view.updateScene({
|
||||||
@@ -3985,6 +4007,11 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
|||||||
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.onPaste, "ExcalidrawView.onPaste", data, event);
|
(process.env.NODE_ENV === 'development') && DEBUGGING && debug(this.onPaste, "ExcalidrawView.onPaste", data, event);
|
||||||
const api = this.excalidrawAPI as ExcalidrawImperativeAPI;
|
const api = this.excalidrawAPI as ExcalidrawImperativeAPI;
|
||||||
const ea = this.getHookServer();
|
const ea = this.getHookServer();
|
||||||
|
if(data?.elements) {
|
||||||
|
data.elements
|
||||||
|
.filter(el=>el.type==="text" && !el.hasOwnProperty("rawText"))
|
||||||
|
.forEach(el=>(el as Mutable<ExcalidrawTextElement>).rawText = (el as ExcalidrawTextElement).originalText);
|
||||||
|
};
|
||||||
if(data && ea.onPasteHook) {
|
if(data && ea.onPasteHook) {
|
||||||
const res = ea.onPasteHook({
|
const res = ea.onPasteHook({
|
||||||
ea,
|
ea,
|
||||||
@@ -4031,7 +4058,20 @@ export default class ExcalidrawView extends TextFileView implements HoverParent{
|
|||||||
} else {
|
} else {
|
||||||
if(link.match(/^[^#]*#page=\d*(&\w*=[^&]+){0,}&rect=\d*,\d*,\d*,\d*/g)) {
|
if(link.match(/^[^#]*#page=\d*(&\w*=[^&]+){0,}&rect=\d*,\d*,\d*,\d*/g)) {
|
||||||
const ea = getEA(this) as ExcalidrawAutomate;
|
const ea = getEA(this) as ExcalidrawAutomate;
|
||||||
await ea.addImage(this.currentPosition.x, this.currentPosition.y,link);
|
const imgID = await ea.addImage(this.currentPosition.x, this.currentPosition.y,link.split("&rect=")[0]);
|
||||||
|
const el = ea.getElement(imgID) as Mutable<ExcalidrawImageElement>;
|
||||||
|
const fd = ea.imagesDict[el.fileId] as FileData;
|
||||||
|
el.crop = getPDFCropRect({
|
||||||
|
scale: this.plugin.settings.pdfScale,
|
||||||
|
link,
|
||||||
|
naturalHeight: fd.size.height,
|
||||||
|
naturalWidth: fd.size.width,
|
||||||
|
});
|
||||||
|
if(el.crop) {
|
||||||
|
el.width = el.crop.width/this.plugin.settings.pdfScale;
|
||||||
|
el.height = el.crop.height/this.plugin.settings.pdfScale;
|
||||||
|
}
|
||||||
|
el.link = `[[${link}]]`;
|
||||||
ea.addElementsToView(false,false).then(()=>ea.destroy());
|
ea.addElementsToView(false,false).then(()=>ea.destroy());
|
||||||
} else {
|
} else {
|
||||||
const modal = new UniversalInsertFileModal(this.plugin, this);
|
const modal = new UniversalInsertFileModal(this.plugin, this);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
App,
|
App,
|
||||||
Instruction,
|
Instruction,
|
||||||
|
normalizePath,
|
||||||
TAbstractFile,
|
TAbstractFile,
|
||||||
TFile,
|
TFile,
|
||||||
WorkspaceLeaf,
|
WorkspaceLeaf,
|
||||||
@@ -22,6 +23,7 @@ export type ScriptIconMap = {
|
|||||||
|
|
||||||
export class ScriptEngine {
|
export class ScriptEngine {
|
||||||
private plugin: ExcalidrawPlugin;
|
private plugin: ExcalidrawPlugin;
|
||||||
|
private app: App;
|
||||||
private scriptPath: string;
|
private scriptPath: string;
|
||||||
//https://stackoverflow.com/questions/60218638/how-to-force-re-render-if-map-value-changes
|
//https://stackoverflow.com/questions/60218638/how-to-force-re-render-if-map-value-changes
|
||||||
public scriptIconMap: ScriptIconMap;
|
public scriptIconMap: ScriptIconMap;
|
||||||
@@ -29,6 +31,7 @@ export class ScriptEngine {
|
|||||||
|
|
||||||
constructor(plugin: ExcalidrawPlugin) {
|
constructor(plugin: ExcalidrawPlugin) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
|
this.app = plugin.app;
|
||||||
this.scriptIconMap = {};
|
this.scriptIconMap = {};
|
||||||
this.loadScripts();
|
this.loadScripts();
|
||||||
this.registerEventHandlers();
|
this.registerEventHandlers();
|
||||||
@@ -58,7 +61,7 @@ export class ScriptEngine {
|
|||||||
if (!path.endsWith(".svg")) {
|
if (!path.endsWith(".svg")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const scriptFile = app.vault.getAbstractFileByPath(
|
const scriptFile = this.app.vault.getAbstractFileByPath(
|
||||||
getIMGFilename(path, "md"),
|
getIMGFilename(path, "md"),
|
||||||
);
|
);
|
||||||
if (scriptFile && scriptFile instanceof TFile) {
|
if (scriptFile && scriptFile instanceof TFile) {
|
||||||
@@ -107,19 +110,19 @@ export class ScriptEngine {
|
|||||||
|
|
||||||
registerEventHandlers() {
|
registerEventHandlers() {
|
||||||
this.plugin.registerEvent(
|
this.plugin.registerEvent(
|
||||||
this.plugin.app.vault.on(
|
this.app.vault.on(
|
||||||
"delete",
|
"delete",
|
||||||
(file: TFile)=>this.deleteEventHandler(file)
|
(file: TFile)=>this.deleteEventHandler(file)
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
this.plugin.registerEvent(
|
this.plugin.registerEvent(
|
||||||
this.plugin.app.vault.on(
|
this.app.vault.on(
|
||||||
"create",
|
"create",
|
||||||
(file: TFile)=>this.createEventHandler(file)
|
(file: TFile)=>this.createEventHandler(file)
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
this.plugin.registerEvent(
|
this.plugin.registerEvent(
|
||||||
this.plugin.app.vault.on(
|
this.app.vault.on(
|
||||||
"rename",
|
"rename",
|
||||||
(file: TAbstractFile, oldPath: string)=>this.renameEventHandler(file, oldPath)
|
(file: TAbstractFile, oldPath: string)=>this.renameEventHandler(file, oldPath)
|
||||||
),
|
),
|
||||||
@@ -138,15 +141,16 @@ export class ScriptEngine {
|
|||||||
|
|
||||||
public getListofScripts(): TFile[] {
|
public getListofScripts(): TFile[] {
|
||||||
this.scriptPath = this.plugin.settings.scriptFolderPath;
|
this.scriptPath = this.plugin.settings.scriptFolderPath;
|
||||||
if (!app.vault.getAbstractFileByPath(this.scriptPath)) {
|
if(!this.scriptPath) return;
|
||||||
//this.scriptPath = null;
|
this.scriptPath = normalizePath(this.scriptPath);
|
||||||
|
if (!this.app.vault.getAbstractFileByPath(this.scriptPath)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
return app.vault
|
return this.app.vault
|
||||||
.getFiles()
|
.getFiles()
|
||||||
.filter(
|
.filter(
|
||||||
(f: TFile) =>
|
(f: TFile) =>
|
||||||
f.path.startsWith(this.scriptPath) && f.extension === "md",
|
f.path.startsWith(this.scriptPath+"/") && f.extension === "md",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,7 +170,10 @@ export class ScriptEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const subpath = path.split(`${this.scriptPath}/`)[1];
|
const subpath = path.split(`${this.scriptPath}/`)[1];
|
||||||
const lastSlash = subpath.lastIndexOf("/");
|
if(!subpath) {
|
||||||
|
console.warn(`ScriptEngine.getScriptName unexpected basename: ${basename}; path: ${path}`)
|
||||||
|
}
|
||||||
|
const lastSlash = subpath?.lastIndexOf("/");
|
||||||
if (lastSlash > -1) {
|
if (lastSlash > -1) {
|
||||||
return subpath.substring(0, lastSlash + 1) + basename;
|
return subpath.substring(0, lastSlash + 1) + basename;
|
||||||
}
|
}
|
||||||
@@ -175,10 +182,10 @@ export class ScriptEngine {
|
|||||||
|
|
||||||
async addScriptIconToMap(scriptPath: string, name: string) {
|
async addScriptIconToMap(scriptPath: string, name: string) {
|
||||||
const svgFilePath = getIMGFilename(scriptPath, "svg");
|
const svgFilePath = getIMGFilename(scriptPath, "svg");
|
||||||
const file = app.vault.getAbstractFileByPath(svgFilePath);
|
const file = this.app.vault.getAbstractFileByPath(svgFilePath);
|
||||||
const svgString: string =
|
const svgString: string =
|
||||||
file && file instanceof TFile
|
file && file instanceof TFile
|
||||||
? await app.vault.read(file)
|
? await this.app.vault.read(file)
|
||||||
: null;
|
: null;
|
||||||
this.scriptIconMap = {
|
this.scriptIconMap = {
|
||||||
...this.scriptIconMap,
|
...this.scriptIconMap,
|
||||||
@@ -199,12 +206,12 @@ export class ScriptEngine {
|
|||||||
name: `(Script) ${scriptName}`,
|
name: `(Script) ${scriptName}`,
|
||||||
checkCallback: (checking: boolean) => {
|
checkCallback: (checking: boolean) => {
|
||||||
if (checking) {
|
if (checking) {
|
||||||
return Boolean(app.workspace.getActiveViewOfType(ExcalidrawView));
|
return Boolean(this.app.workspace.getActiveViewOfType(ExcalidrawView));
|
||||||
}
|
}
|
||||||
const view = app.workspace.getActiveViewOfType(ExcalidrawView);
|
const view = this.app.workspace.getActiveViewOfType(ExcalidrawView);
|
||||||
if (view) {
|
if (view) {
|
||||||
(async()=>{
|
(async()=>{
|
||||||
const script = await app.vault.read(f);
|
const script = await this.app.vault.read(f);
|
||||||
if(script) {
|
if(script) {
|
||||||
//remove YAML frontmatter if present
|
//remove YAML frontmatter if present
|
||||||
this.executeScript(view, script, scriptName,f);
|
this.executeScript(view, script, scriptName,f);
|
||||||
@@ -218,7 +225,7 @@ export class ScriptEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
unloadScripts() {
|
unloadScripts() {
|
||||||
const scripts = app.vault
|
const scripts = this.app.vault
|
||||||
.getFiles()
|
.getFiles()
|
||||||
.filter((f: TFile) => f.path.startsWith(this.scriptPath));
|
.filter((f: TFile) => f.path.startsWith(this.scriptPath));
|
||||||
scripts.forEach((f) => {
|
scripts.forEach((f) => {
|
||||||
@@ -236,11 +243,11 @@ export class ScriptEngine {
|
|||||||
|
|
||||||
const commandId = `${PLUGIN_ID}:${basename}`;
|
const commandId = `${PLUGIN_ID}:${basename}`;
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
if (!this.plugin.app.commands.commands[commandId]) {
|
if (!this.app.commands.commands[commandId]) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
delete this.plugin.app.commands.commands[commandId];
|
delete this.app.commands.commands[commandId];
|
||||||
}
|
}
|
||||||
|
|
||||||
async executeScript(view: ExcalidrawView, script: string, title: string, file: TFile) {
|
async executeScript(view: ExcalidrawView, script: string, title: string, file: TFile) {
|
||||||
@@ -271,7 +278,7 @@ export class ScriptEngine {
|
|||||||
ScriptEngine.inputPrompt(
|
ScriptEngine.inputPrompt(
|
||||||
view,
|
view,
|
||||||
this.plugin,
|
this.plugin,
|
||||||
this.plugin.app,
|
this.app,
|
||||||
header,
|
header,
|
||||||
placeholder,
|
placeholder,
|
||||||
value,
|
value,
|
||||||
@@ -288,7 +295,7 @@ export class ScriptEngine {
|
|||||||
instructions?: Instruction[],
|
instructions?: Instruction[],
|
||||||
) =>
|
) =>
|
||||||
ScriptEngine.suggester(
|
ScriptEngine.suggester(
|
||||||
this.plugin.app,
|
this.app,
|
||||||
displayItems,
|
displayItems,
|
||||||
items,
|
items,
|
||||||
hint,
|
hint,
|
||||||
@@ -304,7 +311,7 @@ export class ScriptEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private updateToolPannels() {
|
private updateToolPannels() {
|
||||||
const excalidrawViews = getExcalidrawViews(this.plugin.app);
|
const excalidrawViews = getExcalidrawViews(this.app);
|
||||||
excalidrawViews.forEach(excalidrawView => {
|
excalidrawViews.forEach(excalidrawView => {
|
||||||
excalidrawView.toolsPanelRef?.current?.updateScriptIconMap(
|
excalidrawView.toolsPanelRef?.current?.updateScriptIconMap(
|
||||||
this.scriptIconMap,
|
this.scriptIconMap,
|
||||||
|
|||||||
@@ -222,6 +222,7 @@ export const FRONTMATTER_KEYS:{[key:string]: {name: string, type: string, depric
|
|||||||
|
|
||||||
export const EMBEDDABLE_THEME_FRONTMATTER_VALUES = ["light", "dark", "auto", "dafault"];
|
export const EMBEDDABLE_THEME_FRONTMATTER_VALUES = ["light", "dark", "auto", "dafault"];
|
||||||
export const VIEW_TYPE_EXCALIDRAW = "excalidraw";
|
export const VIEW_TYPE_EXCALIDRAW = "excalidraw";
|
||||||
|
export const VIEW_TYPE_EXCALIDRAW_LOADING = "excalidraw-loading";
|
||||||
export const ICON_NAME = "excalidraw-icon";
|
export const ICON_NAME = "excalidraw-icon";
|
||||||
export const MAX_COLORS = 5;
|
export const MAX_COLORS = 5;
|
||||||
export const COLOR_FREQ = 6;
|
export const COLOR_FREQ = 6;
|
||||||
|
|||||||
@@ -1,11 +1,16 @@
|
|||||||
import { FileView, TextFileView, View, WorkspaceLeaf } from "obsidian";
|
import { App, FileView, WorkspaceLeaf } from "obsidian";
|
||||||
|
import { VIEW_TYPE_EXCALIDRAW_LOADING } from "src/constants/constants";
|
||||||
import ExcalidrawPlugin from "src/main";
|
import ExcalidrawPlugin from "src/main";
|
||||||
import { setExcalidrawView } from "src/utils/ObsidianUtils";
|
import { setExcalidrawView } from "src/utils/ObsidianUtils";
|
||||||
|
|
||||||
export default class ExcalidrawLoading extends FileView {
|
export function switchToExcalidraw(app: App) {
|
||||||
|
const leaves = app.workspace.getLeavesOfType(VIEW_TYPE_EXCALIDRAW_LOADING).filter(l=>l.view instanceof ExcalidrawLoading);
|
||||||
|
leaves.forEach(l=>(l.view as ExcalidrawLoading).switchToeExcalidraw());
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ExcalidrawLoading extends FileView {
|
||||||
constructor(leaf: WorkspaceLeaf, private plugin: ExcalidrawPlugin) {
|
constructor(leaf: WorkspaceLeaf, private plugin: ExcalidrawPlugin) {
|
||||||
super(leaf);
|
super(leaf);
|
||||||
this.switchToeExcalidraw();
|
|
||||||
this.displayLoadingText();
|
this.displayLoadingText();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -14,13 +19,12 @@ export default class ExcalidrawLoading extends FileView {
|
|||||||
this.displayLoadingText();
|
this.displayLoadingText();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async switchToeExcalidraw() {
|
public switchToeExcalidraw() {
|
||||||
await this.plugin.awaitInit();
|
|
||||||
setExcalidrawView(this.leaf);
|
setExcalidrawView(this.leaf);
|
||||||
}
|
}
|
||||||
|
|
||||||
getViewType(): string {
|
getViewType(): string {
|
||||||
return "excalidra-loading";
|
return VIEW_TYPE_EXCALIDRAW_LOADING;
|
||||||
}
|
}
|
||||||
|
|
||||||
getDisplayText() {
|
getDisplayText() {
|
||||||
|
|||||||
@@ -17,6 +17,45 @@ I develop this plugin as a hobby, spending my free time doing this. If you find
|
|||||||
|
|
||||||
<div class="ex-coffee-div"><a href="https://ko-fi.com/zsolt"><img src="https://storage.ko-fi.com/cdn/kofi6.png?v=6" border="0" alt="Buy Me a Coffee at ko-fi.com" height=45></a></div>
|
<div class="ex-coffee-div"><a href="https://ko-fi.com/zsolt"><img src="https://storage.ko-fi.com/cdn/kofi6.png?v=6" border="0" alt="Buy Me a Coffee at ko-fi.com" height=45></a></div>
|
||||||
`,
|
`,
|
||||||
|
"2.6.4":`
|
||||||
|
## Fixed
|
||||||
|
- Error saving when cropping images embedded from a URL (not from a file in the Vault) [#2096](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2096)
|
||||||
|
`,
|
||||||
|
"2.6.3":`
|
||||||
|
<div class="excalidraw-videoWrapper"><div>
|
||||||
|
<iframe src="https://www.youtube.com/embed/OfUWAvCgbXk" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||||
|
</div></div>
|
||||||
|
|
||||||
|
## New
|
||||||
|
- **Cropping PDF Pages**
|
||||||
|
- Improved PDF++ cropping: You can now double-click cropped images in Excalidraw to adjust the crop area, which will also appear as a highlight in PDF++. This feature applies to PDF cut-outs created in version 2.6.3 and beyond.
|
||||||
|
- **Insert Last Active PDF Page as Image**
|
||||||
|
- New command palette action lets you insert the currently active PDF page into Excalidraw. Ideal for setups with PDF and Excalidraw side-by-side. You can assign a hotkey for quicker access. Cropped areas in Excalidraw will show as highlights in PDF++.
|
||||||
|
|
||||||
|
## Fixed
|
||||||
|
- Fixed **Close Settings** button toggle behavior [#2085](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2085)
|
||||||
|
- Resolved text wrapping issues causing layout shifts due to trailing whitespaces [#8714](https://github.com/excalidraw/excalidraw/pull/8714)
|
||||||
|
- **Aspect Ratio and Size Reset** commands now function correctly with cropped images.
|
||||||
|
- **Cropped Drawings**: Adjustments to cropped Excalidraw drawings are now supported. However, for nested Excalidraw drawings, it's recommended to use area, group, and frame references instead of cropping.
|
||||||
|
|
||||||
|
## Refactoring
|
||||||
|
- Further font loading optimizations on Excalidraw.com; no impact expected in Obsidian [#8693](https://github.com/excalidraw/excalidraw/pull/8693)
|
||||||
|
- Text wrapping improvements [#8715](https://github.com/excalidraw/excalidraw/pull/8715)
|
||||||
|
- Plugin initiation and error handling
|
||||||
|
`,
|
||||||
|
"2.6.2":`
|
||||||
|
## Fixed
|
||||||
|
- Image scaling issue with SVGs that miss the width and height property. [#8729](https://github.com/excalidraw/excalidraw/issues/8729)
|
||||||
|
`,
|
||||||
|
"2.6.1":`
|
||||||
|
## New
|
||||||
|
- Pen-mode single-finger panning enabled also for the "Selection" tool.
|
||||||
|
- You can disable pen-mode single-finger panning in Plugin Settings under Excalidraw Appearance and Behavior [#2080](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2080)
|
||||||
|
|
||||||
|
## Fixed
|
||||||
|
- Text tool did not work in pen-mode using finger [#2080](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2080)
|
||||||
|
- Pasting images to Excalidraw from the web resulted in filenames of "image_1.png", "image_2.png" instead of "Pasted Image TIMESTAMP" [#2081](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2081)
|
||||||
|
`,
|
||||||
"2.6.0":`
|
"2.6.0":`
|
||||||
## Performance
|
## Performance
|
||||||
- Much faster plugin initialization. Down from 1000-3000ms to 100-300ms. According to my testing speed varies on a wide spectrum depending on device, size of Vault and other plugins being loaded. I measured values ranging from 84ms up to 782ms [#2068](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2068)
|
- Much faster plugin initialization. Down from 1000-3000ms to 100-300ms. According to my testing speed varies on a wide spectrum depending on device, size of Vault and other plugins being loaded. I measured values ranging from 84ms up to 782ms [#2068](https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/2068)
|
||||||
|
|||||||
@@ -713,27 +713,70 @@ export class ConfirmationPrompt extends Modal {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function linkPrompt (
|
export async function linkPrompt(
|
||||||
linkText:string,
|
linkText: string,
|
||||||
app: App,
|
app: App,
|
||||||
view?: ExcalidrawView,
|
view?: ExcalidrawView,
|
||||||
message: string = "Select link to open",
|
message: string = t("SELECT_LINK_TO_OPEN"),
|
||||||
):Promise<[file:TFile, linkText:string, subpath: string]> {
|
): Promise<[file: TFile, linkText: string, subpath: string]> {
|
||||||
const linksArray = REGEX_LINK.getResList(linkText);
|
const linksArray = REGEX_LINK.getResList(linkText).filter(x => Boolean(x.value));
|
||||||
const tagsArray = REGEX_TAGS.getResList(linkText.replaceAll(/([^\s])#/g,"$1 "));
|
const links = linksArray.map(x => REGEX_LINK.getLink(x));
|
||||||
|
|
||||||
|
// Create a map to track duplicates by base link (without rect reference)
|
||||||
|
const linkMap = new Map<string, number[]>();
|
||||||
|
links.forEach((link, i) => {
|
||||||
|
const linkBase = link.split("&rect=")[0];
|
||||||
|
if (!linkMap.has(linkBase)) linkMap.set(linkBase, []);
|
||||||
|
linkMap.get(linkBase).push(i);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Determine indices to keep
|
||||||
|
const indicesToKeep = new Set<number>();
|
||||||
|
linkMap.forEach(indices => {
|
||||||
|
if (indices.length === 1) {
|
||||||
|
// Only one link, keep it
|
||||||
|
indicesToKeep.add(indices[0]);
|
||||||
|
} else {
|
||||||
|
// Multiple links: prefer the one with rect reference, if available
|
||||||
|
const rectIndex = indices.find(i => links[i].includes("&rect="));
|
||||||
|
if (rectIndex !== undefined) {
|
||||||
|
indicesToKeep.add(rectIndex);
|
||||||
|
} else {
|
||||||
|
// No rect reference in duplicates, add the first one
|
||||||
|
indicesToKeep.add(indices[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Final validation to ensure each duplicate group has at least one entry
|
||||||
|
linkMap.forEach(indices => {
|
||||||
|
const hasKeptEntry = indices.some(i => indicesToKeep.has(i));
|
||||||
|
if (!hasKeptEntry) {
|
||||||
|
// Add the first index if none were kept
|
||||||
|
indicesToKeep.add(indices[0]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Filter linksArray, links, itemsDisplay, and items based on indicesToKeep
|
||||||
|
const filteredLinksArray = linksArray.filter((_, i) => indicesToKeep.has(i));
|
||||||
|
const tagsArray = REGEX_TAGS.getResList(linkText.replaceAll(/([^\s])#/g, "$1 ")).filter(x => Boolean(x.value));
|
||||||
|
|
||||||
let subpath: string = null;
|
let subpath: string = null;
|
||||||
let file: TFile = null;
|
let file: TFile = null;
|
||||||
let parts = linksArray[0] ?? tagsArray[0];
|
let parts = filteredLinksArray[0] ?? tagsArray[0];
|
||||||
|
|
||||||
|
// Generate filtered itemsDisplay and items arrays
|
||||||
const itemsDisplay = [
|
const itemsDisplay = [
|
||||||
...linksArray.filter(p=> Boolean(p.value)).map(p => {
|
...filteredLinksArray.map(p => {
|
||||||
const alias = REGEX_LINK.getAliasOrLink(p);
|
const alias = REGEX_LINK.getAliasOrLink(p);
|
||||||
return alias === "100%" ? REGEX_LINK.getLink(p) : alias;
|
return alias === "100%" ? REGEX_LINK.getLink(p) : alias;
|
||||||
}),
|
}),
|
||||||
...tagsArray.filter(x=> Boolean(x.value)).map(x => REGEX_TAGS.getTag(x)),
|
...tagsArray.map(x => REGEX_TAGS.getTag(x)),
|
||||||
];
|
];
|
||||||
|
|
||||||
const items = [
|
const items = [
|
||||||
...linksArray.filter(p=>Boolean(p.value)),
|
...filteredLinksArray,
|
||||||
...tagsArray.filter(x=> Boolean(x.value)),
|
...tagsArray,
|
||||||
];
|
];
|
||||||
|
|
||||||
if (items.length>1) {
|
if (items.length>1) {
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import { TAG_AUTOEXPORT, TAG_MDREADINGMODE, TAG_PDFEXPORT } from "src/constants/
|
|||||||
import { labelALT, labelCTRL, labelMETA, labelSHIFT } from "src/utils/ModifierkeyHelper";
|
import { labelALT, labelCTRL, labelMETA, labelSHIFT } from "src/utils/ModifierkeyHelper";
|
||||||
|
|
||||||
const CJK_FONTS = "CJK Fonts";
|
const CJK_FONTS = "CJK Fonts";
|
||||||
|
declare const PLUGIN_VERSION:string;
|
||||||
|
|
||||||
// English
|
// English
|
||||||
export default {
|
export default {
|
||||||
// main.ts
|
// main.ts
|
||||||
@@ -76,6 +78,7 @@ export default {
|
|||||||
IMPORT_SVG_CONTEXTMENU: "Convert SVG to strokes - with limitations",
|
IMPORT_SVG_CONTEXTMENU: "Convert SVG to strokes - with limitations",
|
||||||
INSERT_MD: "Insert markdown file from vault",
|
INSERT_MD: "Insert markdown file from vault",
|
||||||
INSERT_PDF: "Insert PDF file from vault",
|
INSERT_PDF: "Insert PDF file from vault",
|
||||||
|
INSERT_LAST_ACTIVE_PDF_PAGE_AS_IMAGE: "Insert last active PDF page as image",
|
||||||
UNIVERSAL_ADD_FILE: "Insert ANY file",
|
UNIVERSAL_ADD_FILE: "Insert ANY file",
|
||||||
INSERT_CARD: "Add back-of-note card",
|
INSERT_CARD: "Add back-of-note card",
|
||||||
CONVERT_CARD_TO_FILE: "Move back-of-note card to File",
|
CONVERT_CARD_TO_FILE: "Move back-of-note card to File",
|
||||||
@@ -101,6 +104,9 @@ export default {
|
|||||||
FONTS_LOADED: "Excalidraw: CJK Fonts loaded",
|
FONTS_LOADED: "Excalidraw: CJK Fonts loaded",
|
||||||
FONTS_LOAD_ERROR: "Excalidraw: Could not find CJK Fonts in the assets folder\n",
|
FONTS_LOAD_ERROR: "Excalidraw: Could not find CJK Fonts in the assets folder\n",
|
||||||
|
|
||||||
|
//Prompt.ts
|
||||||
|
SELECT_LINK_TO_OPEN: "Select a link to open",
|
||||||
|
|
||||||
//ExcalidrawView.ts
|
//ExcalidrawView.ts
|
||||||
NO_SEARCH_RESULT: "Didn't find a matching element in the drawing",
|
NO_SEARCH_RESULT: "Didn't find a matching element in the drawing",
|
||||||
FORCE_SAVE_ABORTED: "Force Save aborted because saving is in progress",
|
FORCE_SAVE_ABORTED: "Force Save aborted because saving is in progress",
|
||||||
@@ -355,6 +361,7 @@ FILENAME_HEAD: "Filename",
|
|||||||
DEFAULT_PEN_MODE_DESC:
|
DEFAULT_PEN_MODE_DESC:
|
||||||
"Should pen mode be automatically enabled when opening Excalidraw?",
|
"Should pen mode be automatically enabled when opening Excalidraw?",
|
||||||
DISABLE_DOUBLE_TAP_ERASER_NAME: "Enable double-tap eraser in pen mode",
|
DISABLE_DOUBLE_TAP_ERASER_NAME: "Enable double-tap eraser in pen mode",
|
||||||
|
DISABLE_SINGLE_FINGER_PANNING_NAME: "Enable single-finger panning in pen mode",
|
||||||
SHOW_PEN_MODE_FREEDRAW_CROSSHAIR_NAME: "Show (+) crosshair in pen mode",
|
SHOW_PEN_MODE_FREEDRAW_CROSSHAIR_NAME: "Show (+) crosshair in pen mode",
|
||||||
SHOW_PEN_MODE_FREEDRAW_CROSSHAIR_DESC:
|
SHOW_PEN_MODE_FREEDRAW_CROSSHAIR_DESC:
|
||||||
"Show crosshair in pen mode when using the freedraw tool. <b><u>Toggle ON:</u></b> SHOW <b><u>Toggle OFF:</u></b> HIDE<br>"+
|
"Show crosshair in pen mode when using the freedraw tool. <b><u>Toggle ON:</u></b> SHOW <b><u>Toggle OFF:</u></b> HIDE<br>"+
|
||||||
@@ -954,4 +961,7 @@ FILENAME_HEAD: "Filename",
|
|||||||
IPM_GROUP_PAGES_DESC: "This will group all pages into a single group. This is recommended if you are locking the pages after import, because the group will be easier to unlock later rather than unlocking one by one.",
|
IPM_GROUP_PAGES_DESC: "This will group all pages into a single group. This is recommended if you are locking the pages after import, because the group will be easier to unlock later rather than unlocking one by one.",
|
||||||
IPM_SELECT_PDF: "Please select a PDF file",
|
IPM_SELECT_PDF: "Please select a PDF file",
|
||||||
|
|
||||||
|
//Utils.ts
|
||||||
|
UPDATE_AVAILABLE: `A newer version of Excalidraw is available in Community Plugins.\n\nYou are using ${PLUGIN_VERSION}.\nThe latest is`,
|
||||||
|
ERROR_PNG_TOO_LARGE: "Error exporting PNG - PNG too large, try a smaller resolution",
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import { TAG_AUTOEXPORT, TAG_MDREADINGMODE, TAG_PDFEXPORT } from "src/constants/
|
|||||||
import { labelALT, labelCTRL, labelMETA, labelSHIFT } from "src/utils/ModifierkeyHelper";
|
import { labelALT, labelCTRL, labelMETA, labelSHIFT } from "src/utils/ModifierkeyHelper";
|
||||||
|
|
||||||
const CJK_FONTS = "CJK Fonts";
|
const CJK_FONTS = "CJK Fonts";
|
||||||
|
declare const PLUGIN_VERSION:string;
|
||||||
|
|
||||||
// 简体中文
|
// 简体中文
|
||||||
export default {
|
export default {
|
||||||
// main.ts
|
// main.ts
|
||||||
@@ -76,6 +78,7 @@ export default {
|
|||||||
IMPORT_SVG_CONTEXTMENU: "转换 SVG 到线条 - 有限制",
|
IMPORT_SVG_CONTEXTMENU: "转换 SVG 到线条 - 有限制",
|
||||||
INSERT_MD: "插入 Markdown 文档(以图像形式嵌入)到当前绘图中",
|
INSERT_MD: "插入 Markdown 文档(以图像形式嵌入)到当前绘图中",
|
||||||
INSERT_PDF: "插入 PDF 文档(以图像形式嵌入)到当前绘图中",
|
INSERT_PDF: "插入 PDF 文档(以图像形式嵌入)到当前绘图中",
|
||||||
|
INSERT_LAST_ACTIVE_PDF_PAGE_AS_IMAGE: "将最后激活的 PDF 页面插入为图片",
|
||||||
UNIVERSAL_ADD_FILE: "插入任意文件(以交互形式嵌入,或者以图像形式嵌入)到当前绘图中",
|
UNIVERSAL_ADD_FILE: "插入任意文件(以交互形式嵌入,或者以图像形式嵌入)到当前绘图中",
|
||||||
INSERT_CARD: "插入“背景笔记”卡片",
|
INSERT_CARD: "插入“背景笔记”卡片",
|
||||||
CONVERT_CARD_TO_FILE: "将“背景笔记”卡片保存到文件",
|
CONVERT_CARD_TO_FILE: "将“背景笔记”卡片保存到文件",
|
||||||
@@ -101,6 +104,9 @@ export default {
|
|||||||
FONTS_LOADED : "Excalidraw: CJK 字体已加载" ,
|
FONTS_LOADED : "Excalidraw: CJK 字体已加载" ,
|
||||||
FONTS_LOAD_ERROR : "Excalidraw: 在资源文件夹下找不到 CJK 字体\n" ,
|
FONTS_LOAD_ERROR : "Excalidraw: 在资源文件夹下找不到 CJK 字体\n" ,
|
||||||
|
|
||||||
|
//Prompt.ts
|
||||||
|
SELECT_LINK_TO_OPEN: "选择要打开的链接",
|
||||||
|
|
||||||
//ExcalidrawView.ts
|
//ExcalidrawView.ts
|
||||||
NO_SEARCH_RESULT: "在绘图中未找到匹配的元素",
|
NO_SEARCH_RESULT: "在绘图中未找到匹配的元素",
|
||||||
FORCE_SAVE_ABORTED: "自动保存被中止,因为文件正在保存中",
|
FORCE_SAVE_ABORTED: "自动保存被中止,因为文件正在保存中",
|
||||||
@@ -355,6 +361,7 @@ FILENAME_HEAD: "文件名",
|
|||||||
DEFAULT_PEN_MODE_DESC:
|
DEFAULT_PEN_MODE_DESC:
|
||||||
"打开绘图时,是否自动开启触控笔模式?",
|
"打开绘图时,是否自动开启触控笔模式?",
|
||||||
DISABLE_DOUBLE_TAP_ERASER_NAME: "启用手写模式下的双击橡皮擦功能",
|
DISABLE_DOUBLE_TAP_ERASER_NAME: "启用手写模式下的双击橡皮擦功能",
|
||||||
|
DISABLE_SINGLE_FINGER_PANNING_NAME: "启用手写模式下的单指平移功能",
|
||||||
SHOW_PEN_MODE_FREEDRAW_CROSSHAIR_NAME: "在触控笔模式下显示十字准星(+)",
|
SHOW_PEN_MODE_FREEDRAW_CROSSHAIR_NAME: "在触控笔模式下显示十字准星(+)",
|
||||||
SHOW_PEN_MODE_FREEDRAW_CROSSHAIR_DESC:
|
SHOW_PEN_MODE_FREEDRAW_CROSSHAIR_DESC:
|
||||||
"在触控笔模式下使用涂鸦功能会显示十字准星 <b><u>打开:</u></b> 显示 <b><u>关闭:</u></b> 隐藏<br>"+
|
"在触控笔模式下使用涂鸦功能会显示十字准星 <b><u>打开:</u></b> 显示 <b><u>关闭:</u></b> 隐藏<br>"+
|
||||||
@@ -865,6 +872,7 @@ FILENAME_HEAD: "文件名",
|
|||||||
对此带来的不便,我深表歉意。
|
对此带来的不便,我深表歉意。
|
||||||
</p>
|
</p>
|
||||||
`,
|
`,
|
||||||
|
|
||||||
//ObsidianMenu.tsx
|
//ObsidianMenu.tsx
|
||||||
GOTO_FULLSCREEN: "进入全屏模式",
|
GOTO_FULLSCREEN: "进入全屏模式",
|
||||||
EXIT_FULLSCREEN: "退出全屏模式",
|
EXIT_FULLSCREEN: "退出全屏模式",
|
||||||
@@ -953,4 +961,7 @@ FILENAME_HEAD: "文件名",
|
|||||||
IPM_GROUP_PAGES_DESC: "这将把所有页面建立为一个单独的组。如果您在导入后锁定页面,建议使用此方法,因为这样可以更方便地解锁整个组,而不是逐个解锁。",
|
IPM_GROUP_PAGES_DESC: "这将把所有页面建立为一个单独的组。如果您在导入后锁定页面,建议使用此方法,因为这样可以更方便地解锁整个组,而不是逐个解锁。",
|
||||||
IPM_SELECT_PDF: "请选择一个 PDF 文件",
|
IPM_SELECT_PDF: "请选择一个 PDF 文件",
|
||||||
|
|
||||||
};
|
//Utils.ts
|
||||||
|
UPDATE_AVAILABLE: `Excalidraw 的新版本已在社区插件中可用。\n\n您正在使用 ${PLUGIN_VERSION}。\n最新版本是`,
|
||||||
|
ERROR_PNG_TOO_LARGE: "导出 PNG 时出错 - PNG 文件过大,请尝试较小的分辨率",
|
||||||
|
};
|
||||||
|
|||||||
908
src/main.ts
908
src/main.ts
File diff suppressed because it is too large
Load Diff
@@ -85,6 +85,7 @@ export interface ExcalidrawSettings {
|
|||||||
defaultMode: string;
|
defaultMode: string;
|
||||||
defaultPenMode: "never" | "mobile" | "always";
|
defaultPenMode: "never" | "mobile" | "always";
|
||||||
penModeDoubleTapEraser: boolean;
|
penModeDoubleTapEraser: boolean;
|
||||||
|
penModeSingleFingerPanning: boolean;
|
||||||
penModeCrosshairVisible: boolean;
|
penModeCrosshairVisible: boolean;
|
||||||
renderImageInMarkdownReadingMode: boolean,
|
renderImageInMarkdownReadingMode: boolean,
|
||||||
renderImageInHoverPreviewForMDNotes: boolean,
|
renderImageInHoverPreviewForMDNotes: boolean,
|
||||||
@@ -261,6 +262,7 @@ export const DEFAULT_SETTINGS: ExcalidrawSettings = {
|
|||||||
defaultMode: "normal",
|
defaultMode: "normal",
|
||||||
defaultPenMode: "never",
|
defaultPenMode: "never",
|
||||||
penModeDoubleTapEraser: true,
|
penModeDoubleTapEraser: true,
|
||||||
|
penModeSingleFingerPanning: true,
|
||||||
penModeCrosshairVisible: true,
|
penModeCrosshairVisible: true,
|
||||||
renderImageInMarkdownReadingMode: false,
|
renderImageInMarkdownReadingMode: false,
|
||||||
renderImageInHoverPreviewForMDNotes: false,
|
renderImageInHoverPreviewForMDNotes: false,
|
||||||
@@ -1066,6 +1068,17 @@ export class ExcalidrawSettingTab extends PluginSettingTab {
|
|||||||
this.applySettingsUpdate();
|
this.applySettingsUpdate();
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
new Setting(detailsEl)
|
||||||
|
.setName(t("DISABLE_SINGLE_FINGER_PANNING_NAME"))
|
||||||
|
.addToggle((toggle) =>
|
||||||
|
toggle
|
||||||
|
.setValue(this.plugin.settings.penModeSingleFingerPanning)
|
||||||
|
.onChange(async (value) => {
|
||||||
|
this.plugin.settings.penModeSingleFingerPanning = value;
|
||||||
|
this.applySettingsUpdate();
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
new Setting(detailsEl)
|
new Setting(detailsEl)
|
||||||
.setName(t("SHOW_PEN_MODE_FREEDRAW_CROSSHAIR_NAME"))
|
.setName(t("SHOW_PEN_MODE_FREEDRAW_CROSSHAIR_NAME"))
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ export async function insertImageToView(
|
|||||||
file: TFile | string,
|
file: TFile | string,
|
||||||
scale?: boolean,
|
scale?: boolean,
|
||||||
shouldInsertToView: boolean = true,
|
shouldInsertToView: boolean = true,
|
||||||
|
repositionToCursor: boolean = false,
|
||||||
):Promise<string> {
|
):Promise<string> {
|
||||||
if(shouldInsertToView) {ea.clear();}
|
if(shouldInsertToView) {ea.clear();}
|
||||||
ea.style.strokeColor = "transparent";
|
ea.style.strokeColor = "transparent";
|
||||||
@@ -31,7 +32,7 @@ export async function insertImageToView(
|
|||||||
file,
|
file,
|
||||||
scale,
|
scale,
|
||||||
);
|
);
|
||||||
if(shouldInsertToView) {await ea.addElementsToView(false, true, true);}
|
if(shouldInsertToView) {await ea.addElementsToView(repositionToCursor, true, true);}
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
37
src/utils/PDFUtils.ts
Normal file
37
src/utils/PDFUtils.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
//for future use, not used currently
|
||||||
|
|
||||||
|
import { ImageCrop } from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||||
|
|
||||||
|
export function getPDFCropRect (props: {
|
||||||
|
scale: number,
|
||||||
|
link: string,
|
||||||
|
naturalHeight: number,
|
||||||
|
naturalWidth: number,
|
||||||
|
}) : ImageCrop | null {
|
||||||
|
const rectVal = props.link.match(/&rect=(\d*),(\d*),(\d*),(\d*)/);
|
||||||
|
if (!rectVal || rectVal.length !== 5) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const R0 = parseInt(rectVal[1]);
|
||||||
|
const R1 = parseInt(rectVal[2]);
|
||||||
|
const R2 = parseInt(rectVal[3]);
|
||||||
|
const R3 = parseInt(rectVal[4]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
x: R0 * props.scale,
|
||||||
|
y: (props.naturalHeight/props.scale - R3) * props.scale,
|
||||||
|
width: (R2 - R0) * props.scale,
|
||||||
|
height: (R3 - R1) * props.scale,
|
||||||
|
naturalWidth: props.naturalWidth,
|
||||||
|
naturalHeight: props.naturalHeight,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPDFRect(elCrop: ImageCrop, scale: number): string {
|
||||||
|
const R0 = elCrop.x / scale;
|
||||||
|
const R2 = elCrop.width / scale + R0;
|
||||||
|
const R3 = (elCrop.naturalHeight - elCrop.y) / scale;
|
||||||
|
const R1 = R3 - elCrop.height / scale;
|
||||||
|
return `&rect=${Math.round(R0)},${Math.round(R1)},${Math.round(R2)},${Math.round(R3)}`;
|
||||||
|
}
|
||||||
@@ -18,7 +18,7 @@ import {
|
|||||||
getContainerElement,
|
getContainerElement,
|
||||||
} from "../constants/constants";
|
} from "../constants/constants";
|
||||||
import ExcalidrawPlugin from "../main";
|
import ExcalidrawPlugin from "../main";
|
||||||
import { ExcalidrawElement, ExcalidrawTextElement } from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
import { ExcalidrawElement, ExcalidrawTextElement, ImageCrop } from "@zsviczian/excalidraw/types/excalidraw/element/types";
|
||||||
import { ExportSettings } from "../ExcalidrawView";
|
import { ExportSettings } from "../ExcalidrawView";
|
||||||
import { getDataURLFromURL, getIMGFilename, getMimeType, getURLImageExtension } from "./FileUtils";
|
import { getDataURLFromURL, getIMGFilename, getMimeType, getURLImageExtension } from "./FileUtils";
|
||||||
import { generateEmbeddableLink } from "./CustomEmbeddableUtils";
|
import { generateEmbeddableLink } from "./CustomEmbeddableUtils";
|
||||||
@@ -30,6 +30,8 @@ import { CropImage } from "./CropImage";
|
|||||||
import opentype from 'opentype.js';
|
import opentype from 'opentype.js';
|
||||||
import { runCompressionWorker } from "src/workers/compression-worker";
|
import { runCompressionWorker } from "src/workers/compression-worker";
|
||||||
import Pool from "es6-promise-pool";
|
import Pool from "es6-promise-pool";
|
||||||
|
import { FileData } from "src/EmbeddedFileLoader";
|
||||||
|
import { t } from "src/lang/helpers";
|
||||||
|
|
||||||
declare const PLUGIN_VERSION:string;
|
declare const PLUGIN_VERSION:string;
|
||||||
declare var LZString: any;
|
declare var LZString: any;
|
||||||
@@ -76,7 +78,7 @@ export async function checkExcalidrawVersion() {
|
|||||||
|
|
||||||
if (isVersionNewerThanOther(latestVersion,PLUGIN_VERSION)) {
|
if (isVersionNewerThanOther(latestVersion,PLUGIN_VERSION)) {
|
||||||
new Notice(
|
new Notice(
|
||||||
`A newer version of Excalidraw is available in Community Plugins.\n\nYou are using ${PLUGIN_VERSION}.\nThe latest is ${latestVersion}`,
|
t("UPDATE_AVAILABLE") + ` ${latestVersion}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -219,15 +221,6 @@ export async function getFontDataURL (
|
|||||||
const split = dataURL.split(";base64,", 2);
|
const split = dataURL.split(";base64,", 2);
|
||||||
dataURL = `${split[0]};charset=utf-8;base64,${split[1]}`;
|
dataURL = `${split[0]};charset=utf-8;base64,${split[1]}`;
|
||||||
fontDef = ` @font-face {font-family: "${fontName}";src: url("${dataURL}") format("${format}")}`;
|
fontDef = ` @font-face {font-family: "${fontName}";src: url("${dataURL}") format("${format}")}`;
|
||||||
/* const mimeType = f.extension.startsWith("woff")
|
|
||||||
? "application/font-woff"
|
|
||||||
: "font/truetype";
|
|
||||||
fontName = name ?? f.basename;
|
|
||||||
dataURL = await getDataURL(ab, mimeType);
|
|
||||||
fontDef = ` @font-face {font-family: "${fontName}";src: url("${dataURL}")}`;
|
|
||||||
//format("${f.extension === "ttf" ? "truetype" : f.extension}");}`;
|
|
||||||
const split = fontDef.split(";base64,", 2);
|
|
||||||
fontDef = `${split[0]};charset=utf-8;base64,${split[1]}`;*/
|
|
||||||
}
|
}
|
||||||
return { fontDef, fontName, dataURL };
|
return { fontDef, fontName, dataURL };
|
||||||
};
|
};
|
||||||
@@ -374,7 +367,7 @@ export async function getPNG (
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
new Notice("Error exporting PNG - PNG too large, try a smaller resolution");
|
new Notice(t("ERROR_PNG_TOO_LARGE"));
|
||||||
errorlog({ where: "Utils.getPNG", error });
|
errorlog({ where: "Utils.getPNG", error });
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -428,14 +421,14 @@ export function addAppendUpdateCustomData (el: Mutable<ExcalidrawElement>, newDa
|
|||||||
|
|
||||||
export function scaleLoadedImage (
|
export function scaleLoadedImage (
|
||||||
scene: any,
|
scene: any,
|
||||||
files: any
|
files: FileData[],
|
||||||
): { dirty: boolean; scene: any } {
|
): { dirty: boolean; scene: any } {
|
||||||
let dirty = false;
|
let dirty = false;
|
||||||
if (!files || !scene) {
|
if (!files || !scene) {
|
||||||
return { dirty, scene };
|
return { dirty, scene };
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const f of files.filter((f:any)=>{
|
for (const img of files.filter((f:any)=>{
|
||||||
if(!Boolean(EXCALIDRAW_PLUGIN)) return true; //this should never happen
|
if(!Boolean(EXCALIDRAW_PLUGIN)) return true; //this should never happen
|
||||||
const ef = EXCALIDRAW_PLUGIN.filesMaster.get(f.id);
|
const ef = EXCALIDRAW_PLUGIN.filesMaster.get(f.id);
|
||||||
if(!ef) return true; //mermaid SVG or equation
|
if(!ef) return true; //mermaid SVG or equation
|
||||||
@@ -443,34 +436,81 @@ export function scaleLoadedImage (
|
|||||||
if(!file || (file instanceof TFolder)) return false;
|
if(!file || (file instanceof TFolder)) return false;
|
||||||
return (file as TFile).extension==="md" || EXCALIDRAW_PLUGIN.isExcalidrawFile(file as TFile)
|
return (file as TFile).extension==="md" || EXCALIDRAW_PLUGIN.isExcalidrawFile(file as TFile)
|
||||||
})) {
|
})) {
|
||||||
const [w_image, h_image] = [f.size.width, f.size.height];
|
const [imgWidth, imgHeight] = [img.size.width, img.size.height];
|
||||||
const imageAspectRatio = f.size.width / f.size.height;
|
const imgAspectRatio = imgWidth / imgHeight;
|
||||||
|
|
||||||
scene.elements
|
scene.elements
|
||||||
.filter((e: any) => e.type === "image" && e.fileId === f.id)
|
.filter((e: any) => e.type === "image" && e.fileId === img.id)
|
||||||
.forEach((el: any) => {
|
.forEach((el: any) => {
|
||||||
const [w_old, h_old] = [el.width, el.height];
|
const [elWidth, elHeight] = [el.width, el.height];
|
||||||
if(el.customData?.isAnchored && f.shouldScale || !el.customData?.isAnchored && !f.shouldScale) {
|
const maintainArea = img.shouldScale; //true if image should maintain its area, false if image should display at 100% its size
|
||||||
addAppendUpdateCustomData(el, f.shouldScale ? {isAnchored: false} : {isAnchored: true});
|
const elCrop: ImageCrop = el.crop;
|
||||||
|
const isCropped = Boolean(elCrop);
|
||||||
|
|
||||||
|
|
||||||
|
if(el.customData?.isAnchored && img.shouldScale || !el.customData?.isAnchored && !img.shouldScale) {
|
||||||
|
//customData.isAnchored is used by the Excalidraw component to disable resizing of anchored images
|
||||||
|
//customData.isAnchored has no direct role in the calculation in the scaleLoadedImage function
|
||||||
|
addAppendUpdateCustomData(el, img.shouldScale ? {isAnchored: false} : {isAnchored: true});
|
||||||
dirty = true;
|
dirty = true;
|
||||||
}
|
}
|
||||||
if(f.shouldScale) {
|
|
||||||
const elementAspectRatio = w_old / h_old;
|
if(isCropped) {
|
||||||
if (imageAspectRatio !== elementAspectRatio) {
|
if(elCrop.naturalWidth !== imgWidth || elCrop.naturalHeight !== imgHeight) {
|
||||||
dirty = true;
|
dirty = true;
|
||||||
const h_new = Math.sqrt((w_old * h_old * h_image) / w_image);
|
//the current crop area may be maintained, need to calculate the new crop.x, crop.y offsets
|
||||||
const w_new = Math.sqrt((w_old * h_old * w_image) / h_image);
|
el.crop.y += (imgHeight - elCrop.naturalHeight)/2;
|
||||||
el.height = h_new;
|
if(imgWidth < elCrop.width) {
|
||||||
el.width = w_new;
|
const scaleX = el.width / elCrop.width;
|
||||||
el.y += (h_old - h_new) / 2;
|
el.crop.x = 0;
|
||||||
el.x += (w_old - w_new) / 2;
|
el.crop.width = imgWidth;
|
||||||
|
el.width = imgWidth * scaleX;
|
||||||
|
} else {
|
||||||
|
const ratioX = elCrop.x / (elCrop.naturalWidth - elCrop.x - elCrop.width);
|
||||||
|
const gapX = imgWidth - elCrop.width;
|
||||||
|
el.crop.x = ratioX * gapX / (1 + ratioX);
|
||||||
|
if(el.crop.x + elCrop.width > imgWidth) {
|
||||||
|
el.crop.x = (imgWidth - elCrop.width) / 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(imgHeight < elCrop.height) {
|
||||||
|
const scaleY = el.height / elCrop.height;
|
||||||
|
el.crop.y = 0;
|
||||||
|
el.crop.height = imgHeight;
|
||||||
|
el.height = imgHeight * scaleY;
|
||||||
|
} else {
|
||||||
|
const ratioY = elCrop.y / (elCrop.naturalHeight - elCrop.y - elCrop.height);
|
||||||
|
const gapY = imgHeight - elCrop.height;
|
||||||
|
el.crop.y = ratioY * gapY / (1 + ratioY);
|
||||||
|
if(el.crop.y + elCrop.height > imgHeight) {
|
||||||
|
el.crop.y = (imgHeight - elCrop.height)/2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
el.crop.naturalWidth = imgWidth;
|
||||||
|
el.crop.naturalHeight = imgHeight;
|
||||||
|
const noCrop = el.crop.width === imgWidth && el.crop.height === imgHeight;
|
||||||
|
if(noCrop) {
|
||||||
|
el.crop = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else if(maintainArea) {
|
||||||
if(w_old !== w_image || h_old !== h_image) {
|
const elAspectRatio = elWidth / elHeight;
|
||||||
|
if (imgAspectRatio !== elAspectRatio) {
|
||||||
dirty = true;
|
dirty = true;
|
||||||
el.height = h_image;
|
const elNewHeight = Math.sqrt((elWidth * elHeight * imgHeight) / imgWidth);
|
||||||
el.width = w_image;
|
const elNewWidth = Math.sqrt((elWidth * elHeight * imgWidth) / imgHeight);
|
||||||
el.y += (h_old - h_image) / 2;
|
el.height = elNewHeight;
|
||||||
el.x += (w_old - w_image) / 2;
|
el.width = elNewWidth;
|
||||||
|
el.y += (elHeight - elNewHeight) / 2;
|
||||||
|
el.x += (elWidth - elNewWidth) / 2;
|
||||||
|
}
|
||||||
|
} else { //100% size
|
||||||
|
if(elWidth !== imgWidth || elHeight !== imgHeight) {
|
||||||
|
dirty = true;
|
||||||
|
el.height = imgHeight;
|
||||||
|
el.width = imgWidth;
|
||||||
|
el.y += (elHeight - imgHeight) / 2;
|
||||||
|
el.x += (elWidth - imgWidth) / 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -725,6 +765,8 @@ export function getPNGScale (plugin: ExcalidrawPlugin, file: TFile): number {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function isVersionNewerThanOther (version: string, otherVersion: string): boolean {
|
export function isVersionNewerThanOther (version: string, otherVersion: string): boolean {
|
||||||
|
if(!version || !otherVersion) return true;
|
||||||
|
|
||||||
const v = version.match(/(\d*)\.(\d*)\.(\d*)/);
|
const v = version.match(/(\d*)\.(\d*)\.(\d*)/);
|
||||||
const o = otherVersion.match(/(\d*)\.(\d*)\.(\d*)/);
|
const o = otherVersion.match(/(\d*)\.(\d*)\.(\d*)/);
|
||||||
|
|
||||||
|
|||||||
@@ -346,7 +346,7 @@ label.color-input-container > input {
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.excalidraw-settings input:not([type="color"]) {
|
.excalidraw-settings input[type="text"] {
|
||||||
min-width: 10em;
|
min-width: 10em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user