summaryrefslogtreecommitdiffstats
path: root/WebCore/editing/IndentOutdentCommand.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'WebCore/editing/IndentOutdentCommand.cpp')
-rw-r--r--WebCore/editing/IndentOutdentCommand.cpp244
1 files changed, 146 insertions, 98 deletions
diff --git a/WebCore/editing/IndentOutdentCommand.cpp b/WebCore/editing/IndentOutdentCommand.cpp
index 0f9b106..8c4ba01 100644
--- a/WebCore/editing/IndentOutdentCommand.cpp
+++ b/WebCore/editing/IndentOutdentCommand.cpp
@@ -33,6 +33,7 @@
#include "InsertLineBreakCommand.h"
#include "InsertListCommand.h"
#include "Range.h"
+#include "DocumentFragment.h"
#include "SplitElementCommand.h"
#include "TextIterator.h"
#include "htmlediting.h"
@@ -57,18 +58,9 @@ static PassRefPtr<HTMLBlockquoteElement> createIndentBlockquoteElement(Document*
return element.release();
}
-static bool isIndentBlockquote(const Node* node)
-{
- if (!node || !node->hasTagName(blockquoteTag) || !node->isElementNode())
- return false;
-
- const Element* elem = static_cast<const Element*>(node);
- return elem->getAttribute(classAttr) == indentBlockquoteString();
-}
-
static bool isListOrIndentBlockquote(const Node* node)
{
- return node && (node->hasTagName(ulTag) || node->hasTagName(olTag) || isIndentBlockquote(node));
+ return node && (node->hasTagName(ulTag) || node->hasTagName(olTag) || node->hasTagName(blockquoteTag));
}
IndentOutdentCommand::IndentOutdentCommand(Document* document, EIndentType typeOfAction, int marginInPixels)
@@ -76,35 +68,115 @@ IndentOutdentCommand::IndentOutdentCommand(Document* document, EIndentType typeO
{
}
-// This function is a workaround for moveParagraph's tendency to strip blockquotes. It updates lastBlockquote to point to the
-// correct level for the current paragraph, and returns a pointer to a placeholder br where the insertion should be performed.
-PassRefPtr<Element> IndentOutdentCommand::prepareBlockquoteLevelForInsertion(VisiblePosition& currentParagraph, RefPtr<Element>& lastBlockquote)
+bool IndentOutdentCommand::tryIndentingAsListItem(const VisiblePosition& endOfCurrentParagraph)
+{
+ // If our selection is not inside a list, bail out.
+ Node* lastNodeInSelectedParagraph = endOfCurrentParagraph.deepEquivalent().node();
+ RefPtr<Element> listNode = enclosingList(lastNodeInSelectedParagraph);
+ if (!listNode)
+ return false;
+
+ // Find the list item enclosing the current paragraph
+ Element* selectedListItem = static_cast<Element*>(enclosingBlock(endOfCurrentParagraph.deepEquivalent().node()));
+ // FIXME: we need to deal with the case where there is no li (malformed HTML)
+ if (!selectedListItem->hasTagName(liTag))
+ return false;
+
+ // FIXME: previousElementSibling does not ignore non-rendered content like <span></span>. Should we?
+ Element* previousList = selectedListItem->previousElementSibling();
+ Element* nextList = selectedListItem->nextElementSibling();
+
+ RefPtr<Element> newList = document()->createElement(listNode->tagQName(), false);
+ insertNodeBefore(newList, selectedListItem);
+ appendParagraphIntoNode(visiblePositionBeforeNode(selectedListItem), visiblePositionAfterNode(selectedListItem), newList.get());
+
+ if (canMergeLists(previousList, newList.get()))
+ mergeIdenticalElements(previousList, newList);
+ if (canMergeLists(newList.get(), nextList))
+ mergeIdenticalElements(newList, nextList);
+
+ return true;
+}
+
+void IndentOutdentCommand::indentIntoBlockquote(const VisiblePosition& startOfCurrentParagraph, const VisiblePosition& endOfCurrentParagraph, RefPtr<Element>& targetBlockquote, Node* nodeToSplitTo)
{
- int currentBlockquoteLevel = 0;
- int lastBlockquoteLevel = 0;
- Node* node = currentParagraph.deepEquivalent().node();
- while ((node = enclosingNodeOfType(Position(node->parentNode(), 0), &isIndentBlockquote)))
- currentBlockquoteLevel++;
- node = lastBlockquote.get();
- while ((node = enclosingNodeOfType(Position(node->parentNode(), 0), &isIndentBlockquote)))
- lastBlockquoteLevel++;
- while (currentBlockquoteLevel > lastBlockquoteLevel) {
- RefPtr<Element> newBlockquote = createIndentBlockquoteElement(document());
- appendNode(newBlockquote, lastBlockquote);
- lastBlockquote = newBlockquote;
- lastBlockquoteLevel++;
+ Node* enclosingCell = 0;
+
+ if (!targetBlockquote) {
+ // Create a new blockquote and insert it as a child of the enclosing block element. We accomplish
+ // this by splitting all parents of the current paragraph up to that point.
+ targetBlockquote = createIndentBlockquoteElement(document());
+ if (isTableCell(nodeToSplitTo))
+ enclosingCell = nodeToSplitTo;
+ RefPtr<Node> startOfNewBlock = splitTreeToNode(startOfCurrentParagraph.deepEquivalent().node(), nodeToSplitTo);
+ insertNodeBefore(targetBlockquote, startOfNewBlock);
}
- while (currentBlockquoteLevel < lastBlockquoteLevel) {
- lastBlockquote = static_cast<Element*>(enclosingNodeOfType(Position(lastBlockquote->parentNode(), 0), isIndentBlockquote));
- lastBlockquoteLevel--;
+
+ VisiblePosition endOfNextParagraph = endOfParagraph(endOfCurrentParagraph.next());
+ appendParagraphIntoNode(startOfCurrentParagraph, endOfCurrentParagraph, targetBlockquote.get());
+
+ // Don't put the next paragraph in the blockquote we just created for this paragraph unless
+ // the next paragraph is in the same cell.
+ if (enclosingCell && enclosingCell != enclosingNodeOfType(endOfNextParagraph.deepEquivalent(), &isTableCell))
+ targetBlockquote = 0;
+}
+
+bool IndentOutdentCommand::isAtUnsplittableElement(const Position& pos) const
+{
+ Node* node = pos.node();
+ return node == editableRootForPosition(pos) || node == enclosingNodeOfType(pos, &isTableCell);
+}
+
+// Enclose all nodes between start and end by newParent, which is a sibling node of nodes between start and end
+// FIXME: moveParagraph is overly complicated. We need to clean up moveParagraph so that it uses appendParagraphIntoNode
+// or prepare more specialized functions and delete moveParagraph
+void IndentOutdentCommand::appendParagraphIntoNode(const VisiblePosition& start, const VisiblePosition& end, Node* newParent)
+{
+ ASSERT(newParent);
+ ASSERT(newParent->isContentEditable());
+ ASSERT(isStartOfParagraph(start) && isEndOfParagraph(end));
+
+ Position endOfParagraph = end.deepEquivalent().downstream();
+ Node* insertionPoint = newParent->lastChild();// Remember the place to put br later
+ // Look for the beginning of the last paragraph in newParent
+ Node* startOfLastParagraph = startOfParagraph(Position(newParent, newParent->childNodeCount())).deepEquivalent().node();
+ if (startOfLastParagraph && !startOfLastParagraph->isDescendantOf(newParent))
+ startOfLastParagraph = 0;
+
+ // Extend the range so that we can append wrapping nodes as well if they're containd within the paragraph
+ ExceptionCode ec = 0;
+ RefPtr<Range> selectedRange = createRange(document(), start, end, ec);
+ RefPtr<Range> extendedRange = extendRangeToWrappingNodes(selectedRange, selectedRange.get(), newParent->parentNode());
+ newParent->appendChild(extendedRange->extractContents(ec), ec);
+
+ // If the start of paragraph didn't change by appending nodes, we should insert br to seperate the paragraphs.
+ Node* startOfNewParagraph = startOfParagraph(Position(newParent, newParent->childNodeCount())).deepEquivalent().node();
+ if (startOfNewParagraph == startOfLastParagraph) {
+ if (insertionPoint)
+ newParent->insertBefore(createBreakElement(document()), insertionPoint->nextSibling(), ec);
+ else
+ newParent->appendChild(createBreakElement(document()), ec);
}
- RefPtr<Element> placeholder = createBreakElement(document());
- appendNode(placeholder, lastBlockquote);
- // Add another br before the placeholder if it collapsed.
- VisiblePosition visiblePos(Position(placeholder.get(), 0));
- if (!isStartOfParagraph(visiblePos))
- insertNodeBefore(createBreakElement(document()), placeholder);
- return placeholder.release();
+
+ // Remove unnecessary br from the place where we moved the paragraph from
+ removeUnnecessaryLineBreakAt(endOfParagraph);
+}
+
+void IndentOutdentCommand::removeUnnecessaryLineBreakAt(const Position& endOfParagraph)
+{
+ // If there is something in this paragraph, then don't remove br.
+ if (!isStartOfParagraph(endOfParagraph) || !isEndOfParagraph(endOfParagraph))
+ return;
+
+ // We only care about br at the end of paragraph
+ Node* br = endOfParagraph.node();
+ Node* parentNode = br->parentNode();
+
+ // If the node isn't br or the parent node is empty, then don't remove.
+ if (!br->hasTagName(brTag) || isVisiblyAdjacent(positionBeforeNode(parentNode), positionAfterNode(parentNode)))
+ return;
+
+ removeNodeAndPruneAncestors(br);
}
void IndentOutdentCommand::indentRegion()
@@ -112,16 +184,15 @@ void IndentOutdentCommand::indentRegion()
VisibleSelection selection = selectionForParagraphIteration(endingSelection());
VisiblePosition startOfSelection = selection.visibleStart();
VisiblePosition endOfSelection = selection.visibleEnd();
- int startIndex = indexForVisiblePosition(startOfSelection);
- int endIndex = indexForVisiblePosition(endOfSelection);
+ RefPtr<Range> selectedRange = selection.firstRange();
ASSERT(!startOfSelection.isNull());
ASSERT(!endOfSelection.isNull());
-
- // Special case empty root editable elements because there's nothing to split
+
+ // Special case empty unsplittable elements because there's nothing to split
// and there's nothing to move.
Position start = startOfSelection.deepEquivalent().downstream();
- if (start.node() == editableRootForPosition(start)) {
+ if (isAtUnsplittableElement(start)) {
RefPtr<Element> blockquote = createIndentBlockquoteElement(document());
insertNodeAt(blockquote, start);
RefPtr<Element> placeholder = createBreakElement(document());
@@ -129,69 +200,42 @@ void IndentOutdentCommand::indentRegion()
setEndingSelection(VisibleSelection(Position(placeholder.get(), 0), DOWNSTREAM));
return;
}
-
- RefPtr<Element> previousListNode;
- RefPtr<Element> newListNode;
- RefPtr<Element> newBlockquote;
+
+ RefPtr<Element> blockquoteForNextIndent;
VisiblePosition endOfCurrentParagraph = endOfParagraph(startOfSelection);
VisiblePosition endAfterSelection = endOfParagraph(endOfParagraph(endOfSelection).next());
while (endOfCurrentParagraph != endAfterSelection) {
// Iterate across the selected paragraphs...
VisiblePosition endOfNextParagraph = endOfParagraph(endOfCurrentParagraph.next());
- RefPtr<Element> listNode = enclosingList(endOfCurrentParagraph.deepEquivalent().node());
- RefPtr<Element> insertionPoint;
- if (listNode) {
- RefPtr<Element> placeholder = createBreakElement(document());
- insertionPoint = placeholder;
- newBlockquote = 0;
- RefPtr<Element> listItem = createListItemElement(document());
- if (listNode == previousListNode) {
- // The previous paragraph was inside the same list, so add this list item to the list we already created
- appendNode(listItem, newListNode);
- appendNode(placeholder, listItem);
- } else {
- // Clone the list element, insert it before the current paragraph, and move the paragraph into it.
- RefPtr<Element> clonedList = listNode->cloneElementWithoutChildren();
- insertNodeBefore(clonedList, enclosingListChild(endOfCurrentParagraph.deepEquivalent().node()));
- appendNode(listItem, clonedList);
- appendNode(placeholder, listItem);
- newListNode = clonedList;
- previousListNode = listNode;
- }
- } else if (newBlockquote)
- // The previous paragraph was put into a new blockquote, so move this paragraph there as well
- insertionPoint = prepareBlockquoteLevelForInsertion(endOfCurrentParagraph, newBlockquote);
+ if (tryIndentingAsListItem(endOfCurrentParagraph))
+ blockquoteForNextIndent = 0;
else {
- // Create a new blockquote and insert it as a child of the root editable element. We accomplish
- // this by splitting all parents of the current paragraph up to that point.
- RefPtr<Element> blockquote = createIndentBlockquoteElement(document());
- Position start = startOfParagraph(endOfCurrentParagraph).deepEquivalent();
-
- Node* enclosingCell = enclosingNodeOfType(start, &isTableCell);
- Node* nodeToSplitTo = enclosingCell ? enclosingCell : editableRootForPosition(start);
- RefPtr<Node> startOfNewBlock = splitTreeToNode(start.node(), nodeToSplitTo);
- insertNodeBefore(blockquote, startOfNewBlock);
- newBlockquote = blockquote;
- insertionPoint = prepareBlockquoteLevelForInsertion(endOfCurrentParagraph, newBlockquote);
- // Don't put the next paragraph in the blockquote we just created for this paragraph unless
- // the next paragraph is in the same cell.
- if (enclosingCell && enclosingCell != enclosingNodeOfType(endOfNextParagraph.deepEquivalent(), &isTableCell))
- newBlockquote = 0;
+ VisiblePosition startOfCurrentParagraph = startOfParagraph(endOfCurrentParagraph);
+ Node* blockNode = enclosingBlock(endOfCurrentParagraph.deepEquivalent().node());
+ // extend the region so that it contains all the ancestor blocks within the selection
+ ExceptionCode ec;
+ Element* unsplittableNode = unsplittableElementForPosition(endOfCurrentParagraph.deepEquivalent());
+ RefPtr<Range> originalRange = createRange(document(), endOfCurrentParagraph, endOfCurrentParagraph, ec);
+ RefPtr<Range> extendedRange = extendRangeToWrappingNodes(originalRange, selectedRange.get(), unsplittableNode);
+ if (originalRange != extendedRange) {
+ ExceptionCode ec = 0;
+ endOfCurrentParagraph = endOfParagraph(extendedRange->endPosition().previous());
+ blockNode = enclosingBlock(extendedRange->commonAncestorContainer(ec));
+ }
+
+ endOfNextParagraph = endOfParagraph(endOfCurrentParagraph.next());
+ indentIntoBlockquote(startOfCurrentParagraph, endOfCurrentParagraph, blockquoteForNextIndent, blockNode);
+ // blockquoteForNextIndent will be updated in the function
}
- moveParagraph(startOfParagraph(endOfCurrentParagraph), endOfCurrentParagraph, VisiblePosition(Position(insertionPoint, 0)), true);
- // moveParagraph should not destroy content that contains endOfNextParagraph, but if it does, return here
- // to avoid a crash.
+ // Sanity check: Make sure our moveParagraph calls didn't remove endOfNextParagraph.deepEquivalent().node()
+ // If somehow we did, return to prevent crashes.
if (endOfNextParagraph.isNotNull() && !endOfNextParagraph.deepEquivalent().node()->inDocument()) {
ASSERT_NOT_REACHED();
return;
}
endOfCurrentParagraph = endOfNextParagraph;
}
-
- RefPtr<Range> startRange = TextIterator::rangeFromLocationAndLength(document()->documentElement(), startIndex, 0, true);
- RefPtr<Range> endRange = TextIterator::rangeFromLocationAndLength(document()->documentElement(), endIndex, 0, true);
- if (startRange && endRange)
- setEndingSelection(VisibleSelection(startRange->startPosition(), endRange->startPosition(), DOWNSTREAM));
+
}
void IndentOutdentCommand::outdentParagraph()
@@ -200,7 +244,7 @@ void IndentOutdentCommand::outdentParagraph()
VisiblePosition visibleEndOfParagraph = endOfParagraph(visibleStartOfParagraph);
Node* enclosingNode = enclosingNodeOfType(visibleStartOfParagraph.deepEquivalent(), &isListOrIndentBlockquote);
- if (!enclosingNode || !isContentEditable(enclosingNode->parentNode())) // We can't outdent if there is no place to go!
+ if (!enclosingNode || !enclosingNode->parentNode()->isContentEditable()) // We can't outdent if there is no place to go!
return;
// Use InsertListCommand to remove the selection from the list
@@ -213,7 +257,7 @@ void IndentOutdentCommand::outdentParagraph()
return;
}
- // The selection is inside a blockquote
+ // The selection is inside a blockquote i.e. enclosingNode is a blockquote
VisiblePosition positionInEnclosingBlock = VisiblePosition(Position(enclosingNode, 0));
VisiblePosition startOfEnclosingBlock = startOfBlock(positionInEnclosingBlock);
VisiblePosition lastPositionInEnclosingBlock = VisiblePosition(Position(enclosingNode, enclosingNode->childNodeCount()));
@@ -227,9 +271,9 @@ void IndentOutdentCommand::outdentParagraph()
// just removed one, then this assumption isn't true. By splitting the next containing blockquote after this node, we keep this assumption true
if (splitPoint) {
if (Node* splitPointParent = splitPoint->parentNode()) {
- if (isIndentBlockquote(splitPointParent)
- && !isIndentBlockquote(splitPoint)
- && isContentEditable(splitPointParent->parentNode())) // We can't outdent if there is no place to go!
+ if (splitPointParent->hasTagName(blockquoteTag)
+ && !splitPoint->hasTagName(blockquoteTag)
+ && splitPointParent->parentNode()->isContentEditable()) // We can't outdent if there is no place to go!
splitElement(static_cast<Element*>(splitPointParent), splitPoint);
}
}
@@ -244,10 +288,14 @@ void IndentOutdentCommand::outdentParagraph()
return;
}
- Node* enclosingBlockFlow = enclosingBlockFlowElement(visibleStartOfParagraph);
+ Node* enclosingBlockFlow = enclosingBlock(visibleStartOfParagraph.deepEquivalent().node());
RefPtr<Node> splitBlockquoteNode = enclosingNode;
if (enclosingBlockFlow != enclosingNode)
- splitBlockquoteNode = splitTreeToNode(enclosingBlockFlowElement(visibleStartOfParagraph), enclosingNode, true);
+ splitBlockquoteNode = splitTreeToNode(enclosingBlockFlow, enclosingNode, true);
+ else {
+ // We split the blockquote at where we start outdenting.
+ splitElement(static_cast<Element*>(enclosingNode), visibleStartOfParagraph.deepEquivalent().node());
+ }
RefPtr<Node> placeholder = createBreakElement(document());
insertNodeBefore(placeholder, splitBlockquoteNode);
moveParagraph(startOfParagraph(visibleStartOfParagraph), endOfParagraph(visibleEndOfParagraph), VisiblePosition(Position(placeholder.get(), 0)), true);