mirror of
https://github.com/laurent22/joplin.git
synced 2024-12-21 09:38:01 +02:00
This commit is contained in:
parent
72163018b4
commit
b94cf5a107
@ -299,6 +299,7 @@ packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/types.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useContextMenu.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useLinkTooltips.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useScroll.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useTabIndenter.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useWebViewApi.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteEditor.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteTitle/NoteTitleBar.js
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -276,6 +276,7 @@ packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/types.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useContextMenu.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useLinkTooltips.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useScroll.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useTabIndenter.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/useWebViewApi.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteEditor.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteTitle/NoteTitleBar.js
|
||||
|
@ -38,6 +38,7 @@ const md5 = require('md5');
|
||||
const { clipboard } = require('electron');
|
||||
const supportedLocales = require('./supportedLocales');
|
||||
import { hasProtocol } from '@joplin/utils/url';
|
||||
import useTabIndenter from './utils/useTabIndenter';
|
||||
|
||||
const logger = Logger.create('TinyMCE');
|
||||
|
||||
@ -128,6 +129,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
|
||||
|
||||
usePluginServiceRegistration(ref);
|
||||
useContextMenu(editor, props.plugins, props.dispatch, props.htmlToMarkdown, props.markupToHtml);
|
||||
useTabIndenter(editor);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||
const dispatchDidUpdate = (editor: any) => {
|
||||
@ -629,6 +631,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
|
||||
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
|
||||
iframe_aria_text: _('Rich Text editor. Press Escape then Tab to escape focus.'),
|
||||
|
||||
// #p: Pad empty paragraphs with to prevent them from being removed.
|
||||
// *[*]: Allow all elements and attributes -- we already filter in sanitize_html
|
||||
|
@ -0,0 +1,76 @@
|
||||
import { useEffect } from 'react';
|
||||
import type { Editor, EditorEvent } from 'tinymce';
|
||||
|
||||
const useTabIndenter = (editor: Editor) => {
|
||||
useEffect(() => {
|
||||
if (!editor) return () => {};
|
||||
|
||||
const canChangeIndentation = () => {
|
||||
const selectionElement = editor.selection.getNode();
|
||||
// List items and tables have their own tab key handlers.
|
||||
return !selectionElement.closest('li, table') && !editor.readonly;
|
||||
};
|
||||
|
||||
const getSpacesBeforeSelectionRange = (maxLength: number) => {
|
||||
const selectionRange = editor.selection.getRng();
|
||||
|
||||
let rangeStart = selectionRange.startOffset;
|
||||
let outputRange = selectionRange.cloneRange();
|
||||
while (rangeStart >= 0) {
|
||||
rangeStart--;
|
||||
|
||||
const lastRange = outputRange.cloneRange();
|
||||
outputRange.setStart(outputRange.startContainer, Math.max(rangeStart, 0));
|
||||
const rangeContent = outputRange.toString();
|
||||
const isWhitespace = rangeContent.match(/^\s*$/);
|
||||
if (!isWhitespace || rangeContent.length > maxLength) {
|
||||
outputRange = lastRange;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return outputRange;
|
||||
};
|
||||
|
||||
const indentLengthChars = 8;
|
||||
let indentHtml = '';
|
||||
for (let i = 0; i < indentLengthChars; i++) {
|
||||
indentHtml += ' ';
|
||||
}
|
||||
|
||||
let lastKeyWasEscape = false;
|
||||
|
||||
const eventHandler = (event: EditorEvent<KeyboardEvent>) => {
|
||||
if (!event.isDefaultPrevented() && event.key === 'Tab' && canChangeIndentation() && !lastKeyWasEscape) {
|
||||
if (!event.shiftKey) {
|
||||
editor.execCommand('mceInsertContent', false, indentHtml);
|
||||
event.preventDefault();
|
||||
} else {
|
||||
const selectionRange = editor.selection.getRng();
|
||||
if (selectionRange.collapsed) {
|
||||
const spacesRange = getSpacesBeforeSelectionRange(indentLengthChars);
|
||||
|
||||
const hasAtLeastOneSpace = spacesRange.toString().match(/^\s+$/);
|
||||
if (hasAtLeastOneSpace) {
|
||||
editor.selection.setRng(spacesRange);
|
||||
editor.execCommand('Delete', false);
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (event.key === 'Escape' && !event.shiftKey && !event.altKey && !event.metaKey && !event.ctrlKey) {
|
||||
// For accessibility, let Escape followed by tab escape the focus trap.
|
||||
lastKeyWasEscape = true;
|
||||
} else if (event.key !== 'Shift') { // Allows Esc->Shift+Tab to escape the focus trap.
|
||||
lastKeyWasEscape = false;
|
||||
}
|
||||
};
|
||||
|
||||
editor.on('keydown', eventHandler);
|
||||
return () => {
|
||||
editor.off('keydown', eventHandler);
|
||||
};
|
||||
}, [editor]);
|
||||
};
|
||||
|
||||
export default useTabIndenter;
|
@ -82,5 +82,42 @@ test.describe('richTextEditor', () => {
|
||||
expect(await openPathResult).toContain(basename(pathToAttach));
|
||||
});
|
||||
|
||||
test('pressing Tab should indent', async ({ mainWindow }) => {
|
||||
const mainScreen = new MainScreen(mainWindow);
|
||||
await mainScreen.createNewNote('Testing tabs!');
|
||||
const editor = mainScreen.noteEditor;
|
||||
|
||||
await editor.toggleEditorsButton.click();
|
||||
await editor.richTextEditor.click();
|
||||
|
||||
await mainWindow.keyboard.type('This is a');
|
||||
// Tab should add spaces
|
||||
await mainWindow.keyboard.press('Tab');
|
||||
await mainWindow.keyboard.type('test.');
|
||||
|
||||
// Shift-tab should remove spaces
|
||||
await mainWindow.keyboard.press('Tab');
|
||||
await mainWindow.keyboard.press('Tab');
|
||||
await mainWindow.keyboard.press('Shift+Tab');
|
||||
await mainWindow.keyboard.type('Test!');
|
||||
|
||||
// Escape then tab should move focus
|
||||
await mainWindow.keyboard.press('Escape');
|
||||
await expect(editor.richTextEditor).toBeFocused();
|
||||
await mainWindow.keyboard.press('Tab');
|
||||
await expect(editor.richTextEditor).not.toBeFocused();
|
||||
|
||||
// After re-focusing the editor, Tab should indent again.
|
||||
await mainWindow.keyboard.press('Shift+Tab');
|
||||
await expect(editor.richTextEditor).toBeFocused();
|
||||
await mainWindow.keyboard.type(' Another:');
|
||||
await mainWindow.keyboard.press('Tab');
|
||||
await mainWindow.keyboard.type('!');
|
||||
|
||||
// After switching back to the Markdown editor,
|
||||
await expect(editor.toggleEditorsButton).not.toBeDisabled();
|
||||
await editor.toggleEditorsButton.click();
|
||||
await expect(editor.codeMirrorEditor).toHaveText('This is a test. Test! Another: !');
|
||||
});
|
||||
});
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user