WYSIWYG: Improved a range of text direction/alignment scenarios

- Removes 'span' from being a valid part of alignment formats so it's
  not used to align contents, since it's going to mostly be an inline
  format, wheras you'd really want alignment on the parent block.
- Adds direction cleaning to all direction change events, to remove
  direction styles and child direction controls which may complicate
  matters and cause direction changes not to show.
- Makes text direction controls work with table cell range selections,
  which TinyMCE does not consider by default, via manual handling.

For #4843
This commit is contained in:
Dan Brown 2024-02-20 14:15:22 +00:00
parent 16327cf40c
commit c290d01adb
No known key found for this signature in database
GPG Key ID: 46D9F943C24A2EF9
2 changed files with 71 additions and 11 deletions

View File

@ -14,7 +14,11 @@ import {getPlugin as getAboutPlugin} from './plugins-about';
import {getPlugin as getDetailsPlugin} from './plugins-details'; import {getPlugin as getDetailsPlugin} from './plugins-details';
import {getPlugin as getTableAdditionsPlugin} from './plugins-table-additions'; import {getPlugin as getTableAdditionsPlugin} from './plugins-table-additions';
import {getPlugin as getTasklistPlugin} from './plugins-tasklist'; import {getPlugin as getTasklistPlugin} from './plugins-tasklist';
import {handleClearFormattingOnTableCells, handleEmbedAlignmentChanges} from './fixes'; import {
handleTableCellRangeEvents,
handleEmbedAlignmentChanges,
handleTextDirectionCleaning,
} from './fixes';
const styleFormats = [ const styleFormats = [
{title: 'Large Header', format: 'h2', preview: 'color: blue;'}, {title: 'Large Header', format: 'h2', preview: 'color: blue;'},
@ -37,9 +41,9 @@ const styleFormats = [
]; ];
const formats = { 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'}, 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,span', classes: 'align-center'}, 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,span', classes: 'align-right'}, 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'}}, calloutsuccess: {block: 'p', exact: true, attributes: {class: 'callout success'}},
calloutinfo: {block: 'p', exact: true, attributes: {class: 'callout info'}}, calloutinfo: {block: 'p', exact: true, attributes: {class: 'callout info'}},
calloutwarning: {block: 'p', exact: true, attributes: {class: 'callout warning'}}, calloutwarning: {block: 'p', exact: true, attributes: {class: 'callout warning'}},
@ -194,7 +198,8 @@ function getSetupCallback(options) {
}); });
handleEmbedAlignmentChanges(editor); handleEmbedAlignmentChanges(editor);
handleClearFormattingOnTableCells(editor); handleTableCellRangeEvents(editor);
handleTextDirectionCleaning(editor);
// Custom handler hook // Custom handler hook
window.$events.emitPublic(options.containerElement, 'editor-tinymce::setup', {editor}); window.$events.emitPublic(options.containerElement, 'editor-tinymce::setup', {editor});

View File

@ -55,16 +55,30 @@ export function handleEmbedAlignmentChanges(editor) {
} }
/** /**
* TinyMCE does not seem to do a great job on clearing styles in complex * Cleans up the direction property for an element.
* scenarios (like copied word content) when a range of table cells * Removes all inline direction control from child elements.
* are selected. This tracks the selected table cells, and watches * Removes non "dir" attribute direction control from provided element.
* for clear formatting events, so some manual cleanup can be performed. * @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. * 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 * 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 * @param {Editor} editor
*/ */
export function handleClearFormattingOnTableCells(editor) { export function handleTableCellRangeEvents(editor) {
/** @var {HTMLTableCellElement[]} * */ /** @var {HTMLTableCellElement[]} * */
let selectedCells = []; let selectedCells = [];
@ -75,6 +89,10 @@ export function handleClearFormattingOnTableCells(editor) {
selectedCells = []; 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']; const attrsToRemove = ['class', 'style', 'width', 'height'];
editor.on('FormatRemove', () => { editor.on('FormatRemove', () => {
for (const cell of selectedCells) { 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);
}
});
} }