UNPKG

@plone/volto

Version:
203 lines (181 loc) 4.99 kB
import React, { useCallback, useEffect, useMemo, useRef, useState, } from 'react'; import { Editor, Node, Transforms, Range, createEditor } from 'slate'; import { ReactEditor, Editable, Slate, withReact } from 'slate-react'; import PropTypes from 'prop-types'; import { defineMessages, useIntl } from 'react-intl'; import config from '@plone/volto/registry'; import { P } from '@plone/volto-slate/constants'; import cx from 'classnames'; const messages = defineMessages({ description: { id: 'Add a description…', defaultMessage: 'Add a description…', }, }); function usePrevious(value) { const ref = useRef(); useEffect(() => { ref.current = value; }, [value]); return ref.current; } export const DescriptionBlockEdit = (props) => { const { block, blockNode, data, detached, editable, index, metadata, onAddBlock, onChangeField, onDeleteBlock, onFocusNextBlock, onFocusPreviousBlock, onSelectBlock, properties, selected, } = props; const [editor] = useState(withReact(createEditor())); const [initialValue] = useState([ { type: P, children: [ { text: metadata?.['description'] || properties?.['description'] || '', }, ], }, ]); const intl = useIntl(); const prevSelected = usePrevious(selected); const text = useMemo( () => metadata?.['description'] || properties?.['description'] || '', [metadata, properties], ); const placeholder = useMemo( () => data.placeholder || intl.formatMessage(messages['description']), [data.placeholder, intl], ); const disableNewBlocks = useMemo(() => detached, [detached]); useEffect(() => { if (!prevSelected && selected) { if (editor.selection && Range.isCollapsed(editor.selection)) { // keep selection ReactEditor.focus(editor); } else { // nothing is selected, move focus to end ReactEditor.focus(editor); Transforms.select(editor, Editor.end(editor, [])); } } }, [prevSelected, selected, editor]); useEffect(() => { // undo/redo handlerr const oldText = Node.string(editor); if (oldText !== text) { Transforms.insertText(editor, text, { at: [0, 0], }); } }, [editor, text]); const handleChange = useCallback(() => { const newText = Node.string(editor); if (newText !== text) { onChangeField('description', newText); } }, [editor, onChangeField, text]); const handleKeyDown = useCallback( (ev) => { if (ev.key === 'Backspace' && Node.string(editor).length === 0) { ev.preventDefault(); onDeleteBlock(block, true); } else if (ev.key === 'Return' || ev.key === 'Enter') { ev.preventDefault(); if (!disableNewBlocks) { onSelectBlock( onAddBlock(config.settings.defaultBlockType, index + 1), ); } } else if (ev.key === 'ArrowUp') { ev.preventDefault(); onFocusPreviousBlock(block, blockNode.current); } else if (ev.key === 'ArrowDown') { ev.preventDefault(); onFocusNextBlock(block, blockNode.current); } }, [ index, blockNode, editor, onDeleteBlock, disableNewBlocks, onSelectBlock, onAddBlock, onFocusPreviousBlock, onFocusNextBlock, block, ], ); const handleFocus = useCallback(() => { onSelectBlock(block); }, [block, onSelectBlock]); const renderElement = useCallback(({ attributes, children }) => { return ( <div {...attributes} className="documentDescription"> {children} </div> ); }, []); if (typeof window.__SERVER__ !== 'undefined') { return <div />; } return ( <Slate editor={editor} onChange={handleChange} initialValue={initialValue} className={cx('block description', { selected: selected, })} > <Editable readOnly={!editable} onKeyDown={handleKeyDown} placeholder={placeholder} renderElement={renderElement} onFocus={handleFocus} aria-multiline="false" ></Editable> </Slate> ); }; DescriptionBlockEdit.propTypes = { properties: PropTypes.objectOf(PropTypes.any).isRequired, selected: PropTypes.bool.isRequired, block: PropTypes.string.isRequired, index: PropTypes.number.isRequired, onChangeField: PropTypes.func.isRequired, onSelectBlock: PropTypes.func.isRequired, onDeleteBlock: PropTypes.func.isRequired, onAddBlock: PropTypes.func.isRequired, onFocusPreviousBlock: PropTypes.func.isRequired, onFocusNextBlock: PropTypes.func.isRequired, data: PropTypes.objectOf(PropTypes.any).isRequired, editable: PropTypes.bool, detached: PropTypes.bool, blockNode: PropTypes.any, }; DescriptionBlockEdit.defaultProps = { detached: false, editable: true, }; export default DescriptionBlockEdit;