blob: dde7fcb9393a6e19a2cd6dd0460ffb4e960faa7c [file] [log] [blame]
mjs84e724c2005-05-24 07:21:47 +00001/*
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
mjsb64c50a2005-10-03 21:13:12 +000026#include "config.h"
darina68e0432006-02-14 21:40:54 +000027#include "BreakBlockquoteCommand.h"
mjs84e724c2005-05-24 07:21:47 +000028
darin@apple.com930f4382009-03-29 17:15:41 +000029#include "HTMLElement.h"
darin98fa8b82006-03-20 08:03:57 +000030#include "HTMLNames.h"
antti@apple.com5d47b582012-12-11 00:13:29 +000031#include "NodeTraversal.h"
darin@apple.com930f4382009-03-29 17:15:41 +000032#include "RenderListItem.h"
eseidel40eb1b92006-03-25 22:20:36 +000033#include "Text.h"
eseidel40eb1b92006-03-25 22:20:36 +000034#include "htmlediting.h"
35
darin4e051a02006-01-23 17:07:29 +000036namespace WebCore {
darinedbc5e42005-08-25 23:13:58 +000037
darin4e051a02006-01-23 17:07:29 +000038using namespace HTMLNames;
mjs84e724c2005-05-24 07:21:47 +000039
akling@apple.comd455eb62013-09-01 05:30:31 +000040BreakBlockquoteCommand::BreakBlockquoteCommand(Document& document)
harrisona34f5242005-11-03 02:20:15 +000041 : CompositeEditCommand(document)
mjs84e724c2005-05-24 07:21:47 +000042{
43}
44
mjs84e724c2005-05-24 07:21:47 +000045void BreakBlockquoteCommand::doApply()
46{
justin.garcia@apple.com8c6832c2008-10-30 23:46:31 +000047 if (endingSelection().isNone())
mjs84e724c2005-05-24 07:21:47 +000048 return;
49
50 // Delete the current selection.
justin.garcia@apple.com8c6832c2008-10-30 23:46:31 +000051 if (endingSelection().isRange())
mjs84e724c2005-05-24 07:21:47 +000052 deleteSelection(false, false);
justin.garcia@apple.com8c6832c2008-10-30 23:46:31 +000053
eric@webkit.org5fbc25c2009-09-29 23:10:01 +000054 // This is a scenario that should never happen, but we want to
55 // make sure we don't dereference a null pointer below.
56
57 ASSERT(!endingSelection().isNone());
58
59 if (endingSelection().isNone())
60 return;
61
justin.garcia@apple.com8c6832c2008-10-30 23:46:31 +000062 VisiblePosition visiblePos = endingSelection().visibleStart();
eric@webkit.org5fbc25c2009-09-29 23:10:01 +000063
justin.garcia@apple.com8c6832c2008-10-30 23:46:31 +000064 // pos is a position equivalent to the caret. We use downstream() so that pos will
65 // be in the first node that we need to move (there are a few exceptions to this, see below).
66 Position pos = endingSelection().start().downstream();
mjs84e724c2005-05-24 07:21:47 +000067
68 // Find the top-most blockquote from the start.
rniwa@webkit.org24bd1d32011-03-17 00:03:22 +000069 Node* topBlockquote = highestEnclosingNodeOfType(pos, isMailBlockquote);
70 if (!topBlockquote || !topBlockquote->parentNode() || !topBlockquote->isElementNode())
mjs84e724c2005-05-24 07:21:47 +000071 return;
72
weinig@apple.com8d80b862013-09-23 20:24:47 +000073 RefPtr<Element> breakNode = createBreakElement(document());
adele@apple.comdbe21c62009-04-13 15:12:41 +000074
kmccullough@apple.comc3263512009-06-23 19:12:32 +000075 bool isLastVisPosInNode = isLastVisiblePositionInNode(visiblePos, topBlockquote);
76
adele@apple.comdbe21c62009-04-13 15:12:41 +000077 // If the position is at the beginning of the top quoted content, we don't need to break the quote.
kmccullough@apple.comc3263512009-06-23 19:12:32 +000078 // Instead, insert the break before the blockquote, unless the position is as the end of the the quoted content.
79 if (isFirstVisiblePositionInNode(visiblePos, topBlockquote) && !isLastVisPosInNode) {
adele@apple.comdbe21c62009-04-13 15:12:41 +000080 insertNodeBefore(breakNode.get(), topBlockquote);
rniwa@webkit.orgeccfd3f2011-08-16 18:39:52 +000081 setEndingSelection(VisibleSelection(positionBeforeNode(breakNode.get()), DOWNSTREAM, endingSelection().isDirectional()));
adele@apple.comdbe21c62009-04-13 15:12:41 +000082 rebalanceWhitespace();
83 return;
84 }
85
86 // Insert a break after the top blockquote.
darin7191dd12006-02-10 00:09:17 +000087 insertNodeAfter(breakNode.get(), topBlockquote);
kmccullough@apple.comc3263512009-06-23 19:12:32 +000088
adele@apple.comdbe21c62009-04-13 15:12:41 +000089 // If we're inserting the break at the end of the quoted content, we don't need to break the quote.
kmccullough@apple.comc3263512009-06-23 19:12:32 +000090 if (isLastVisPosInNode) {
rniwa@webkit.orgeccfd3f2011-08-16 18:39:52 +000091 setEndingSelection(VisibleSelection(positionBeforeNode(breakNode.get()), DOWNSTREAM, endingSelection().isDirectional()));
adele@apple.com446417602009-04-14 17:40:28 +000092 rebalanceWhitespace();
justin.garcia@apple.com8cba38d2008-10-29 21:43:47 +000093 return;
mjs84e724c2005-05-24 07:21:47 +000094 }
justin.garcia@apple.com484cb6b2008-10-31 00:11:48 +000095
96 // Don't move a line break just after the caret. Doing so would create an extra, empty paragraph
97 // in the new blockquote.
justin.garcia@apple.comd5c8f432009-04-14 18:48:16 +000098 if (lineBreakExistsAtVisiblePosition(visiblePos))
justin.garcia@apple.com484cb6b2008-10-31 00:11:48 +000099 pos = pos.next();
justin.garcia@apple.com8cba38d2008-10-29 21:43:47 +0000100
adele@apple.comdbe21c62009-04-13 15:12:41 +0000101 // Adjust the position so we don't split at the beginning of a quote.
rniwa@webkit.org24bd1d32011-03-17 00:03:22 +0000102 while (isFirstVisiblePositionInNode(VisiblePosition(pos), enclosingNodeOfType(pos, isMailBlockquote)))
adele@apple.comdbe21c62009-04-13 15:12:41 +0000103 pos = pos.previous();
104
105 // startNode is the first node that we need to move to the new blockquote.
rniwa@webkit.org62b16972011-02-21 09:28:48 +0000106 Node* startNode = pos.deprecatedNode();
adele@apple.comdbe21c62009-04-13 15:12:41 +0000107
justin.garcia@apple.com8cba38d2008-10-29 21:43:47 +0000108 // Split at pos if in the middle of a text node.
109 if (startNode->isTextNode()) {
commit-queue@webkit.org9b335e42012-02-12 11:27:56 +0000110 Text* textNode = toText(startNode);
eric@webkit.org4e32ebc2009-04-30 01:09:57 +0000111 if ((unsigned)pos.deprecatedEditingOffset() >= textNode->length()) {
antti@apple.com5d47b582012-12-11 00:13:29 +0000112 startNode = NodeTraversal::next(startNode);
justin.garcia@apple.com74f5c372008-10-30 20:06:24 +0000113 ASSERT(startNode);
eric@webkit.org4e32ebc2009-04-30 01:09:57 +0000114 } else if (pos.deprecatedEditingOffset() > 0)
115 splitTextNode(textNode, pos.deprecatedEditingOffset());
116 } else if (pos.deprecatedEditingOffset() > 0) {
mitz@apple.comffea9c72009-05-07 03:38:25 +0000117 Node* childAtOffset = startNode->childNode(pos.deprecatedEditingOffset());
antti@apple.com5d47b582012-12-11 00:13:29 +0000118 startNode = childAtOffset ? childAtOffset : NodeTraversal::next(startNode);
justin.garcia@apple.com74f5c372008-10-30 20:06:24 +0000119 ASSERT(startNode);
justin.garcia@apple.com8cba38d2008-10-29 21:43:47 +0000120 }
121
justin.garcia@apple.com74f5c372008-10-30 20:06:24 +0000122 // If there's nothing inside topBlockquote to move, we're finished.
123 if (!startNode->isDescendantOf(topBlockquote)) {
rniwa@webkit.orgeccfd3f2011-08-16 18:39:52 +0000124 setEndingSelection(VisibleSelection(VisiblePosition(firstPositionInOrBeforeNode(startNode)), endingSelection().isDirectional()));
justin.garcia@apple.com74f5c372008-10-30 20:06:24 +0000125 return;
justin.garcia@apple.com8cba38d2008-10-29 21:43:47 +0000126 }
127
128 // Build up list of ancestors in between the start node and the top blockquote.
andersca@apple.comc3523f82013-10-18 23:41:24 +0000129 Vector<RefPtr<Element>> ancestors;
darin@apple.come95b53d2008-12-23 21:42:46 +0000130 for (Element* node = startNode->parentElement(); node && node != topBlockquote; node = node->parentElement())
justin.garcia@apple.com8cba38d2008-10-29 21:43:47 +0000131 ancestors.append(node);
132
133 // Insert a clone of the top blockquote after the break.
inferno@chromium.orgb9af1c12013-03-12 00:50:46 +0000134 RefPtr<Element> clonedBlockquote = toElement(topBlockquote)->cloneElementWithoutChildren();
justin.garcia@apple.com8cba38d2008-10-29 21:43:47 +0000135 insertNodeAfter(clonedBlockquote.get(), breakNode.get());
136
137 // Clone startNode's ancestors into the cloned blockquote.
138 // On exiting this loop, clonedAncestor is the lowest ancestor
139 // that was cloned (i.e. the clone of either ancestors.last()
140 // or clonedBlockquote if ancestors is empty).
darin@apple.come95b53d2008-12-23 21:42:46 +0000141 RefPtr<Element> clonedAncestor = clonedBlockquote;
justin.garcia@apple.com8cba38d2008-10-29 21:43:47 +0000142 for (size_t i = ancestors.size(); i != 0; --i) {
jchaffraix@webkit.orgba7c01d2009-03-12 12:42:09 +0000143 RefPtr<Element> clonedChild = ancestors[i - 1]->cloneElementWithoutChildren();
justin.garcia@apple.com8cba38d2008-10-29 21:43:47 +0000144 // Preserve list item numbering in cloned lists.
145 if (clonedChild->isElementNode() && clonedChild->hasTagName(olTag)) {
rniwa@webkit.orgb6a8d0c2012-06-08 22:35:35 +0000146 Node* listChildNode = i > 1 ? ancestors[i - 2].get() : startNode;
justin.garcia@apple.com8cba38d2008-10-29 21:43:47 +0000147 // The first child of the cloned list might not be a list item element,
148 // find the first one so that we know where to start numbering.
149 while (listChildNode && !listChildNode->hasTagName(liTag))
150 listChildNode = listChildNode->nextSibling();
leviw@chromium.org6d74acc2011-03-30 10:46:13 +0000151 if (listChildNode && listChildNode->renderer() && listChildNode->renderer()->isListItem())
darin@apple.com73f3fc02013-10-05 18:22:39 +0000152 setNodeAttribute(clonedChild, startAttr, AtomicString::number(toRenderListItem(listChildNode->renderer())->value()));
justin.garcia@apple.com8cba38d2008-10-29 21:43:47 +0000153 }
154
155 appendNode(clonedChild.get(), clonedAncestor.get());
156 clonedAncestor = clonedChild;
157 }
rniwa@webkit.orgb6a8d0c2012-06-08 22:35:35 +0000158
159 moveRemainingSiblingsToNewParent(startNode, 0, clonedAncestor);
justin.garcia@apple.com8cba38d2008-10-29 21:43:47 +0000160
justin.garcia@apple.com8cba38d2008-10-29 21:43:47 +0000161 if (!ancestors.isEmpty()) {
justin.garcia@apple.com8cba38d2008-10-29 21:43:47 +0000162 // Split the tree up the ancestor chain until the topBlockquote
163 // Throughout this loop, clonedParent is the clone of ancestor's parent.
164 // This is so we can clone ancestor's siblings and place the clones
165 // into the clone corresponding to the ancestor's parent.
rniwa@webkit.orgb6a8d0c2012-06-08 22:35:35 +0000166 RefPtr<Element> ancestor;
167 RefPtr<Element> clonedParent;
darin@apple.come95b53d2008-12-23 21:42:46 +0000168 for (ancestor = ancestors.first(), clonedParent = clonedAncestor->parentElement();
justin.garcia@apple.com8cba38d2008-10-29 21:43:47 +0000169 ancestor && ancestor != topBlockquote;
rniwa@webkit.orgb6a8d0c2012-06-08 22:35:35 +0000170 ancestor = ancestor->parentElement(), clonedParent = clonedParent->parentElement())
171 moveRemainingSiblingsToNewParent(ancestor->nextSibling(), 0, clonedParent);
172
adele@apple.com446417602009-04-14 17:40:28 +0000173 // If the startNode's original parent is now empty, remove it
rniwa@webkit.orgb6a8d0c2012-06-08 22:35:35 +0000174 Node* originalParent = ancestors.first().get();
adele@apple.com446417602009-04-14 17:40:28 +0000175 if (!originalParent->hasChildNodes())
176 removeNode(originalParent);
justin.garcia@apple.com8cba38d2008-10-29 21:43:47 +0000177 }
178
179 // Make sure the cloned block quote renders.
180 addBlockPlaceholderIfNeeded(clonedBlockquote.get());
mjs84e724c2005-05-24 07:21:47 +0000181
182 // Put the selection right before the break.
rniwa@webkit.orgeccfd3f2011-08-16 18:39:52 +0000183 setEndingSelection(VisibleSelection(positionBeforeNode(breakNode.get()), DOWNSTREAM, endingSelection().isDirectional()));
mjs84e724c2005-05-24 07:21:47 +0000184 rebalanceWhitespace();
185}
186
darinb9481ed2006-03-20 02:57:59 +0000187} // namespace WebCore