Desktop: Upgrade to TinyMCE v6 (#11652)

pull/11679/head
Henry Heino 2025-01-18 04:37:46 -08:00 committed by GitHub
parent cbf81d1257
commit 5782ee6ba1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 57 additions and 38 deletions

View File

@ -8,7 +8,6 @@
import { Node } from '@ephox/dom-globals';
import { Arr, Option } from '@ephox/katamari';
import { HTMLElement } from '@ephox/sand';
import DomQuery from 'tinymce/core/api/dom/DomQuery';
import Editor from 'tinymce/core/api/Editor';
import Tools from 'tinymce/core/api/util/Tools';
import * as NodeType from './NodeType';
@ -49,7 +48,7 @@ const findParentListItemsNodes = function (editor, elms) {
return parentLi ? parentLi : elm;
});
return DomQuery.unique(listItemsElms);
return [...new Set(listItemsElms)];
};
const getSelectedListItems = function (editor) {
@ -89,7 +88,7 @@ const getSelectedListRoots = (editor: Editor): Node[] => {
const getUniqueListRoots = (editor: Editor, lists: Node[]): Node[] => {
const listRoots = Arr.map(lists, (list) => findLastParentListNode(editor, list).getOr(list));
return DomQuery.unique(listRoots);
return [...new Set(listRoots)];
};
const isList = (editor: Editor): boolean => {

View File

@ -48,8 +48,7 @@ const listState = function (editor: Editor, listName, options:any = {}) {
const register = function (editor: Editor) {
const hasPlugin = function (editor, plugin) {
const plugins = editor.settings.plugins ? editor.settings.plugins : '';
return Tools.inArray(plugins.split(/[ ,]/), plugin) !== -1;
return editor.hasPlugin(plugin);
};
const _ = Settings.getLocalizationFunction(editor);

View File

@ -24,7 +24,7 @@ import { themeStyle } from '@joplin/lib/theme';
import { loadScript } from '../../../utils/loadScript';
import bridge from '../../../../services/bridge';
import { TinyMceEditorEvents } from './utils/types';
import type { Editor } from 'tinymce';
import type { Editor, EditorEvent } from 'tinymce';
import { joplinCommandToTinyMceCommands, TinyMceCommand } from './utils/joplinCommandToTinyMceCommands';
import shouldPasteResources from './utils/shouldPasteResources';
import lightTheme from '@joplin/lib/themes/light';
@ -412,9 +412,11 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
element.setAttribute('id', 'tinyMceStyle');
editorContainerDom.head.appendChild(element);
element.appendChild(editorContainerDom.createTextNode(`
.joplin-tinymce .tox-editor-header {
padding-left: ${styles.leftExtraToolbarContainer.width + styles.leftExtraToolbarContainer.padding * 2}px;
padding-right: ${styles.rightExtraToolbarContainer.width + styles.rightExtraToolbarContainer.padding * 2}px;
.joplin-tinymce .tox-editor-header.tox-editor-header {
margin-left: ${styles.leftExtraToolbarContainer.width + styles.leftExtraToolbarContainer.padding * 2}px;
margin-right: ${styles.rightExtraToolbarContainer.width + styles.rightExtraToolbarContainer.padding * 2}px;
padding: 0;
box-shadow: none;
}
.tox .tox-toolbar,
@ -434,7 +436,8 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
}
.tox .tox-dialog__body-content,
.tox .tox-collection__item {
.tox .tox-collection__item,
.tox .tox-insert-table-picker__label {
color: ${theme.color};
}
@ -473,7 +476,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
*/
.tox .tox-dialog textarea {
font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
font-family: Menlo, Monaco, Consolas, "Courier New", monospace !important;
}
.tox .tox-dialog-wrap__backdrop {
@ -498,6 +501,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
.tox .tox-toolbar-label {
color: ${theme.color3} !important;
fill: ${theme.color3} !important;
background: transparent;
}
.tox .tox-statusbar a,
@ -525,6 +529,11 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
background-color: ${theme.backgroundColor3}
}
.tox .tox-tbtn:focus-visible,
.tox .tox-split-button:focus-visible {
background-color: ${theme.backgroundColorHover3}
}
.tox .tox-tbtn:hover,
.tox .tox-menu button:hover > svg {
color: ${theme.colorHover3} !important;
@ -561,6 +570,12 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
box-shadow: none;
}
/* Decrease the spacing between groups */
.tox .tox-toolbar__group {
padding-left: 7px;
padding-right: 7px;
}
.tox-tinymce,
.tox .tox-toolbar__group,
.tox.tox-tinymce-aux .tox-toolbar__overflow,
@ -628,7 +643,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
useEffect(() => {
if (!editor) return;
editor.setMode(props.disabled ? 'readonly' : 'design');
editor.mode.set(props.disabled ? 'readonly' : 'design');
}, [editor, props.disabled]);
// -----------------------------------------------------------------------------------------
@ -675,14 +690,19 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
const containerWindow = editorContainerDom.defaultView as any;
const editors = await containerWindow.tinymce.init({
selector: `#${editorContainer.id}`,
// Ensures that the "Premium plugins" toolbar option is disabled. See
// https://www.tiny.cloud/docs/tinymce/latest/editor-premium-upgrade-promotion/
promotion: false,
width: '100%',
body_class: 'jop-tinymce',
height: '100%',
resize: false,
highlight_on_focus: false,
icons: 'Joplin',
icons_url: 'gui/NoteEditor/NoteBody/TinyMCE/icons.js',
plugins: 'noneditable link joplinLists hr searchreplace codesample table',
noneditable_noneditable_class: 'joplin-editable', // Can be a regex too
plugins: 'link joplinLists searchreplace codesample table',
noneditable_class: 'joplin-editable', // Can be a regex too
iframe_aria_text: _('Rich Text editor. Press Escape then Tab to escape focus.'),
// #p: Pad empty paragraphs with   to prevent them from being removed.
@ -694,7 +714,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
relative_urls: false,
branding: false,
statusbar: false,
target_list: false,
link_target_list: false,
// Handle the first table row as table header.
// https://www.tiny.cloud/docs/plugins/table/#table_header_type
table_header_type: 'sectionCells',
@ -704,13 +724,6 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
contextmenu: false,
browser_spellcheck: true,
// Work around an issue where images with a base64 SVG data URL would be broken.
//
// See https://github.com/tinymce/tinymce/issues/3864
//
// This was fixed in TinyMCE 6.1, so remove it when we upgrade.
images_dataimg_filter: (img: HTMLImageElement) => !img.src.startsWith('data:'),
formats: {
joplinHighlight: { inline: 'mark', remove: 'all' },
joplinStrikethrough: { inline: 's', remove: 'all' },
@ -747,14 +760,15 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
tooltip: _('Inline Code'),
icon: 'sourcecode',
onAction: function() {
editor.execCommand('mceToggleFormat', false, 'code', { class: 'inline-code' });
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
editor.execCommand('mceToggleFormat', false, 'code', { class: 'inline-code' } as any);
},
onSetup: function(api) {
api.setActive(editor.formatter.match('code'));
const unbind = editor.formatter.formatChanged('code', api.setActive).unbind;
const handle = editor.formatter.formatChanged('code', active => api.setActive(active));
return function() {
if (unbind) unbind();
handle?.unbind();
};
},
});
@ -1206,9 +1220,10 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
const onSetAttrib = (event: any) => {
const onSetAttrib = (event: EditorEvent<any>) => {
// Dispatch onChange when a link is edited
if (event.attrElm[0].nodeName === 'A') {
const target = Array.isArray(event.attrElm) ? event.attrElm[0] : event.attrElm;
if (target.nodeName === 'A') {
if (event.attrName === 'title' || event.attrName === 'href' || event.attrName === 'rel') {
onChangeHandler();
}

View File

@ -886,7 +886,7 @@
var parentLi = editor.dom.getParent(elm, 'li,dd,dt', getClosestListRootElm(editor, elm));
return parentLi ? parentLi : elm;
});
return DomQuery.unique(listItemsElms);
return [...new Set(listItemsElms)];
};
var getSelectedListItems = function (editor) {
var selectedBlocks = editor.selection.getSelectedBlocks();
@ -919,7 +919,7 @@
var listRoots = map(lists, function (list) {
return findLastParentListNode(editor, list).getOr(list);
});
return DomQuery.unique(listRoots);
return [...new Set(listRoots)];
};
var shouldIndentOnTab = function (editor) {
@ -2119,8 +2119,7 @@
};
var register$1 = function (editor) {
var hasPlugin = function (editor, plugin) {
var plugins = editor.settings.plugins ? editor.settings.plugins : '';
return Tools.inArray(plugins.split(/[ ,]/), plugin) !== -1;
return editor.hasPlugin(plugin);
};
var _ = getLocalizationFunction(editor);
var exec = function (command) {

View File

@ -7,6 +7,7 @@ import { Page } from '@playwright/test';
const createScanner = (page: Page) => {
return new AxeBuilder({ page })
.disableRules(['page-has-heading-one'])
// Needed because we're using Electron. See https://github.com/dequelabs/axe-core-npm/issues/1141
.setLegacyMode(true);
};
@ -63,6 +64,12 @@ test.describe('wcag', () => {
await mainScreen.noteEditor.noteTitleInput.hover();
await expectNoViolations(mainWindow);
// Should not find issues with the Rich Text Editor
await mainScreen.noteEditor.toggleEditorsButton.click();
await mainScreen.noteEditor.richTextEditor.click();
await expectNoViolations(mainWindow);
});
});

View File

@ -208,6 +208,6 @@
"styled-system": "5.1.5",
"taboverride": "4.0.3",
"tesseract.js": "5.1.0",
"tinymce": "5.10.6"
"tinymce": "6.8.5"
}
}

View File

@ -8340,7 +8340,7 @@ __metadata:
styled-system: 5.1.5
taboverride: 4.0.3
tesseract.js: 5.1.0
tinymce: 5.10.6
tinymce: 6.8.5
ts-jest: 29.1.5
ts-node: 10.9.2
typescript: 5.4.5
@ -45807,10 +45807,10 @@ __metadata:
languageName: node
linkType: hard
"tinymce@npm:5.10.6":
version: 5.10.6
resolution: "tinymce@npm:5.10.6"
checksum: 806cd733fde872f97d84b22ed267d2ee0404aa003d89b6c295b91867ac8b2bafc99542bf04924f253cf093c920a9da4b37a7ef73a00ff57b2b75490fb9705caf
"tinymce@npm:6.8.5":
version: 6.8.5
resolution: "tinymce@npm:6.8.5"
checksum: 7f7ad8dd2b117b8a671f97e41fc094935cfe4d4b525c90e97c6fdb480b19514e334f4360d89f34c04915a928e0cf5264fc1a60559554770452d6fce17b884940
languageName: node
linkType: hard