b328f44e0af1df2ad95272cc9f5648886a0f9c22
[Packages/TYPO3.CMS.git] / typo3 / sysext / rtehtmlarea / htmlarea / htmlarea-ie.js
1 /***************************************************************
2 * Copyright notice
3 *
4 * (c) 2002-2004 interactivetools.com, inc.
5 * (c) 2003-2004 dynarch.com
6 * (c) 2004-2009 Stanislas Rolland <typo3(arobas)sjbr.ca>
7 * All rights reserved
8 *
9 * This script is part of the TYPO3 project. The TYPO3 project is
10 * free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * The GNU General Public License can be found at
16 * http://www.gnu.org/copyleft/gpl.html.
17 * A copy is found in the textfile GPL.txt and important notices to the license
18 * from the author is found in LICENSE.txt distributed with these scripts.
19 *
20 *
21 * This script is distributed in the hope that it will be useful,
22 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 * GNU General Public License for more details.
25 *
26 * This script is a modified version of a script published under the htmlArea License.
27 * A copy of the htmlArea License may be found in the textfile HTMLAREA_LICENSE.txt.
28 *
29 * This copyright notice MUST APPEAR in all copies of the script!
30 ***************************************************************/
31 /*
32 * TYPO3 SVN ID: $Id$
33 */
34
35 /***************************************************
36 * IE-SPECIFIC FUNCTIONS
37 ***************************************************/
38 HTMLArea.prototype.isEditable = function() {
39 return this._doc.body.contentEditable;
40 };
41
42 /***************************************************
43 * SELECTIONS AND RANGES
44 ***************************************************/
45 /*
46 * Get the current selection object
47 */
48 HTMLArea.prototype._getSelection = function() {
49 return this._doc.selection;
50 };
51
52 /*
53 * Create a range for the current selection
54 */
55 HTMLArea.prototype._createRange = function(sel) {
56 if (typeof(sel) == "undefined") {
57 var sel = this._getSelection();
58 }
59 if (sel.type.toLowerCase() == "none") {
60 this.focusEditor();
61 }
62 return sel.createRange();
63 };
64
65 /*
66 * Select a node AND the contents inside the node
67 */
68 HTMLArea.prototype.selectNode = function(node) {
69 this.focusEditor();
70 this.forceRedraw();
71 var range = this._doc.body.createTextRange();
72 range.moveToElementText(node);
73 range.select();
74 };
75
76 /*
77 * Select ONLY the contents inside the given node
78 */
79 HTMLArea.prototype.selectNodeContents = function(node, endPoint) {
80 this.focusEditor();
81 this.forceRedraw();
82 var range = this._doc.body.createTextRange();
83 range.moveToElementText(node);
84 if (typeof(endPoint) !== "undefined") {
85 range.collapse(endPoint);
86 }
87 range.select();
88 };
89
90 /*
91 * Determine whether the node intersects the range
92 */
93 HTMLArea.prototype.rangeIntersectsNode = function(range, node) {
94 this.focusEditor();
95 var nodeRange = this._doc.body.createTextRange();
96 nodeRange.moveToElementText(node);
97 return (range.compareEndPoints("EndToStart", nodeRange) == -1 && range.compareEndPoints("StartToEnd", nodeRange) == 1) ||
98 (range.compareEndPoints("EndToStart", nodeRange) == 1 && range.compareEndPoints("StartToEnd", nodeRange) == -1);
99 };
100
101 /*
102 * Retrieve the HTML contents of selected block
103 */
104 HTMLArea.prototype.getSelectedHTML = function() {
105 var sel = this._getSelection();
106 var range = this._createRange(sel);
107 if (sel.type.toLowerCase() == "control") {
108 var r1 = this._doc.body.createTextRange();
109 r1.moveToElementText(range(0));
110 return r1.htmlText;
111 } else {
112 return range.htmlText;
113 }
114 };
115
116 /*
117 * Retrieve simply HTML contents of the selected block, IE ignoring control ranges
118 */
119 HTMLArea.prototype.getSelectedHTMLContents = function() {
120 var sel = this._getSelection();
121 var range = this._createRange(sel);
122 return range.htmlText;
123 };
124
125 /*
126 * Get the deepest node that contains both endpoints of the current selection.
127 */
128 HTMLArea.prototype.getParentElement = function(selection, range) {
129 if (!selection) {
130 var selection = this._getSelection();
131 }
132 if (typeof(range) === "undefined") {
133 var range = this._createRange(selection);
134 }
135 switch (selection.type.toLowerCase()) {
136 case "text":
137 case "none":
138 var el = range.parentElement();
139 if(el.nodeName.toLowerCase() == "li" && range.htmlText.replace(/\s/g,"") == el.parentNode.outerHTML.replace(/\s/g,"")) return el.parentNode;
140 return el;
141 case "control": return range.item(0);
142 default: return this._doc.body;
143 }
144 };
145
146 /*
147 * Get the selected element, if any. That is, the element that you have last selected in the "path"
148 * at the bottom of the editor, or a "control" (eg image)
149 *
150 * @returns null | element
151 * Borrowed from Xinha (is not htmlArea) - http://xinha.gogo.co.nz/
152 */
153 HTMLArea.prototype._activeElement = function(sel) {
154 if (sel == null) {
155 return null;
156 }
157 if (this._selectionEmpty(sel)) {
158 return null;
159 }
160 if (sel.type.toLowerCase() == "control") {
161 return sel.createRange().item(0);
162 } else {
163 // If it's not a control, then we need to see if the selection is the _entire_ text of a parent node
164 // (this happens when a node is clicked in the tree)
165 var range = sel.createRange();
166 var p_elm = this.getParentElement(sel);
167 if(p_elm.outerHTML == range.htmlText) return p_elm;
168 return null;
169 }
170 };
171
172 /*
173 * Determine if the current selection is empty or not.
174 */
175 HTMLArea.prototype._selectionEmpty = function(selection) {
176 if (!selection || selection.type.toLowerCase() === "none") return true;
177 if (selection.type.toLowerCase() === "text") {
178 return !this._createRange(selection).text;
179 }
180 return !this._createRange(selection).htmlText;
181 };
182
183 /*
184 * Get a bookmark
185 */
186 HTMLArea.prototype.getBookmark = function (range) {
187 return range.getBookmark();
188 };
189
190 /*
191 * Move the range to the bookmark
192 */
193 HTMLArea.prototype.moveToBookmark = function (bookmark) {
194 var range = this._createRange();
195 range.moveToBookmark(bookmark);
196 return range;
197 };
198
199 /*
200 * Select range
201 */
202 HTMLArea.prototype.selectRange = function (range) {
203 range.select();
204 };
205 /***************************************************
206 * DOM TREE MANIPULATION
207 ***************************************************/
208
209 /*
210 * Insert a node at the current position.
211 * Delete the current selection, if any.
212 * Split the text node, if needed.
213 */
214 HTMLArea.prototype.insertNodeAtSelection = function(toBeInserted) {
215 this.insertHTML(toBeInserted.outerHTML);
216 };
217
218 /*
219 * Insert HTML source code at the current position.
220 * Delete the current selection, if any.
221 */
222 HTMLArea.prototype.insertHTML = function(html) {
223 var sel = this._getSelection();
224 if (sel.type.toLowerCase() == "control") {
225 sel.clear();
226 sel = this._getSelection();
227 }
228 var range = this._createRange(sel);
229 range.pasteHTML(html);
230 };
231
232 /*
233 * Wrap the range with an inline element
234 *
235 * @param string element: the node that will wrap the range
236 * @param object selection: the selection object
237 * @param object range: the range to be wrapped
238 *
239 * @return void
240 */
241 HTMLArea.prototype.wrapWithInlineElement = function(element, selection, range) {
242 var nodeName = element.nodeName;
243 var parent = this.getParentElement(selection, range);
244 var bookmark = this.getBookmark(range);
245 if (selection.type !== "Control") {
246 var rangeStart = range.duplicate();
247 rangeStart.collapse(true);
248 var parentStart = rangeStart.parentElement();
249 var rangeEnd = range.duplicate();
250 rangeEnd.collapse(true);
251 var newRange = this._createRange();
252
253 var parentEnd = rangeEnd.parentElement();
254 var upperParentStart = parentStart;
255 if (parentStart !== parent) {
256 while (upperParentStart.parentNode !== parent) {
257 upperParentStart = upperParentStart.parentNode;
258 }
259 }
260
261 element.innerHTML = range.htmlText;
262 // IE eats spaces on the start boundary
263 if (range.htmlText.charAt(0) === "\x20") {
264 element.innerHTML = "&nbsp;" + element.innerHTML;
265 }
266 var elementClone = element.cloneNode(true);
267 range.pasteHTML(element.outerHTML);
268 // IE inserts the element as the last child of the start container
269 if (parentStart !== parent
270 && parentStart.lastChild
271 && parentStart.lastChild.nodeType === 1
272 && parentStart.lastChild.nodeName.toLowerCase() === nodeName) {
273 parent.insertBefore(elementClone, upperParentStart.nextSibling);
274 parentStart.removeChild(parentStart.lastChild);
275 // Sometimes an empty previous sibling was created
276 if (elementClone.previousSibling
277 && elementClone.previousSibling.nodeType === 1
278 && !elementClone.previousSibling.innerText) {
279 parent.removeChild(elementClone.previousSibling);
280 }
281 // The bookmark will not work anymore
282 newRange.moveToElementText(elementClone);
283 newRange.collapse(false);
284 newRange.select();
285 } else {
286 // Working around IE boookmark bug
287 if (parentStart != parentEnd) {
288 var newRange = this._createRange();
289 if (newRange.moveToBookmark(bookmark)) {
290 newRange.collapse(false);
291 newRange.select();
292 }
293 } else {
294 range.collapse(false);
295 }
296 }
297 // normalize() is not available in IE5.5
298 try {
299 parent.normalize();
300 } catch(e) { }
301 } else {
302 element = parent.parentNode.insertBefore(element, parent);
303 element.appendChild(parent);
304 this.moveToBookmark(bookmark);
305 }
306 };
307
308 /***************************************************
309 * EVENT HANDLERS
310 ***************************************************/
311
312 /*
313 * Handle the backspace event in IE browsers
314 */
315 HTMLArea.prototype._checkBackspace = function() {
316 var selection = this._getSelection();
317 var range = this._createRange(selection);
318 if (selection.type == "Control"){ // Deleting or backspacing on a control selection : delete the element
319 var el = this.getParentElement();
320 var p = el.parentNode;
321 p.removeChild(el);
322 return true;
323 } else if (this._selectionEmpty(selection)) { // Check if deleting an empty block with a table as next sibling
324 var el = this.getParentElement();
325 if (!el.innerHTML && HTMLArea.isBlockElement(el) && el.nextSibling && /^table$/i.test(el.nextSibling.nodeName)) {
326 var previous = el.previousSibling;
327 if (!previous) {
328 this.selectNodeContents(el.nextSibling.rows[0].cells[0], true);
329 } else if (/^table$/i.test(previous.nodeName)) {
330 this.selectNodeContents(previous.rows[previous.rows.length-1].cells[previous.rows[previous.rows.length-1].cells.length-1], false);
331 } else {
332 range.moveStart("character", -1);
333 range.collapse(true);
334 range.select();
335 }
336 el.parentNode.removeChild(el);
337 return true;
338 }
339 } else { // Backspacing into a link
340 var r2 = range.duplicate();
341 r2.moveStart("character", -1);
342 var a = r2.parentElement();
343 if (a != range.parentElement() && /^a$/i.test(a.nodeName)) {
344 r2.collapse(true);
345 r2.moveEnd("character", 1);
346 r2.pasteHTML('');
347 r2.select();
348 return true;
349 }
350 return false;
351 }
352 };