]> BookStack Code Mirror - bookstack/blobdiff - resources/js/wysiwyg/ui/framework/manager.ts
Perms: Removed entity perm regen on general update
[bookstack] / resources / js / wysiwyg / ui / framework / manager.ts
index 92891b5408466146f1085d1a33401bba86cbe59e..2d15b341bdba6ae9a39e9236c77e5a6742854c4f 100644 (file)
@@ -1,10 +1,12 @@
 import {EditorFormModal, EditorFormModalDefinition} from "./modals";
 import {EditorContainerUiElement, EditorUiContext, EditorUiElement, EditorUiStateUpdate} from "./core";
 import {EditorDecorator, EditorDecoratorAdapter} from "./decorator";
-import {$getSelection, BaseSelection, COMMAND_PRIORITY_LOW, LexicalEditor, SELECTION_CHANGE_COMMAND} from "lexical";
+import {$getSelection, BaseSelection, LexicalEditor} from "lexical";
 import {DecoratorListener} from "lexical/LexicalEditor";
 import type {NodeKey} from "lexical/LexicalNode";
 import {EditorContextToolbar, EditorContextToolbarDefinition} from "./toolbars";
+import {getLastSelection, setLastSelection} from "../../utils/selection";
+import {DropDownManager} from "./helpers/dropdowns";
 
 export type SelectionChangeHandler = (selection: BaseSelection|null) => void;
 
@@ -20,6 +22,8 @@ export class EditorUIManager {
     protected activeContextToolbars: EditorContextToolbar[] = [];
     protected selectionChangeHandlers: Set<SelectionChangeHandler> = new Set();
 
+    public dropdowns: DropDownManager = new DropDownManager();
+
     setContext(context: EditorUiContext) {
         this.context = context;
         this.setupEventListeners(context);
@@ -107,9 +111,8 @@ export class EditorUIManager {
         this.contextToolbarDefinitionsByKey[key] = definition;
     }
 
-    protected triggerStateUpdate(update: EditorUiStateUpdate): void {
-        const context = this.getContext();
-        context.lastSelection = update.selection;
+    triggerStateUpdate(update: EditorUiStateUpdate): void {
+        setLastSelection(update.editor, update.selection);
         this.toolbar?.updateState(update);
         this.updateContextToolbars(update);
         for (const toolbar of this.activeContextToolbars) {
@@ -119,9 +122,21 @@ export class EditorUIManager {
     }
 
     triggerStateRefresh(): void {
-        this.triggerStateUpdate({
-            editor: this.getContext().editor,
-            selection: this.getContext().lastSelection,
+        const editor = this.getContext().editor;
+        const update = {
+            editor,
+            selection: getLastSelection(editor),
+        };
+
+        this.triggerStateUpdate(update);
+        this.updateContextToolbars(update);
+    }
+
+    triggerFutureStateRefresh(): void {
+        requestAnimationFrame(() => {
+            this.getContext().editor.getEditorState().read(() => {
+                this.triggerStateRefresh();
+            });
         });
     }
 
@@ -143,6 +158,18 @@ export class EditorUIManager {
         this.selectionChangeHandlers.delete(handler);
     }
 
+    triggerLayoutUpdate(): void {
+        window.requestAnimationFrame(() => {
+            for (const toolbar of this.activeContextToolbars) {
+                toolbar.updatePosition();
+            }
+        });
+    }
+
+    getDefaultDirection(): 'rtl' | 'ltr' {
+        return this.getContext().options.textDirection === 'rtl' ? 'rtl' : 'ltr';
+    }
+
     protected updateContextToolbars(update: EditorUiStateUpdate): void {
         for (let i = this.activeContextToolbars.length - 1; i >= 0; i--) {
             const toolbar = this.activeContextToolbars[i];
@@ -186,15 +213,6 @@ export class EditorUIManager {
     }
 
     protected setupEditor(editor: LexicalEditor) {
-        // Update button states on editor selection change
-        editor.registerCommand(SELECTION_CHANGE_COMMAND, () => {
-            this.triggerStateUpdate({
-                editor: editor,
-                selection: $getSelection(),
-            });
-            return false;
-        }, COMMAND_PRIORITY_LOW);
-
         // Register our DOM decorate listener with the editor
         const domDecorateListener: DecoratorListener<EditorDecoratorAdapter> = (decorators: Record<NodeKey, EditorDecoratorAdapter>) => {
             editor.getEditorState().read(() => {
@@ -216,16 +234,28 @@ export class EditorUIManager {
             });
         }
         editor.registerDecoratorListener(domDecorateListener);
-    }
 
-    protected setupEventListeners(context: EditorUiContext) {
-        const updateToolbars = (event: Event) => {
-            for (const toolbar of this.activeContextToolbars) {
-                toolbar.updatePosition();
+        // Watch for changes to update local state
+        editor.registerUpdateListener(({editorState, prevEditorState}) => {
+            // Watch for selection changes to update the UI on change
+            // Used to be done via SELECTION_CHANGE_COMMAND but this would not always emit
+            // for all selection changes, so this proved more reliable.
+            const selectionChange = !(prevEditorState._selection?.is(editorState._selection) || false);
+            if (selectionChange) {
+                editor.update(() => {
+                    const selection = $getSelection();
+                    // console.log('manager::selection', selection);
+                    this.triggerStateUpdate({
+                        editor, selection,
+                    });
+                });
             }
-        };
+        });
+    }
 
-        window.addEventListener('scroll', updateToolbars, {capture: true, passive: true});
-        window.addEventListener('resize', updateToolbars, {passive: true});
+    protected setupEventListeners(context: EditorUiContext) {
+        const layoutUpdate = this.triggerLayoutUpdate.bind(this);
+        window.addEventListener('scroll', layoutUpdate, {capture: true, passive: true});
+        window.addEventListener('resize', layoutUpdate, {passive: true});
     }
 }
\ No newline at end of file