mirror of
https://github.com/TriliumNext/Notes.git
synced 2025-09-27 15:31:33 +08:00
chore(client/ts): port canvas
This commit is contained in:
parent
2167948509
commit
7c7fd044c6
@ -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>) {
|
function goToLinkExt(evt: MouseEvent | JQuery.ClickEvent, hrefLink: string | undefined, $link: JQuery<HTMLElement> | null) {
|
||||||
if (hrefLink?.startsWith("data:")) {
|
if (hrefLink?.startsWith("data:")) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -242,7 +242,7 @@ function goToLinkExt(evt: MouseEvent | JQuery.ClickEvent, hrefLink: string | und
|
|||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
evt.stopPropagation();
|
evt.stopPropagation();
|
||||||
|
|
||||||
if (hrefLink?.startsWith("#fn")) {
|
if (hrefLink?.startsWith("#fn") && $link) {
|
||||||
return handleFootnote(hrefLink, $link);
|
return handleFootnote(hrefLink, $link);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
66
src/public/app/types.d.ts
vendored
66
src/public/app/types.d.ts
vendored
@ -54,6 +54,72 @@ 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 {
|
||||||
|
@ -3,6 +3,7 @@ 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";
|
||||||
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>
|
||||||
@ -51,6 +52,17 @@ const TPL = `
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
interface CanvasContent {
|
||||||
|
elements: ExcalidrawElement[],
|
||||||
|
files: ExcalidrawElement[],
|
||||||
|
appState: ExcalidrawAppState
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AttachmentMetadata {
|
||||||
|
title: string;
|
||||||
|
attachmentId: string;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* # Canvas note with excalidraw
|
* # Canvas note with excalidraw
|
||||||
* @author thfrei 2022-05-11
|
* @author thfrei 2022-05-11
|
||||||
@ -95,6 +107,24 @@ const TPL = `
|
|||||||
* - Make it easy to include a canvas note inside a text note
|
* - Make it easy to include a canvas note inside a text note
|
||||||
*/
|
*/
|
||||||
export default class ExcalidrawTypeWidget extends TypeWidget {
|
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: ExcalidrawLibrary[];
|
||||||
|
private attachmentMetadata: AttachmentMetadata[];
|
||||||
|
private themeStyle!: string;
|
||||||
|
private excalidrawApi!: ExcalidrawApi;
|
||||||
|
private excalidrawWrapperRef!: {
|
||||||
|
current: HTMLElement
|
||||||
|
};
|
||||||
|
|
||||||
|
private $render!: JQuery<HTMLElement>;
|
||||||
|
private reactHandlers!: JQuery<HTMLElement>;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
@ -145,6 +175,9 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
|
|||||||
const React = window.React;
|
const React = window.React;
|
||||||
const ReactDOM = window.ReactDOM;
|
const ReactDOM = window.ReactDOM;
|
||||||
const renderElement = this.$render.get(0);
|
const renderElement = this.$render.get(0);
|
||||||
|
if (!renderElement) {
|
||||||
|
throw new Error("Unable to find element to render.");
|
||||||
|
}
|
||||||
|
|
||||||
ReactDOM.unmountComponentAtNode(renderElement);
|
ReactDOM.unmountComponentAtNode(renderElement);
|
||||||
const root = ReactDOM.createRoot(renderElement);
|
const root = ReactDOM.createRoot(renderElement);
|
||||||
@ -156,10 +189,8 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* called to populate the widget container with the note content
|
* called to populate the widget container with the note content
|
||||||
*
|
|
||||||
* @param {FNote} note
|
|
||||||
*/
|
*/
|
||||||
async doRefresh(note) {
|
async doRefresh(note: FNote) {
|
||||||
// see if the note changed, since we do not get a new class for a new note
|
// see if the note changed, since we do not get a new class for a new note
|
||||||
const noteChanged = this.currentNoteId !== note.noteId;
|
const noteChanged = this.currentNoteId !== note.noteId;
|
||||||
if (noteChanged) {
|
if (noteChanged) {
|
||||||
@ -183,7 +214,7 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
|
|||||||
* note into this fresh note. Probably due to that this note-instance does not get
|
* note into this fresh note. Probably due to that this note-instance does not get
|
||||||
* newly instantiated?
|
* newly instantiated?
|
||||||
*/
|
*/
|
||||||
if (!blob.content?.trim()) {
|
if (!blob?.content?.trim()) {
|
||||||
const sceneData = {
|
const sceneData = {
|
||||||
elements: [],
|
elements: [],
|
||||||
appState: {
|
appState: {
|
||||||
@ -194,11 +225,11 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
|
|||||||
|
|
||||||
this.excalidrawApi.updateScene(sceneData);
|
this.excalidrawApi.updateScene(sceneData);
|
||||||
} else if (blob.content) {
|
} else if (blob.content) {
|
||||||
// load saved content into excalidraw canvas
|
let content: CanvasContent;
|
||||||
let content;
|
|
||||||
|
|
||||||
|
// load saved content into excalidraw canvas
|
||||||
try {
|
try {
|
||||||
content = blob.getJsonContent();
|
content = blob.getJsonContent() as CanvasContent;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Error parsing content. Probably note.type changed. Starting with empty canvas", note, blob, err);
|
console.error("Error parsing content. Probably note.type changed. Starting with empty canvas", note, blob, err);
|
||||||
|
|
||||||
@ -219,7 +250,7 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
|
|||||||
appState.offsetLeft = boundingClientRect.left;
|
appState.offsetLeft = boundingClientRect.left;
|
||||||
appState.offsetTop = boundingClientRect.top;
|
appState.offsetTop = boundingClientRect.top;
|
||||||
|
|
||||||
const sceneData = {
|
const sceneData: ExcalidrawScene = {
|
||||||
elements,
|
elements,
|
||||||
appState,
|
appState,
|
||||||
collaborators: []
|
collaborators: []
|
||||||
@ -257,7 +288,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);
|
const libraryItems = results.map((result) => result?.blob?.getJsonContentSafely()).filter((item) => !!item) as ExcalidrawLibrary[];
|
||||||
|
|
||||||
// Extract metadata for each attachment
|
// Extract metadata for each attachment
|
||||||
const metadata = results.map((result) => result.metadata);
|
const metadata = results.map((result) => result.metadata);
|
||||||
@ -297,7 +328,7 @@ 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 ExcalidrawLib.exportToSvg({
|
const svg = await window.ExcalidrawLib.exportToSvg({
|
||||||
elements,
|
elements,
|
||||||
appState,
|
appState,
|
||||||
exportPadding: 5, // 5 px padding
|
exportPadding: 5, // 5 px padding
|
||||||
@ -306,7 +337,7 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
|
|||||||
});
|
});
|
||||||
const svgString = svg.outerHTML;
|
const svgString = svg.outerHTML;
|
||||||
|
|
||||||
const activeFiles = {};
|
const activeFiles: Record<string, ExcalidrawElement> = {};
|
||||||
elements.forEach((element) => {
|
elements.forEach((element) => {
|
||||||
if (element.fileId) {
|
if (element.fileId) {
|
||||||
activeFiles[element.fileId] = files[element.fileId];
|
activeFiles[element.fileId] = files[element.fileId];
|
||||||
@ -474,12 +505,12 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
|
|||||||
React.createElement(Excalidraw, {
|
React.createElement(Excalidraw, {
|
||||||
// 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: (api: ExcalidrawApi) => {
|
||||||
this.excalidrawApi = api;
|
this.excalidrawApi = api;
|
||||||
},
|
},
|
||||||
width: dimensions.width,
|
width: dimensions.width,
|
||||||
height: dimensions.height,
|
height: dimensions.height,
|
||||||
onPaste: (data, event) => {
|
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);
|
||||||
},
|
},
|
||||||
onLibraryChange: () => {
|
onLibraryChange: () => {
|
Loading…
x
Reference in New Issue
Block a user