diff --git a/resources/js/wysiwyg/config.js b/resources/js/wysiwyg/config.js index 30dc6969d..e5a780d18 100644 --- a/resources/js/wysiwyg/config.js +++ b/resources/js/wysiwyg/config.js @@ -14,7 +14,11 @@ import {getPlugin as getAboutPlugin} from './plugins-about'; import {getPlugin as getDetailsPlugin} from './plugins-details'; import {getPlugin as getTableAdditionsPlugin} from './plugins-table-additions'; import {getPlugin as getTasklistPlugin} from './plugins-tasklist'; -import {handleClearFormattingOnTableCells, handleEmbedAlignmentChanges} from './fixes'; +import { + handleTableCellRangeEvents, + handleEmbedAlignmentChanges, + handleTextDirectionCleaning, +} from './fixes'; const styleFormats = [ {title: 'Large Header', format: 'h2', preview: 'color: blue;'}, @@ -37,9 +41,9 @@ const styleFormats = [ ]; const formats = { - alignleft: {selector: 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img,iframe,video,span', classes: 'align-left'}, - aligncenter: {selector: 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img,iframe,video,span', classes: 'align-center'}, - alignright: {selector: 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img,iframe,video,span', classes: 'align-right'}, + alignleft: {selector: 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img,iframe,video', classes: 'align-left'}, + aligncenter: {selector: 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img,iframe,video', classes: 'align-center'}, + alignright: {selector: 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img,iframe,video', classes: 'align-right'}, calloutsuccess: {block: 'p', exact: true, attributes: {class: 'callout success'}}, calloutinfo: {block: 'p', exact: true, attributes: {class: 'callout info'}}, calloutwarning: {block: 'p', exact: true, attributes: {class: 'callout warning'}}, @@ -194,7 +198,8 @@ function getSetupCallback(options) { }); handleEmbedAlignmentChanges(editor); - handleClearFormattingOnTableCells(editor); + handleTableCellRangeEvents(editor); + handleTextDirectionCleaning(editor); // Custom handler hook window.$events.emitPublic(options.containerElement, 'editor-tinymce::setup', {editor}); diff --git a/resources/js/wysiwyg/fixes.js b/resources/js/wysiwyg/fixes.js index e51c373d9..7f87d4378 100644 --- a/resources/js/wysiwyg/fixes.js +++ b/resources/js/wysiwyg/fixes.js @@ -55,16 +55,30 @@ 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 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"],[style*="text-align"]'); + for (const child of directionChildren) { + child.removeAttribute('dir'); + child.style.direction = null; + child.style.textAlign = null; + } + element.style.direction = null; + element.style.textAlign = null; +} + +/** + * 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,6 +89,10 @@ export function handleClearFormattingOnTableCells(editor) { selectedCells = []; }); + // 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. const attrsToRemove = ['class', 'style', 'width', 'height']; editor.on('FormatRemove', () => { for (const cell of selectedCells) { @@ -83,4 +101,41 @@ export function handleClearFormattingOnTableCells(editor) { } } }); + + // 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. + editor.on('ExecCommand', event => { + const command = event.command; + if (command !== 'mceDirectionLTR' && command !== 'mceDirectionRTL') { + return; + } + + const dir = command === 'mceDirectionLTR' ? 'ltr' : 'rtl'; + for (const cell of selectedCells) { + cell.setAttribute('dir', dir); + cleanElementDirection(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); + } + }); }