44fdf669cacf94b1024dc0e48b3c6e117e9d2f56
[Packages/TYPO3.CMS.git] / typo3 / sysext / rtehtmlarea / Resources / Public / JavaScript / HTMLArea / DOM / HTMLArea.DOM.Node.js
1 /***************************************************
2 * HTMLArea.DOM.Node: Node object
3 ***************************************************/
4 HTMLArea.DOM.Node = function (config) {
5 };
6 HTMLArea.DOM.Node = Ext.extend(HTMLArea.DOM.Node, {
7 /*
8 * Reference to the editor MUST be set in config
9 */
10 editor: null,
11 /*
12 * Reference to the editor document
13 */
14 document: null,
15 /*
16 * Reference to the editor selection object
17 */
18 selection: null,
19 /*
20 * Reference to the editor bookmark object
21 */
22 bookMark: null,
23 /*
24 * HTMLArea.DOM.Selection constructor
25 */
26 constructor: function (config) {
27 // Apply config
28 Ext.apply(this, config);
29 // Initialize references
30 this.document = this.editor.document;
31 this.selection = this.editor.getSelection();
32 this.bookMark = this.editor.getBookMark();
33 },
34 /*
35 * Remove the given element
36 *
37 * @param object element: the element to be removed, content and selection being preserved
38 *
39 * @return void
40 */
41 removeMarkup: function (element) {
42 var bookMark = this.bookMark.get(this.selection.createRange());
43 var parent = element.parentNode;
44 while (element.firstChild) {
45 parent.insertBefore(element.firstChild, element);
46 }
47 parent.removeChild(element);
48 this.selection.selectRange(this.bookMark.moveTo(bookMark));
49 },
50 /*
51 * Wrap the range with an inline element
52 *
53 * @param string element: the node that will wrap the range
54 * @param object range: the range to be wrapped
55 *
56 * @return void
57 */
58 wrapWithInlineElement: function (element, range) {
59 if (HTMLArea.UserAgent.isIEBeforeIE9) {
60 var nodeName = element.nodeName;
61 var bookMark = this.bookMark.get(range);
62 if (range.parentElement) {
63 var parent = range.parentElement();
64 var rangeStart = range.duplicate();
65 rangeStart.collapse(true);
66 var parentStart = rangeStart.parentElement();
67 var rangeEnd = range.duplicate();
68 rangeEnd.collapse(true);
69 var newRange = this.selection.createRange();
70
71 var parentEnd = rangeEnd.parentElement();
72 var upperParentStart = parentStart;
73 if (parentStart !== parent) {
74 while (upperParentStart.parentNode !== parent) {
75 upperParentStart = upperParentStart.parentNode;
76 }
77 }
78
79 element.innerHTML = range.htmlText;
80 // IE eats spaces on the start boundary
81 if (range.htmlText.charAt(0) === '\x20') {
82 element.innerHTML = ' ' + element.innerHTML;
83 }
84 var elementClone = element.cloneNode(true);
85 range.pasteHTML(element.outerHTML);
86 // IE inserts the element as the last child of the start container
87 if (parentStart !== parent
88 && parentStart.lastChild
89 && parentStart.lastChild.nodeType === HTMLArea.DOM.ELEMENT_NODE
90 && parentStart.lastChild.nodeName.toLowerCase() === nodeName) {
91 parent.insertBefore(elementClone, upperParentStart.nextSibling);
92 parentStart.removeChild(parentStart.lastChild);
93 // Sometimes an empty previous sibling was created
94 if (elementClone.previousSibling
95 && elementClone.previousSibling.nodeType === HTMLArea.DOM.ELEMENT_NODE
96 && !elementClone.previousSibling.innerText) {
97 parent.removeChild(elementClone.previousSibling);
98 }
99 // The bookmark will not work anymore
100 newRange.moveToElementText(elementClone);
101 newRange.collapse(false);
102 newRange.select();
103 } else {
104 // Working around IE boookmark bug
105 if (parentStart != parentEnd) {
106 var newRange = this.selection.createRange();
107 if (newRange.moveToBookmark(bookMark)) {
108 newRange.collapse(false);
109 newRange.select();
110 }
111 } else {
112 range.collapse(false);
113 }
114 }
115 parent.normalize();
116 } else {
117 var parent = range.item(0);
118 element = parent.parentNode.insertBefore(element, parent);
119 element.appendChild(parent);
120 this.bookMark.moveTo(bookMark);
121 }
122 } else {
123 element.appendChild(range.extractContents());
124 range.insertNode(element);
125 element.normalize();
126 // Sometimes Firefox inserts empty elements just outside the boundaries of the range
127 var neighbour = element.previousSibling;
128 if (neighbour && (neighbour.nodeType !== HTMLArea.DOM.TEXT_NODE) && !/\S/.test(neighbour.textContent)) {
129 HTMLArea.DOM.removeFromParent(neighbour);
130 }
131 neighbour = element.nextSibling;
132 if (neighbour && (neighbour.nodeType !== HTMLArea.DOM.TEXT_NODE) && !/\S/.test(neighbour.textContent)) {
133 HTMLArea.DOM.removeFromParent(neighbour);
134 }
135 this.selection.selectNodeContents(element, false);
136 }
137 },
138 /*
139 * Get the position of the node within the document tree.
140 * The tree address returned is an array of integers, with each integer
141 * indicating a child index of a DOM node, starting from
142 * document.documentElement.
143 * The position cannot be used for finding back the DOM tree node once
144 * the DOM tree structure has been modified.
145 * Adapted from FCKeditor
146 *
147 * @param object node: the DOM node
148 * @param boolean normalized: if true, a normalized position is calculated
149 *
150 * @return array the position of the node
151 */
152 getPositionWithinTree: function (node, normalized) {
153 var documentElement = this.document.documentElement,
154 current = node,
155 position = [];
156 while (current && current != documentElement) {
157 var parentNode = current.parentNode;
158 if (parentNode) {
159 // Get the current node position
160 position.unshift(HTMLArea.DOM.getPositionWithinParent(current, normalized));
161 }
162 current = parentNode;
163 }
164 return position;
165 },
166 /**
167 * Clean Apple wrapping span and font elements under the specified node
168 *
169 * @param object node: the node in the subtree of which cleaning is performed
170 *
171 * @return void
172 */
173 cleanAppleStyleSpans: function (node) {
174 if (HTMLArea.UserAgent.isWebKit || HTMLArea.UserAgent.isOpera) {
175 if (node.getElementsByClassName) {
176 var spans = node.getElementsByClassName('Apple-style-span');
177 for (var i = spans.length; --i >= 0;) {
178 this.removeMarkup(spans[i]);
179 }
180 }
181 var spans = node.getElementsByTagName('span');
182 for (var i = spans.length; --i >= 0;) {
183 if (HTMLArea.DOM.hasClass(spans[i], 'Apple-style-span')) {
184 this.removeMarkup(spans[i]);
185 }
186 if (/^(li)$/i.test(spans[i].parentNode.nodeName) && (spans[i].style.cssText.indexOf('line-height') !== -1 || spans[i].style.cssText.indexOf('font-family') !== -1 || spans[i].style.cssText.indexOf('font-size') !== -1)) {
187 this.removeMarkup(spans[i]);
188 }
189 }
190 var fonts = node.getElementsByTagName('font');
191 for (i = fonts.length; --i >= 0;) {
192 if (HTMLArea.DOM.hasClass(fonts[i], 'Apple-style-span')) {
193 this.removeMarkup(fonts[i]);
194 }
195 }
196 }
197 }
198 });