Converted events service to TS as part of this.
-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';
// 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
import {ComponentStore} from "./services/components";
+import {EventManager} from "./services/events";
declare global {
interface Window {
$components: ComponentStore,
+ $events: EventManager,
}
}
\ No newline at end of file
// 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(() => {
}
}
-function drawEventSave(message) {
+function drawEventSave(message: DrawioSaveEventResponse) {
drawPostMessage({
action: 'export', format: 'xmlpng', xml: message.xml, spin: 'Updating drawing',
});
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() {
/**
* 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;
} 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();
}
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;
* 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;
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,
/**
* 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}`;
+++ /dev/null
-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);
- }
-}
--- /dev/null
+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);
+ }
+ }
+}
# 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
- Link heading-based ID reference menu
- Image gallery integration for insert
- Image gallery integration for form
+- Drawing gallery integration
## Bugs
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";
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;
}
// "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. */