diff options
Diffstat (limited to 'WebCore/editing/IndentOutdentCommand.cpp')
-rw-r--r-- | WebCore/editing/IndentOutdentCommand.cpp | 244 |
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); |