refactor(deps): use webpack import for canvas

This commit is contained in:
Elian Doran 2025-01-18 11:09:57 +02:00
parent ab65913e52
commit 7d3f506efb
No known key found for this signature in database
10 changed files with 144 additions and 159 deletions

View File

@ -73,7 +73,6 @@ const copy = async () => {
} }
const nodeModulesFolder = [ const nodeModulesFolder = [
"node_modules/@excalidraw/excalidraw/dist/",
"node_modules/katex/dist/", "node_modules/katex/dist/",
"node_modules/dayjs/", "node_modules/dayjs/",
"node_modules/boxicons/css/", "node_modules/boxicons/css/",

View File

@ -49,7 +49,6 @@ cp "$script_dir/../build/electron-main.js" "$DIR"
if [[ -d "$DIR"/node_modules ]]; then if [[ -d "$DIR"/node_modules ]]; then
# cleanup of useless files in dependencies # cleanup of useless files in dependencies
for d in 'image-q/demo' \ for d in 'image-q/demo' \
'@excalidraw/excalidraw/dist/excalidraw-assets-dev' '@excalidraw/excalidraw/dist/excalidraw.development.js' '@excalidraw/excalidraw/dist/excalidraw-with-preact.development.js' \
'mermaid/dist/mermaid.js' \ 'mermaid/dist/mermaid.js' \
'boxicons/svg' 'boxicons/node_modules/react'/* \ 'boxicons/svg' 'boxicons/node_modules/react'/* \
'@jimp/plugin-print/fonts' 'jimp/browser' 'jimp/fonts'; do '@jimp/plugin-print/fonts' 'jimp/browser' 'jimp/fonts'; do

33
package-lock.json generated
View File

@ -16,6 +16,7 @@
"@mermaid-js/layout-elk": "0.1.7", "@mermaid-js/layout-elk": "0.1.7",
"@mind-elixir/node-menu": "1.0.3", "@mind-elixir/node-menu": "1.0.3",
"@triliumnext/express-partial-content": "1.0.1", "@triliumnext/express-partial-content": "1.0.1",
"@types/react-dom": "18.3.1",
"archiver": "7.0.1", "archiver": "7.0.1",
"async-mutex": "0.5.0", "async-mutex": "0.5.0",
"autocomplete.js": "0.38.1", "autocomplete.js": "0.38.1",
@ -133,6 +134,7 @@
"@types/mime-types": "2.1.4", "@types/mime-types": "2.1.4",
"@types/multer": "1.4.12", "@types/multer": "1.4.12",
"@types/node": "22.10.7", "@types/node": "22.10.7",
"@types/react": "18.3.1",
"@types/safe-compare": "1.1.2", "@types/safe-compare": "1.1.2",
"@types/sanitize-html": "2.13.0", "@types/sanitize-html": "2.13.0",
"@types/sax": "1.2.7", "@types/sax": "1.2.7",
@ -3912,6 +3914,12 @@
"undici-types": "~6.20.0" "undici-types": "~6.20.0"
} }
}, },
"node_modules/@types/prop-types": {
"version": "15.7.14",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz",
"integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==",
"license": "MIT"
},
"node_modules/@types/qs": { "node_modules/@types/qs": {
"version": "6.9.17", "version": "6.9.17",
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.17.tgz", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.17.tgz",
@ -3926,6 +3934,25 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/react": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.1.tgz",
"integrity": "sha512-V0kuGBX3+prX+DQ/7r2qsv1NsdfnCLnTgnRJ1pYnxykBhGMz+qj+box5lq7XsO5mtZsBqpjwwTu/7wszPfMBcw==",
"license": "MIT",
"dependencies": {
"@types/prop-types": "*",
"csstype": "^3.0.2"
}
},
"node_modules/@types/react-dom": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.1.tgz",
"integrity": "sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ==",
"license": "MIT",
"dependencies": {
"@types/react": "*"
}
},
"node_modules/@types/readdir-glob": { "node_modules/@types/readdir-glob": {
"version": "1.1.5", "version": "1.1.5",
"resolved": "https://registry.npmjs.org/@types/readdir-glob/-/readdir-glob-1.1.5.tgz", "resolved": "https://registry.npmjs.org/@types/readdir-glob/-/readdir-glob-1.1.5.tgz",
@ -6652,6 +6679,12 @@
"node": ">=18" "node": ">=18"
} }
}, },
"node_modules/csstype": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"license": "MIT"
},
"node_modules/cytoscape": { "node_modules/cytoscape": {
"version": "3.30.4", "version": "3.30.4",
"resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.30.4.tgz", "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.30.4.tgz",

View File

@ -61,6 +61,7 @@
"@mermaid-js/layout-elk": "0.1.7", "@mermaid-js/layout-elk": "0.1.7",
"@mind-elixir/node-menu": "1.0.3", "@mind-elixir/node-menu": "1.0.3",
"@triliumnext/express-partial-content": "1.0.1", "@triliumnext/express-partial-content": "1.0.1",
"@types/react-dom": "18.3.1",
"archiver": "7.0.1", "archiver": "7.0.1",
"async-mutex": "0.5.0", "async-mutex": "0.5.0",
"autocomplete.js": "0.38.1", "autocomplete.js": "0.38.1",
@ -175,6 +176,7 @@
"@types/mime-types": "2.1.4", "@types/mime-types": "2.1.4",
"@types/multer": "1.4.12", "@types/multer": "1.4.12",
"@types/node": "22.10.7", "@types/node": "22.10.7",
"@types/react": "18.3.1",
"@types/safe-compare": "1.1.2", "@types/safe-compare": "1.1.2",
"@types/sanitize-html": "2.13.0", "@types/sanitize-html": "2.13.0",
"@types/sax": "1.2.7", "@types/sax": "1.2.7",

View File

@ -72,10 +72,6 @@ const MERMAID: Library = {
js: ["node_modules/mermaid/dist/mermaid.min.js"] js: ["node_modules/mermaid/dist/mermaid.min.js"]
}; };
const EXCALIDRAW: Library = {
js: ["node_modules/react/umd/react.production.min.js", "node_modules/react-dom/umd/react-dom.production.min.js", "node_modules/@excalidraw/excalidraw/dist/excalidraw.production.min.js"]
};
const MARKJS: Library = { const MARKJS: Library = {
js: ["node_modules/mark.js/dist/jquery.mark.es6.min.js"] js: ["node_modules/mark.js/dist/jquery.mark.es6.min.js"]
}; };
@ -198,7 +194,6 @@ export default {
KATEX, KATEX,
WHEEL_ZOOM, WHEEL_ZOOM,
MERMAID, MERMAID,
EXCALIDRAW,
MARKJS, MARKJS,
I18NEXT, I18NEXT,
HIGHLIGHT_JS HIGHLIGHT_JS

View File

@ -234,7 +234,7 @@ function goToLink(evt: MouseEvent | JQuery.ClickEvent) {
return goToLinkExt(evt, hrefLink, $link); return goToLinkExt(evt, hrefLink, $link);
} }
function goToLinkExt(evt: MouseEvent | JQuery.ClickEvent, hrefLink: string | undefined, $link: JQuery<HTMLElement> | null) { function goToLinkExt(evt: MouseEvent | JQuery.ClickEvent | React.PointerEvent<HTMLCanvasElement>, hrefLink: string | undefined, $link: JQuery<HTMLElement> | null) {
if (hrefLink?.startsWith("data:")) { if (hrefLink?.startsWith("data:")) {
return true; return true;
} }
@ -249,13 +249,10 @@ function goToLinkExt(evt: MouseEvent | JQuery.ClickEvent, hrefLink: string | und
const { notePath, viewScope } = parseNavigationStateFromUrl(hrefLink); const { notePath, viewScope } = parseNavigationStateFromUrl(hrefLink);
const ctrlKey = utils.isCtrlKey(evt); const ctrlKey = utils.isCtrlKey(evt);
const isLeftClick = evt.which === 1; const isLeftClick = ("which" in evt && evt.which === 1);
const isMiddleClick = evt.which === 2; const isMiddleClick = ("which" in evt && evt.which === 2);
const openInNewTab = (isLeftClick && ctrlKey) || isMiddleClick; const openInNewTab = (isLeftClick && ctrlKey) || isMiddleClick;
const leftClick = evt.which === 1;
const middleClick = evt.which === 2;
if (notePath) { if (notePath) {
if (openInNewTab) { if (openInNewTab) {
appContext.tabManager.openTabWithNoteWithHoisting(notePath, { viewScope }); appContext.tabManager.openTabWithNoteWithHoisting(notePath, { viewScope });
@ -276,7 +273,7 @@ function goToLinkExt(evt: MouseEvent | JQuery.ClickEvent, hrefLink: string | und
const withinEditLink = $link?.hasClass("ck-link-actions__preview"); const withinEditLink = $link?.hasClass("ck-link-actions__preview");
const outsideOfCKEditor = !$link || $link.closest("[contenteditable]").length === 0; const outsideOfCKEditor = !$link || $link.closest("[contenteditable]").length === 0;
if (openInNewTab || (withinEditLink && (leftClick || middleClick)) || (outsideOfCKEditor && (leftClick || middleClick))) { if (openInNewTab || (withinEditLink && (isLeftClick || isMiddleClick)) || (outsideOfCKEditor && (isLeftClick || isMiddleClick))) {
if (hrefLink.toLowerCase().startsWith("http") || hrefLink.startsWith("api/")) { if (hrefLink.toLowerCase().startsWith("http") || hrefLink.startsWith("api/")) {
window.open(hrefLink, "_blank"); window.open(hrefLink, "_blank");
} else if ((hrefLink.toLowerCase().startsWith("file:") || hrefLink.toLowerCase().startsWith("geo:")) && utils.isElectron()) { } else if ((hrefLink.toLowerCase().startsWith("file:") || hrefLink.toLowerCase().startsWith("geo:")) && utils.isElectron()) {

View File

@ -97,7 +97,7 @@ function isMac() {
return navigator.platform.indexOf("Mac") > -1; return navigator.platform.indexOf("Mac") > -1;
} }
function isCtrlKey(evt: KeyboardEvent | MouseEvent | JQuery.ClickEvent | JQuery.ContextMenuEvent | JQuery.TriggeredEvent) { function isCtrlKey(evt: KeyboardEvent | MouseEvent | JQuery.ClickEvent | JQuery.ContextMenuEvent | JQuery.TriggeredEvent | React.PointerEvent<HTMLCanvasElement>) {
return (!isMac() && evt.ctrlKey) || (isMac() && evt.metaKey); return (!isMac() && evt.ctrlKey) || (isMac() && evt.metaKey);
} }

View File

@ -54,72 +54,6 @@ declare global {
process?: ElectronProcess; process?: ElectronProcess;
glob?: CustomGlobals; glob?: CustomGlobals;
React: {
createElement(any, any?, any?);
Fragment: any;
useState({
width: undefined,
height: undefined
});
useRef(ref: null);
useEffect(cb: () => void, args: unknown[]);
useCallback(cb: (el, ev) => void, args: unknown[]);
};
ReactDOM: {
unmountComponentAtNode(el: HTMLElement);
createRoot(el: HTMLElement);
}
ExcalidrawLib: {
getSceneVersion(el: unknown[]): number;
exportToSvg(opts: {
elements: ExcalidrawElement[],
appState: ExcalidrawAppState,
exportPadding: number,
metadata: string,
files: ExcalidrawElement[]
}): Promise<HTMLElement>;
updateScene,
Excalidraw: unknown
}
EXCALIDRAW_ASSET_PATH: string;
}
interface ExcalidrawApi {
getSceneElements(): ExcalidrawElement[];
getAppState(): ExcalidrawAppState;
getFiles(): ExcalidrawElement[];
updateScene(scene: ExcalidrawScene);
updateLibrary(opts: { libraryItems?: ExcalidrawLibrary[], merge: boolean }): Promise<ExcalidrawLibrary[]>;
addFiles(files: ExcalidrawElement[]);
history: {
clear();
}
}
interface ExcalidrawElement {
fileId: number;
}
interface ExcalidrawLibrary {
id: string;
name: string;
}
interface ExcalidrawScene {
elements: unknown[];
appState: ExcalidrawAppState;
collaborators: unknown[];
}
interface ExcalidrawAppState {
scrollX?: number;
scrollY?: number;
zoom?: number;
theme?: string;
width?: number;
height?: number;
offsetLeft?: number;
offsetTop?: number;
} }
interface AutoCompleteConfig { interface AutoCompleteConfig {

View File

@ -1,9 +1,14 @@
import libraryLoader from "../../services/library_loader.js";
import TypeWidget from "./type_widget.js"; import TypeWidget from "./type_widget.js";
import utils from "../../services/utils.js"; import utils from "../../services/utils.js";
import linkService from "../../services/link.js"; import linkService from "../../services/link.js";
import server from "../../services/server.js"; import server from "../../services/server.js";
import type FNote from "../../entities/fnote.js"; import type FNote from "../../entities/fnote.js";
import type { default as ExcalidrawLib } from "@excalidraw/excalidraw";
import type { ExcalidrawElement, Theme } from "@excalidraw/excalidraw/types/element/types.js";
import type { AppState, BinaryFileData, ExcalidrawImperativeAPI, ExcalidrawProps, LibraryItem, SceneData } from "@excalidraw/excalidraw/types/types.js";
import type { JSX } from "react";
import type React from "react";
const TPL = ` const TPL = `
<div class="canvas-widget note-detail-canvas note-detail-printable note-detail"> <div class="canvas-widget note-detail-canvas note-detail-printable note-detail">
<style> <style>
@ -54,8 +59,8 @@ const TPL = `
interface CanvasContent { interface CanvasContent {
elements: ExcalidrawElement[], elements: ExcalidrawElement[],
files: ExcalidrawElement[], files: BinaryFileData[],
appState: ExcalidrawAppState appState: Partial<AppState>
} }
interface AttachmentMetadata { interface AttachmentMetadata {
@ -114,13 +119,12 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
private currentNoteId: string; private currentNoteId: string;
private currentSceneVersion: number; private currentSceneVersion: number;
private libraryChanged: boolean; private libraryChanged: boolean;
private librarycache: ExcalidrawLibrary[]; private librarycache: LibraryItem[];
private attachmentMetadata: AttachmentMetadata[]; private attachmentMetadata: AttachmentMetadata[];
private themeStyle!: string; private themeStyle!: Theme;
private excalidrawApi!: ExcalidrawApi; private excalidrawLib!: typeof ExcalidrawLib;
private excalidrawWrapperRef!: { private excalidrawApi!: ExcalidrawImperativeAPI;
current: HTMLElement private excalidrawWrapperRef!: React.RefObject<HTMLElement | null>;
};
private $render!: JQuery<HTMLElement>; private $render!: JQuery<HTMLElement>;
private reactHandlers!: JQuery<HTMLElement>; private reactHandlers!: JQuery<HTMLElement>;
@ -133,7 +137,8 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
this.SCENE_VERSION_ERROR = -2; // -2 indicates error this.SCENE_VERSION_ERROR = -2; // -2 indicates error
// ensure that assets are loaded from trilium // ensure that assets are loaded from trilium
window.EXCALIDRAW_ASSET_PATH = `${window.location.origin}/node_modules/@excalidraw/excalidraw/dist/`; // TODO:
(window as any).EXCALIDRAW_ASSET_PATH = `${window.location.origin}/node_modules/@excalidraw/excalidraw/dist/`;
// temporary vars // temporary vars
this.currentNoteId = ""; this.currentNoteId = "";
@ -169,24 +174,39 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
this.$widget.toggleClass("full-height", true); this.$widget.toggleClass("full-height", true);
this.$render = this.$widget.find(".canvas-render"); this.$render = this.$widget.find(".canvas-render");
const documentStyle = window.getComputedStyle(document.documentElement); const documentStyle = window.getComputedStyle(document.documentElement);
this.themeStyle = documentStyle.getPropertyValue("--theme-style")?.trim(); this.themeStyle = documentStyle.getPropertyValue("--theme-style")?.trim() as Theme;
libraryLoader.requireLibrary(libraryLoader.EXCALIDRAW).then(() => { this.#init();
const React = window.React;
const ReactDOM = window.ReactDOM;
const renderElement = this.$render.get(0);
if (!renderElement) {
throw new Error("Unable to find element to render.");
}
ReactDOM.unmountComponentAtNode(renderElement);
const root = ReactDOM.createRoot(renderElement);
root.render(React.createElement(() => this.createExcalidrawReactApp()));
});
return this.$widget; return this.$widget;
} }
async #init() {
const renderElement = this.$render.get(0);
if (!renderElement) {
throw new Error("Unable to find element to render.");
}
// See https://github.com/excalidraw/excalidraw/issues/7899.
if (!window.process) {
(window.process as any) = {};
}
if (!window.process.env) {
window.process.env = {};
}
(window.process.env as any).PREACT = false;
const excalidraw = (await import("@excalidraw/excalidraw"));
this.excalidrawLib = excalidraw;
const { unmountComponentAtNode } = await import("react-dom");
const { createRoot } = await import("react-dom/client");
const React = (await import("react")).default;
unmountComponentAtNode(renderElement);
const root = createRoot(renderElement);
root.render(React.createElement(() => this.createExcalidrawReactApp(React, excalidraw.Excalidraw)));
}
/** /**
* called to populate the widget container with the note content * called to populate the widget container with the note content
*/ */
@ -215,15 +235,15 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
* newly instantiated? * newly instantiated?
*/ */
if (!blob?.content?.trim()) { if (!blob?.content?.trim()) {
const sceneData = { const sceneData: SceneData = {
elements: [], elements: [],
appState: { appState: {
theme: this.themeStyle theme: this.themeStyle
}, }
collaborators: []
}; };
this.excalidrawApi.updateScene(sceneData); // TODO: Props mismatch.
this.excalidrawApi.updateScene(sceneData as any);
} else if (blob.content) { } else if (blob.content) {
let content: CanvasContent; let content: CanvasContent;
@ -240,26 +260,28 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
}; };
} }
const { elements, files, appState = {} } = content; const { elements, files } = content;
const appState: Partial<AppState> = content.appState ?? {};
appState.theme = this.themeStyle; appState.theme = this.themeStyle;
const boundingClientRect = this.excalidrawWrapperRef.current.getBoundingClientRect(); if (this.excalidrawWrapperRef.current) {
appState.width = boundingClientRect.width; const boundingClientRect = this.excalidrawWrapperRef.current.getBoundingClientRect();
appState.height = boundingClientRect.height; appState.width = boundingClientRect.width;
appState.offsetLeft = boundingClientRect.left; appState.height = boundingClientRect.height;
appState.offsetTop = boundingClientRect.top; appState.offsetLeft = boundingClientRect.left;
appState.offsetTop = boundingClientRect.top;
}
const sceneData: ExcalidrawScene = { const sceneData: SceneData = {
elements, elements,
appState, appState
collaborators: []
}; };
// files are expected in an array when loading. they are stored as a key-index object // files are expected in an array when loading. they are stored as a key-index object
// see example for loading here: // see example for loading here:
// https://github.com/excalidraw/excalidraw/blob/c5a7723185f6ca05e0ceb0b0d45c4e3fbcb81b2a/src/packages/excalidraw/example/App.js#L68 // https://github.com/excalidraw/excalidraw/blob/c5a7723185f6ca05e0ceb0b0d45c4e3fbcb81b2a/src/packages/excalidraw/example/App.js#L68
const fileArray = []; const fileArray: BinaryFileData[] = [];
for (const fileId in files) { for (const fileId in files) {
const file = files[fileId]; const file = files[fileId];
// TODO: dataURL is replaceable with a trilium image url // TODO: dataURL is replaceable with a trilium image url
@ -288,7 +310,7 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
} }
// Extract libraryItems from the blobs // Extract libraryItems from the blobs
const libraryItems = results.map((result) => result?.blob?.getJsonContentSafely()).filter((item) => !!item) as ExcalidrawLibrary[]; const libraryItems = results.map((result) => result?.blob?.getJsonContentSafely()).filter((item) => !!item) as LibraryItem[];
// Extract metadata for each attachment // Extract metadata for each attachment
const metadata = results.map((result) => result.metadata); const metadata = results.map((result) => result.metadata);
@ -302,7 +324,8 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
}); });
// Update the scene // Update the scene
this.excalidrawApi.updateScene(sceneData); // TODO: Fix type of sceneData
this.excalidrawApi.updateScene(sceneData as any);
this.excalidrawApi.addFiles(fileArray); this.excalidrawApi.addFiles(fileArray);
this.excalidrawApi.history.clear(); this.excalidrawApi.history.clear();
} }
@ -328,18 +351,17 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
const files = this.excalidrawApi.getFiles(); const files = this.excalidrawApi.getFiles();
// parallel svg export to combat bitrot and enable rendering image for note inclusion, preview, and share // parallel svg export to combat bitrot and enable rendering image for note inclusion, preview, and share
const svg = await window.ExcalidrawLib.exportToSvg({ const svg = await this.excalidrawLib.exportToSvg({
elements, elements,
appState, appState,
exportPadding: 5, // 5 px padding exportPadding: 5, // 5 px padding
metadata: "trilium-export",
files files
}); });
const svgString = svg.outerHTML; const svgString = svg.outerHTML;
const activeFiles: Record<string, ExcalidrawElement> = {}; const activeFiles: Record<string, BinaryFileData> = {};
elements.forEach((element) => { elements.forEach((element) => {
if (element.fileId) { if ("fileId" in element && element.fileId) {
activeFiles[element.fileId] = files[element.fileId]; activeFiles[element.fileId] = files[element.fileId];
} }
}); });
@ -362,7 +384,12 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
// this.libraryChanged is unset in dataSaved() // this.libraryChanged is unset in dataSaved()
// there's no separate method to get library items, so have to abuse this one // there's no separate method to get library items, so have to abuse this one
const libraryItems = await this.excalidrawApi.updateLibrary({ merge: true }); const libraryItems = await this.excalidrawApi.updateLibrary({
libraryItems(currentLibraryItems) {
return [];
},
merge: true
});
// excalidraw saves the library as a own state. the items are saved to libraryItems. then we compare the library right now with a libraryitemcache. The cache is filled when we first load the Library into the note. // excalidraw saves the library as a own state. the items are saved to libraryItems. then we compare the library right now with a libraryitemcache. The cache is filled when we first load the Library into the note.
//We need the cache to delete old attachments later in the server. //We need the cache to delete old attachments later in the server.
@ -445,33 +472,35 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
} }
} }
createExcalidrawReactApp() { createExcalidrawReactApp(react: typeof React, excalidrawComponent: React.MemoExoticComponent<(props: ExcalidrawProps) => JSX.Element>) {
const React = window.React; const excalidrawWrapperRef = react.useRef<HTMLElement>(null);
const { Excalidraw } = window.ExcalidrawLib;
const excalidrawWrapperRef = React.useRef(null);
this.excalidrawWrapperRef = excalidrawWrapperRef; this.excalidrawWrapperRef = excalidrawWrapperRef;
const [dimensions, setDimensions] = React.useState({ const [dimensions, setDimensions] = react.useState<{ width?: number, height?: number}>({
width: undefined, width: undefined,
height: undefined height: undefined
}); });
React.useEffect(() => { react.useEffect(() => {
const dimensions = { if (excalidrawWrapperRef.current) {
width: excalidrawWrapperRef.current.getBoundingClientRect().width, const dimensions = {
height: excalidrawWrapperRef.current.getBoundingClientRect().height width: excalidrawWrapperRef.current.getBoundingClientRect().width,
}; height: excalidrawWrapperRef.current.getBoundingClientRect().height
setDimensions(dimensions); };
setDimensions(dimensions);
}
const onResize = () => { const onResize = () => {
if (this.note?.type !== "canvas") { if (this.note?.type !== "canvas") {
return; return;
} }
const dimensions = { if (excalidrawWrapperRef.current) {
width: excalidrawWrapperRef.current.getBoundingClientRect().width, const dimensions = {
height: excalidrawWrapperRef.current.getBoundingClientRect().height width: excalidrawWrapperRef.current.getBoundingClientRect().width,
}; height: excalidrawWrapperRef.current.getBoundingClientRect().height
setDimensions(dimensions); };
setDimensions(dimensions);
}
}; };
window.addEventListener("resize", onResize); window.addEventListener("resize", onResize);
@ -479,8 +508,11 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
return () => window.removeEventListener("resize", onResize); return () => window.removeEventListener("resize", onResize);
}, [excalidrawWrapperRef]); }, [excalidrawWrapperRef]);
const onLinkOpen = React.useCallback((element, event) => { const onLinkOpen = react.useCallback<NonNullable<ExcalidrawProps["onLinkOpen"]>>((element, event) => {
let link = element.link; let link = element.link;
if (!link) {
return false;
}
if (link.startsWith("root/")) { if (link.startsWith("root/")) {
link = "#" + link; link = "#" + link;
@ -493,25 +525,24 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
return linkService.goToLinkExt(nativeEvent, link, null); return linkService.goToLinkExt(nativeEvent, link, null);
}, []); }, []);
return React.createElement( return react.createElement(
React.Fragment, react.Fragment,
null, null,
React.createElement( react.createElement(
"div", "div",
{ {
className: "excalidraw-wrapper", className: "excalidraw-wrapper",
ref: excalidrawWrapperRef ref: excalidrawWrapperRef
}, },
React.createElement(Excalidraw, { react.createElement(excalidrawComponent, {
// this makes sure that 1) manual theme switch button is hidden 2) theme stays as it should after opening menu // this makes sure that 1) manual theme switch button is hidden 2) theme stays as it should after opening menu
theme: this.themeStyle, theme: this.themeStyle,
excalidrawAPI: (api: ExcalidrawApi) => { excalidrawAPI: (api: ExcalidrawImperativeAPI) => {
this.excalidrawApi = api; this.excalidrawApi = api;
}, },
width: dimensions.width,
height: dimensions.height,
onPaste: (data: unknown, event: unknown) => { onPaste: (data: unknown, event: unknown) => {
console.log("Verbose: excalidraw internal paste. No trilium action implemented.", data, event); console.log("Verbose: excalidraw internal paste. No trilium action implemented.", data, event);
return false;
}, },
onLibraryChange: () => { onLibraryChange: () => {
this.libraryChanged = true; this.libraryChanged = true;
@ -528,8 +559,10 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
autoFocus: false, autoFocus: false,
onLinkOpen, onLinkOpen,
UIOptions: { UIOptions: {
saveToActiveFile: false, canvasActions: {
saveAsImage: false saveToActiveFile: false,
saveAsImage: false
}
} }
}) })
) )
@ -555,7 +588,7 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
getSceneVersion() { getSceneVersion() {
if (this.excalidrawApi) { if (this.excalidrawApi) {
const elements = this.excalidrawApi.getSceneElements(); const elements = this.excalidrawApi.getSceneElements();
return window.ExcalidrawLib.getSceneVersion(elements); return this.excalidrawLib.getSceneVersion(elements);
} else { } else {
return this.SCENE_VERSION_ERROR; return this.SCENE_VERSION_ERROR;
} }

View File

@ -44,13 +44,6 @@ async function register(app: express.Application) {
app.use(`/assets/vX/stylesheets`, express.static(path.join(srcRoot, "public/stylesheets"))); app.use(`/assets/vX/stylesheets`, express.static(path.join(srcRoot, "public/stylesheets")));
app.use(`/${assetPath}/libraries`, persistentCacheStatic(path.join(srcRoot, "..", "libraries"))); app.use(`/${assetPath}/libraries`, persistentCacheStatic(path.join(srcRoot, "..", "libraries")));
app.use(`/assets/vX/libraries`, express.static(path.join(srcRoot, "..", "libraries"))); app.use(`/assets/vX/libraries`, express.static(path.join(srcRoot, "..", "libraries")));
// excalidraw-view mode in shared notes
app.use(`/${assetPath}/node_modules/react/umd/react.production.min.js`, persistentCacheStatic(path.join(srcRoot, "..", "node_modules/react/umd/react.production.min.js")));
app.use(`/${assetPath}/node_modules/react/umd/react.development.js`, persistentCacheStatic(path.join(srcRoot, "..", "node_modules/react/umd/react.development.js")));
app.use(`/${assetPath}/node_modules/react-dom/umd/react-dom.production.min.js`, persistentCacheStatic(path.join(srcRoot, "..", "node_modules/react-dom/umd/react-dom.production.min.js")));
app.use(`/${assetPath}/node_modules/react-dom/umd/react-dom.development.js`, persistentCacheStatic(path.join(srcRoot, "..", "node_modules/react-dom/umd/react-dom.development.js")));
// expose the whole dist folder since complete assets are needed in edit and share
app.use(`/node_modules/@excalidraw/excalidraw/dist/`, express.static(path.join(srcRoot, "..", "node_modules/@excalidraw/excalidraw/dist/"))); app.use(`/node_modules/@excalidraw/excalidraw/dist/`, express.static(path.join(srcRoot, "..", "node_modules/@excalidraw/excalidraw/dist/")));
app.use(`/${assetPath}/node_modules/@excalidraw/excalidraw/dist/`, persistentCacheStatic(path.join(srcRoot, "..", "node_modules/@excalidraw/excalidraw/dist/"))); app.use(`/${assetPath}/node_modules/@excalidraw/excalidraw/dist/`, persistentCacheStatic(path.join(srcRoot, "..", "node_modules/@excalidraw/excalidraw/dist/")));