diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 7504eca93..bd0b4e0ec 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -11,6 +11,7 @@ on: pull_request: paths: - .github/actions/build-electron/* + - .github/workflows/nightly.yml - forge.config.cjs concurrency: @@ -76,7 +77,7 @@ jobs: WINDOWS_SIGN_EXECUTABLE: ${{ vars.WINDOWS_SIGN_EXECUTABLE }} - name: Publish release - uses: softprops/action-gh-release@v2.2.2 + uses: softprops/action-gh-release@v2.3.2 if: ${{ github.event_name != 'pull_request' }} with: make_latest: false @@ -116,7 +117,7 @@ jobs: arch: ${{ matrix.arch }} - name: Publish release - uses: softprops/action-gh-release@v2.2.2 + uses: softprops/action-gh-release@v2.3.2 if: ${{ github.event_name != 'pull_request' }} with: make_latest: false diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5dee14fed..9533621f0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -114,7 +114,7 @@ jobs: path: upload - name: Publish stable release - uses: softprops/action-gh-release@v2.2.2 + uses: softprops/action-gh-release@v2.3.2 with: draft: false body_path: docs/Release Notes/Release Notes/${{ github.ref_name }}.md diff --git a/apps/client/package.json b/apps/client/package.json index 0b33bdacd..e269fd112 100644 --- a/apps/client/package.json +++ b/apps/client/package.json @@ -51,8 +51,7 @@ "mind-elixir": "4.6.0", "normalize.css": "8.0.1", "panzoom": "9.4.3", - "react": "19.1.0", - "react-dom": "19.1.0", + "preact": "10.26.8", "split.js": "1.6.5", "svg-pan-zoom": "3.6.2", "vanilla-js-wheel-zoom": "9.0.4" @@ -64,10 +63,8 @@ "@types/leaflet": "1.9.18", "@types/leaflet-gpx": "1.3.7", "@types/mark.js": "8.11.12", - "@types/react": "19.1.7", - "@types/react-dom": "19.1.6", "copy-webpack-plugin": "13.0.0", - "happy-dom": "17.6.3", + "happy-dom": "18.0.1", "script-loader": "0.7.2", "vite-plugin-static-copy": "3.0.0" }, @@ -75,7 +72,9 @@ "name": "client", "targets": { "serve": { - "dependsOn": ["^build"] + "dependsOn": [ + "^build" + ] } } } diff --git a/apps/client/src/translations/cn/translation.json b/apps/client/src/translations/cn/translation.json index fa2460f26..cf852e56b 100644 --- a/apps/client/src/translations/cn/translation.json +++ b/apps/client/src/translations/cn/translation.json @@ -1333,7 +1333,7 @@ "recovery_keys_used": "已使用: {{date}}", "recovery_keys_unused": "恢复代码 {{index}} 未使用", "oauth_title": "OAuth/OpenID 认证", - "oauth_description": "OpenID 是一种标准化方式,允许您使用其他服务(如 Google)的账户登录网站,以验证您的身份。请参阅这些 指南 通过 Google 设置 OpenID 服务。", + "oauth_description": "OpenID 是一种标准化方式,允许您使用其他服务(如 Google)的账号登录网站来验证您的身份。默认的身份提供者是 Google,但您可以更改为任何其他 OpenID 提供者。点击这里了解更多信息。请参阅这些 指南 通过 Google 设置 OpenID 服务。", "oauth_description_warning": "要启用 OAuth/OpenID,您需要设置 config.ini 文件中的 OAuth/OpenID 基础 URL、客户端 ID 和客户端密钥,并重新启动应用程序。如果要从环境变量设置,请设置 TRILIUM_OAUTH_BASE_URL、TRILIUM_OAUTH_CLIENT_ID 和 TRILIUM_OAUTH_CLIENT_SECRET 环境变量。", "oauth_missing_vars": "缺少以下设置项: {{missingVars}}", "oauth_user_account": "用户账号:", diff --git a/apps/client/src/translations/en/translation.json b/apps/client/src/translations/en/translation.json index 44b50445b..460158055 100644 --- a/apps/client/src/translations/en/translation.json +++ b/apps/client/src/translations/en/translation.json @@ -1493,7 +1493,7 @@ "recovery_keys_used": "Used: {{date}}", "recovery_keys_unused": "Recovery code {{index}} is unused", "oauth_title": "OAuth/OpenID", - "oauth_description": "OpenID is a standardized way to let you log into websites using an account from another service, like Google, to verify your identity. Follow these instructions to setup an OpenID service through Google.", + "oauth_description": "OpenID is a standardized way to let you log into websites using an account from another service, like Google, to verify your identity. The default issuer is Google, but you can change it to any other OpenID provider. Check here for more information. Follow these instructions to setup an OpenID service through Google.", "oauth_description_warning": "To enable OAuth/OpenID, you need to set the OAuth/OpenID base URL, client ID and client secret in the config.ini file and restart the application. If you want to set from environment variables, please set TRILIUM_OAUTH_BASE_URL, TRILIUM_OAUTH_CLIENT_ID and TRILIUM_OAUTH_CLIENT_SECRET.", "oauth_missing_vars": "Missing settings: {{variables}}", "oauth_user_account": "User Account: ", diff --git a/apps/client/src/types-assets.d.ts b/apps/client/src/types-assets.d.ts index 34a964be8..010ec6b44 100644 --- a/apps/client/src/types-assets.d.ts +++ b/apps/client/src/types-assets.d.ts @@ -3,7 +3,7 @@ declare module "*.png" { export default path; } -declare module "@triliumnext/ckeditor5/emoji_definitions/en.json?url" { +declare module "*?url" { var path: string; export default path; } diff --git a/apps/client/src/types.d.ts b/apps/client/src/types.d.ts index 113b94d76..be42284c7 100644 --- a/apps/client/src/types.d.ts +++ b/apps/client/src/types.d.ts @@ -57,6 +57,8 @@ declare global { process?: ElectronProcess; glob?: CustomGlobals; + + EXCALIDRAW_ASSET_PATH?: string; } interface AutoCompleteConfig { diff --git a/apps/client/src/widgets/type_widgets/canvas.ts b/apps/client/src/widgets/type_widgets/canvas.ts index 53c940970..a15715c64 100644 --- a/apps/client/src/widgets/type_widgets/canvas.ts +++ b/apps/client/src/widgets/type_widgets/canvas.ts @@ -1,16 +1,11 @@ import TypeWidget from "./type_widget.js"; -import utils from "../../services/utils.js"; -import linkService from "../../services/link.js"; import server from "../../services/server.js"; import type FNote from "../../entities/fnote.js"; import options from "../../services/options.js"; -import type { ExcalidrawElement, Theme } from "@excalidraw/excalidraw/element/types"; -import type { AppState, BinaryFileData, ExcalidrawImperativeAPI, ExcalidrawProps, LibraryItem, SceneData } from "@excalidraw/excalidraw/types"; -import type { JSX } from "react"; -import type React from "react"; -import type { Root } from "react-dom/client"; -import "@excalidraw/excalidraw/index.css"; -import asset_path from "../../asset_path.js"; +import type { LibraryItem } from "@excalidraw/excalidraw/types"; +import type { Theme } from "@excalidraw/excalidraw/element/types"; +import type Canvas from "./canvas_el.js"; +import { CanvasContent } from "./canvas_el.js"; const TPL = /*html*/`
@@ -28,6 +23,7 @@ const TPL = /*html*/` .excalidraw-wrapper { height: 100%; + } :root[dir="ltr"] .excalidraw @@ -51,11 +47,7 @@ const TPL = /*html*/`
`; -interface CanvasContent { - elements: ExcalidrawElement[]; - files: BinaryFileData[]; - appState: Partial; -} + interface AttachmentMetadata { title: string; @@ -107,37 +99,22 @@ interface AttachmentMetadata { */ export default class ExcalidrawTypeWidget extends TypeWidget { - private readonly SCENE_VERSION_INITIAL: number; - private readonly SCENE_VERSION_ERROR: number; - private currentNoteId: string; - private currentSceneVersion: number; + private libraryChanged: boolean; private librarycache: LibraryItem[]; private attachmentMetadata: AttachmentMetadata[]; private themeStyle!: Theme; - private excalidrawLib!: typeof import("@excalidraw/excalidraw"); - private excalidrawApi!: ExcalidrawImperativeAPI; - private excalidrawWrapperRef!: React.RefObject; private $render!: JQuery; - private root?: Root; private reactHandlers!: JQuery; + private canvasInstance!: Canvas; constructor() { super(); - // constants - this.SCENE_VERSION_INITIAL = -1; // -1 indicates that it is fresh. excalidraw scene version is always >0 - this.SCENE_VERSION_ERROR = -2; // -2 indicates error - - // currently required by excalidraw, in order to allows self-hosting fonts locally. - // this avoids making excalidraw load the fonts from an external CDN. - (window as any).EXCALIDRAW_ASSET_PATH = `${window.location.pathname}/node_modules/@excalidraw/excalidraw/dist/prod`; - // temporary vars this.currentNoteId = ""; - this.currentSceneVersion = this.SCENE_VERSION_INITIAL; // will be overwritten this.$render; @@ -182,34 +159,48 @@ export default class ExcalidrawTypeWidget extends TypeWidget { 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 Canvas = (await import("./canvas_el.js")).default; + this.canvasInstance = new Canvas({ + // this makes sure that 1) manual theme switch button is hidden 2) theme stays as it should after opening menu + theme: this.themeStyle, + onChange: () => this.onChangeHandler(), + viewModeEnabled: options.is("databaseReadonly"), + zenModeEnabled: false, + gridModeEnabled: false, + isCollaborating: false, + detectScroll: false, + handleKeyboardGlobally: false, + autoFocus: false, + UIOptions: { + canvasActions: { + saveToActiveFile: false, + export: false + } + }, + onLibraryChange: () => { + this.libraryChanged = true; - const excalidraw = await import("@excalidraw/excalidraw"); - this.excalidrawLib = excalidraw; + this.saveData(); + }, + }); - const { createRoot } = await import("react-dom/client"); - const React = (await import("react")).default; - this.root?.unmount(); - this.root = createRoot(renderElement); - this.root.render(React.createElement(() => this.createExcalidrawReactApp(React, excalidraw.Excalidraw))); + await setupFonts(); + this.canvasInstance.renderCanvas(renderElement); } /** * called to populate the widget container with the note content */ async doRefresh(note: FNote) { + if (!this.canvasInstance) { + await this.#init(); + } + // see if the note changed, since we do not get a new class for a new note const noteChanged = this.currentNoteId !== note.noteId; if (noteChanged) { // reset the scene to omit unnecessary onchange handler - this.currentSceneVersion = this.SCENE_VERSION_INITIAL; + this.canvasInstance.resetSceneVersion(); } this.currentNoteId = note.noteId; @@ -217,10 +208,7 @@ export default class ExcalidrawTypeWidget extends TypeWidget { const blob = await note.getBlob(); // before we load content into excalidraw, make sure excalidraw has loaded - while (!this.excalidrawApi) { - console.log("excalidrawApi not yet loaded, sleep 200ms..."); - await utils.sleep(200); - } + await this.canvasInstance.waitForApiToBecomeAvailable(); /** * new and empty note - make sure that canvas is empty. @@ -229,15 +217,7 @@ export default class ExcalidrawTypeWidget extends TypeWidget { * newly instantiated? */ if (!blob?.content?.trim()) { - const sceneData: SceneData = { - elements: [], - appState: { - theme: this.themeStyle - } - }; - - // TODO: Props mismatch. - this.excalidrawApi.updateScene(sceneData as any); + this.canvasInstance.resetScene(this.themeStyle); } else if (blob.content) { let content: CanvasContent; @@ -254,36 +234,7 @@ export default class ExcalidrawTypeWidget extends TypeWidget { }; } - const { elements, files } = content; - const appState: Partial = content.appState ?? {}; - - appState.theme = this.themeStyle; - - if (this.excalidrawWrapperRef.current) { - const boundingClientRect = this.excalidrawWrapperRef.current.getBoundingClientRect(); - appState.width = boundingClientRect.width; - appState.height = boundingClientRect.height; - appState.offsetLeft = boundingClientRect.left; - appState.offsetTop = boundingClientRect.top; - } - - const sceneData: SceneData = { - elements, - appState - }; - - // files are expected in an array when loading. they are stored as a key-index object - // see example for loading here: - // https://github.com/excalidraw/excalidraw/blob/c5a7723185f6ca05e0ceb0b0d45c4e3fbcb81b2a/src/packages/excalidraw/example/App.js#L68 - const fileArray: BinaryFileData[] = []; - for (const fileId in files) { - const file = files[fileId]; - // TODO: dataURL is replaceable with a trilium image url - // maybe we can save normal images (pasted) with base64 data url, and trilium images - // with their respective url! nice - // file.dataURL = "http://localhost:8080/api/images/ltjOiU8nwoZx/start.png"; - fileArray.push(file); - } + this.canvasInstance.loadData(content, this.themeStyle); Promise.all( (await note.getAttachmentsByRole("canvasLibraryItem")).map(async (attachment) => { @@ -310,23 +261,19 @@ export default class ExcalidrawTypeWidget extends TypeWidget { const metadata = results.map((result) => result.metadata); // Update the library and save to independent variables - this.excalidrawApi.updateLibrary({ libraryItems, merge: false }); + this.canvasInstance.updateLibrary(libraryItems); // save state of library to compare it to the new state later. this.librarycache = libraryItems; this.attachmentMetadata = metadata; }); - // Update the scene - // TODO: Fix type of sceneData - this.excalidrawApi.updateScene(sceneData as any); - this.excalidrawApi.addFiles(fileArray); - this.excalidrawApi.history.clear(); + } // set initial scene version - if (this.currentSceneVersion === this.SCENE_VERSION_INITIAL) { - this.currentSceneVersion = this.getSceneVersion(); + if (this.canvasInstance.isInitialScene()) { + this.canvasInstance.updateSceneVersion(); } } @@ -335,56 +282,14 @@ export default class ExcalidrawTypeWidget extends TypeWidget { * this is automatically called after this.saveData(); */ async getData() { - const elements = this.excalidrawApi.getSceneElements(); - const appState = this.excalidrawApi.getAppState(); - - /** - * A file is not deleted, even though removed from canvas. Therefore, we only keep - * files that are referenced by an element. Maybe this will change with a new excalidraw version? - */ - const files = this.excalidrawApi.getFiles(); - - // parallel svg export to combat bitrot and enable rendering image for note inclusion, preview, and share - const svg = await this.excalidrawLib.exportToSvg({ - elements, - appState, - exportPadding: 5, // 5 px padding - files - }); - const svgString = svg.outerHTML; - - const activeFiles: Record = {}; - // TODO: Used any where upstream typings appear to be broken. - elements.forEach((element: any) => { - if ("fileId" in element && element.fileId) { - activeFiles[element.fileId] = files[element.fileId]; - } - }); - - const content = { - type: "excalidraw", - version: 2, - elements, - files: activeFiles, - appState: { - scrollX: appState.scrollX, - scrollY: appState.scrollY, - zoom: appState.zoom - } - }; - - const attachments = [{ role: "image", title: "canvas-export.svg", mime: "image/svg+xml", content: svgString, position: 0 }]; + const { content, svg } = await this.canvasInstance.getData(); + const attachments = [{ role: "image", title: "canvas-export.svg", mime: "image/svg+xml", content: svg, position: 0 }]; if (this.libraryChanged) { // this.libraryChanged is unset in dataSaved() // there's no separate method to get library items, so have to abuse this one - const libraryItems = await this.excalidrawApi.updateLibrary({ - libraryItems() { - return []; - }, - merge: true - }); + const libraryItems = await this.canvasInstance.getLibraryItems(); // 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. @@ -453,146 +358,39 @@ export default class ExcalidrawTypeWidget extends TypeWidget { } // changeHandler is called upon any tiny change in excalidraw. button clicked, hover, etc. // make sure only when a new element is added, we actually save something. - const isNewSceneVersion = this.isNewSceneVersion(); + const isNewSceneVersion = this.canvasInstance.isNewSceneVersion(); /** * FIXME: however, we might want to make an exception, if viewport changed, since viewport * is desired to save? (add) and appState background, and some things */ // upon updateScene, onchange is called, even though "nothing really changed" that is worth saving - const isNotInitialScene = this.currentSceneVersion !== this.SCENE_VERSION_INITIAL; - + const isNotInitialScene = !this.canvasInstance.isInitialScene(); const shouldSave = isNewSceneVersion && isNotInitialScene; if (shouldSave) { - this.updateSceneVersion(); + this.canvasInstance.updateSceneVersion(); this.saveData(); } } - createExcalidrawReactApp(react: typeof React, excalidrawComponent: React.MemoExoticComponent<(props: ExcalidrawProps) => JSX.Element>) { - const excalidrawWrapperRef = react.useRef(null); - this.excalidrawWrapperRef = excalidrawWrapperRef; - const [dimensions, setDimensions] = react.useState<{ width?: number; height?: number }>({ - width: undefined, - height: undefined - }); - - react.useEffect(() => { - if (excalidrawWrapperRef.current) { - const dimensions = { - width: excalidrawWrapperRef.current.getBoundingClientRect().width, - height: excalidrawWrapperRef.current.getBoundingClientRect().height - }; - setDimensions(dimensions); - } - - const onResize = () => { - if (this.note?.type !== "canvas") { - return; - } - - if (excalidrawWrapperRef.current) { - const dimensions = { - width: excalidrawWrapperRef.current.getBoundingClientRect().width, - height: excalidrawWrapperRef.current.getBoundingClientRect().height - }; - setDimensions(dimensions); - } - }; - - window.addEventListener("resize", onResize); - - return () => window.removeEventListener("resize", onResize); - }, [excalidrawWrapperRef]); - - const onLinkOpen = react.useCallback>((element, event) => { - let link = element.link; - if (!link) { - return false; - } - - if (link.startsWith("root/")) { - link = "#" + link; - } - - const { nativeEvent } = event.detail; - - event.preventDefault(); - - return linkService.goToLinkExt(nativeEvent, link, null); - }, []); - - return react.createElement( - react.Fragment, - null, - react.createElement( - "div", - { - className: "excalidraw-wrapper", - ref: excalidrawWrapperRef - }, - react.createElement(excalidrawComponent, { - // this makes sure that 1) manual theme switch button is hidden 2) theme stays as it should after opening menu - theme: this.themeStyle, - excalidrawAPI: (api: ExcalidrawImperativeAPI) => { - this.excalidrawApi = api; - }, - onLibraryChange: () => { - this.libraryChanged = true; - - this.saveData(); - }, - onChange: () => this.onChangeHandler(), - viewModeEnabled: options.is("databaseReadonly"), - zenModeEnabled: false, - gridModeEnabled: false, - isCollaborating: false, - detectScroll: false, - handleKeyboardGlobally: false, - autoFocus: false, - onLinkOpen, - UIOptions: { - canvasActions: { - saveToActiveFile: false, - export: false - } - } - }) - ) - ); - } - - /** - * needed to ensure, that multipleOnChangeHandler calls do not trigger a save. - * we compare the scene version as suggested in: - * https://github.com/excalidraw/excalidraw/issues/3014#issuecomment-778115329 - * - * info: sceneVersions are not incrementing. it seems to be a pseudo-random number - */ - isNewSceneVersion() { - if (options.is("databaseReadonly")) { - return false; - } - - const sceneVersion = this.getSceneVersion(); - - return ( - this.currentSceneVersion === this.SCENE_VERSION_INITIAL || // initial scene version update - this.currentSceneVersion !== sceneVersion - ); // ensure scene changed - } - - getSceneVersion() { - if (this.excalidrawApi) { - const elements = this.excalidrawApi.getSceneElements(); - return this.excalidrawLib.getSceneVersion(elements); - } else { - return this.SCENE_VERSION_ERROR; - } - } - - updateSceneVersion() { - this.currentSceneVersion = this.getSceneVersion(); - } +} + +async function setupFonts() { + if (window.EXCALIDRAW_ASSET_PATH) { + return; + } + + // currently required by excalidraw, in order to allows self-hosting fonts locally. + // this avoids making excalidraw load the fonts from an external CDN. + let path: string; + if (!glob.isDev) { + path = `${window.location.pathname}/node_modules/@excalidraw/excalidraw/dist/prod`; + } else { + path = (await import("../../../node_modules/@excalidraw/excalidraw/dist/prod/fonts/Excalifont/Excalifont-Regular-a88b72a24fb54c9f94e3b5fdaa7481c9.woff2?url")).default; + let pathComponents = path.split("/"); + path = pathComponents.slice(0, pathComponents.length - 2).join("/"); + } + + window.EXCALIDRAW_ASSET_PATH = path; } diff --git a/apps/client/src/widgets/type_widgets/canvas_el.ts b/apps/client/src/widgets/type_widgets/canvas_el.ts new file mode 100644 index 000000000..1ba54d2c2 --- /dev/null +++ b/apps/client/src/widgets/type_widgets/canvas_el.ts @@ -0,0 +1,179 @@ +import "@excalidraw/excalidraw/index.css"; +import { Excalidraw, getSceneVersion, exportToSvg } from "@excalidraw/excalidraw"; +import { createElement, render, unmountComponentAtNode } from "preact/compat"; +import { AppState, BinaryFileData, ExcalidrawImperativeAPI, ExcalidrawProps, LibraryItem } from "@excalidraw/excalidraw/types"; +import type { ComponentType } from "preact"; +import { ExcalidrawElement, NonDeletedExcalidrawElement, Theme } from "@excalidraw/excalidraw/element/types"; + +export interface CanvasContent { + elements: ExcalidrawElement[]; + files: BinaryFileData[]; + appState: Partial; +} + +/** Indicates that it is fresh. excalidraw scene version is always >0 */ +const SCENE_VERSION_INITIAL = -1; + +export default class Canvas { + + private currentSceneVersion: number; + private opts: ExcalidrawProps; + private excalidrawApi!: ExcalidrawImperativeAPI; + private initializedPromise: JQuery.Deferred; + + constructor(opts: ExcalidrawProps) { + this.opts = opts; + this.currentSceneVersion = SCENE_VERSION_INITIAL; + this.initializedPromise = $.Deferred(); + } + + renderCanvas(targetEl: HTMLElement) { + unmountComponentAtNode(targetEl); + render(this.createCanvasElement({ + ...this.opts, + excalidrawAPI: (api: ExcalidrawImperativeAPI) => { + this.excalidrawApi = api; + this.initializedPromise.resolve(); + }, + }), targetEl); + } + + async waitForApiToBecomeAvailable() { + while (!this.excalidrawApi) { + await this.initializedPromise; + } + } + + private createCanvasElement(opts: ExcalidrawProps) { + return createElement("div", { className: "excalidraw-wrapper", }, + createElement(Excalidraw as ComponentType, opts) + ); + } + + /** + * needed to ensure, that multipleOnChangeHandler calls do not trigger a save. + * we compare the scene version as suggested in: + * https://github.com/excalidraw/excalidraw/issues/3014#issuecomment-778115329 + * + * info: sceneVersions are not incrementing. it seems to be a pseudo-random number + */ + isNewSceneVersion() { + const sceneVersion = this.getSceneVersion(); + + return ( + this.currentSceneVersion === SCENE_VERSION_INITIAL || // initial scene version update + this.currentSceneVersion !== sceneVersion + ); // ensure scene changed + } + + getSceneVersion() { + const elements = this.excalidrawApi.getSceneElements(); + return getSceneVersion(elements); + } + + updateSceneVersion() { + this.currentSceneVersion = this.getSceneVersion(); + } + + resetSceneVersion() { + this.currentSceneVersion = SCENE_VERSION_INITIAL; + } + + isInitialScene() { + return this.currentSceneVersion === SCENE_VERSION_INITIAL; + } + + resetScene(theme: Theme) { + this.excalidrawApi.updateScene({ + elements: [], + appState: { + theme + } + }); + } + + loadData(content: CanvasContent, theme: Theme) { + const { elements, files } = content; + const appState: Partial = content.appState ?? {}; + appState.theme = theme; + + // files are expected in an array when loading. they are stored as a key-index object + // see example for loading here: + // https://github.com/excalidraw/excalidraw/blob/c5a7723185f6ca05e0ceb0b0d45c4e3fbcb81b2a/src/packages/excalidraw/example/App.js#L68 + const fileArray: BinaryFileData[] = []; + for (const fileId in files) { + const file = files[fileId]; + // TODO: dataURL is replaceable with a trilium image url + // maybe we can save normal images (pasted) with base64 data url, and trilium images + // with their respective url! nice + // file.dataURL = "http://localhost:8080/api/images/ltjOiU8nwoZx/start.png"; + fileArray.push(file); + } + + // Update the scene + // TODO: Fix type of sceneData + this.excalidrawApi.updateScene({ + elements, + appState: appState as AppState + }); + this.excalidrawApi.addFiles(fileArray); + this.excalidrawApi.history.clear(); + } + + async getData() { + const elements = this.excalidrawApi.getSceneElements(); + const appState = this.excalidrawApi.getAppState(); + + /** + * A file is not deleted, even though removed from canvas. Therefore, we only keep + * files that are referenced by an element. Maybe this will change with a new excalidraw version? + */ + const files = this.excalidrawApi.getFiles(); + // parallel svg export to combat bitrot and enable rendering image for note inclusion, preview, and share + const svg = await exportToSvg({ + elements, + appState, + exportPadding: 5, // 5 px padding + files + }); + const svgString = svg.outerHTML; + + const activeFiles: Record = {}; + elements.forEach((element: NonDeletedExcalidrawElement) => { + if ("fileId" in element && element.fileId) { + activeFiles[element.fileId] = files[element.fileId]; + } + }); + + const content = { + type: "excalidraw", + version: 2, + elements, + files: activeFiles, + appState: { + scrollX: appState.scrollX, + scrollY: appState.scrollY, + zoom: appState.zoom + } + }; + + return { + content, + svg: svgString + } + } + + async getLibraryItems() { + return this.excalidrawApi.updateLibrary({ + libraryItems() { + return []; + }, + merge: true + }); + } + + async updateLibrary(libraryItems: LibraryItem[]) { + this.excalidrawApi.updateLibrary({ libraryItems, merge: false }); + } + +} diff --git a/apps/client/src/widgets/type_widgets/content_widget.ts b/apps/client/src/widgets/type_widgets/content_widget.ts index 614159cb7..77b86231d 100644 --- a/apps/client/src/widgets/type_widgets/content_widget.ts +++ b/apps/client/src/widgets/type_widgets/content_widget.ts @@ -45,7 +45,7 @@ import { t } from "../../services/i18n.js"; import LanguageOptions from "./options/i18n/language.js"; import type BasicWidget from "../basic_widget.js"; import CodeTheme from "./options/code_notes/code_theme.js"; -import RelatedSettings from "./options/related_settings.js"; +import RelatedSettings from "./options/appearance/related_settings.js"; const TPL = /*html*/`