You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2025-07-06 23:56:13 +02:00
77 lines
2.4 KiB
TypeScript
77 lines
2.4 KiB
TypeScript
![]() |
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;
|