Skip to content

Commit

Permalink
Add undo/redo to inline editable texts
Browse files Browse the repository at this point in the history
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).
  • Loading branch information
tf committed Oct 17, 2024
1 parent 97ca969 commit 4d58b5a
Show file tree
Hide file tree
Showing 6 changed files with 63 additions and 6 deletions.
1 change: 1 addition & 0 deletions entry_types/scrolled/package/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,34 @@
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';

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';

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: [{
Expand All @@ -37,7 +48,7 @@ export const EditableInlineText = memo(function EditableInlineText({
spellCheck="false">
<Slate editor={editor} value={cachedValue} onChange={setCachedValue}>
<Editable decorate={decorateLineBreaks}
onKeyDown={handleLineBreaks}
onKeyDown={handleKeyDown}
renderLeaf={renderLeaf} />
</Slate>
<TextPlaceholder text={placeholder}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import {useCallback} from 'react';

export function useShortcutHandler(editor) {
return useCallback(event => {
if (!event.ctrlKey) {
return;
}

if (event.key === 'z') {
event.preventDefault()
editor.undo();
}
else if (event.key === 'y') {
event.preventDefault()
editor.redo();
}
}, [editor]);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -48,7 +49,9 @@ export const EditableText = React.memo(function EditableText({
{onlyParagraphs: !selectionRect},
withLineBreakNormalization(
withReact(
createEditor()
withHistory(
createEditor()
)
)
)
)
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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 = [];
}
Original file line number Diff line number Diff line change
Expand Up @@ -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');
}
Expand Down
8 changes: 8 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down

0 comments on commit 4d58b5a

Please sign in to comment.