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