d2-ui
Version:
141 lines (113 loc) • 5.42 kB
JavaScript
/**
* Copyright (c) 2013-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule editOnInput
*
*/
'use strict';
var DraftModifier = require('./DraftModifier');
var DraftOffsetKey = require('./DraftOffsetKey');
var EditorState = require('./EditorState');
var Entity = require('./DraftEntity');
var UserAgent = require('fbjs/lib/UserAgent');
var findAncestorOffsetKey = require('./findAncestorOffsetKey');
var nullthrows = require('fbjs/lib/nullthrows');
var isGecko = UserAgent.isEngine('Gecko');
var DOUBLE_NEWLINE = '\n\n';
/**
* This function is intended to handle spellcheck and autocorrect changes,
* which occur in the DOM natively without any opportunity to observe or
* interpret the changes before they occur.
*
* The `input` event fires in contentEditable elements reliably for non-IE
* browsers, immediately after changes occur to the editor DOM. Since our other
* handlers override or otherwise handle cover other varieties of text input,
* the DOM state should match the model in all controlled input cases. Thus,
* when an `input` change leads to a DOM/model mismatch, the change should be
* due to a spellcheck change, and we can incorporate it into our model.
*/
function editOnInput() {
var domSelection = global.getSelection();
var anchorNode = domSelection.anchorNode;
var isCollapsed = domSelection.isCollapsed;
if (anchorNode.nodeType !== Node.TEXT_NODE) {
return;
}
var domText = anchorNode.textContent;
var editorState = this.props.editorState;
var offsetKey = nullthrows(findAncestorOffsetKey(anchorNode));
var _DraftOffsetKey$decode = DraftOffsetKey.decode(offsetKey);
var blockKey = _DraftOffsetKey$decode.blockKey;
var decoratorKey = _DraftOffsetKey$decode.decoratorKey;
var leafKey = _DraftOffsetKey$decode.leafKey;
var _editorState$getBlockTree$getIn = editorState.getBlockTree(blockKey).getIn([decoratorKey, 'leaves', leafKey]);
var start = _editorState$getBlockTree$getIn.start;
var end = _editorState$getBlockTree$getIn.end;
var content = editorState.getCurrentContent();
var block = content.getBlockForKey(blockKey);
var modelText = block.getText().slice(start, end);
// Special-case soft newlines here. If the DOM text ends in a soft newline,
// we will have manually inserted an extra soft newline in DraftEditorLeaf.
// We want to remove this extra newline for the purpose of our comparison
// of DOM and model text.
if (domText.endsWith(DOUBLE_NEWLINE)) {
domText = domText.slice(0, -1);
}
// No change -- the DOM is up to date. Nothing to do here.
if (domText === modelText) {
return;
}
var selection = editorState.getSelection();
// We'll replace the entire leaf with the text content of the target.
var targetRange = selection.merge({
anchorOffset: start,
focusOffset: end,
isBackward: false
});
var entityKey = block.getEntityAt(start);
var entity = entityKey && Entity.get(entityKey);
var entityType = entity && entity.getMutability();
var preserveEntity = entityType === 'MUTABLE';
// Immutable or segmented entities cannot properly be handled by the
// default browser undo, so we have to use a different change type to
// force using our internal undo method instead of falling through to the
// native browser undo.
var changeType = preserveEntity ? 'spellcheck-change' : 'apply-entity';
var newContent = DraftModifier.replaceText(content, targetRange, domText, block.getInlineStyleAt(start), preserveEntity ? block.getEntityAt(start) : null);
var anchorOffset, focusOffset, startOffset, endOffset;
if (isGecko) {
// Firefox selection does not change while the context menu is open, so
// we preserve the anchor and focus values of the DOM selection.
anchorOffset = domSelection.anchorOffset;
focusOffset = domSelection.focusOffset;
startOffset = start + Math.min(anchorOffset, focusOffset);
endOffset = startOffset + Math.abs(anchorOffset - focusOffset);
anchorOffset = startOffset;
focusOffset = endOffset;
} else {
// Browsers other than Firefox may adjust DOM selection while the context
// menu is open, and Safari autocorrect is prone to providing an inaccurate
// DOM selection. Don't trust it. Instead, use our existing SelectionState
// and adjust it based on the number of characters changed during the
// mutation.
var charDelta = domText.length - modelText.length;
startOffset = selection.getStartOffset();
endOffset = selection.getEndOffset();
anchorOffset = isCollapsed ? endOffset + charDelta : startOffset;
focusOffset = endOffset + charDelta;
}
// Segmented entities are completely or partially removed when their
// text content changes. For this case we do not want any text to be selected
// after the change, so we are not merging the selection.
var contentWithAdjustedDOMSelection = newContent.merge({
selectionBefore: content.getSelectionAfter(),
selectionAfter: selection.merge({ anchorOffset: anchorOffset, focusOffset: focusOffset })
});
this.update(EditorState.push(editorState, contentWithAdjustedDOMSelection, changeType));
}
module.exports = editOnInput;