1 /* 2 * Copyright (C) 2005 Apple Computer, Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY 14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR 17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26 #include "config.h" 27 #include "core/editing/BreakBlockquoteCommand.h" 28 29 #include "core/HTMLNames.h" 30 #include "core/dom/NodeTraversal.h" 31 #include "core/dom/Text.h" 32 #include "core/editing/VisiblePosition.h" 33 #include "core/editing/htmlediting.h" 34 #include "core/html/HTMLBRElement.h" 35 #include "core/html/HTMLElement.h" 36 #include "core/html/HTMLQuoteElement.h" 37 #include "core/rendering/RenderListItem.h" 38 39 namespace blink { 40 41 using namespace HTMLNames; 42 43 BreakBlockquoteCommand::BreakBlockquoteCommand(Document& document) 44 : CompositeEditCommand(document) 45 { 46 } 47 48 void BreakBlockquoteCommand::doApply() 49 { 50 if (endingSelection().isNone()) 51 return; 52 53 // Delete the current selection. 54 if (endingSelection().isRange()) 55 deleteSelection(false, false); 56 57 // This is a scenario that should never happen, but we want to 58 // make sure we don't dereference a null pointer below. 59 60 ASSERT(!endingSelection().isNone()); 61 62 if (endingSelection().isNone()) 63 return; 64 65 VisiblePosition visiblePos = endingSelection().visibleStart(); 66 67 // pos is a position equivalent to the caret. We use downstream() so that pos will 68 // be in the first node that we need to move (there are a few exceptions to this, see below). 69 Position pos = endingSelection().start().downstream(); 70 71 // Find the top-most blockquote from the start. 72 HTMLQuoteElement* topBlockquote = toHTMLQuoteElement(highestEnclosingNodeOfType(pos, isMailHTMLBlockquoteElement)); 73 if (!topBlockquote || !topBlockquote->parentNode()) 74 return; 75 76 RefPtrWillBeRawPtr<HTMLBRElement> breakElement = createBreakElement(document()); 77 78 bool isLastVisPosInNode = isLastVisiblePositionInNode(visiblePos, topBlockquote); 79 80 // If the position is at the beginning of the top quoted content, we don't need to break the quote. 81 // Instead, insert the break before the blockquote, unless the position is as the end of the the quoted content. 82 if (isFirstVisiblePositionInNode(visiblePos, topBlockquote) && !isLastVisPosInNode) { 83 insertNodeBefore(breakElement.get(), topBlockquote); 84 setEndingSelection(VisibleSelection(positionBeforeNode(breakElement.get()), DOWNSTREAM, endingSelection().isDirectional())); 85 rebalanceWhitespace(); 86 return; 87 } 88 89 // Insert a break after the top blockquote. 90 insertNodeAfter(breakElement.get(), topBlockquote); 91 92 // If we're inserting the break at the end of the quoted content, we don't need to break the quote. 93 if (isLastVisPosInNode) { 94 setEndingSelection(VisibleSelection(positionBeforeNode(breakElement.get()), DOWNSTREAM, endingSelection().isDirectional())); 95 rebalanceWhitespace(); 96 return; 97 } 98 99 // Don't move a line break just after the caret. Doing so would create an extra, empty paragraph 100 // in the new blockquote. 101 if (lineBreakExistsAtVisiblePosition(visiblePos)) 102 pos = pos.next(); 103 104 // Adjust the position so we don't split at the beginning of a quote. 105 while (isFirstVisiblePositionInNode(VisiblePosition(pos), toHTMLQuoteElement(enclosingNodeOfType(pos, isMailHTMLBlockquoteElement)))) 106 pos = pos.previous(); 107 108 // startNode is the first node that we need to move to the new blockquote. 109 Node* startNode = pos.deprecatedNode(); 110 ASSERT(startNode); 111 112 // Split at pos if in the middle of a text node. 113 if (startNode->isTextNode()) { 114 Text* textNode = toText(startNode); 115 if ((unsigned)pos.deprecatedEditingOffset() >= textNode->length()) { 116 startNode = NodeTraversal::next(*startNode); 117 ASSERT(startNode); 118 } else if (pos.deprecatedEditingOffset() > 0) 119 splitTextNode(textNode, pos.deprecatedEditingOffset()); 120 } else if (pos.deprecatedEditingOffset() > 0) { 121 Node* childAtOffset = NodeTraversal::childAt(*startNode, pos.deprecatedEditingOffset()); 122 startNode = childAtOffset ? childAtOffset : NodeTraversal::next(*startNode); 123 ASSERT(startNode); 124 } 125 126 // If there's nothing inside topBlockquote to move, we're finished. 127 if (!startNode->isDescendantOf(topBlockquote)) { 128 setEndingSelection(VisibleSelection(VisiblePosition(firstPositionInOrBeforeNode(startNode)), endingSelection().isDirectional())); 129 return; 130 } 131 132 // Build up list of ancestors in between the start node and the top blockquote. 133 WillBeHeapVector<RefPtrWillBeMember<Element> > ancestors; 134 for (Element* node = startNode->parentElement(); node && node != topBlockquote; node = node->parentElement()) 135 ancestors.append(node); 136 137 // Insert a clone of the top blockquote after the break. 138 RefPtrWillBeRawPtr<Element> clonedBlockquote = topBlockquote->cloneElementWithoutChildren(); 139 insertNodeAfter(clonedBlockquote.get(), breakElement.get()); 140 141 // Clone startNode's ancestors into the cloned blockquote. 142 // On exiting this loop, clonedAncestor is the lowest ancestor 143 // that was cloned (i.e. the clone of either ancestors.last() 144 // or clonedBlockquote if ancestors is empty). 145 RefPtrWillBeRawPtr<Element> clonedAncestor = clonedBlockquote; 146 for (size_t i = ancestors.size(); i != 0; --i) { 147 RefPtrWillBeRawPtr<Element> clonedChild = ancestors[i - 1]->cloneElementWithoutChildren(); 148 // Preserve list item numbering in cloned lists. 149 if (isHTMLOListElement(*clonedChild)) { 150 Node* listChildNode = i > 1 ? ancestors[i - 2].get() : startNode; 151 // The first child of the cloned list might not be a list item element, 152 // find the first one so that we know where to start numbering. 153 while (listChildNode && !isHTMLLIElement(*listChildNode)) 154 listChildNode = listChildNode->nextSibling(); 155 if (listChildNode && listChildNode->renderer() && listChildNode->renderer()->isListItem()) 156 setNodeAttribute(clonedChild, startAttr, AtomicString::number(toRenderListItem(listChildNode->renderer())->value())); 157 } 158 159 appendNode(clonedChild.get(), clonedAncestor.get()); 160 clonedAncestor = clonedChild; 161 } 162 163 moveRemainingSiblingsToNewParent(startNode, 0, clonedAncestor); 164 165 if (!ancestors.isEmpty()) { 166 // Split the tree up the ancestor chain until the topBlockquote 167 // Throughout this loop, clonedParent is the clone of ancestor's parent. 168 // This is so we can clone ancestor's siblings and place the clones 169 // into the clone corresponding to the ancestor's parent. 170 RefPtrWillBeRawPtr<Element> ancestor = nullptr; 171 RefPtrWillBeRawPtr<Element> clonedParent = nullptr; 172 for (ancestor = ancestors.first(), clonedParent = clonedAncestor->parentElement(); 173 ancestor && ancestor != topBlockquote; 174 ancestor = ancestor->parentElement(), clonedParent = clonedParent->parentElement()) 175 moveRemainingSiblingsToNewParent(ancestor->nextSibling(), 0, clonedParent); 176 177 // If the startNode's original parent is now empty, remove it 178 Element* originalParent = ancestors.first().get(); 179 if (!originalParent->hasChildren()) 180 removeNode(originalParent); 181 } 182 183 // Make sure the cloned block quote renders. 184 addBlockPlaceholderIfNeeded(clonedBlockquote.get()); 185 186 // Put the selection right before the break. 187 setEndingSelection(VisibleSelection(positionBeforeNode(breakElement.get()), DOWNSTREAM, endingSelection().isDirectional())); 188 rebalanceWhitespace(); 189 } 190 191 } // namespace blink 192