1 | /*
|
---|
2 | * file: InsertHTMLAction.js
|
---|
3 | *
|
---|
4 | * @BEGINLICENSE
|
---|
5 | * Copyright 2010 Brook Novak (email : [email protected])
|
---|
6 | * This program is free software; you can redistribute it and/or modify
|
---|
7 | * it under the terms of the GNU General Public License as published by
|
---|
8 | * the Free Software Foundation; either version 2 of the License, or
|
---|
9 | * (at your option) any later version.
|
---|
10 | * This program is distributed in the hope that it will be useful,
|
---|
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
---|
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
---|
13 | * GNU General Public License for more details.
|
---|
14 | * You should have received a copy of the GNU General Public License
|
---|
15 | * along with this program; if not, write to the Free Software
|
---|
16 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
---|
17 | * @ENDLICENSE
|
---|
18 | */
|
---|
19 |
|
---|
20 | // @DEPENDS: UndoMan
|
---|
21 | bootstrap.provides("actions.InsertHTMLAction");
|
---|
22 |
|
---|
23 | _registerAction("InsertHTML", {
|
---|
24 |
|
---|
25 | /**
|
---|
26 | * @class
|
---|
27 | * An undoable insertion action. Inserts HTML into the document
|
---|
28 | *
|
---|
29 | * @author Brook Novak
|
---|
30 | *
|
---|
31 | * @param {String} html The HTML to insert.
|
---|
32 | *
|
---|
33 | * @param {Node} parentNode The parent node of the HTML to insert into.
|
---|
34 | *
|
---|
35 | * @param {Node} refNode The node to insert next to, or inside of (if a text node).
|
---|
36 | * Null if parentNode has no children, in which case the HTML will be inserted
|
---|
37 | * as the only children of parentNode.
|
---|
38 | *
|
---|
39 | * @param {Number} index If refNode is null this is not applicable.
|
---|
40 | * If refNode is a Element node, then index can either be 0 or 1. 0 indicates that the
|
---|
41 | * HTML should be inserted before the refNode, and 1 indicates that it should be inserted after.
|
---|
42 | * Otherwise, if refNode is a Text Node, then index can range from 0 to the length of the text.
|
---|
43 | * Where the index indicates that the html should be inserted before the charactor at the given index.
|
---|
44 | * If index is zero, then the html will be inserted before the text node. If index is the length of the
|
---|
45 | * text, then the html will be inserted after the text node. Otherwise the HTML text will be
|
---|
46 | * inserted within the text run... splitting the text node in two.
|
---|
47 | *
|
---|
48 | */
|
---|
49 | exec : function(html, parentNode, refNode, index) {
|
---|
50 |
|
---|
51 | debug.assert(refNode || (!refNode && parentNode.childNodes.length == 0));
|
---|
52 | debug.assert(!refNode || index >= 0);
|
---|
53 | debug.assert(!refNode ||
|
---|
54 | (refNode.nodeType == Node.TEXT_NODE && index <= refNode.nodeValue.length) ||
|
---|
55 | (refNode.nodeType != Node.TEXT_NODE && index <= 1)
|
---|
56 | );
|
---|
57 |
|
---|
58 | var domRoots = [];
|
---|
59 |
|
---|
60 | // Get all root elements in HTML
|
---|
61 | var domTree = parentNode == docBody ? $createElement("div") : parentNode.cloneNode(false);
|
---|
62 | domTree.innerHTML = html;
|
---|
63 |
|
---|
64 | var root = domTree.firstChild;
|
---|
65 | while(root) {
|
---|
66 | domRoots.push(root);
|
---|
67 | root = root.nextSibling;
|
---|
68 | }
|
---|
69 |
|
---|
70 | // Disconnect roots from temporary container
|
---|
71 | for (var i = 0; i < domRoots.length; i++) {
|
---|
72 |
|
---|
73 | domTree.removeChild(domRoots[i]);
|
---|
74 |
|
---|
75 | var spanWrapper = 0;
|
---|
76 | if (domRoots[i].nodeType == Node.TEXT_NODE) {
|
---|
77 | spanWrapper = $createElement("span");
|
---|
78 | spanWrapper.appendChild(domRoots[i]);
|
---|
79 | }
|
---|
80 |
|
---|
81 | // Add a renderable char at the end of the container... this will avoid
|
---|
82 | // consildation from changing existing contents
|
---|
83 | var endNode = document.createTextNode("X");
|
---|
84 | parentNode.appendChild(endNode);
|
---|
85 |
|
---|
86 | // Consolidate the dom root's whitespace sequences
|
---|
87 | parentNode.appendChild(spanWrapper ? spanWrapper : domRoots[i]);
|
---|
88 |
|
---|
89 | _recordOperations = false;
|
---|
90 | _consolidateWSSeqs(domRoots[i], false);
|
---|
91 | _recordOperations = true;
|
---|
92 |
|
---|
93 | if (spanWrapper) {
|
---|
94 |
|
---|
95 | domRoots.splice(i, 1); // remove this dom root, it is to be replaced or entirly removes
|
---|
96 |
|
---|
97 | // Consolidation can completly remove nodes, so test each consolidated sub-tree to
|
---|
98 | // see if they should be inserted
|
---|
99 | if (!spanWrapper.firstChild)
|
---|
100 | i --;
|
---|
101 |
|
---|
102 | else {
|
---|
103 | // A text node can be split into several text nodes after consolidation
|
---|
104 | var wrappedChild;
|
---|
105 | while(wrappedChild = spanWrapper.firstChild) {
|
---|
106 | spanWrapper.removeChild(wrappedChild);
|
---|
107 | domRoots.splice(i, 0, wrappedChild);
|
---|
108 | i++;
|
---|
109 | }
|
---|
110 | i--;
|
---|
111 | }
|
---|
112 |
|
---|
113 | parentNode.removeChild(spanWrapper);
|
---|
114 |
|
---|
115 | } else
|
---|
116 | parentNode.removeChild(domRoots[i]);
|
---|
117 |
|
---|
118 |
|
---|
119 | parentNode.removeChild(endNode);
|
---|
120 |
|
---|
121 | }
|
---|
122 |
|
---|
123 | // If the target node is a placeholder, then the placeholder should be replaced with the new html
|
---|
124 | var phRoot;
|
---|
125 | if (de.doc.isESPlaceHolder(parentNode, false) || de.doc.isMNPlaceHolder(parentNode, false))
|
---|
126 | phRoot = parentNode;
|
---|
127 | else if (refNode && (de.doc.isESPlaceHolder(refNode, false) || de.doc.isMNPlaceHolder(refNode, false)))
|
---|
128 | phRoot = refNode;
|
---|
129 |
|
---|
130 | if (phRoot) { // Get the placeholder root and adjust the parent node / ref node
|
---|
131 | while (phRoot.parentNode &&
|
---|
132 | (de.doc.isESPlaceHolder(phRoot.parentNode, false) || de.doc.isMNPlaceHolder(phRoot.parentNode, false))) {
|
---|
133 | phRoot = phRoot.parentNode;
|
---|
134 | }
|
---|
135 | targetNode = phRoot;
|
---|
136 |
|
---|
137 | parentNode = phRoot.parentNode;
|
---|
138 | refNode = phRoot; // See later .. it will be removed
|
---|
139 | }
|
---|
140 |
|
---|
141 | var normalizeTargetWS;
|
---|
142 |
|
---|
143 | // Does the insertion point need to split a text node in two?
|
---|
144 | if (refNode &&
|
---|
145 | refNode.nodeType == Node.TEXT_NODE &&
|
---|
146 | index > 0 &&
|
---|
147 | index < _nodeLength(refNode)) {
|
---|
148 |
|
---|
149 | var rem = _execOp(_Operation.SPLIT_TEXT_NODE, refNode, index);
|
---|
150 |
|
---|
151 | // If inserting HTML an a whitespace point, then must convert to all NBSP then normalize later
|
---|
152 | if (_isAllWhiteSpace(rem.nodeValue.charAt(0)) || _isAllWhiteSpace(refNode.nodeValue.charAt(index-1))) {
|
---|
153 | normalizeTargetWS = refNode.parentNode;
|
---|
154 | _convertWSToNBSP(normalizeTargetWS);
|
---|
155 | }
|
---|
156 |
|
---|
157 | }
|
---|
158 |
|
---|
159 | // Get the sibling node to begin inserting the dom afterwards.
|
---|
160 | var afterNode;
|
---|
161 | if (!refNode) afterNode = null;
|
---|
162 | else if (index == 0) afterNode = refNode.previousSibling;
|
---|
163 | else afterNode = refNode;
|
---|
164 |
|
---|
165 | // Insert the DOM nodes
|
---|
166 | for (var i in domRoots) {
|
---|
167 | if (!afterNode)
|
---|
168 | _execOp(_Operation.INSERT_NODE, domRoots[i], parentNode, parentNode.firstChild ? _indexInParent(parentNode.firstChild) : null)
|
---|
169 | else _execOp(_Operation.INSERT_NODE, domRoots[i], parentNode, _indexInParent(afterNode) + 1);
|
---|
170 | afterNode = domRoots[i];
|
---|
171 | }
|
---|
172 |
|
---|
173 | // Normalize whitespace sequences if need to
|
---|
174 | if (normalizeTargetWS)
|
---|
175 | _consolidateWSSeqs(normalizeTargetWS);
|
---|
176 |
|
---|
177 | // Need to remove any place holders?
|
---|
178 | if (refNode && (de.doc.isMNPlaceHolder(refNode) || de.doc.isESPlaceHolder(refNode)))
|
---|
179 | _execOp(_Operation.REMOVE_NODE, refNode);
|
---|
180 |
|
---|
181 | if (this.flags & de.UndoMan.ExecFlag.UPDATE_SELECTION) {
|
---|
182 | var cDesc = de.cursor.getNearestCursorDesc(
|
---|
183 | domRoots[domRoots.length-1],
|
---|
184 | _nodeLength(domRoots[domRoots.length-1], 2) - 1,
|
---|
185 | true,
|
---|
186 | _nodeName(domRoots[domRoots.length-1]) != "br");
|
---|
187 | if (cDesc)
|
---|
188 | this.selAfter = {startNode : cDesc.domNode, startIndex : cDesc.relIndex + (cDesc.domNode.nodeType == Node.TEXT_NODE && cDesc.isRightOf ? 1 : 0)};
|
---|
189 | }
|
---|
190 |
|
---|
191 | }
|
---|
192 | });
|
---|
193 |
|
---|
194 |
|
---|