]> BookStack Code Mirror - bookstack/blobdiff - resources/js/wysiwyg/fixes.js
Actually add the test this time
[bookstack] / resources / js / wysiwyg / fixes.js
index e51c373d94f3306c5b477e618053a68d2568bf1f..b6d627ff79652b39a74a5d8a2b3986294597f8de 100644 (file)
@@ -29,12 +29,13 @@ export function handleEmbedAlignmentChanges(editor) {
 
     editor.on('FormatApply', event => {
         const isAlignment = event.format.startsWith('align');
-        if (!event.node || !event.node.matches('.mce-preview-object')) {
+        const isElement = event.node instanceof editor.dom.doc.defaultView.HTMLElement;
+        if (!isElement || !isAlignment || !event.node.matches('.mce-preview-object')) {
             return;
         }
 
         const realTarget = event.node.querySelector('iframe, video');
-        if (isAlignment && realTarget) {
+        if (realTarget) {
             const className = (editor.formatter.get(event.format)[0]?.classes || [])[0];
             const toAdd = !realTarget.classList.contains(className);
 
@@ -55,16 +56,50 @@ export function handleEmbedAlignmentChanges(editor) {
 }
 
 /**
- * TinyMCE does not seem to do a great job on clearing styles in complex
- * scenarios (like copied word content) when a range of table cells
- * are selected. This tracks the selected table cells, and watches
- * for clear formatting events, so some manual cleanup can be performed.
- *
+ * Cleans up and removes text-alignment specific properties on all child elements.
+ * @param {HTMLElement} element
+ */
+function cleanChildAlignment(element) {
+    const alignedChildren = element.querySelectorAll('[align],[style*="text-align"],.align-center,.align-left,.align-right');
+    for (const child of alignedChildren) {
+        child.removeAttribute('align');
+        child.style.textAlign = null;
+        child.classList.remove('align-center', 'align-right', 'align-left');
+    }
+}
+
+/**
+ * Cleans up the direction property for an element.
+ * Removes all inline direction control from child elements.
+ * Removes non "dir" attribute direction control from provided element.
+ * @param {HTMLElement} element
+ */
+function cleanElementDirection(element) {
+    const directionChildren = element.querySelectorAll('[dir],[style*="direction"]');
+    for (const child of directionChildren) {
+        child.removeAttribute('dir');
+        child.style.direction = null;
+    }
+
+    cleanChildAlignment(element);
+    element.style.direction = null;
+    element.style.textAlign = null;
+    element.removeAttribute('align');
+}
+
+/**
+ * @typedef {Function} TableCellHandler
+ * @param {HTMLTableCellElement} cell
+ */
+
+/**
+ * This tracks table cell range selection, so we can apply custom handling where
+ * required to actions applied to such selections.
  * The events used don't seem to be advertised by TinyMCE.
  * Found at https://github.com/tinymce/tinymce/blob/6.8.3/modules/tinymce/src/models/dom/main/ts/table/api/Events.ts
  * @param {Editor} editor
  */
-export function handleClearFormattingOnTableCells(editor) {
+export function handleTableCellRangeEvents(editor) {
     /** @var {HTMLTableCellElement[]} * */
     let selectedCells = [];
 
@@ -75,12 +110,72 @@ export function handleClearFormattingOnTableCells(editor) {
         selectedCells = [];
     });
 
-    const attrsToRemove = ['class', 'style', 'width', 'height'];
-    editor.on('FormatRemove', () => {
-        for (const cell of selectedCells) {
+    /**
+     * @type {Object<String, TableCellHandler>}
+     */
+    const actionByCommand = {
+        // TinyMCE does not seem to do a great job on clearing styles in complex
+        // scenarios (like copied word content) when a range of table cells
+        // are selected. Here we watch for clear formatting events, so some manual
+        // cleanup can be performed.
+        RemoveFormat: cell => {
+            const attrsToRemove = ['class', 'style', 'width', 'height', 'align'];
             for (const attr of attrsToRemove) {
                 cell.removeAttribute(attr);
             }
+        },
+
+        // TinyMCE does not apply direction events to table cell range selections
+        // so here we hastily patch in that ability by setting the direction ourselves
+        // when a direction event is fired.
+        mceDirectionLTR: cell => {
+            cell.setAttribute('dir', 'ltr');
+            cleanElementDirection(cell);
+        },
+        mceDirectionRTL: cell => {
+            cell.setAttribute('dir', 'rtl');
+            cleanElementDirection(cell);
+        },
+
+        // The "align" attribute can exist on table elements so this clears
+        // the attribute, and also clears common child alignment properties,
+        // when a text direction action is made for a table cell range.
+        JustifyLeft: cell => {
+            cell.removeAttribute('align');
+            cleanChildAlignment(cell);
+        },
+        JustifyRight: this.JustifyLeft,
+        JustifyCenter: this.JustifyLeft,
+        JustifyFull: this.JustifyLeft,
+    };
+
+    editor.on('ExecCommand', event => {
+        const action = actionByCommand[event.command];
+        if (action) {
+            for (const cell of selectedCells) {
+                action(cell);
+            }
+        }
+    });
+}
+
+/**
+ * Direction control might not work if there are other unexpected direction-handling styles
+ * or attributes involved nearby. This watches for direction change events to clean
+ * up direction controls, removing non-dir-attr direction controls, while removing
+ * directions from child elements that may be involved.
+ * @param {Editor} editor
+ */
+export function handleTextDirectionCleaning(editor) {
+    editor.on('ExecCommand', event => {
+        const command = event.command;
+        if (command !== 'mceDirectionLTR' && command !== 'mceDirectionRTL') {
+            return;
+        }
+
+        const blocks = editor.selection.getSelectedBlocks();
+        for (const block of blocks) {
+            cleanElementDirection(block);
         }
     });
 }