|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566 |
- /**
- * 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';
-
- function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty(target, key, source[key]); }); } return target; }
-
- function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
-
- var BlockTree = require("./BlockTree");
-
- var ContentState = require("./ContentState");
-
- var EditorBidiService = require("./EditorBidiService");
-
- var SelectionState = require("./SelectionState");
-
- var Immutable = require("immutable");
-
- var OrderedSet = Immutable.OrderedSet,
- Record = Immutable.Record,
- Stack = Immutable.Stack; // When configuring an editor, the user can chose to provide or not provide
- // basically all keys. `currentContent` varies, so this type doesn't include it.
- // (See the types defined below.)
-
- var defaultRecord = {
- allowUndo: true,
- currentContent: null,
- decorator: null,
- directionMap: null,
- forceSelection: false,
- inCompositionMode: false,
- inlineStyleOverride: null,
- lastChangeType: null,
- nativelyRenderedContent: null,
- redoStack: Stack(),
- selection: null,
- treeMap: null,
- undoStack: Stack()
- };
- var EditorStateRecord = Record(defaultRecord);
-
- var EditorState =
- /*#__PURE__*/
- function () {
- EditorState.createEmpty = function createEmpty(decorator) {
- return EditorState.createWithContent(ContentState.createFromText(''), decorator);
- };
-
- EditorState.createWithContent = function createWithContent(contentState, decorator) {
- if (contentState.getBlockMap().count() === 0) {
- return EditorState.createEmpty(decorator);
- }
-
- var firstKey = contentState.getBlockMap().first().getKey();
- return EditorState.create({
- currentContent: contentState,
- undoStack: Stack(),
- redoStack: Stack(),
- decorator: decorator || null,
- selection: SelectionState.createEmpty(firstKey)
- });
- };
-
- EditorState.create = function create(config) {
- var currentContent = config.currentContent,
- decorator = config.decorator;
-
- var recordConfig = _objectSpread({}, config, {
- treeMap: generateNewTreeMap(currentContent, decorator),
- directionMap: EditorBidiService.getDirectionMap(currentContent)
- });
-
- return new EditorState(new EditorStateRecord(recordConfig));
- };
-
- EditorState.set = function set(editorState, put) {
- var map = editorState.getImmutable().withMutations(function (state) {
- var existingDecorator = state.get('decorator');
- var decorator = existingDecorator;
-
- if (put.decorator === null) {
- decorator = null;
- } else if (put.decorator) {
- decorator = put.decorator;
- }
-
- var newContent = put.currentContent || editorState.getCurrentContent();
-
- if (decorator !== existingDecorator) {
- var treeMap = state.get('treeMap');
- var newTreeMap;
-
- if (decorator && existingDecorator) {
- newTreeMap = regenerateTreeForNewDecorator(newContent, newContent.getBlockMap(), treeMap, decorator, existingDecorator);
- } else {
- newTreeMap = generateNewTreeMap(newContent, decorator);
- }
-
- state.merge({
- decorator: decorator,
- treeMap: newTreeMap,
- nativelyRenderedContent: null
- });
- return;
- }
-
- var existingContent = editorState.getCurrentContent();
-
- if (newContent !== existingContent) {
- state.set('treeMap', regenerateTreeForNewBlocks(editorState, newContent.getBlockMap(), newContent.getEntityMap(), decorator));
- }
-
- state.merge(put);
- });
- return new EditorState(map);
- };
-
- var _proto = EditorState.prototype;
-
- _proto.toJS = function toJS() {
- return this.getImmutable().toJS();
- };
-
- _proto.getAllowUndo = function getAllowUndo() {
- return this.getImmutable().get('allowUndo');
- };
-
- _proto.getCurrentContent = function getCurrentContent() {
- return this.getImmutable().get('currentContent');
- };
-
- _proto.getUndoStack = function getUndoStack() {
- return this.getImmutable().get('undoStack');
- };
-
- _proto.getRedoStack = function getRedoStack() {
- return this.getImmutable().get('redoStack');
- };
-
- _proto.getSelection = function getSelection() {
- return this.getImmutable().get('selection');
- };
-
- _proto.getDecorator = function getDecorator() {
- return this.getImmutable().get('decorator');
- };
-
- _proto.isInCompositionMode = function isInCompositionMode() {
- return this.getImmutable().get('inCompositionMode');
- };
-
- _proto.mustForceSelection = function mustForceSelection() {
- return this.getImmutable().get('forceSelection');
- };
-
- _proto.getNativelyRenderedContent = function getNativelyRenderedContent() {
- return this.getImmutable().get('nativelyRenderedContent');
- };
-
- _proto.getLastChangeType = function getLastChangeType() {
- return this.getImmutable().get('lastChangeType');
- }
- /**
- * While editing, the user may apply inline style commands with a collapsed
- * cursor, intending to type text that adopts the specified style. In this
- * case, we track the specified style as an "override" that takes precedence
- * over the inline style of the text adjacent to the cursor.
- *
- * If null, there is no override in place.
- */
- ;
-
- _proto.getInlineStyleOverride = function getInlineStyleOverride() {
- return this.getImmutable().get('inlineStyleOverride');
- };
-
- EditorState.setInlineStyleOverride = function setInlineStyleOverride(editorState, inlineStyleOverride) {
- return EditorState.set(editorState, {
- inlineStyleOverride: inlineStyleOverride
- });
- }
- /**
- * Get the appropriate inline style for the editor state. If an
- * override is in place, use it. Otherwise, the current style is
- * based on the location of the selection state.
- */
- ;
-
- _proto.getCurrentInlineStyle = function getCurrentInlineStyle() {
- var override = this.getInlineStyleOverride();
-
- if (override != null) {
- return override;
- }
-
- var content = this.getCurrentContent();
- var selection = this.getSelection();
-
- if (selection.isCollapsed()) {
- return getInlineStyleForCollapsedSelection(content, selection);
- }
-
- return getInlineStyleForNonCollapsedSelection(content, selection);
- };
-
- _proto.getBlockTree = function getBlockTree(blockKey) {
- return this.getImmutable().getIn(['treeMap', blockKey]);
- };
-
- _proto.isSelectionAtStartOfContent = function isSelectionAtStartOfContent() {
- var firstKey = this.getCurrentContent().getBlockMap().first().getKey();
- return this.getSelection().hasEdgeWithin(firstKey, 0, 0);
- };
-
- _proto.isSelectionAtEndOfContent = function isSelectionAtEndOfContent() {
- var content = this.getCurrentContent();
- var blockMap = content.getBlockMap();
- var last = blockMap.last();
- var end = last.getLength();
- return this.getSelection().hasEdgeWithin(last.getKey(), end, end);
- };
-
- _proto.getDirectionMap = function getDirectionMap() {
- return this.getImmutable().get('directionMap');
- }
- /**
- * Incorporate native DOM selection changes into the EditorState. This
- * method can be used when we simply want to accept whatever the DOM
- * has given us to represent selection, and we do not need to re-render
- * the editor.
- *
- * To forcibly move the DOM selection, see `EditorState.forceSelection`.
- */
- ;
-
- EditorState.acceptSelection = function acceptSelection(editorState, selection) {
- return updateSelection(editorState, selection, false);
- }
- /**
- * At times, we need to force the DOM selection to be where we
- * need it to be. This can occur when the anchor or focus nodes
- * are non-text nodes, for instance. In this case, we want to trigger
- * a re-render of the editor, which in turn forces selection into
- * the correct place in the DOM. The `forceSelection` method
- * accomplishes this.
- *
- * This method should be used in cases where you need to explicitly
- * move the DOM selection from one place to another without a change
- * in ContentState.
- */
- ;
-
- EditorState.forceSelection = function forceSelection(editorState, selection) {
- if (!selection.getHasFocus()) {
- selection = selection.set('hasFocus', true);
- }
-
- return updateSelection(editorState, selection, true);
- }
- /**
- * Move selection to the end of the editor without forcing focus.
- */
- ;
-
- EditorState.moveSelectionToEnd = function moveSelectionToEnd(editorState) {
- var content = editorState.getCurrentContent();
- var lastBlock = content.getLastBlock();
- var lastKey = lastBlock.getKey();
- var length = lastBlock.getLength();
- return EditorState.acceptSelection(editorState, new SelectionState({
- anchorKey: lastKey,
- anchorOffset: length,
- focusKey: lastKey,
- focusOffset: length,
- isBackward: false
- }));
- }
- /**
- * Force focus to the end of the editor. This is useful in scenarios
- * where we want to programmatically focus the input and it makes sense
- * to allow the user to continue working seamlessly.
- */
- ;
-
- EditorState.moveFocusToEnd = function moveFocusToEnd(editorState) {
- var afterSelectionMove = EditorState.moveSelectionToEnd(editorState);
- return EditorState.forceSelection(afterSelectionMove, afterSelectionMove.getSelection());
- }
- /**
- * Push the current ContentState onto the undo stack if it should be
- * considered a boundary state, and set the provided ContentState as the
- * new current content.
- */
- ;
-
- EditorState.push = function push(editorState, contentState, changeType) {
- var forceSelection = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true;
-
- if (editorState.getCurrentContent() === contentState) {
- return editorState;
- }
-
- var directionMap = EditorBidiService.getDirectionMap(contentState, editorState.getDirectionMap());
-
- if (!editorState.getAllowUndo()) {
- return EditorState.set(editorState, {
- currentContent: contentState,
- directionMap: directionMap,
- lastChangeType: changeType,
- selection: contentState.getSelectionAfter(),
- forceSelection: forceSelection,
- inlineStyleOverride: null
- });
- }
-
- var selection = editorState.getSelection();
- var currentContent = editorState.getCurrentContent();
- var undoStack = editorState.getUndoStack();
- var newContent = contentState;
-
- if (selection !== currentContent.getSelectionAfter() || mustBecomeBoundary(editorState, changeType)) {
- undoStack = undoStack.push(currentContent);
- newContent = newContent.set('selectionBefore', selection);
- } else if (changeType === 'insert-characters' || changeType === 'backspace-character' || changeType === 'delete-character') {
- // Preserve the previous selection.
- newContent = newContent.set('selectionBefore', currentContent.getSelectionBefore());
- }
-
- var inlineStyleOverride = editorState.getInlineStyleOverride(); // Don't discard inline style overrides for the following change types:
-
- var overrideChangeTypes = ['adjust-depth', 'change-block-type', 'split-block'];
-
- if (overrideChangeTypes.indexOf(changeType) === -1) {
- inlineStyleOverride = null;
- }
-
- var editorStateChanges = {
- currentContent: newContent,
- directionMap: directionMap,
- undoStack: undoStack,
- redoStack: Stack(),
- lastChangeType: changeType,
- selection: contentState.getSelectionAfter(),
- forceSelection: forceSelection,
- inlineStyleOverride: inlineStyleOverride
- };
- return EditorState.set(editorState, editorStateChanges);
- }
- /**
- * Make the top ContentState in the undo stack the new current content and
- * push the current content onto the redo stack.
- */
- ;
-
- EditorState.undo = function undo(editorState) {
- if (!editorState.getAllowUndo()) {
- return editorState;
- }
-
- var undoStack = editorState.getUndoStack();
- var newCurrentContent = undoStack.peek();
-
- if (!newCurrentContent) {
- return editorState;
- }
-
- var currentContent = editorState.getCurrentContent();
- var directionMap = EditorBidiService.getDirectionMap(newCurrentContent, editorState.getDirectionMap());
- return EditorState.set(editorState, {
- currentContent: newCurrentContent,
- directionMap: directionMap,
- undoStack: undoStack.shift(),
- redoStack: editorState.getRedoStack().push(currentContent),
- forceSelection: true,
- inlineStyleOverride: null,
- lastChangeType: 'undo',
- nativelyRenderedContent: null,
- selection: currentContent.getSelectionBefore()
- });
- }
- /**
- * Make the top ContentState in the redo stack the new current content and
- * push the current content onto the undo stack.
- */
- ;
-
- EditorState.redo = function redo(editorState) {
- if (!editorState.getAllowUndo()) {
- return editorState;
- }
-
- var redoStack = editorState.getRedoStack();
- var newCurrentContent = redoStack.peek();
-
- if (!newCurrentContent) {
- return editorState;
- }
-
- var currentContent = editorState.getCurrentContent();
- var directionMap = EditorBidiService.getDirectionMap(newCurrentContent, editorState.getDirectionMap());
- return EditorState.set(editorState, {
- currentContent: newCurrentContent,
- directionMap: directionMap,
- undoStack: editorState.getUndoStack().push(currentContent),
- redoStack: redoStack.shift(),
- forceSelection: true,
- inlineStyleOverride: null,
- lastChangeType: 'redo',
- nativelyRenderedContent: null,
- selection: newCurrentContent.getSelectionAfter()
- });
- }
- /**
- * Not for public consumption.
- */
- ;
-
- function EditorState(immutable) {
- _defineProperty(this, "_immutable", void 0);
-
- this._immutable = immutable;
- }
- /**
- * Not for public consumption.
- */
-
-
- _proto.getImmutable = function getImmutable() {
- return this._immutable;
- };
-
- return EditorState;
- }();
- /**
- * Set the supplied SelectionState as the new current selection, and set
- * the `force` flag to trigger manual selection placement by the view.
- */
-
-
- function updateSelection(editorState, selection, forceSelection) {
- return EditorState.set(editorState, {
- selection: selection,
- forceSelection: forceSelection,
- nativelyRenderedContent: null,
- inlineStyleOverride: null
- });
- }
- /**
- * Regenerate the entire tree map for a given ContentState and decorator.
- * Returns an OrderedMap that maps all available ContentBlock objects.
- */
-
-
- function generateNewTreeMap(contentState, decorator) {
- return contentState.getBlockMap().map(function (block) {
- return BlockTree.generate(contentState, block, decorator);
- }).toOrderedMap();
- }
- /**
- * Regenerate tree map objects for all ContentBlocks that have changed
- * between the current editorState and newContent. Returns an OrderedMap
- * with only changed regenerated tree map objects.
- */
-
-
- function regenerateTreeForNewBlocks(editorState, newBlockMap, newEntityMap, decorator) {
- var contentState = editorState.getCurrentContent().set('entityMap', newEntityMap);
- var prevBlockMap = contentState.getBlockMap();
- var prevTreeMap = editorState.getImmutable().get('treeMap');
- return prevTreeMap.merge(newBlockMap.toSeq().filter(function (block, key) {
- return block !== prevBlockMap.get(key);
- }).map(function (block) {
- return BlockTree.generate(contentState, block, decorator);
- }));
- }
- /**
- * Generate tree map objects for a new decorator object, preserving any
- * decorations that are unchanged from the previous decorator.
- *
- * Note that in order for this to perform optimally, decoration Lists for
- * decorators should be preserved when possible to allow for direct immutable
- * List comparison.
- */
-
-
- function regenerateTreeForNewDecorator(content, blockMap, previousTreeMap, decorator, existingDecorator) {
- return previousTreeMap.merge(blockMap.toSeq().filter(function (block) {
- return decorator.getDecorations(block, content) !== existingDecorator.getDecorations(block, content);
- }).map(function (block) {
- return BlockTree.generate(content, block, decorator);
- }));
- }
- /**
- * Return whether a change should be considered a boundary state, given
- * the previous change type. Allows us to discard potential boundary states
- * during standard typing or deletion behavior.
- */
-
-
- function mustBecomeBoundary(editorState, changeType) {
- var lastChangeType = editorState.getLastChangeType();
- return changeType !== lastChangeType || changeType !== 'insert-characters' && changeType !== 'backspace-character' && changeType !== 'delete-character';
- }
-
- function getInlineStyleForCollapsedSelection(content, selection) {
- var startKey = selection.getStartKey();
- var startOffset = selection.getStartOffset();
- var startBlock = content.getBlockForKey(startKey); // If the cursor is not at the start of the block, look backward to
- // preserve the style of the preceding character.
-
- if (startOffset > 0) {
- return startBlock.getInlineStyleAt(startOffset - 1);
- } // The caret is at position zero in this block. If the block has any
- // text at all, use the style of the first character.
-
-
- if (startBlock.getLength()) {
- return startBlock.getInlineStyleAt(0);
- } // Otherwise, look upward in the document to find the closest character.
-
-
- return lookUpwardForInlineStyle(content, startKey);
- }
-
- function getInlineStyleForNonCollapsedSelection(content, selection) {
- var startKey = selection.getStartKey();
- var startOffset = selection.getStartOffset();
- var startBlock = content.getBlockForKey(startKey); // If there is a character just inside the selection, use its style.
-
- if (startOffset < startBlock.getLength()) {
- return startBlock.getInlineStyleAt(startOffset);
- } // Check if the selection at the end of a non-empty block. Use the last
- // style in the block.
-
-
- if (startOffset > 0) {
- return startBlock.getInlineStyleAt(startOffset - 1);
- } // Otherwise, look upward in the document to find the closest character.
-
-
- return lookUpwardForInlineStyle(content, startKey);
- }
-
- function lookUpwardForInlineStyle(content, fromKey) {
- var lastNonEmpty = content.getBlockMap().reverse().skipUntil(function (_, k) {
- return k === fromKey;
- }).skip(1).skipUntil(function (block, _) {
- return block.getLength();
- }).first();
-
- if (lastNonEmpty) {
- return lastNonEmpty.getInlineStyleAt(lastNonEmpty.getLength() - 1);
- }
-
- return OrderedSet();
- }
-
- module.exports = EditorState;
|