]> BookStack Code Mirror - bookstack/commitdiff
Lexical: Started converting drawio to TS
authorDan Brown <redacted>
Thu, 18 Jul 2024 10:19:11 +0000 (11:19 +0100)
committerDan Brown <redacted>
Thu, 18 Jul 2024 10:19:11 +0000 (11:19 +0100)
Converted events service to TS as part of this.

resources/js/app.js
resources/js/global.d.ts
resources/js/services/drawio.ts [moved from resources/js/services/drawio.js with 69% similarity]
resources/js/services/events.js [deleted file]
resources/js/services/events.ts [new file with mode: 0644]
resources/js/wysiwyg/todo.md
resources/js/wysiwyg/ui/decorators/code-block.ts
resources/js/wysiwyg/ui/decorators/diagram.ts
tsconfig.json

index 123d6c8f5cd55ef30f9403f961db3d5532ab46e2..812a451f2333d6206473cff03be3dbefae8e785e 100644 (file)
@@ -1,4 +1,4 @@
-import * as events from './services/events';
+import {EventManager} from './services/events.ts';
 import * as httpInstance from './services/http';
 import Translations from './services/translations';
 import * as componentMap from './components';
@@ -21,7 +21,7 @@ window.importVersioned = function importVersioned(moduleName) {
 
 // Set events and http services on window
 window.$http = httpInstance;
-window.$events = events;
+window.$events = new EventManager();
 
 // Translation setup
 // Creates a global function with name 'trans' to be used in the same way as the Laravel translation system
index da19545d1101ee47e5670d7ead71fbf91820c566..a9b9275e92cc1a0b8b3d82a8f5298bbbc671276a 100644 (file)
@@ -1,7 +1,9 @@
 import {ComponentStore} from "./services/components";
+import {EventManager} from "./services/events";
 
 declare global {
     interface Window {
         $components: ComponentStore,
+        $events: EventManager,
     }
 }
\ No newline at end of file
similarity index 69%
rename from resources/js/services/drawio.js
rename to resources/js/services/drawio.ts
index 46e10327a26f7438be542a48df3a1b0266202ca2..75b161f75d5c2c57150e5c252cc9fce82c5399ae 100644 (file)
@@ -1,17 +1,31 @@
 // Docs: https://www.diagrams.net/doc/faq/embed-mode
 import * as store from './store';
-
-let iFrame = null;
-let lastApprovedOrigin;
-let onInit;
-let onSave;
+import {ConfirmDialog} from "../components";
+
+type DrawioExportEventResponse = {
+    action: 'export',
+    format: string,
+    message: string,
+    data: string,
+    xml: string,
+};
+
+type DrawioSaveEventResponse = {
+    action: 'save',
+    xml: string,
+};
+
+let iFrame: HTMLIFrameElement|null = null;
+let lastApprovedOrigin: string;
+let onInit: () => Promise<string>;
+let onSave: (data: string) => Promise<any>;
 const saveBackupKey = 'last-drawing-save';
 
-function drawPostMessage(data) {
-    iFrame.contentWindow.postMessage(JSON.stringify(data), lastApprovedOrigin);
+function drawPostMessage(data: Record<any, any>): void {
+    iFrame?.contentWindow?.postMessage(JSON.stringify(data), lastApprovedOrigin);
 }
 
-function drawEventExport(message) {
+function drawEventExport(message: DrawioExportEventResponse) {
     store.set(saveBackupKey, message.data);
     if (onSave) {
         onSave(message.data).then(() => {
@@ -20,7 +34,7 @@ function drawEventExport(message) {
     }
 }
 
-function drawEventSave(message) {
+function drawEventSave(message: DrawioSaveEventResponse) {
     drawPostMessage({
         action: 'export', format: 'xmlpng', xml: message.xml, spin: 'Updating drawing',
     });
@@ -35,8 +49,10 @@ function drawEventInit() {
 
 function drawEventConfigure() {
     const config = {};
-    window.$events.emitPublic(iFrame, 'editor-drawio::configure', {config});
-    drawPostMessage({action: 'configure', config});
+    if (iFrame) {
+        window.$events.emitPublic(iFrame, 'editor-drawio::configure', {config});
+        drawPostMessage({action: 'configure', config});
+    }
 }
 
 function drawEventClose() {
@@ -47,9 +63,8 @@ function drawEventClose() {
 
 /**
  * Receive and handle a message event from the draw.io window.
- * @param {MessageEvent} event
  */
-function drawReceive(event) {
+function drawReceive(event: MessageEvent) {
     if (!event.data || event.data.length < 1) return;
     if (event.origin !== lastApprovedOrigin) return;
 
@@ -59,9 +74,9 @@ function drawReceive(event) {
     } else if (message.event === 'exit') {
         drawEventClose();
     } else if (message.event === 'save') {
-        drawEventSave(message);
+        drawEventSave(message as DrawioSaveEventResponse);
     } else if (message.event === 'export') {
-        drawEventExport(message);
+        drawEventExport(message as DrawioExportEventResponse);
     } else if (message.event === 'configure') {
         drawEventConfigure();
     }
@@ -79,9 +94,8 @@ async function attemptRestoreIfExists() {
         console.error('Missing expected unsaved-drawing dialog');
     }
 
-    if (backupVal) {
-        /** @var {ConfirmDialog} */
-        const dialog = window.$components.firstOnElement(dialogEl, 'confirm-dialog');
+    if (backupVal && dialogEl) {
+        const dialog = window.$components.firstOnElement(dialogEl, 'confirm-dialog') as ConfirmDialog;
         const restore = await dialog.show();
         if (restore) {
             onInit = async () => backupVal;
@@ -94,11 +108,9 @@ async function attemptRestoreIfExists() {
  * onSaveCallback must return a promise that resolves on successful save and errors on failure.
  * onInitCallback must return a promise with the xml to load for the editor.
  * Will attempt to provide an option to restore unsaved changes if found to exist.
- * @param {String} drawioUrl
- * @param {Function<Promise<String>>} onInitCallback
- * @param {Function<Promise>} onSaveCallback - Is called with the drawing data on save.
+ * onSaveCallback Is called with the drawing data on save.
  */
-export async function show(drawioUrl, onInitCallback, onSaveCallback) {
+export async function show(drawioUrl: string, onInitCallback: () => Promise<string>, onSaveCallback: (data: string) => Promise<void>): Promise<void> {
     onInit = onInitCallback;
     onSave = onSaveCallback;
 
@@ -114,7 +126,7 @@ export async function show(drawioUrl, onInitCallback, onSaveCallback) {
     lastApprovedOrigin = (new URL(drawioUrl)).origin;
 }
 
-export async function upload(imageData, pageUploadedToId) {
+export async function upload(imageData: string, pageUploadedToId: string): Promise<{}|string> {
     const data = {
         image: imageData,
         uploaded_to: pageUploadedToId,
@@ -129,10 +141,8 @@ export function close() {
 
 /**
  * Load an existing image, by fetching it as Base64 from the system.
- * @param drawingId
- * @returns {Promise<string>}
  */
-export async function load(drawingId) {
+export async function load(drawingId: string): Promise<string> {
     try {
         const resp = await window.$http.get(window.baseUrl(`/images/drawio/base64/${drawingId}`));
         return `data:image/png;base64,${resp.data.content}`;
diff --git a/resources/js/services/events.js b/resources/js/services/events.js
deleted file mode 100644 (file)
index 7613057..0000000
+++ /dev/null
@@ -1,81 +0,0 @@
-const listeners = {};
-const stack = [];
-
-/**
- * Emit a custom event for any handlers to pick-up.
- * @param {String} eventName
- * @param {*} eventData
- */
-export function emit(eventName, eventData) {
-    stack.push({name: eventName, data: eventData});
-
-    const listenersToRun = listeners[eventName] || [];
-    for (const listener of listenersToRun) {
-        listener(eventData);
-    }
-}
-
-/**
- * Listen to a custom event and run the given callback when that event occurs.
- * @param {String} eventName
- * @param {Function} callback
- * @returns {Events}
- */
-export function listen(eventName, callback) {
-    if (typeof listeners[eventName] === 'undefined') listeners[eventName] = [];
-    listeners[eventName].push(callback);
-}
-
-/**
- * Emit an event for public use.
- * Sends the event via the native DOM event handling system.
- * @param {Element} targetElement
- * @param {String} eventName
- * @param {Object} eventData
- */
-export function emitPublic(targetElement, eventName, eventData) {
-    const event = new CustomEvent(eventName, {
-        detail: eventData,
-        bubbles: true,
-    });
-    targetElement.dispatchEvent(event);
-}
-
-/**
- * Emit a success event with the provided message.
- * @param {String} message
- */
-export function success(message) {
-    emit('success', message);
-}
-
-/**
- * Emit an error event with the provided message.
- * @param {String} message
- */
-export function error(message) {
-    emit('error', message);
-}
-
-/**
- * Notify of standard server-provided validation errors.
- * @param {Object} responseErr
- */
-export function showValidationErrors(responseErr) {
-    if (!responseErr.status) return;
-    if (responseErr.status === 422 && responseErr.data) {
-        const message = Object.values(responseErr.data).flat().join('\n');
-        error(message);
-    }
-}
-
-/**
- * Notify standard server-provided error messages.
- * @param {Object} responseErr
- */
-export function showResponseError(responseErr) {
-    if (!responseErr.status) return;
-    if (responseErr.status >= 400 && responseErr.data && responseErr.data.message) {
-        error(responseErr.data.message);
-    }
-}
diff --git a/resources/js/services/events.ts b/resources/js/services/events.ts
new file mode 100644 (file)
index 0000000..c251ee2
--- /dev/null
@@ -0,0 +1,71 @@
+export class EventManager {
+    protected listeners: Record<string, ((data: {}) => void)[]> = {};
+    protected stack: {name: string, data: {}}[] = [];
+
+    /**
+     * Emit a custom event for any handlers to pick-up.
+     */
+    emit(eventName: string, eventData: {}): void {
+        this.stack.push({name: eventName, data: eventData});
+
+        const listenersToRun = this.listeners[eventName] || [];
+        for (const listener of listenersToRun) {
+            listener(eventData);
+        }
+    }
+
+    /**
+     * Listen to a custom event and run the given callback when that event occurs.
+     */
+    listen(eventName: string, callback: (data: {}) => void): void {
+        if (typeof this.listeners[eventName] === 'undefined') this.listeners[eventName] = [];
+        this.listeners[eventName].push(callback);
+    }
+
+    /**
+     * Emit an event for public use.
+     * Sends the event via the native DOM event handling system.
+     */
+    emitPublic(targetElement: Element, eventName: string, eventData: {}): void {
+        const event = new CustomEvent(eventName, {
+            detail: eventData,
+            bubbles: true,
+        });
+        targetElement.dispatchEvent(event);
+    }
+
+    /**
+     * Emit a success event with the provided message.
+     */
+    success(message: string): void {
+        this.emit('success', message);
+    }
+
+    /**
+     * Emit an error event with the provided message.
+     */
+    error(message: string): void {
+        this.emit('error', message);
+    }
+
+    /**
+     * Notify of standard server-provided validation errors.
+     */
+    showValidationErrors(responseErr: {status?: number, data?: object}): void {
+        if (!responseErr.status) return;
+        if (responseErr.status === 422 && responseErr.data) {
+            const message = Object.values(responseErr.data).flat().join('\n');
+            this.error(message);
+        }
+    }
+
+    /**
+     * Notify standard server-provided error messages.
+     */
+    showResponseError(responseErr: {status?: number, data?: {message?: string}}): void {
+        if (!responseErr.status) return;
+        if (responseErr.status >= 400 && responseErr.data && responseErr.data.message) {
+            this.error(responseErr.data.message);
+        }
+    }
+}
index 67b5fb78062d113bfdd942158167057cdc276312..61b592ca023e3c38b429a60d6ddd188fa5077e73 100644 (file)
@@ -1,11 +1,16 @@
 # Lexical based editor todo
 
+## In progress
+
+- Add Type: Drawings
+  - Continue converting drawio to typescript
+  - Next step to convert http service to ts.
+
 ## Main Todo
 
 - Alignments: Use existing classes for blocks
 - Alignments: Handle inline block content (image, video)
 - Add Type: Video/media/embed
-- Add Type: Drawings
 - Handle toolbars on scroll
 - Table features
 - Image paste upload
@@ -20,6 +25,7 @@
 - Link heading-based ID reference menu
 - Image gallery integration for insert
 - Image gallery integration for form
+- Drawing gallery integration
 
 ## Bugs
 
index cfb2c6aefb232989b7836c20ac2f87a6e7645c6b..d6947ea756dffefef2f8c2c4458dbb99e9073ee8 100644 (file)
@@ -2,7 +2,6 @@ import {EditorDecorator} from "../framework/decorator";
 import {EditorUiContext} from "../framework/core";
 import {$openCodeEditorForNode, CodeBlockNode} from "../../nodes/code-block";
 import {$selectionContainsNode, $selectSingleNode} from "../../helpers";
-import {context} from "esbuild";
 import {BaseSelection} from "lexical";
 
 
index 2f092bd203e597ce7cb687ada12c0710662b2346..9c48f8c24727d37228ecba8ee970d281d847cc18 100644 (file)
@@ -1,12 +1,35 @@
 import {EditorDecorator} from "../framework/decorator";
 import {EditorUiContext} from "../framework/core";
+import {$selectionContainsNode, $selectSingleNode} from "../../helpers";
+import {$openCodeEditorForNode, CodeBlockNode} from "../../nodes/code-block";
+import {BaseSelection} from "lexical";
+import {$openDrawingEditorForNode, DiagramNode} from "../../nodes/diagram";
 
 
 export class DiagramDecorator extends EditorDecorator {
     protected completedSetup: boolean = false;
 
     setup(context: EditorUiContext, element: HTMLElement) {
-        //
+        const diagramNode = this.getNode();
+        element.addEventListener('click', event => {
+            context.editor.update(() => {
+                $selectSingleNode(this.getNode());
+            })
+        });
+
+        element.addEventListener('dblclick', event => {
+            context.editor.getEditorState().read(() => {
+                $openDrawingEditorForNode(context.editor, (this.getNode() as DiagramNode));
+            });
+        });
+
+        const selectionChange = (selection: BaseSelection|null): void => {
+            element.classList.toggle('selected', $selectionContainsNode(selection, diagramNode));
+        };
+        context.manager.onSelectionChange(selectionChange);
+        this.onDestroy(() => {
+            context.manager.offSelectionChange(selectionChange);
+        });
 
         this.completedSetup = true;
     }
index 3ca03da303d781cb1d8ce48f5e1de64462539eba..0be5421c7c8d9661b86a2a355a6315d4f33c48c1 100644 (file)
@@ -12,7 +12,7 @@
     // "disableReferencedProjectLoad": true,             /* Reduce the number of projects loaded automatically by TypeScript. */
 
     /* Language and Environment */
-    "target": "es2016",                                  /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
+    "target": "es2019",                                  /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
     // "lib": [],                                        /* Specify a set of bundled library declaration files that describe the target runtime environment. */
     // "jsx": "preserve",                                /* Specify what JSX code is generated. */
     // "experimentalDecorators": true,                   /* Enable experimental support for legacy experimental decorators. */