in some situations,
// such as when breaking out of a list that is contained in a
.
if (goog.userAgent.OPERA && paragraph) {
if (nodeName == tag &&
paragraph == container.lastChild &&
goog.editor.node.isEmpty(paragraph)) {
goog.dom.insertSiblingAfter(paragraph, container);
goog.dom.Range.createFromNodeContents(paragraph).select();
}
break;
}
return;
}
if (goog.userAgent.OPERA && opt_keyUp && nodeName == goog.dom.TagName.P &&
nodeName != tag) {
paragraph = container;
}
container = container.parentNode;
}
if (goog.userAgent.IE && !goog.userAgent.isVersion(9)) {
// IE (before IE9) has a bug where if the cursor is directly before a block
// node (e.g., the content is "foo[cursor]
bar
"),
// the FormatBlock command actually formats the "bar" instead of the "foo".
// This is just wrong. To work-around this, we want to move the
// selection back one character, and then restore it to its prior position.
// NOTE: We use the following "range math" to detect this situation because
// using Closure ranges here triggers a bug in IE that causes a crash.
// parent2 != parent3 ensures moving the cursor forward one character
// crosses at least 1 element boundary, and therefore tests if the cursor is
// at such a boundary. The second check, parent3 != range.parentElement()
// weeds out some cases where the elements are siblings instead of cousins.
var needsHelp = false;
range = range.getBrowserRangeObject();
var range2 = range.duplicate();
range2.moveEnd('character', 1);
// In whitebox mode, when the cursor is at the end of the field, trying to
// move the end of the range will do nothing, and hence the range's text
// will be empty. In this case, the cursor clearly isn't sitting just
// before a block node, since it isn't before anything.
if (range2.text.length) {
var parent2 = range2.parentElement();
var range3 = range2.duplicate();
range3.collapse(false);
var parent3 = range3.parentElement();
if ((needsHelp = parent2 != parent3 &&
parent3 != range.parentElement())) {
range.move('character', -1);
range.select();
}
}
}
this.getFieldObject().getEditableDomHelper().getDocument().execCommand(
'FormatBlock', false, '<' + tag + '>');
if (needsHelp) {
range.move('character', 1);
range.select();
}
};
/**
* Deletes the content at the current cursor position.
* @return {Node|Object} Something representing the current cursor position.
* See deleteCursorSelectionIE_ and deleteCursorSelectionW3C_ for details.
* Should be passed to releasePositionObject_ when no longer in use.
* @private
*/
goog.editor.plugins.EnterHandler.prototype.deleteCursorSelection_ = function() {
return goog.editor.BrowserFeature.HAS_W3C_RANGES ?
this.deleteCursorSelectionW3C_() : this.deleteCursorSelectionIE_();
};
/**
* Releases the object returned by deleteCursorSelection_.
* @param {Node|Object} position The object returned by deleteCursorSelection_.
* @private
*/
goog.editor.plugins.EnterHandler.prototype.releasePositionObject_ =
function(position) {
if (!goog.editor.BrowserFeature.HAS_W3C_RANGES) {
(/** @type {Node} */ (position)).removeNode(true);
}
};
/**
* Delete the selection at the current cursor position, then returns a temporary
* node at the current position.
* @return {Node} A temporary node marking the current cursor position. This
* node should eventually be removed from the DOM.
* @private
*/
goog.editor.plugins.EnterHandler.prototype.deleteCursorSelectionIE_ =
function() {
var doc = this.getFieldDomHelper().getDocument();
var range = doc.selection.createRange();
var id = goog.string.createUniqueString();
range.pasteHTML('
');
var splitNode = doc.getElementById(id);
splitNode.id = '';
return splitNode;
};
/**
* Delete the selection at the current cursor position, then returns the node
* at the current position.
* @return {goog.editor.range.Point} The current cursor position. Note that
* unlike simulateEnterIE_, this should not be removed from the DOM.
* @private
*/
goog.editor.plugins.EnterHandler.prototype.deleteCursorSelectionW3C_ =
function() {
var range = this.getFieldObject().getRange();
// Delete the current selection if it's is non-collapsed.
// Although this is redundant in FF, it's necessary for Safari
if (!range.isCollapsed()) {
var shouldDelete = true;
// Opera selects the
in an empty block if there is no text node
// preceding it. To preserve inline formatting when pressing [enter] inside
// an empty block, don't delete the selection if it only selects a
at
// the end of the block.
// TODO(user): Move this into goog.dom.Range. It should detect this state
// when creating a range from the window selection and fix it in the created
// range.
if (goog.userAgent.OPERA) {
var startNode = range.getStartNode();
var startOffset = range.getStartOffset();
if (startNode == range.getEndNode() &&
// This weeds out cases where startNode is a text node.
startNode.lastChild &&
startNode.lastChild.tagName == goog.dom.TagName.BR &&
// If this check is true, then endOffset is implied to be
// startOffset + 1, because the selection is not collapsed and
// it starts and ends within the same element.
startOffset == startNode.childNodes.length - 1) {
shouldDelete = false;
}
}
if (shouldDelete) {
goog.editor.plugins.EnterHandler.deleteW3cRange_(range);
}
}
return goog.editor.range.getDeepEndPoint(range, true);
};
/**
* Deletes the contents of the selection from the DOM.
* @param {goog.dom.AbstractRange} range The range to remove contents from.
* @return {goog.dom.AbstractRange} The resulting range. Used for testing.
* @private
*/
goog.editor.plugins.EnterHandler.deleteW3cRange_ = function(range) {
if (range && !range.isCollapsed()) {
var reselect = true;
var baseNode = range.getContainerElement();
var nodeOffset = new goog.dom.NodeOffset(range.getStartNode(), baseNode);
var rangeOffset = range.getStartOffset();
// Whether the selection crosses no container boundaries.
var isInOneContainer =
goog.editor.plugins.EnterHandler.isInOneContainerW3c_(range);
// Whether the selection ends in a container it doesn't fully select.
var isPartialEnd = !isInOneContainer &&
goog.editor.plugins.EnterHandler.isPartialEndW3c_(range);
// Remove The range contents, and ensure the correct content stays selected.
range.removeContents();
var node = nodeOffset.findTargetNode(baseNode);
if (node) {
range = goog.dom.Range.createCaret(node, rangeOffset);
} else {
// This occurs when the node that would have been referenced has now been
// deleted and there are no other nodes in the baseNode. Thus need to
// set the caret to the end of the base node.
range =
goog.dom.Range.createCaret(baseNode, baseNode.childNodes.length);
reselect = false;
}
range.select();
// If we just deleted everything from the container, add an nbsp
// to the container, and leave the cursor inside of it
if (isInOneContainer) {
var container = goog.editor.style.getContainer(range.getStartNode());
if (goog.editor.node.isEmpty(container, true)) {
var html = ' ';
if (goog.userAgent.OPERA &&
container.tagName == goog.dom.TagName.LI) {
// Don't break Opera's native break-out-of-lists behavior.
html = '
';
}
container.innerHTML = html;
goog.editor.range.selectNodeStart(container.firstChild);
reselect = false;
}
}
if (isPartialEnd) {
/*
This code handles the following, where | is the cursor:
a|b
c|d
After removeContents, the remaining HTML is
a
d
which means the line break between the two divs remains. This block
moves children of the second div in to the first div to get the correct
result:
ad
TODO(robbyw): Should we wrap the second div's contents in a span if they
have inline style?
*/
var rangeStart = goog.editor.style.getContainer(range.getStartNode());
var redundantContainer = goog.editor.node.getNextSibling(rangeStart);
if (rangeStart && redundantContainer) {
goog.dom.append(rangeStart, redundantContainer.childNodes);
goog.dom.removeNode(redundantContainer);
}
}
if (reselect) {
// The contents of the original range are gone, so restore the cursor
// position at the start of where the range once was.
range = goog.dom.Range.createCaret(nodeOffset.findTargetNode(baseNode),
rangeOffset);
range.select();
}
}
return range;
};
/**
* Checks whether the whole range is in a single block-level element.
* @param {goog.dom.AbstractRange} range The range to check.
* @return {boolean} Whether the whole range is in a single block-level element.
* @private
*/
goog.editor.plugins.EnterHandler.isInOneContainerW3c_ = function(range) {
// Find the block element containing the start of the selection.
var startContainer = range.getStartNode();
if (goog.editor.style.isContainer(startContainer)) {
startContainer = startContainer.childNodes[range.getStartOffset()] ||
startContainer;
}
startContainer = goog.editor.style.getContainer(startContainer);
// Find the block element containing the end of the selection.
var endContainer = range.getEndNode();
if (goog.editor.style.isContainer(endContainer)) {
endContainer = endContainer.childNodes[range.getEndOffset()] ||
endContainer;
}
endContainer = goog.editor.style.getContainer(endContainer);
// Compare the two.
return startContainer == endContainer;
};
/**
* Checks whether the end of the range is not at the end of a block-level
* element.
* @param {goog.dom.AbstractRange} range The range to check.
* @return {boolean} Whether the end of the range is not at the end of a
* block-level element.
* @private
*/
goog.editor.plugins.EnterHandler.isPartialEndW3c_ = function(range) {
var endContainer = range.getEndNode();
var endOffset = range.getEndOffset();
var node = endContainer;
if (goog.editor.style.isContainer(node)) {
var child = node.childNodes[endOffset];
// Child is null when end offset is >= length, which indicates the entire
// container is selected. Otherwise, we also know the entire container
// is selected if the selection ends at a new container.
if (!child ||
child.nodeType == goog.dom.NodeType.ELEMENT &&
goog.editor.style.isContainer(child)) {
return false;
}
}
var container = goog.editor.style.getContainer(node);
while (container != node) {
if (goog.editor.node.getNextSibling(node)) {
return true;
}
node = node.parentNode;
}
return endOffset != goog.editor.node.getLength(endContainer);
};