|
- /**
- * Copyright (c) Facebook, Inc. and its affiliates.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- *
- * @format
- *
- * @emails oncall+draft_js
- */
- 'use strict';
-
- var DraftModifier = require("./DraftModifier");
-
- var EditorState = require("./EditorState");
-
- var adjustBlockDepthForContentState = require("./adjustBlockDepthForContentState");
-
- var nullthrows = require("fbjs/lib/nullthrows");
-
- var RichTextEditorUtil = {
- currentBlockContainsLink: function currentBlockContainsLink(editorState) {
- var selection = editorState.getSelection();
- var contentState = editorState.getCurrentContent();
- var entityMap = contentState.getEntityMap();
- return contentState.getBlockForKey(selection.getAnchorKey()).getCharacterList().slice(selection.getStartOffset(), selection.getEndOffset()).some(function (v) {
- var entity = v.getEntity();
- return !!entity && entityMap.__get(entity).getType() === 'LINK';
- });
- },
- getCurrentBlockType: function getCurrentBlockType(editorState) {
- var selection = editorState.getSelection();
- return editorState.getCurrentContent().getBlockForKey(selection.getStartKey()).getType();
- },
- getDataObjectForLinkURL: function getDataObjectForLinkURL(uri) {
- return {
- url: uri.toString()
- };
- },
- handleKeyCommand: function handleKeyCommand(editorState, command, eventTimeStamp) {
- switch (command) {
- case 'bold':
- return RichTextEditorUtil.toggleInlineStyle(editorState, 'BOLD');
-
- case 'italic':
- return RichTextEditorUtil.toggleInlineStyle(editorState, 'ITALIC');
-
- case 'underline':
- return RichTextEditorUtil.toggleInlineStyle(editorState, 'UNDERLINE');
-
- case 'code':
- return RichTextEditorUtil.toggleCode(editorState);
-
- case 'backspace':
- case 'backspace-word':
- case 'backspace-to-start-of-line':
- return RichTextEditorUtil.onBackspace(editorState);
-
- case 'delete':
- case 'delete-word':
- case 'delete-to-end-of-block':
- return RichTextEditorUtil.onDelete(editorState);
-
- default:
- // they may have custom editor commands; ignore those
- return null;
- }
- },
- insertSoftNewline: function insertSoftNewline(editorState) {
- var contentState = DraftModifier.insertText(editorState.getCurrentContent(), editorState.getSelection(), '\n', editorState.getCurrentInlineStyle(), null);
- var newEditorState = EditorState.push(editorState, contentState, 'insert-characters');
- return EditorState.forceSelection(newEditorState, contentState.getSelectionAfter());
- },
-
- /**
- * For collapsed selections at the start of styled blocks, backspace should
- * just remove the existing style.
- */
- onBackspace: function onBackspace(editorState) {
- var selection = editorState.getSelection();
-
- if (!selection.isCollapsed() || selection.getAnchorOffset() || selection.getFocusOffset()) {
- return null;
- } // First, try to remove a preceding atomic block.
-
-
- var content = editorState.getCurrentContent();
- var startKey = selection.getStartKey();
- var blockBefore = content.getBlockBefore(startKey);
-
- if (blockBefore && blockBefore.getType() === 'atomic') {
- var blockMap = content.getBlockMap()["delete"](blockBefore.getKey());
- var withoutAtomicBlock = content.merge({
- blockMap: blockMap,
- selectionAfter: selection
- });
-
- if (withoutAtomicBlock !== content) {
- return EditorState.push(editorState, withoutAtomicBlock, 'remove-range');
- }
- } // If that doesn't succeed, try to remove the current block style.
-
-
- var withoutBlockStyle = RichTextEditorUtil.tryToRemoveBlockStyle(editorState);
-
- if (withoutBlockStyle) {
- return EditorState.push(editorState, withoutBlockStyle, 'change-block-type');
- }
-
- return null;
- },
- onDelete: function onDelete(editorState) {
- var selection = editorState.getSelection();
-
- if (!selection.isCollapsed()) {
- return null;
- }
-
- var content = editorState.getCurrentContent();
- var startKey = selection.getStartKey();
- var block = content.getBlockForKey(startKey);
- var length = block.getLength(); // The cursor is somewhere within the text. Behave normally.
-
- if (selection.getStartOffset() < length) {
- return null;
- }
-
- var blockAfter = content.getBlockAfter(startKey);
-
- if (!blockAfter || blockAfter.getType() !== 'atomic') {
- return null;
- }
-
- var atomicBlockTarget = selection.merge({
- focusKey: blockAfter.getKey(),
- focusOffset: blockAfter.getLength()
- });
- var withoutAtomicBlock = DraftModifier.removeRange(content, atomicBlockTarget, 'forward');
-
- if (withoutAtomicBlock !== content) {
- return EditorState.push(editorState, withoutAtomicBlock, 'remove-range');
- }
-
- return null;
- },
- onTab: function onTab(event, editorState, maxDepth) {
- var selection = editorState.getSelection();
- var key = selection.getAnchorKey();
-
- if (key !== selection.getFocusKey()) {
- return editorState;
- }
-
- var content = editorState.getCurrentContent();
- var block = content.getBlockForKey(key);
- var type = block.getType();
-
- if (type !== 'unordered-list-item' && type !== 'ordered-list-item') {
- return editorState;
- }
-
- event.preventDefault();
- var depth = block.getDepth();
-
- if (!event.shiftKey && depth === maxDepth) {
- return editorState;
- }
-
- var withAdjustment = adjustBlockDepthForContentState(content, selection, event.shiftKey ? -1 : 1, maxDepth);
- return EditorState.push(editorState, withAdjustment, 'adjust-depth');
- },
- toggleBlockType: function toggleBlockType(editorState, blockType) {
- var selection = editorState.getSelection();
- var startKey = selection.getStartKey();
- var endKey = selection.getEndKey();
- var content = editorState.getCurrentContent();
- var target = selection; // Triple-click can lead to a selection that includes offset 0 of the
- // following block. The `SelectionState` for this case is accurate, but
- // we should avoid toggling block type for the trailing block because it
- // is a confusing interaction.
-
- if (startKey !== endKey && selection.getEndOffset() === 0) {
- var blockBefore = nullthrows(content.getBlockBefore(endKey));
- endKey = blockBefore.getKey();
- target = target.merge({
- anchorKey: startKey,
- anchorOffset: selection.getStartOffset(),
- focusKey: endKey,
- focusOffset: blockBefore.getLength(),
- isBackward: false
- });
- }
-
- var hasAtomicBlock = content.getBlockMap().skipWhile(function (_, k) {
- return k !== startKey;
- }).reverse().skipWhile(function (_, k) {
- return k !== endKey;
- }).some(function (v) {
- return v.getType() === 'atomic';
- });
-
- if (hasAtomicBlock) {
- return editorState;
- }
-
- var typeToSet = content.getBlockForKey(startKey).getType() === blockType ? 'unstyled' : blockType;
- return EditorState.push(editorState, DraftModifier.setBlockType(content, target, typeToSet), 'change-block-type');
- },
- toggleCode: function toggleCode(editorState) {
- var selection = editorState.getSelection();
- var anchorKey = selection.getAnchorKey();
- var focusKey = selection.getFocusKey();
-
- if (selection.isCollapsed() || anchorKey !== focusKey) {
- return RichTextEditorUtil.toggleBlockType(editorState, 'code-block');
- }
-
- return RichTextEditorUtil.toggleInlineStyle(editorState, 'CODE');
- },
-
- /**
- * Toggle the specified inline style for the selection. If the
- * user's selection is collapsed, apply or remove the style for the
- * internal state. If it is not collapsed, apply the change directly
- * to the document state.
- */
- toggleInlineStyle: function toggleInlineStyle(editorState, inlineStyle) {
- var selection = editorState.getSelection();
- var currentStyle = editorState.getCurrentInlineStyle(); // If the selection is collapsed, toggle the specified style on or off and
- // set the result as the new inline style override. This will then be
- // used as the inline style for the next character to be inserted.
-
- if (selection.isCollapsed()) {
- return EditorState.setInlineStyleOverride(editorState, currentStyle.has(inlineStyle) ? currentStyle.remove(inlineStyle) : currentStyle.add(inlineStyle));
- } // If characters are selected, immediately apply or remove the
- // inline style on the document state itself.
-
-
- var content = editorState.getCurrentContent();
- var newContent; // If the style is already present for the selection range, remove it.
- // Otherwise, apply it.
-
- if (currentStyle.has(inlineStyle)) {
- newContent = DraftModifier.removeInlineStyle(content, selection, inlineStyle);
- } else {
- newContent = DraftModifier.applyInlineStyle(content, selection, inlineStyle);
- }
-
- return EditorState.push(editorState, newContent, 'change-inline-style');
- },
- toggleLink: function toggleLink(editorState, targetSelection, entityKey) {
- var withoutLink = DraftModifier.applyEntity(editorState.getCurrentContent(), targetSelection, entityKey);
- return EditorState.push(editorState, withoutLink, 'apply-entity');
- },
-
- /**
- * When a collapsed cursor is at the start of a styled block, changes block
- * type to 'unstyled'. Returns null if selection does not meet that criteria.
- */
- tryToRemoveBlockStyle: function tryToRemoveBlockStyle(editorState) {
- var selection = editorState.getSelection();
- var offset = selection.getAnchorOffset();
-
- if (selection.isCollapsed() && offset === 0) {
- var key = selection.getAnchorKey();
- var content = editorState.getCurrentContent();
- var block = content.getBlockForKey(key);
- var type = block.getType();
- var blockBefore = content.getBlockBefore(key);
-
- if (type === 'code-block' && blockBefore && blockBefore.getType() === 'code-block' && blockBefore.getLength() !== 0) {
- return null;
- }
-
- if (type !== 'unstyled') {
- return DraftModifier.setBlockType(content, selection, 'unstyled');
- }
- }
-
- return null;
- }
- };
- module.exports = RichTextEditorUtil;
|