mirror of
https://github.com/BookStackApp/BookStack.git
synced 2024-10-01 01:36:00 -04:00
Lexical: Started converting drawio to TS
Converted events service to TS as part of this.
This commit is contained in:
parent
5002a89754
commit
634b0aaa07
@ -1,4 +1,4 @@
|
|||||||
import * as events from './services/events';
|
import {EventManager} from './services/events.ts';
|
||||||
import * as httpInstance from './services/http';
|
import * as httpInstance from './services/http';
|
||||||
import Translations from './services/translations';
|
import Translations from './services/translations';
|
||||||
import * as componentMap from './components';
|
import * as componentMap from './components';
|
||||||
@ -21,7 +21,7 @@ window.importVersioned = function importVersioned(moduleName) {
|
|||||||
|
|
||||||
// Set events and http services on window
|
// Set events and http services on window
|
||||||
window.$http = httpInstance;
|
window.$http = httpInstance;
|
||||||
window.$events = events;
|
window.$events = new EventManager();
|
||||||
|
|
||||||
// Translation setup
|
// Translation setup
|
||||||
// Creates a global function with name 'trans' to be used in the same way as the Laravel translation system
|
// Creates a global function with name 'trans' to be used in the same way as the Laravel translation system
|
||||||
|
2
resources/js/global.d.ts
vendored
2
resources/js/global.d.ts
vendored
@ -1,7 +1,9 @@
|
|||||||
import {ComponentStore} from "./services/components";
|
import {ComponentStore} from "./services/components";
|
||||||
|
import {EventManager} from "./services/events";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
$components: ComponentStore,
|
$components: ComponentStore,
|
||||||
|
$events: EventManager,
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,17 +1,31 @@
|
|||||||
// Docs: https://www.diagrams.net/doc/faq/embed-mode
|
// Docs: https://www.diagrams.net/doc/faq/embed-mode
|
||||||
import * as store from './store';
|
import * as store from './store';
|
||||||
|
import {ConfirmDialog} from "../components";
|
||||||
|
|
||||||
let iFrame = null;
|
type DrawioExportEventResponse = {
|
||||||
let lastApprovedOrigin;
|
action: 'export',
|
||||||
let onInit;
|
format: string,
|
||||||
let onSave;
|
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';
|
const saveBackupKey = 'last-drawing-save';
|
||||||
|
|
||||||
function drawPostMessage(data) {
|
function drawPostMessage(data: Record<any, any>): void {
|
||||||
iFrame.contentWindow.postMessage(JSON.stringify(data), lastApprovedOrigin);
|
iFrame?.contentWindow?.postMessage(JSON.stringify(data), lastApprovedOrigin);
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawEventExport(message) {
|
function drawEventExport(message: DrawioExportEventResponse) {
|
||||||
store.set(saveBackupKey, message.data);
|
store.set(saveBackupKey, message.data);
|
||||||
if (onSave) {
|
if (onSave) {
|
||||||
onSave(message.data).then(() => {
|
onSave(message.data).then(() => {
|
||||||
@ -20,7 +34,7 @@ function drawEventExport(message) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawEventSave(message) {
|
function drawEventSave(message: DrawioSaveEventResponse) {
|
||||||
drawPostMessage({
|
drawPostMessage({
|
||||||
action: 'export', format: 'xmlpng', xml: message.xml, spin: 'Updating drawing',
|
action: 'export', format: 'xmlpng', xml: message.xml, spin: 'Updating drawing',
|
||||||
});
|
});
|
||||||
@ -35,9 +49,11 @@ function drawEventInit() {
|
|||||||
|
|
||||||
function drawEventConfigure() {
|
function drawEventConfigure() {
|
||||||
const config = {};
|
const config = {};
|
||||||
|
if (iFrame) {
|
||||||
window.$events.emitPublic(iFrame, 'editor-drawio::configure', {config});
|
window.$events.emitPublic(iFrame, 'editor-drawio::configure', {config});
|
||||||
drawPostMessage({action: 'configure', config});
|
drawPostMessage({action: 'configure', config});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function drawEventClose() {
|
function drawEventClose() {
|
||||||
// eslint-disable-next-line no-use-before-define
|
// eslint-disable-next-line no-use-before-define
|
||||||
@ -47,9 +63,8 @@ function drawEventClose() {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Receive and handle a message event from the draw.io window.
|
* 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.data || event.data.length < 1) return;
|
||||||
if (event.origin !== lastApprovedOrigin) return;
|
if (event.origin !== lastApprovedOrigin) return;
|
||||||
|
|
||||||
@ -59,9 +74,9 @@ function drawReceive(event) {
|
|||||||
} else if (message.event === 'exit') {
|
} else if (message.event === 'exit') {
|
||||||
drawEventClose();
|
drawEventClose();
|
||||||
} else if (message.event === 'save') {
|
} else if (message.event === 'save') {
|
||||||
drawEventSave(message);
|
drawEventSave(message as DrawioSaveEventResponse);
|
||||||
} else if (message.event === 'export') {
|
} else if (message.event === 'export') {
|
||||||
drawEventExport(message);
|
drawEventExport(message as DrawioExportEventResponse);
|
||||||
} else if (message.event === 'configure') {
|
} else if (message.event === 'configure') {
|
||||||
drawEventConfigure();
|
drawEventConfigure();
|
||||||
}
|
}
|
||||||
@ -79,9 +94,8 @@ async function attemptRestoreIfExists() {
|
|||||||
console.error('Missing expected unsaved-drawing dialog');
|
console.error('Missing expected unsaved-drawing dialog');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (backupVal) {
|
if (backupVal && dialogEl) {
|
||||||
/** @var {ConfirmDialog} */
|
const dialog = window.$components.firstOnElement(dialogEl, 'confirm-dialog') as ConfirmDialog;
|
||||||
const dialog = window.$components.firstOnElement(dialogEl, 'confirm-dialog');
|
|
||||||
const restore = await dialog.show();
|
const restore = await dialog.show();
|
||||||
if (restore) {
|
if (restore) {
|
||||||
onInit = async () => backupVal;
|
onInit = async () => backupVal;
|
||||||
@ -94,11 +108,9 @@ async function attemptRestoreIfExists() {
|
|||||||
* onSaveCallback must return a promise that resolves on successful save and errors on failure.
|
* 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.
|
* 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.
|
* Will attempt to provide an option to restore unsaved changes if found to exist.
|
||||||
* @param {String} drawioUrl
|
* onSaveCallback Is called with the drawing data on save.
|
||||||
* @param {Function<Promise<String>>} onInitCallback
|
|
||||||
* @param {Function<Promise>} 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;
|
onInit = onInitCallback;
|
||||||
onSave = onSaveCallback;
|
onSave = onSaveCallback;
|
||||||
|
|
||||||
@ -114,7 +126,7 @@ export async function show(drawioUrl, onInitCallback, onSaveCallback) {
|
|||||||
lastApprovedOrigin = (new URL(drawioUrl)).origin;
|
lastApprovedOrigin = (new URL(drawioUrl)).origin;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function upload(imageData, pageUploadedToId) {
|
export async function upload(imageData: string, pageUploadedToId: string): Promise<{}|string> {
|
||||||
const data = {
|
const data = {
|
||||||
image: imageData,
|
image: imageData,
|
||||||
uploaded_to: pageUploadedToId,
|
uploaded_to: pageUploadedToId,
|
||||||
@ -129,10 +141,8 @@ export function close() {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Load an existing image, by fetching it as Base64 from the system.
|
* 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 {
|
try {
|
||||||
const resp = await window.$http.get(window.baseUrl(`/images/drawio/base64/${drawingId}`));
|
const resp = await window.$http.get(window.baseUrl(`/images/drawio/base64/${drawingId}`));
|
||||||
return `data:image/png;base64,${resp.data.content}`;
|
return `data:image/png;base64,${resp.data.content}`;
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
71
resources/js/services/events.ts
Normal file
71
resources/js/services/events.ts
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,11 +1,16 @@
|
|||||||
# Lexical based editor todo
|
# Lexical based editor todo
|
||||||
|
|
||||||
|
## In progress
|
||||||
|
|
||||||
|
- Add Type: Drawings
|
||||||
|
- Continue converting drawio to typescript
|
||||||
|
- Next step to convert http service to ts.
|
||||||
|
|
||||||
## Main Todo
|
## Main Todo
|
||||||
|
|
||||||
- Alignments: Use existing classes for blocks
|
- Alignments: Use existing classes for blocks
|
||||||
- Alignments: Handle inline block content (image, video)
|
- Alignments: Handle inline block content (image, video)
|
||||||
- Add Type: Video/media/embed
|
- Add Type: Video/media/embed
|
||||||
- Add Type: Drawings
|
|
||||||
- Handle toolbars on scroll
|
- Handle toolbars on scroll
|
||||||
- Table features
|
- Table features
|
||||||
- Image paste upload
|
- Image paste upload
|
||||||
@ -20,6 +25,7 @@
|
|||||||
- Link heading-based ID reference menu
|
- Link heading-based ID reference menu
|
||||||
- Image gallery integration for insert
|
- Image gallery integration for insert
|
||||||
- Image gallery integration for form
|
- Image gallery integration for form
|
||||||
|
- Drawing gallery integration
|
||||||
|
|
||||||
## Bugs
|
## Bugs
|
||||||
|
|
||||||
|
@ -2,7 +2,6 @@ import {EditorDecorator} from "../framework/decorator";
|
|||||||
import {EditorUiContext} from "../framework/core";
|
import {EditorUiContext} from "../framework/core";
|
||||||
import {$openCodeEditorForNode, CodeBlockNode} from "../../nodes/code-block";
|
import {$openCodeEditorForNode, CodeBlockNode} from "../../nodes/code-block";
|
||||||
import {$selectionContainsNode, $selectSingleNode} from "../../helpers";
|
import {$selectionContainsNode, $selectSingleNode} from "../../helpers";
|
||||||
import {context} from "esbuild";
|
|
||||||
import {BaseSelection} from "lexical";
|
import {BaseSelection} from "lexical";
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,12 +1,35 @@
|
|||||||
import {EditorDecorator} from "../framework/decorator";
|
import {EditorDecorator} from "../framework/decorator";
|
||||||
import {EditorUiContext} from "../framework/core";
|
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 {
|
export class DiagramDecorator extends EditorDecorator {
|
||||||
protected completedSetup: boolean = false;
|
protected completedSetup: boolean = false;
|
||||||
|
|
||||||
setup(context: EditorUiContext, element: HTMLElement) {
|
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;
|
this.completedSetup = true;
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
||||||
|
|
||||||
/* Language and Environment */
|
/* 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. */
|
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||||
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
||||||
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
|
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
|
||||||
|
Loading…
Reference in New Issue
Block a user