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 "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/HTMLElement.h" 35 #include "core/rendering/RenderListItem.h" 36 37 namespace WebCore { 38 39 using namespace HTMLNames; 40 41 BreakBlockquoteCommand::BreakBlockquoteCommand(Document& document) 42 : CompositeEditCommand(document) 43 { 44 } 45 46 void BreakBlockquoteCommand::doApply() 47 { 48 if (endingSelection().isNone()) 49 return; 50 51 // Delete the current selection. 52 if (endingSelection().isRange()) 53 deleteSelection(false, false); 54 55 // This is a scenario that should never happen, but we want to 56 // make sure we don't dereference a null pointer below. 57 58 ASSERT(!endingSelection().isNone()); 59 60 if (endingSelection().isNone()) 61 return; 62 63 VisiblePosition visiblePos = endingSelection().visibleStart(); 64 65 // pos is a position equivalent to the caret. We use downstream() so that pos will 66 // be in the first node that we need to move (there are a few exceptions to this, see below). 67 Position pos = endingSelection().start().downstream(); 68 69 // Find the top-most blockquote from the start. 70 Node* topBlockquote = highestEnclosingNodeOfType(pos, isMailBlockquote); 71 if (!topBlockquote || !topBlockquote->parentNode() || !topBlockquote->isElementNode()) 72 return; 73 74 RefPtr<Element> breakNode = createBreakElement(document()); 75 76 bool isLastVisPosInNode = isLastVisiblePositionInNode(visiblePos, topBlockquote); 77 78 // If the position is at the beginning of the top quoted content, we don't need to break the quote. 79 // Instead, insert the break before the blockquote, unless the position is as the end of the the quoted content. 80 if (isFirstVisiblePositionInNode(visiblePos, topBlockquote) && !isLastVisPosInNode) { 81 insertNodeBefore(breakNode.get(), topBlockquote); 82 setEndingSelection(VisibleSelection(positionBeforeNode(breakNode.get()), DOWNSTREAM, endingSelection().isDirectional())); 83 rebalanceWhitespace(); 84 return; 85 } 86 87 // Insert a break after the top blockquote. 88 insertNodeAfter(breakNode.get(), topBlockquote); 89 90 // If we're inserting the break at the end of the quoted content, we don't need to break the quote. 91 if (isLastVisPosInNode) { 92 setEndingSelection(VisibleSelection(positionBeforeNode(breakNode.get()), DOWNSTREAM, endingSelection().isDirectional())); 93 rebalanceWhitespace(); 94 return; 95 } 96 97 // Don't move a line break just after the caret. Doing so would create an extra, empty paragraph 98 // in the new blockquote. 99 if (lineBreakExistsAtVisiblePosition(visiblePos)) 100 pos = pos.next(); 101 102 // Adjust the position so we don't split at the beginning of a quote. 103 while (isFirstVisiblePositionInNode(VisiblePosition(pos), enclosingNodeOfType(pos, isMailBlockquote))) 104 pos = pos.previous(); 105 106 // startNode is the first node that we need to move to the new blockquote. 107 Node* startNode = pos.deprecatedNode(); 108 ASSERT(startNode); 109 110 // Split at pos if in the middle of a text node. 111 if (startNode->isTextNode()) { 112 Text* textNode = toText(startNode); 113 if ((unsigned)pos.deprecatedEditingOffset() >= textNode->length()) { 114 startNode = NodeTraversal::next(*startNode); 115 ASSERT(startNode); 116 } else if (pos.deprecatedEditingOffset() > 0) 117 splitTextNode(textNode, pos.deprecatedEditingOffset()); 118 } else if (pos.deprecatedEditingOffset() > 0) { 119 Node* childAtOffset = startNode->childNode(pos.deprecatedEditingOffset()); 120 startNode = childAtOffset ? childAtOffset : NodeTraversal::next(*startNode); 121 ASSERT(startNode); 122 } 123 124 // If there's nothing inside topBlockquote to move, we're finished. 125 if (!startNode->isDescendantOf(topBlockquote)) { 126 setEndingSelection(VisibleSelection(VisiblePosition(firstPositionInOrBeforeNode(startNode)), endingSelection().isDirectional())); 127 return; 128 } 129 130 // Build up list of ancestors in between the start node and the top blockquote. 131 Vector<RefPtr<Element> > ancestors; 132 for (Element* node = startNode->parentElement(); node && node != topBlockquote; node = node->parentElement()) 133 ancestors.append(node); 134 135 // Insert a clone of the top blockquote after the break. 136 RefPtr<Element> clonedBlockquote = toElement(topBlockquote)->cloneElementWithoutChildren(); 137 insertNodeAfter(clonedBlockquote.get(), breakNode.get()); 138 139 // Clone startNode's ancestors into the cloned blockquote. 140 // On exiting this loop, clonedAncestor is the lowest ancestor 141 // that was cloned (i.e. the clone of either ancestors.last() 142 // or clonedBlockquote if ancestors is empty). 143 RefPtr<Element> clonedAncestor = clonedBlockquote; 144 for (size_t i = ancestors.size(); i != 0; --i) { 145 RefPtr<Element> clonedChild = ancestors[i - 1]->cloneElementWithoutChildren(); 146 // Preserve list item numbering in cloned lists. 147 if (clonedChild->isElementNode() && clonedChild->hasTagName(olTag)) { 148 Node* listChildNode = i > 1 ? ancestors[i - 2].get() : startNode; 149 // The first child of the cloned list might not be a list item element, 150 // find the first one so that we know where to start numbering. 151 while (listChildNode && !listChildNode->hasTagName(liTag)) 152 listChildNode = listChildNode->nextSibling(); 153 if (listChildNode && listChildNode->renderer() && listChildNode->renderer()->isListItem()) 154 setNodeAttribute(clonedChild, startAttr, AtomicString::number(toRenderListItem(listChildNode->renderer())->value())); 155 } 156 157 appendNode(clonedChild.get(), clonedAncestor.get()); 158 clonedAncestor = clonedChild; 159 } 160 161 moveRemainingSiblingsToNewParent(startNode, 0, clonedAncestor); 162 163 if (!ancestors.isEmpty()) { 164 // Split the tree up the ancestor chain until the topBlockquote 165 // Throughout this loop, clonedParent is the clone of ancestor's parent. 166 // This is so we can clone ancestor's siblings and place the clones 167 // into the clone corresponding to the ancestor's parent. 168 RefPtr<Element> ancestor; 169 RefPtr<Element> clonedParent; 170 for (ancestor = ancestors.first(), clonedParent = clonedAncestor->parentElement(); 171 ancestor && ancestor != topBlockquote; 172 ancestor = ancestor->parentElement(), clonedParent = clonedParent->parentElement()) 173 moveRemainingSiblingsToNewParent(ancestor->nextSibling(), 0, clonedParent); 174 175 // If the startNode's original parent is now empty, remove it 176 Node* originalParent = ancestors.first().get(); 177 if (!originalParent->hasChildNodes()) 178 removeNode(originalParent); 179 } 180 181 // Make sure the cloned block quote renders. 182 addBlockPlaceholderIfNeeded(clonedBlockquote.get()); 183 184 // Put the selection right before the break. 185 setEndingSelection(VisibleSelection(positionBeforeNode(breakNode.get()), DOWNSTREAM, endingSelection().isDirectional())); 186 rebalanceWhitespace(); 187 } 188 189 } // namespace WebCore 190