UNPKG

@kedao/utils

Version:

Utils for Kedao Editor

444 lines 18.7 kB
import { Modifier, EditorState, SelectionState, RichUtils, CharacterMetadata, AtomicBlockUtils, convertFromRaw } from 'draft-js'; import { setBlockData, getSelectionEntity } from 'draftjs-utils'; import { convertHTMLToRaw } from '@kedao/convert'; import Immutable from 'immutable'; const strictBlockTypes = ['atomic']; export const registerStrictBlockType = (blockType) => { !strictBlockTypes.includes(blockType) && strictBlockTypes.push(blockType); }; export const isEditorState = (editorState) => { return editorState instanceof EditorState; }; export const createEmptyEditorState = (editorDecorators) => { return EditorState.createEmpty(editorDecorators); }; export const createEditorState = (contentState, editorDecorators) => { return EditorState.createWithContent(contentState, editorDecorators); }; export const isSelectionCollapsed = (editorState) => { return editorState.getSelection().isCollapsed(); }; export const selectionContainsBlockType = (editorState, blockType) => { return getSelectedBlocks(editorState).find((block) => block.getType() === blockType); }; export const selectionContainsStrictBlock = (editorState) => { return getSelectedBlocks(editorState).find((block) => ~strictBlockTypes.indexOf(block.getType())); }; export const selectBlock = (editorState, block) => { const blockKey = block.getKey(); return EditorState.forceSelection(editorState, new SelectionState({ anchorKey: blockKey, anchorOffset: 0, focusKey: blockKey, focusOffset: block.getLength() })); }; export const selectNextBlock = (editorState, block) => { const nextBlock = editorState .getCurrentContent() .getBlockAfter(block.getKey()); return nextBlock ? selectBlock(editorState, nextBlock) : editorState; }; export const removeBlock = (editorState, block, lastSelection = null) => { let nextContentState; const blockKey = block.getKey(); nextContentState = Modifier.removeRange(editorState.getCurrentContent(), new SelectionState({ anchorKey: blockKey, anchorOffset: 0, focusKey: blockKey, focusOffset: block.getLength() }), 'backward'); nextContentState = Modifier.setBlockType(nextContentState, nextContentState.getSelectionAfter(), 'unstyled'); const nextEditorState = EditorState.push(editorState, nextContentState, 'remove-range'); return EditorState.forceSelection(nextEditorState, lastSelection || nextContentState.getSelectionAfter()); }; export const getSelectionBlock = (editorState) => { return editorState .getCurrentContent() .getBlockForKey(editorState.getSelection().getAnchorKey()); }; export const updateEachCharacterOfSelection = (editorState, callback) => { const selectionState = editorState.getSelection(); const contentState = editorState.getCurrentContent(); const contentBlocks = contentState.getBlockMap(); const selectedBlocks = getSelectedBlocks(editorState); if (selectedBlocks.length === 0) { return editorState; } const startKey = selectionState.getStartKey(); const startOffset = selectionState.getStartOffset(); const endKey = selectionState.getEndKey(); const endOffset = selectionState.getEndOffset(); const nextContentBlocks = contentBlocks.map((block) => { if (!selectedBlocks.includes(block)) { return block; } const blockKey = block.getKey(); const charactersList = block.getCharacterList(); let nextCharactersList = null; if (blockKey === startKey && blockKey === endKey) { nextCharactersList = charactersList.map((character, index) => { if (index >= startOffset && index < endOffset) { return callback(character); } return character; }); } else if (blockKey === startKey) { nextCharactersList = charactersList.map((character, index) => { if (index >= startOffset) { return callback(character); } return character; }); } else if (blockKey === endKey) { nextCharactersList = charactersList.map((character, index) => { if (index < endOffset) { return callback(character); } return character; }); } else { nextCharactersList = charactersList.map((character) => { return callback(character); }); } return block.merge({ characterList: nextCharactersList }); }); return EditorState.push(editorState, contentState.merge({ blockMap: nextContentBlocks, selectionBefore: selectionState, selectionAfter: selectionState }), 'update-selection-character-list'); }; export const getSelectedBlocks = (editorState) => { const selectionState = editorState.getSelection(); const contentState = editorState.getCurrentContent(); const startKey = selectionState.getStartKey(); const endKey = selectionState.getEndKey(); const isSameBlock = startKey === endKey; const startingBlock = contentState.getBlockForKey(startKey); const selectedBlocks = [startingBlock]; if (!isSameBlock) { let blockKey = startKey; while (blockKey !== endKey) { const nextBlock = contentState.getBlockAfter(blockKey); selectedBlocks.push(nextBlock); blockKey = nextBlock.getKey(); } } return selectedBlocks; }; export const setSelectionBlockData = (editorState, blockData, override = false) => { const newBlockData = override ? blockData : Object.assign({}, getSelectionBlockData(editorState).toJS(), blockData); Object.keys(newBlockData).forEach((key) => { // eslint-disable-next-line no-prototype-builtins if (newBlockData.hasOwnProperty(key) && newBlockData[key] === undefined) { // eslint-disable-next-line @typescript-eslint/no-dynamic-delete delete newBlockData[key]; } }); return setBlockData(editorState, newBlockData); }; export const getSelectionBlockData = (editorState, name) => { const blockData = getSelectionBlock(editorState).getData(); return name ? blockData.get(name) : blockData; }; export const getSelectionBlockType = (editorState) => { return getSelectionBlock(editorState).getType(); }; export const getSelectionText = (editorState) => { const selectionState = editorState.getSelection(); const contentState = editorState.getCurrentContent(); if (selectionState.isCollapsed() || getSelectionBlockType(editorState) === 'atomic') { return ''; } const anchorKey = selectionState.getAnchorKey(); const currentContentBlock = contentState.getBlockForKey(anchorKey); const start = selectionState.getStartOffset(); const end = selectionState.getEndOffset(); return currentContentBlock.getText().slice(start, end); }; export const toggleSelectionBlockType = (editorState, blockType) => { if (selectionContainsStrictBlock(editorState)) { return editorState; } return RichUtils.toggleBlockType(editorState, blockType); }; export const getSelectionEntityType = (editorState) => { const entityKey = getSelectionEntity(editorState); if (entityKey) { const entity = editorState.getCurrentContent().getEntity(entityKey); return entity ? entity.get('type') : null; } return null; }; export const getSelectionEntityData = (editorState, type) => { const entityKey = getSelectionEntity(editorState); if (entityKey) { const entity = editorState.getCurrentContent().getEntity(entityKey); if (entity && entity.get('type') === type) { return entity.getData(); } else { return {}; } } else { return {}; } }; export const toggleSelectionEntity = (editorState, entity) => { const contentState = editorState.getCurrentContent(); const selectionState = editorState.getSelection(); if (selectionState.isCollapsed() || getSelectionBlockType(editorState) === 'atomic') { return editorState; } if (!entity || !entity.type || getSelectionEntityType(editorState) === entity.type) { return EditorState.push(editorState, Modifier.applyEntity(contentState, selectionState, null), 'apply-entity'); } try { const nextContentState = contentState.createEntity(entity.type, entity.mutability, entity.data); const entityKey = nextContentState.getLastCreatedEntityKey(); const nextEditorState = EditorState.set(editorState, { currentContent: nextContentState }); return EditorState.push(nextEditorState, Modifier.applyEntity(nextContentState, selectionState, entityKey), 'apply-entity'); } catch (error) { console.warn(error); return editorState; } }; export const toggleSelectionLink = (editorState, href, target) => { const contentState = editorState.getCurrentContent(); const selectionState = editorState.getSelection(); const entityData = { href, target }; if (selectionState.isCollapsed() || getSelectionBlockType(editorState) === 'atomic') { return editorState; } if (href === false) { return RichUtils.toggleLink(editorState, selectionState, null); } if (href === null) { delete entityData.href; } try { const nextContentState = contentState.createEntity('LINK', 'MUTABLE', entityData); const entityKey = nextContentState.getLastCreatedEntityKey(); let nextEditorState = EditorState.set(editorState, { currentContent: nextContentState }); nextEditorState = RichUtils.toggleLink(nextEditorState, selectionState, entityKey); nextEditorState = EditorState.forceSelection(nextEditorState, selectionState.merge({ anchorOffset: selectionState.getEndOffset(), focusOffset: selectionState.getEndOffset() })); nextEditorState = EditorState.push(nextEditorState, Modifier.insertText(nextEditorState.getCurrentContent(), nextEditorState.getSelection(), ''), 'insert-text'); return nextEditorState; } catch (error) { console.warn(error); return editorState; } }; export const getSelectionInlineStyle = (editorState) => { return editorState.getCurrentInlineStyle(); }; export const selectionHasInlineStyle = (editorState, style) => { return getSelectionInlineStyle(editorState).has(style.toUpperCase()); }; export const toggleSelectionInlineStyle = (editorState, style, prefix = '') => { let nextEditorState = editorState; style = prefix + style.toUpperCase(); if (prefix) { nextEditorState = updateEachCharacterOfSelection(nextEditorState, (characterMetadata) => { return characterMetadata .toJS() .style.reduce((characterMetadata, characterStyle) => { if (characterStyle.indexOf(prefix) === 0 && style !== characterStyle) { return CharacterMetadata.removeStyle(characterMetadata, characterStyle); } else { return characterMetadata; } }, characterMetadata); }); } return RichUtils.toggleInlineStyle(nextEditorState, style); }; export const removeSelectionInlineStyles = (editorState) => { return updateEachCharacterOfSelection(editorState, (characterMetadata) => { return characterMetadata.merge({ style: Immutable.OrderedSet([]) }); }); }; export const toggleSelectionAlignment = (editorState, alignment) => { return setSelectionBlockData(editorState, { textAlign: getSelectionBlockData(editorState, 'textAlign') !== alignment ? alignment : undefined }); }; export const toggleSelectionIndent = (editorState, textIndent, maxIndent = 6) => { return textIndent < 0 || textIndent > maxIndent || isNaN(textIndent) ? editorState : setSelectionBlockData(editorState, { textIndent: textIndent || undefined }); }; export const increaseSelectionIndent = (editorState, maxIndent = 6) => { const currentIndent = getSelectionBlockData(editorState, 'textIndent') || 0; return toggleSelectionIndent(editorState, currentIndent + 1, maxIndent); }; export const decreaseSelectionIndent = (editorState, maxIndent) => { const currentIndent = getSelectionBlockData(editorState, 'textIndent') || 0; return toggleSelectionIndent(editorState, currentIndent - 1, maxIndent); }; export const toggleSelectionColor = (editorState, color) => { return toggleSelectionInlineStyle(editorState, color.replace('#', ''), 'COLOR-'); }; export const toggleSelectionBackgroundColor = (editorState, color) => { return toggleSelectionInlineStyle(editorState, color.replace('#', ''), 'BGCOLOR-'); }; export const toggleSelectionFontSize = (editorState, fontSize) => { return toggleSelectionInlineStyle(editorState, fontSize, 'FONTSIZE-'); }; export const toggleSelectionLineHeight = (editorState, lineHeight) => { return toggleSelectionInlineStyle(editorState, lineHeight, 'LINEHEIGHT-'); }; export const toggleSelectionFontFamily = (editorState, fontFamily) => { return toggleSelectionInlineStyle(editorState, fontFamily, 'FONTFAMILY-'); }; export const toggleSelectionLetterSpacing = (editorState, letterSpacing) => { return toggleSelectionInlineStyle(editorState, letterSpacing, 'LETTERSPACING-'); }; export const insertText = (editorState, text, inlineStyle, entity) => { const selectionState = editorState.getSelection(); const currentSelectedBlockType = getSelectionBlockType(editorState); if (currentSelectedBlockType === 'atomic') { return editorState; } let entityKey; let contentState = editorState.getCurrentContent(); if (entity === null || entity === void 0 ? void 0 : entity.type) { contentState = contentState.createEntity(entity.type, entity.mutability || 'MUTABLE', // entity.data || entityData entity.data); entityKey = contentState.getLastCreatedEntityKey(); } if (!selectionState.isCollapsed()) { return EditorState.push(editorState, Modifier.replaceText(contentState, selectionState, text, inlineStyle, entityKey), 'replace-text'); } else { return EditorState.push(editorState, Modifier.insertText(contentState, selectionState, text, inlineStyle, entityKey), 'insert-text'); } }; export const insertHTML = (editorState, htmlString, source) => { if (!htmlString) { return editorState; } const selectionState = editorState.getSelection(); const contentState = editorState.getCurrentContent(); const options = editorState.convertOptions || {}; try { const { blockMap } = convertFromRaw(convertHTMLToRaw(htmlString, options, source)); return EditorState.push(editorState, Modifier.replaceWithFragment(contentState, selectionState, blockMap), 'insert-fragment'); } catch (error) { console.warn(error); return editorState; } }; export const insertAtomicBlock = (editorState, type, immutable = true, data = {}) => { if (selectionContainsStrictBlock(editorState)) { return insertAtomicBlock(selectNextBlock(editorState, getSelectionBlock(editorState)), type, immutable, data); } const selectionState = editorState.getSelection(); const contentState = editorState.getCurrentContent(); if (!selectionState.isCollapsed() || getSelectionBlockType(editorState) === 'atomic') { return editorState; } const contentStateWithEntity = contentState.createEntity(type, immutable ? 'IMMUTABLE' : 'MUTABLE', data); const entityKey = contentStateWithEntity.getLastCreatedEntityKey(); const newEditorState = AtomicBlockUtils.insertAtomicBlock(editorState, entityKey, ' '); return newEditorState; }; export const insertHorizontalLine = (editorState) => { return insertAtomicBlock(editorState, 'HR'); }; export const insertMedias = (editorState, medias = []) => { if (!medias.length) { return editorState; } return medias.reduce((editorState, media) => { // eslint-disable-next-line @typescript-eslint/naming-convention const { url, link, link_target, name, type, width, height, meta } = media; return insertAtomicBlock(editorState, type, true, { url, link, link_target, name, type, width, height, meta }); }, editorState); }; export const setMediaData = (editorState, entityKey, data) => { return EditorState.push(editorState, editorState.getCurrentContent().mergeEntityData(entityKey, data), 'change-block-data'); }; export const removeMedia = (editorState, mediaBlock) => { return removeBlock(editorState, mediaBlock); }; export const setMediaPosition = (editorState, mediaBlock, position) => { const newPosition = {}; const { float, alignment } = position; if (typeof float !== 'undefined') { newPosition.float = mediaBlock.getData().get('float') === float ? null : float; } if (typeof alignment !== 'undefined') { newPosition.alignment = mediaBlock.getData().get('alignment') === alignment ? null : alignment; } return setSelectionBlockData(selectBlock(editorState, mediaBlock), newPosition); }; export const clear = (editorState) => { const contentState = editorState.getCurrentContent(); const firstBlock = contentState.getFirstBlock(); const lastBlock = contentState.getLastBlock(); const allSelected = new SelectionState({ anchorKey: firstBlock.getKey(), anchorOffset: 0, focusKey: lastBlock.getKey(), focusOffset: lastBlock.getLength(), hasFocus: true }); return RichUtils.toggleBlockType(EditorState.push(editorState, Modifier.removeRange(contentState, allSelected, 'backward'), 'remove-range'), 'unstyled'); }; export const handleKeyCommand = (editorState, command) => { return RichUtils.handleKeyCommand(editorState, command); }; export const undo = (editorState) => { return EditorState.undo(editorState); }; export const redo = (editorState) => { return EditorState.redo(editorState); }; //# sourceMappingURL=content.js.map