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 { Node } from '@ephox/dom-globals';
import { Arr, Option } from '@ephox/katamari'; import { Arr, Option } from '@ephox/katamari';
import { HTMLElement } from '@ephox/sand'; import { HTMLElement } from '@ephox/sand';
import DomQuery from 'tinymce/core/api/dom/DomQuery';
import Editor from 'tinymce/core/api/Editor'; import Editor from 'tinymce/core/api/Editor';
import Tools from 'tinymce/core/api/util/Tools'; import Tools from 'tinymce/core/api/util/Tools';
import * as NodeType from './NodeType'; import * as NodeType from './NodeType';
@ -49,7 +48,7 @@ const findParentListItemsNodes = function (editor, elms) {
return parentLi ? parentLi : elm; return parentLi ? parentLi : elm;
}); });
return DomQuery.unique(listItemsElms); return [...new Set(listItemsElms)];
}; };
const getSelectedListItems = function (editor) { const getSelectedListItems = function (editor) {
@ -89,7 +88,7 @@ const getSelectedListRoots = (editor: Editor): Node[] => {
const getUniqueListRoots = (editor: Editor, lists: Node[]): Node[] => { const getUniqueListRoots = (editor: Editor, lists: Node[]): Node[] => {
const listRoots = Arr.map(lists, (list) => findLastParentListNode(editor, list).getOr(list)); const listRoots = Arr.map(lists, (list) => findLastParentListNode(editor, list).getOr(list));
return DomQuery.unique(listRoots); return [...new Set(listRoots)];
}; };
const isList = (editor: Editor): boolean => { 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 register = function (editor: Editor) {
const hasPlugin = function (editor, plugin) { const hasPlugin = function (editor, plugin) {
const plugins = editor.settings.plugins ? editor.settings.plugins : ''; return editor.hasPlugin(plugin);
return Tools.inArray(plugins.split(/[ ,]/), plugin) !== -1;
}; };
const _ = Settings.getLocalizationFunction(editor); const _ = Settings.getLocalizationFunction(editor);

View File

@ -24,7 +24,7 @@ import { themeStyle } from '@joplin/lib/theme';
import { loadScript } from '../../../utils/loadScript'; import { loadScript } from '../../../utils/loadScript';
import bridge from '../../../../services/bridge'; import bridge from '../../../../services/bridge';
import { TinyMceEditorEvents } from './utils/types'; import { TinyMceEditorEvents } from './utils/types';
import type { Editor } from 'tinymce'; import type { Editor, EditorEvent } from 'tinymce';
import { joplinCommandToTinyMceCommands, TinyMceCommand } from './utils/joplinCommandToTinyMceCommands'; import { joplinCommandToTinyMceCommands, TinyMceCommand } from './utils/joplinCommandToTinyMceCommands';
import shouldPasteResources from './utils/shouldPasteResources'; import shouldPasteResources from './utils/shouldPasteResources';
import lightTheme from '@joplin/lib/themes/light'; import lightTheme from '@joplin/lib/themes/light';
@ -412,9 +412,11 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
element.setAttribute('id', 'tinyMceStyle'); element.setAttribute('id', 'tinyMceStyle');
editorContainerDom.head.appendChild(element); editorContainerDom.head.appendChild(element);
element.appendChild(editorContainerDom.createTextNode(` element.appendChild(editorContainerDom.createTextNode(`
.joplin-tinymce .tox-editor-header { .joplin-tinymce .tox-editor-header.tox-editor-header {
padding-left: ${styles.leftExtraToolbarContainer.width + styles.leftExtraToolbarContainer.padding * 2}px; margin-left: ${styles.leftExtraToolbarContainer.width + styles.leftExtraToolbarContainer.padding * 2}px;
padding-right: ${styles.rightExtraToolbarContainer.width + styles.rightExtraToolbarContainer.padding * 2}px; margin-right: ${styles.rightExtraToolbarContainer.width + styles.rightExtraToolbarContainer.padding * 2}px;
padding: 0;
box-shadow: none;
} }
.tox .tox-toolbar, .tox .tox-toolbar,
@ -434,7 +436,8 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
} }
.tox .tox-dialog__body-content, .tox .tox-dialog__body-content,
.tox .tox-collection__item { .tox .tox-collection__item,
.tox .tox-insert-table-picker__label {
color: ${theme.color}; color: ${theme.color};
} }
@ -473,7 +476,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
*/ */
.tox .tox-dialog textarea { .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 { .tox .tox-dialog-wrap__backdrop {
@ -498,6 +501,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
.tox .tox-toolbar-label { .tox .tox-toolbar-label {
color: ${theme.color3} !important; color: ${theme.color3} !important;
fill: ${theme.color3} !important; fill: ${theme.color3} !important;
background: transparent;
} }
.tox .tox-statusbar a, .tox .tox-statusbar a,
@ -524,6 +528,11 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
.tox .tox-split-button:focus { .tox .tox-split-button:focus {
background-color: ${theme.backgroundColor3} 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-tbtn:hover,
.tox .tox-menu button:hover > svg { .tox .tox-menu button:hover > svg {
@ -560,6 +569,12 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
.tox .tox-split-button:hover { .tox .tox-split-button:hover {
box-shadow: none; box-shadow: none;
} }
/* Decrease the spacing between groups */
.tox .tox-toolbar__group {
padding-left: 7px;
padding-right: 7px;
}
.tox-tinymce, .tox-tinymce,
.tox .tox-toolbar__group, .tox .tox-toolbar__group,
@ -628,7 +643,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
useEffect(() => { useEffect(() => {
if (!editor) return; if (!editor) return;
editor.setMode(props.disabled ? 'readonly' : 'design'); editor.mode.set(props.disabled ? 'readonly' : 'design');
}, [editor, props.disabled]); }, [editor, props.disabled]);
// ----------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------
@ -675,14 +690,19 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
const containerWindow = editorContainerDom.defaultView as any; const containerWindow = editorContainerDom.defaultView as any;
const editors = await containerWindow.tinymce.init({ const editors = await containerWindow.tinymce.init({
selector: `#${editorContainer.id}`, 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%', width: '100%',
body_class: 'jop-tinymce', body_class: 'jop-tinymce',
height: '100%', height: '100%',
resize: false, resize: false,
highlight_on_focus: false,
icons: 'Joplin', icons: 'Joplin',
icons_url: 'gui/NoteEditor/NoteBody/TinyMCE/icons.js', icons_url: 'gui/NoteEditor/NoteBody/TinyMCE/icons.js',
plugins: 'noneditable link joplinLists hr searchreplace codesample table', plugins: 'link joplinLists searchreplace codesample table',
noneditable_noneditable_class: 'joplin-editable', // Can be a regex too noneditable_class: 'joplin-editable', // Can be a regex too
iframe_aria_text: _('Rich Text editor. Press Escape then Tab to escape focus.'), iframe_aria_text: _('Rich Text editor. Press Escape then Tab to escape focus.'),
// #p: Pad empty paragraphs with   to prevent them from being removed. // #p: Pad empty paragraphs with   to prevent them from being removed.
@ -694,7 +714,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
relative_urls: false, relative_urls: false,
branding: false, branding: false,
statusbar: false, statusbar: false,
target_list: false, link_target_list: false,
// Handle the first table row as table header. // Handle the first table row as table header.
// https://www.tiny.cloud/docs/plugins/table/#table_header_type // https://www.tiny.cloud/docs/plugins/table/#table_header_type
table_header_type: 'sectionCells', table_header_type: 'sectionCells',
@ -704,13 +724,6 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
contextmenu: false, contextmenu: false,
browser_spellcheck: true, 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: { formats: {
joplinHighlight: { inline: 'mark', remove: 'all' }, joplinHighlight: { inline: 'mark', remove: 'all' },
joplinStrikethrough: { inline: 's', remove: 'all' }, joplinStrikethrough: { inline: 's', remove: 'all' },
@ -747,14 +760,15 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
tooltip: _('Inline Code'), tooltip: _('Inline Code'),
icon: 'sourcecode', icon: 'sourcecode',
onAction: function() { 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) { onSetup: function(api) {
api.setActive(editor.formatter.match('code')); 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() { 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 // 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 // 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') { if (event.attrName === 'title' || event.attrName === 'href' || event.attrName === 'rel') {
onChangeHandler(); onChangeHandler();
} }

View File

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

View File

@ -7,6 +7,7 @@ import { Page } from '@playwright/test';
const createScanner = (page: Page) => { const createScanner = (page: Page) => {
return new AxeBuilder({ page }) return new AxeBuilder({ page })
.disableRules(['page-has-heading-one']) .disableRules(['page-has-heading-one'])
// Needed because we're using Electron. See https://github.com/dequelabs/axe-core-npm/issues/1141
.setLegacyMode(true); .setLegacyMode(true);
}; };
@ -63,6 +64,12 @@ test.describe('wcag', () => {
await mainScreen.noteEditor.noteTitleInput.hover(); await mainScreen.noteEditor.noteTitleInput.hover();
await expectNoViolations(mainWindow); 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", "styled-system": "5.1.5",
"taboverride": "4.0.3", "taboverride": "4.0.3",
"tesseract.js": "5.1.0", "tesseract.js": "5.1.0",
"tinymce": "5.10.6" "tinymce": "6.8.5"
} }
} }

View File

@ -158,4 +158,4 @@ ldaps
Bluesky Bluesky
Tebi Tebi
unwatcher unwatcher
pedr pedr

View File

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