From 4d58b5ac0881af6241a77f72c71f52c6743e440c Mon Sep 17 00:00:00 2001 From: Tim Fischbach Date: Thu, 17 Oct 2024 16:23:16 +0200 Subject: [PATCH] Add undo/redo to inline editable texts While it would be nice to fully support undo/redo for all editor actions, inline editable text looks like a good place to start. For text blocks we reset the undo history when the value is altered from the outside (e.g., when splitting or merging text blocks by moving elements). --- entry_types/scrolled/package/package.json | 1 + .../inlineEditing/EditableInlineText/index.js | 17 ++++++++++++++--- .../EditableInlineText/shortcuts.js | 18 ++++++++++++++++++ .../inlineEditing/EditableText/index.js | 15 +++++++++++++-- .../inlineEditing/EditableText/shortcuts.js | 10 +++++++++- yarn.lock | 8 ++++++++ 6 files changed, 63 insertions(+), 6 deletions(-) create mode 100644 entry_types/scrolled/package/src/frontend/inlineEditing/EditableInlineText/shortcuts.js diff --git a/entry_types/scrolled/package/package.json b/entry_types/scrolled/package/package.json index ca1026fb54..ceae367a18 100644 --- a/entry_types/scrolled/package/package.json +++ b/entry_types/scrolled/package/package.json @@ -30,6 +30,7 @@ "screenfull": "^5.1.0", "scroll-timeline": "https://github.com/tf/scroll-timeline#pageflow-scrolled-2", "slate": "^0.57.3", + "slate-history": "^0.57.3", "slate-react": "^0.57.3", "slugify": "^1.4.6", "striptags": "^3.2.0", diff --git a/entry_types/scrolled/package/src/frontend/inlineEditing/EditableInlineText/index.js b/entry_types/scrolled/package/src/frontend/inlineEditing/EditableInlineText/index.js index 76f2379ece..faaee50dbf 100644 --- a/entry_types/scrolled/package/src/frontend/inlineEditing/EditableInlineText/index.js +++ b/entry_types/scrolled/package/src/frontend/inlineEditing/EditableInlineText/index.js @@ -1,7 +1,8 @@ -import React, {memo, useMemo} from 'react'; +import React, {memo, useCallback, useMemo} from 'react'; import classNames from 'classnames'; import {createEditor} from 'slate'; import {Slate, Editable, withReact} from 'slate-react'; +import {withHistory} from 'slate-history'; import {TextPlaceholder} from '../TextPlaceholder'; import {useCachedValue} from '../useCachedValue'; @@ -9,6 +10,7 @@ import {useCachedValue} from '../useCachedValue'; import {useContentElementEditorState} from '../../useContentElementEditorState'; import {decorateLineBreaks, useLineBreakHandler, withLineBreakNormalization} from './lineBreaks' +import {useShortcutHandler} from './shortcuts'; import styles from './index.module.css'; import frontendStyles from '../../EditableInlineText.module.css'; @@ -16,8 +18,17 @@ import frontendStyles from '../../EditableInlineText.module.css'; export const EditableInlineText = memo(function EditableInlineText({ value, defaultValue = '', hyphens, placeholder, onChange }) { - const editor = useMemo(() => withLineBreakNormalization(withReact(createEditor())), []); + const editor = useMemo( + () => withLineBreakNormalization(withReact(withHistory(createEditor()))), + [] + ); const handleLineBreaks = useLineBreakHandler(editor); + const handleShortcuts = useShortcutHandler(editor); + + const handleKeyDown = useCallback(event => { + handleLineBreaks(event); + handleShortcuts(event); + }, [handleLineBreaks, handleShortcuts]); const [cachedValue, setCachedValue] = useCachedValue(value, { defaultValue: [{ @@ -37,7 +48,7 @@ export const EditableInlineText = memo(function EditableInlineText({ spellCheck="false"> { + if (!event.ctrlKey) { + return; + } + + if (event.key === 'z') { + event.preventDefault() + editor.undo(); + } + else if (event.key === 'y') { + event.preventDefault() + editor.redo(); + } + }, [editor]); +} diff --git a/entry_types/scrolled/package/src/frontend/inlineEditing/EditableText/index.js b/entry_types/scrolled/package/src/frontend/inlineEditing/EditableText/index.js index e2382f640a..6f5b8697db 100644 --- a/entry_types/scrolled/package/src/frontend/inlineEditing/EditableText/index.js +++ b/entry_types/scrolled/package/src/frontend/inlineEditing/EditableText/index.js @@ -2,6 +2,7 @@ import React, {useCallback, useMemo, useEffect} from 'react'; import classNames from 'classnames'; import {createEditor, Transforms, Node, Text as SlateText, Range} from 'slate'; import {Slate, Editable, withReact, ReactEditor} from 'slate-react'; +import {withHistory} from 'slate-history'; import {Text} from '../../Text'; import {useCachedValue} from '../useCachedValue'; @@ -48,7 +49,9 @@ export const EditableText = React.memo(function EditableText({ {onlyParagraphs: !selectionRect}, withLineBreakNormalization( withReact( - createEditor() + withHistory( + createEditor() + ) ) ) ) @@ -77,7 +80,10 @@ export const EditableText = React.memo(function EditableText({ children: [{ text: '' }], }], onDebouncedChange: onChange, - onReset: nextValue => resetSelectionIfOutsideNextValue(editor, nextValue) + onReset: nextValue => { + resetSelectionIfOutsideNextValue(editor, nextValue); + resetHistory(editor); + } }); const {isSelected} = useContentElementEditorState(); @@ -148,3 +154,8 @@ function hasTextAtPoint(editor, point) { const node = Node.get(editor, point.path); return SlateText.isText(node) && point.offset <= node.text.length; } + +function resetHistory(editor) { + editor.history.undos = []; + editor.history.redos = []; +} diff --git a/entry_types/scrolled/package/src/frontend/inlineEditing/EditableText/shortcuts.js b/entry_types/scrolled/package/src/frontend/inlineEditing/EditableText/shortcuts.js index 30a689b382..31045d0c48 100644 --- a/entry_types/scrolled/package/src/frontend/inlineEditing/EditableText/shortcuts.js +++ b/entry_types/scrolled/package/src/frontend/inlineEditing/EditableText/shortcuts.js @@ -7,7 +7,15 @@ export function useShortcutHandler(editor) { return; } - if (event.key === 'b') { + if (event.key === 'z') { + event.preventDefault() + editor.undo(); + } + else if (event.key === 'y') { + event.preventDefault() + editor.redo(); + } + else if (event.key === 'b') { event.preventDefault() toggleMark(editor, 'bold'); } diff --git a/yarn.lock b/yarn.lock index f3d1b113b0..3e8113264e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11720,6 +11720,14 @@ slash@^3.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== +slate-history@^0.57.3: + version "0.57.3" + resolved "https://registry.yarnpkg.com/slate-history/-/slate-history-0.57.3.tgz#22e8263610d53d5753c8ede2cc90394c93fb6727" + integrity sha512-8vqjLWAsX79117uhfMPqDPXgi7QnFXSZjjSJotzhMRnuqYUw9RneWf/VGVKmsJXZ8JliaQ+QFYEd3EctvmDOlQ== + dependencies: + immer "^5.0.0" + is-plain-object "^3.0.0" + slate-hyperscript@^0.57.3: version "0.57.3" resolved "https://registry.yarnpkg.com/slate-hyperscript/-/slate-hyperscript-0.57.3.tgz#69462c2f436f75f0c17d9a727514402fc8abbf3c"