rev |
line source |
bsw/jbe@1309
|
1 /**
|
bsw/jbe@1309
|
2 * @license wysihtml v0.6.0-beta1
|
bsw/jbe@1309
|
3 * https://github.com/Voog/wysihtml
|
bsw/jbe@1309
|
4 *
|
bsw/jbe@1309
|
5 * Author: Christopher Blum (https://github.com/tiff)
|
bsw/jbe@1309
|
6 * Secondary author of extended features: Oliver Pulges (https://github.com/pulges)
|
bsw/jbe@1309
|
7 *
|
bsw/jbe@1309
|
8 * Copyright (C) 2012 XING AG
|
bsw/jbe@1309
|
9 * Licensed under the MIT license (MIT)
|
bsw/jbe@1309
|
10 *
|
bsw/jbe@1309
|
11 */
|
bsw/jbe@1309
|
12 var wysihtml = {
|
bsw/jbe@1309
|
13 version: '0.6.0-beta1',
|
bsw/jbe@1309
|
14
|
bsw/jbe@1309
|
15 // namespaces
|
bsw/jbe@1309
|
16 commands: {},
|
bsw/jbe@1309
|
17 dom: {},
|
bsw/jbe@1309
|
18 quirks: {},
|
bsw/jbe@1309
|
19 toolbar: {},
|
bsw/jbe@1309
|
20 lang: {},
|
bsw/jbe@1309
|
21 selection: {},
|
bsw/jbe@1309
|
22 views: {},
|
bsw/jbe@1309
|
23
|
bsw/jbe@1309
|
24 editorExtenders: [],
|
bsw/jbe@1309
|
25 extendEditor: function(extender) {
|
bsw/jbe@1309
|
26 this.editorExtenders.push(extender);
|
bsw/jbe@1309
|
27 },
|
bsw/jbe@1309
|
28
|
bsw/jbe@1309
|
29 INVISIBLE_SPACE: '\uFEFF',
|
bsw/jbe@1309
|
30 INVISIBLE_SPACE_REG_EXP: /\uFEFF/g,
|
bsw/jbe@1309
|
31
|
bsw/jbe@1309
|
32 VOID_ELEMENTS: 'area, base, br, col, embed, hr, img, input, keygen, link, meta, param, source, track, wbr',
|
bsw/jbe@1309
|
33 PERMITTED_PHRASING_CONTENT_ONLY: 'h1, h2, h3, h4, h5, h6, p, pre',
|
bsw/jbe@1309
|
34
|
bsw/jbe@1309
|
35 EMPTY_FUNCTION: function() {},
|
bsw/jbe@1309
|
36
|
bsw/jbe@1309
|
37 ELEMENT_NODE: 1,
|
bsw/jbe@1309
|
38 TEXT_NODE: 3,
|
bsw/jbe@1309
|
39
|
bsw/jbe@1309
|
40 BACKSPACE_KEY: 8,
|
bsw/jbe@1309
|
41 ENTER_KEY: 13,
|
bsw/jbe@1309
|
42 ESCAPE_KEY: 27,
|
bsw/jbe@1309
|
43 SPACE_KEY: 32,
|
bsw/jbe@1309
|
44 TAB_KEY: 9,
|
bsw/jbe@1309
|
45 DELETE_KEY: 46
|
bsw/jbe@1309
|
46 };
|
bsw/jbe@1309
|
47
|
bsw/jbe@1309
|
48 wysihtml.polyfills = function(win, doc) {
|
bsw/jbe@1309
|
49
|
bsw/jbe@1309
|
50 var methods = {
|
bsw/jbe@1309
|
51
|
bsw/jbe@1309
|
52 // Safary has a bug of not restoring selection after node.normalize correctly.
|
bsw/jbe@1309
|
53 // Detects the misbegaviour and patches it
|
bsw/jbe@1309
|
54 normalizeHasCaretError: function() {
|
bsw/jbe@1309
|
55 if ("createRange" in doc && "getSelection" in win) {
|
bsw/jbe@1309
|
56 var originalTarget,
|
bsw/jbe@1309
|
57 scrollTop = window.pageYOffset,
|
bsw/jbe@1309
|
58 scrollLeft = window.pageXOffset,
|
bsw/jbe@1309
|
59 e = doc.createElement('div'),
|
bsw/jbe@1309
|
60 t1 = doc.createTextNode('a'),
|
bsw/jbe@1309
|
61 t2 = doc.createTextNode('a'),
|
bsw/jbe@1309
|
62 t3 = doc.createTextNode('a'),
|
bsw/jbe@1309
|
63 r = doc.createRange(),
|
bsw/jbe@1309
|
64 s, ret;
|
bsw/jbe@1309
|
65
|
bsw/jbe@1309
|
66 if (document.activeElement) {
|
bsw/jbe@1309
|
67 if (document.activeElement.nodeType === 1 && ['INPUT', 'TEXTAREA', 'SELECT', 'BUTTON'].indexOf(document.activeElement.nodeName) > -1) {
|
bsw/jbe@1309
|
68 originalTarget = {
|
bsw/jbe@1309
|
69 type: 'form',
|
bsw/jbe@1309
|
70 node: document.activeElement,
|
bsw/jbe@1309
|
71 start: document.activeElement.selectionStart,
|
bsw/jbe@1309
|
72 end: document.activeElement.selectionEnd,
|
bsw/jbe@1309
|
73 };
|
bsw/jbe@1309
|
74 } else {
|
bsw/jbe@1309
|
75 s = win.getSelection();
|
bsw/jbe@1309
|
76 if (s && s.anchorNode) {
|
bsw/jbe@1309
|
77 originalTarget = {
|
bsw/jbe@1309
|
78 type: 'range',
|
bsw/jbe@1309
|
79 anchorNode: s.anchorNode,
|
bsw/jbe@1309
|
80 anchorOffset: s.anchorOffset,
|
bsw/jbe@1309
|
81 focusNode: s.focusNode,
|
bsw/jbe@1309
|
82 focusOffset: s.focusOffset
|
bsw/jbe@1309
|
83 };
|
bsw/jbe@1309
|
84 }
|
bsw/jbe@1309
|
85 }
|
bsw/jbe@1309
|
86 }
|
bsw/jbe@1309
|
87
|
bsw/jbe@1309
|
88 e.setAttribute('contenteditable', 'true');
|
bsw/jbe@1309
|
89 e.appendChild(t1);
|
bsw/jbe@1309
|
90 e.appendChild(t2);
|
bsw/jbe@1309
|
91 e.appendChild(t3);
|
bsw/jbe@1309
|
92 doc.body.appendChild(e);
|
bsw/jbe@1309
|
93 r.setStart(t2, 1);
|
bsw/jbe@1309
|
94 r.setEnd(t2, 1);
|
bsw/jbe@1309
|
95
|
bsw/jbe@1309
|
96 s = win.getSelection();
|
bsw/jbe@1309
|
97 s.removeAllRanges();
|
bsw/jbe@1309
|
98 s.addRange(r);
|
bsw/jbe@1309
|
99 e.normalize();
|
bsw/jbe@1309
|
100 s = win.getSelection();
|
bsw/jbe@1309
|
101
|
bsw/jbe@1309
|
102 ret = (e.childNodes.length !== 1 || s.anchorNode !== e.firstChild || s.anchorOffset !== 2);
|
bsw/jbe@1309
|
103 e.parentNode.removeChild(e);
|
bsw/jbe@1309
|
104 s.removeAllRanges();
|
bsw/jbe@1309
|
105
|
bsw/jbe@1309
|
106 if (originalTarget) {
|
bsw/jbe@1309
|
107 if (originalTarget.type === 'form') {
|
bsw/jbe@1309
|
108 // The selection parameters are not present for all form elements
|
bsw/jbe@1309
|
109 if (typeof originalTarget.start !== 'undefined' && typeof originalTarget.end !== 'undefined') {
|
bsw/jbe@1309
|
110 originalTarget.node.setSelectionRange(originalTarget.start, originalTarget.end);
|
bsw/jbe@1309
|
111 }
|
bsw/jbe@1309
|
112 originalTarget.node.focus();
|
bsw/jbe@1309
|
113 } else if (originalTarget.type === 'range') {
|
bsw/jbe@1309
|
114 r = doc.createRange();
|
bsw/jbe@1309
|
115 r.setStart(originalTarget.anchorNode, originalTarget.anchorOffset);
|
bsw/jbe@1309
|
116 r.setEnd(originalTarget.focusNode, originalTarget.focusOffset);
|
bsw/jbe@1309
|
117 s.addRange(r);
|
bsw/jbe@1309
|
118 }
|
bsw/jbe@1309
|
119 }
|
bsw/jbe@1309
|
120
|
bsw/jbe@1309
|
121 if (scrollTop !== window.pageYOffset || scrollLeft !== window.pageXOffset) {
|
bsw/jbe@1309
|
122 win.scrollTo(scrollLeft, scrollTop);
|
bsw/jbe@1309
|
123 }
|
bsw/jbe@1309
|
124
|
bsw/jbe@1309
|
125 return ret;
|
bsw/jbe@1309
|
126 }
|
bsw/jbe@1309
|
127 },
|
bsw/jbe@1309
|
128
|
bsw/jbe@1309
|
129 apply: function() {
|
bsw/jbe@1309
|
130 // closest, matches, and remove polyfill
|
bsw/jbe@1309
|
131 // https://github.com/jonathantneal/closest
|
bsw/jbe@1309
|
132 (function (ELEMENT) {
|
bsw/jbe@1309
|
133 ELEMENT.matches = ELEMENT.matches || ELEMENT.mozMatchesSelector || ELEMENT.msMatchesSelector || ELEMENT.oMatchesSelector || ELEMENT.webkitMatchesSelector || function matches(selector) {
|
bsw/jbe@1309
|
134 var
|
bsw/jbe@1309
|
135 element = this,
|
bsw/jbe@1309
|
136 elements = (element.document || element.ownerDocument).querySelectorAll(selector),
|
bsw/jbe@1309
|
137 index = 0;
|
bsw/jbe@1309
|
138
|
bsw/jbe@1309
|
139 while (elements[index] && elements[index] !== element) {
|
bsw/jbe@1309
|
140 ++index;
|
bsw/jbe@1309
|
141 }
|
bsw/jbe@1309
|
142
|
bsw/jbe@1309
|
143 return elements[index] ? true : false;
|
bsw/jbe@1309
|
144 };
|
bsw/jbe@1309
|
145
|
bsw/jbe@1309
|
146 ELEMENT.closest = ELEMENT.closest || function closest(selector) {
|
bsw/jbe@1309
|
147 var element = this;
|
bsw/jbe@1309
|
148
|
bsw/jbe@1309
|
149 while (element) {
|
bsw/jbe@1309
|
150 if (element.matches(selector)) {
|
bsw/jbe@1309
|
151 break;
|
bsw/jbe@1309
|
152 }
|
bsw/jbe@1309
|
153
|
bsw/jbe@1309
|
154 element = element.parentElement;
|
bsw/jbe@1309
|
155 }
|
bsw/jbe@1309
|
156
|
bsw/jbe@1309
|
157 return element;
|
bsw/jbe@1309
|
158 };
|
bsw/jbe@1309
|
159
|
bsw/jbe@1309
|
160 ELEMENT.remove = ELEMENT.remove || function remove() {
|
bsw/jbe@1309
|
161 if (this.parentNode) {
|
bsw/jbe@1309
|
162 this.parentNode.removeChild(this);
|
bsw/jbe@1309
|
163 }
|
bsw/jbe@1309
|
164 };
|
bsw/jbe@1309
|
165
|
bsw/jbe@1309
|
166 }(win.Element.prototype));
|
bsw/jbe@1309
|
167
|
bsw/jbe@1309
|
168 if (!('classList' in doc.documentElement) && win.Object.defineProperty && typeof win.HTMLElement !== 'undefined') {
|
bsw/jbe@1309
|
169 win.Object.defineProperty(win.HTMLElement.prototype, 'classList', {
|
bsw/jbe@1309
|
170 get: function() {
|
bsw/jbe@1309
|
171 var self = this;
|
bsw/jbe@1309
|
172 function update(fn) {
|
bsw/jbe@1309
|
173 return function(value) {
|
bsw/jbe@1309
|
174 var classes = self.className.split(/\s+/),
|
bsw/jbe@1309
|
175 index = classes.indexOf(value);
|
bsw/jbe@1309
|
176
|
bsw/jbe@1309
|
177 fn(classes, index, value);
|
bsw/jbe@1309
|
178 self.className = classes.join(' ');
|
bsw/jbe@1309
|
179 };
|
bsw/jbe@1309
|
180 }
|
bsw/jbe@1309
|
181
|
bsw/jbe@1309
|
182 var ret = {
|
bsw/jbe@1309
|
183 add: update(function(classes, index, value) {
|
bsw/jbe@1309
|
184 ~index || classes.push(value);
|
bsw/jbe@1309
|
185 }),
|
bsw/jbe@1309
|
186
|
bsw/jbe@1309
|
187 remove: update(function(classes, index) {
|
bsw/jbe@1309
|
188 ~index && classes.splice(index, 1);
|
bsw/jbe@1309
|
189 }),
|
bsw/jbe@1309
|
190
|
bsw/jbe@1309
|
191 toggle: update(function(classes, index, value) {
|
bsw/jbe@1309
|
192 ~index ? classes.splice(index, 1) : classes.push(value);
|
bsw/jbe@1309
|
193 }),
|
bsw/jbe@1309
|
194
|
bsw/jbe@1309
|
195 contains: function(value) {
|
bsw/jbe@1309
|
196 return !!~self.className.split(/\s+/).indexOf(value);
|
bsw/jbe@1309
|
197 },
|
bsw/jbe@1309
|
198
|
bsw/jbe@1309
|
199 item: function(i) {
|
bsw/jbe@1309
|
200 return self.className.split(/\s+/)[i] || null;
|
bsw/jbe@1309
|
201 }
|
bsw/jbe@1309
|
202 };
|
bsw/jbe@1309
|
203
|
bsw/jbe@1309
|
204 win.Object.defineProperty(ret, 'length', {
|
bsw/jbe@1309
|
205 get: function() {
|
bsw/jbe@1309
|
206 return self.className.split(/\s+/).length;
|
bsw/jbe@1309
|
207 }
|
bsw/jbe@1309
|
208 });
|
bsw/jbe@1309
|
209
|
bsw/jbe@1309
|
210 return ret;
|
bsw/jbe@1309
|
211 }
|
bsw/jbe@1309
|
212 });
|
bsw/jbe@1309
|
213 }
|
bsw/jbe@1309
|
214
|
bsw/jbe@1309
|
215 var getTextNodes = function(node){
|
bsw/jbe@1309
|
216 var all = [];
|
bsw/jbe@1309
|
217 for (node=node.firstChild;node;node=node.nextSibling){
|
bsw/jbe@1309
|
218 if (node.nodeType == 3) {
|
bsw/jbe@1309
|
219 all.push(node);
|
bsw/jbe@1309
|
220 } else {
|
bsw/jbe@1309
|
221 all = all.concat(getTextNodes(node));
|
bsw/jbe@1309
|
222 }
|
bsw/jbe@1309
|
223 }
|
bsw/jbe@1309
|
224 return all;
|
bsw/jbe@1309
|
225 };
|
bsw/jbe@1309
|
226
|
bsw/jbe@1309
|
227 var isInDom = function(node) {
|
bsw/jbe@1309
|
228 var doc = node.ownerDocument,
|
bsw/jbe@1309
|
229 n = node;
|
bsw/jbe@1309
|
230
|
bsw/jbe@1309
|
231 do {
|
bsw/jbe@1309
|
232 if (n === doc) {
|
bsw/jbe@1309
|
233 return true;
|
bsw/jbe@1309
|
234 }
|
bsw/jbe@1309
|
235 n = n.parentNode;
|
bsw/jbe@1309
|
236 } while(n);
|
bsw/jbe@1309
|
237
|
bsw/jbe@1309
|
238 return false;
|
bsw/jbe@1309
|
239 };
|
bsw/jbe@1309
|
240
|
bsw/jbe@1309
|
241 var normalizeFix = function() {
|
bsw/jbe@1309
|
242 var f = win.Node.prototype.normalize;
|
bsw/jbe@1309
|
243 var nf = function() {
|
bsw/jbe@1309
|
244 var texts = getTextNodes(this),
|
bsw/jbe@1309
|
245 s = this.ownerDocument.defaultView.getSelection(),
|
bsw/jbe@1309
|
246 anode = s.anchorNode,
|
bsw/jbe@1309
|
247 aoffset = s.anchorOffset,
|
bsw/jbe@1309
|
248 aelement = anode && anode.nodeType === 1 && anode.childNodes.length > 0 ? anode.childNodes[aoffset] : undefined,
|
bsw/jbe@1309
|
249 fnode = s.focusNode,
|
bsw/jbe@1309
|
250 foffset = s.focusOffset,
|
bsw/jbe@1309
|
251 felement = fnode && fnode.nodeType === 1 && foffset > 0 ? fnode.childNodes[foffset -1] : undefined,
|
bsw/jbe@1309
|
252 r = this.ownerDocument.createRange(),
|
bsw/jbe@1309
|
253 prevTxt = texts.shift(),
|
bsw/jbe@1309
|
254 curText = prevTxt ? texts.shift() : null;
|
bsw/jbe@1309
|
255
|
bsw/jbe@1309
|
256 if (felement && felement.nodeType === 3) {
|
bsw/jbe@1309
|
257 fnode = felement;
|
bsw/jbe@1309
|
258 foffset = felement.nodeValue.length;
|
bsw/jbe@1309
|
259 felement = undefined;
|
bsw/jbe@1309
|
260 }
|
bsw/jbe@1309
|
261
|
bsw/jbe@1309
|
262 if (aelement && aelement.nodeType === 3) {
|
bsw/jbe@1309
|
263 anode = aelement;
|
bsw/jbe@1309
|
264 aoffset = 0;
|
bsw/jbe@1309
|
265 aelement = undefined;
|
bsw/jbe@1309
|
266 }
|
bsw/jbe@1309
|
267
|
bsw/jbe@1309
|
268 if ((anode === fnode && foffset < aoffset) || (anode !== fnode && (anode.compareDocumentPosition(fnode) & win.Node.DOCUMENT_POSITION_PRECEDING) && !(anode.compareDocumentPosition(fnode) & win.Node.DOCUMENT_POSITION_CONTAINS))) {
|
bsw/jbe@1309
|
269 fnode = [anode, anode = fnode][0];
|
bsw/jbe@1309
|
270 foffset = [aoffset, aoffset = foffset][0];
|
bsw/jbe@1309
|
271 }
|
bsw/jbe@1309
|
272
|
bsw/jbe@1309
|
273 while(prevTxt && curText) {
|
bsw/jbe@1309
|
274 if (curText.previousSibling && curText.previousSibling === prevTxt) {
|
bsw/jbe@1309
|
275 if (anode === curText) {
|
bsw/jbe@1309
|
276 anode = prevTxt;
|
bsw/jbe@1309
|
277 aoffset = prevTxt.nodeValue.length + aoffset;
|
bsw/jbe@1309
|
278 }
|
bsw/jbe@1309
|
279 if (fnode === curText) {
|
bsw/jbe@1309
|
280 fnode = prevTxt;
|
bsw/jbe@1309
|
281 foffset = prevTxt.nodeValue.length + foffset;
|
bsw/jbe@1309
|
282 }
|
bsw/jbe@1309
|
283 prevTxt.nodeValue = prevTxt.nodeValue + curText.nodeValue;
|
bsw/jbe@1309
|
284 curText.parentNode.removeChild(curText);
|
bsw/jbe@1309
|
285 curText = texts.shift();
|
bsw/jbe@1309
|
286 } else {
|
bsw/jbe@1309
|
287 prevTxt = curText;
|
bsw/jbe@1309
|
288 curText = texts.shift();
|
bsw/jbe@1309
|
289 }
|
bsw/jbe@1309
|
290 }
|
bsw/jbe@1309
|
291
|
bsw/jbe@1309
|
292 if (felement) {
|
bsw/jbe@1309
|
293 foffset = Array.prototype.indexOf.call(felement.parentNode.childNodes, felement) + 1;
|
bsw/jbe@1309
|
294 }
|
bsw/jbe@1309
|
295
|
bsw/jbe@1309
|
296 if (aelement) {
|
bsw/jbe@1309
|
297 aoffset = Array.prototype.indexOf.call(aelement.parentNode.childNodes, aelement);
|
bsw/jbe@1309
|
298 }
|
bsw/jbe@1309
|
299
|
bsw/jbe@1309
|
300 if (isInDom(this) && anode && anode.parentNode && fnode && fnode.parentNode) {
|
bsw/jbe@1309
|
301 r.setStart(anode, aoffset);
|
bsw/jbe@1309
|
302 r.setEnd(fnode, foffset);
|
bsw/jbe@1309
|
303 s.removeAllRanges();
|
bsw/jbe@1309
|
304 s.addRange(r);
|
bsw/jbe@1309
|
305 }
|
bsw/jbe@1309
|
306 };
|
bsw/jbe@1309
|
307 win.Node.prototype.normalize = nf;
|
bsw/jbe@1309
|
308 };
|
bsw/jbe@1309
|
309
|
bsw/jbe@1309
|
310 var F = function() {
|
bsw/jbe@1309
|
311 win.removeEventListener("load", F);
|
bsw/jbe@1309
|
312 if ("Node" in win && "normalize" in win.Node.prototype && methods.normalizeHasCaretError()) {
|
bsw/jbe@1309
|
313 normalizeFix();
|
bsw/jbe@1309
|
314 }
|
bsw/jbe@1309
|
315 };
|
bsw/jbe@1309
|
316
|
bsw/jbe@1309
|
317 if (doc.readyState !== "complete") {
|
bsw/jbe@1309
|
318 win.addEventListener("load", F);
|
bsw/jbe@1309
|
319 } else {
|
bsw/jbe@1309
|
320 F();
|
bsw/jbe@1309
|
321 }
|
bsw/jbe@1309
|
322
|
bsw/jbe@1309
|
323 // CustomEvent for ie9 and up
|
bsw/jbe@1309
|
324 function nativeCustomEventSupported() {
|
bsw/jbe@1309
|
325 try {
|
bsw/jbe@1309
|
326 var p = new win.CustomEvent('cat', {detail: {foo: 'bar'}});
|
bsw/jbe@1309
|
327 return 'cat' === p.type && 'bar' === p.detail.foo;
|
bsw/jbe@1309
|
328 } catch (e) {}
|
bsw/jbe@1309
|
329 return false;
|
bsw/jbe@1309
|
330 }
|
bsw/jbe@1309
|
331
|
bsw/jbe@1309
|
332 // Polyfills CustomEvent object for IE9 and up
|
bsw/jbe@1309
|
333 (function() {
|
bsw/jbe@1309
|
334 if (!nativeCustomEventSupported() && "CustomEvent" in win) {
|
bsw/jbe@1309
|
335 function CustomEvent(event, params) {
|
bsw/jbe@1309
|
336 params = params || {bubbles: false, cancelable: false, detail: undefined};
|
bsw/jbe@1309
|
337 var evt = doc.createEvent('CustomEvent');
|
bsw/jbe@1309
|
338 evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
|
bsw/jbe@1309
|
339 return evt;
|
bsw/jbe@1309
|
340 }
|
bsw/jbe@1309
|
341 CustomEvent.prototype = win.Event.prototype;
|
bsw/jbe@1309
|
342 win.CustomEvent = CustomEvent;
|
bsw/jbe@1309
|
343 }
|
bsw/jbe@1309
|
344 })();
|
bsw/jbe@1309
|
345 }
|
bsw/jbe@1309
|
346 }
|
bsw/jbe@1309
|
347
|
bsw/jbe@1309
|
348 return methods;
|
bsw/jbe@1309
|
349 };
|
bsw/jbe@1309
|
350
|
bsw/jbe@1309
|
351 wysihtml.polyfills(window, document).apply();
|
bsw/jbe@1309
|
352
|
bsw/jbe@1309
|
353 /*
|
bsw/jbe@1309
|
354 Base.js, version 1.1a
|
bsw/jbe@1309
|
355 Copyright 2006-2010, Dean Edwards
|
bsw/jbe@1309
|
356 License: http://www.opensource.org/licenses/mit-license.php
|
bsw/jbe@1309
|
357 */
|
bsw/jbe@1309
|
358
|
bsw/jbe@1309
|
359 var Base = function() {
|
bsw/jbe@1309
|
360 // dummy
|
bsw/jbe@1309
|
361 };
|
bsw/jbe@1309
|
362
|
bsw/jbe@1309
|
363 Base.extend = function(_instance, _static) { // subclass
|
bsw/jbe@1309
|
364 var extend = Base.prototype.extend;
|
bsw/jbe@1309
|
365
|
bsw/jbe@1309
|
366 // build the prototype
|
bsw/jbe@1309
|
367 Base._prototyping = true;
|
bsw/jbe@1309
|
368 var proto = new this;
|
bsw/jbe@1309
|
369 extend.call(proto, _instance);
|
bsw/jbe@1309
|
370 proto.base = function() {
|
bsw/jbe@1309
|
371 // call this method from any other method to invoke that method's ancestor
|
bsw/jbe@1309
|
372 };
|
bsw/jbe@1309
|
373 delete Base._prototyping;
|
bsw/jbe@1309
|
374
|
bsw/jbe@1309
|
375 // create the wrapper for the constructor function
|
bsw/jbe@1309
|
376 //var constructor = proto.constructor.valueOf(); //-dean
|
bsw/jbe@1309
|
377 var constructor = proto.constructor;
|
bsw/jbe@1309
|
378 var klass = proto.constructor = function() {
|
bsw/jbe@1309
|
379 if (!Base._prototyping) {
|
bsw/jbe@1309
|
380 if (this._constructing || this.constructor == klass) { // instantiation
|
bsw/jbe@1309
|
381 this._constructing = true;
|
bsw/jbe@1309
|
382 constructor.apply(this, arguments);
|
bsw/jbe@1309
|
383 delete this._constructing;
|
bsw/jbe@1309
|
384 } else if (arguments[0] != null) { // casting
|
bsw/jbe@1309
|
385 return (arguments[0].extend || extend).call(arguments[0], proto);
|
bsw/jbe@1309
|
386 }
|
bsw/jbe@1309
|
387 }
|
bsw/jbe@1309
|
388 };
|
bsw/jbe@1309
|
389
|
bsw/jbe@1309
|
390 // build the class interface
|
bsw/jbe@1309
|
391 klass.ancestor = this;
|
bsw/jbe@1309
|
392 klass.extend = this.extend;
|
bsw/jbe@1309
|
393 klass.forEach = this.forEach;
|
bsw/jbe@1309
|
394 klass.implement = this.implement;
|
bsw/jbe@1309
|
395 klass.prototype = proto;
|
bsw/jbe@1309
|
396 klass.toString = this.toString;
|
bsw/jbe@1309
|
397 klass.valueOf = function(type) {
|
bsw/jbe@1309
|
398 //return (type == "object") ? klass : constructor; //-dean
|
bsw/jbe@1309
|
399 return (type == "object") ? klass : constructor.valueOf();
|
bsw/jbe@1309
|
400 };
|
bsw/jbe@1309
|
401 extend.call(klass, _static);
|
bsw/jbe@1309
|
402 // class initialisation
|
bsw/jbe@1309
|
403 if (typeof klass.init == "function") klass.init();
|
bsw/jbe@1309
|
404 return klass;
|
bsw/jbe@1309
|
405 };
|
bsw/jbe@1309
|
406
|
bsw/jbe@1309
|
407 Base.prototype = {
|
bsw/jbe@1309
|
408 extend: function(source, value) {
|
bsw/jbe@1309
|
409 if (arguments.length > 1) { // extending with a name/value pair
|
bsw/jbe@1309
|
410 var ancestor = this[source];
|
bsw/jbe@1309
|
411 if (ancestor && (typeof value == "function") && // overriding a method?
|
bsw/jbe@1309
|
412 // the valueOf() comparison is to avoid circular references
|
bsw/jbe@1309
|
413 (!ancestor.valueOf || ancestor.valueOf() != value.valueOf()) &&
|
bsw/jbe@1309
|
414 /\bbase\b/.test(value)) {
|
bsw/jbe@1309
|
415 // get the underlying method
|
bsw/jbe@1309
|
416 var method = value.valueOf();
|
bsw/jbe@1309
|
417 // override
|
bsw/jbe@1309
|
418 value = function() {
|
bsw/jbe@1309
|
419 var previous = this.base || Base.prototype.base;
|
bsw/jbe@1309
|
420 this.base = ancestor;
|
bsw/jbe@1309
|
421 var returnValue = method.apply(this, arguments);
|
bsw/jbe@1309
|
422 this.base = previous;
|
bsw/jbe@1309
|
423 return returnValue;
|
bsw/jbe@1309
|
424 };
|
bsw/jbe@1309
|
425 // point to the underlying method
|
bsw/jbe@1309
|
426 value.valueOf = function(type) {
|
bsw/jbe@1309
|
427 return (type == "object") ? value : method;
|
bsw/jbe@1309
|
428 };
|
bsw/jbe@1309
|
429 value.toString = Base.toString;
|
bsw/jbe@1309
|
430 }
|
bsw/jbe@1309
|
431 this[source] = value;
|
bsw/jbe@1309
|
432 } else if (source) { // extending with an object literal
|
bsw/jbe@1309
|
433 var extend = Base.prototype.extend;
|
bsw/jbe@1309
|
434 // if this object has a customised extend method then use it
|
bsw/jbe@1309
|
435 if (!Base._prototyping && typeof this != "function") {
|
bsw/jbe@1309
|
436 extend = this.extend || extend;
|
bsw/jbe@1309
|
437 }
|
bsw/jbe@1309
|
438 var proto = {toSource: null};
|
bsw/jbe@1309
|
439 // do the "toString" and other methods manually
|
bsw/jbe@1309
|
440 var hidden = ["constructor", "toString", "valueOf"];
|
bsw/jbe@1309
|
441 // if we are prototyping then include the constructor
|
bsw/jbe@1309
|
442 var i = Base._prototyping ? 0 : 1;
|
bsw/jbe@1309
|
443 while (key = hidden[i++]) {
|
bsw/jbe@1309
|
444 if (source[key] != proto[key]) {
|
bsw/jbe@1309
|
445 extend.call(this, key, source[key]);
|
bsw/jbe@1309
|
446
|
bsw/jbe@1309
|
447 }
|
bsw/jbe@1309
|
448 }
|
bsw/jbe@1309
|
449 // copy each of the source object's properties to this object
|
bsw/jbe@1309
|
450 for (var key in source) {
|
bsw/jbe@1309
|
451 if (!proto[key]) extend.call(this, key, source[key]);
|
bsw/jbe@1309
|
452 }
|
bsw/jbe@1309
|
453 }
|
bsw/jbe@1309
|
454 return this;
|
bsw/jbe@1309
|
455 }
|
bsw/jbe@1309
|
456 };
|
bsw/jbe@1309
|
457
|
bsw/jbe@1309
|
458 // initialise
|
bsw/jbe@1309
|
459 Base = Base.extend({
|
bsw/jbe@1309
|
460 constructor: function() {
|
bsw/jbe@1309
|
461 this.extend(arguments[0]);
|
bsw/jbe@1309
|
462 }
|
bsw/jbe@1309
|
463 }, {
|
bsw/jbe@1309
|
464 ancestor: Object,
|
bsw/jbe@1309
|
465 version: "1.1",
|
bsw/jbe@1309
|
466
|
bsw/jbe@1309
|
467 forEach: function(object, block, context) {
|
bsw/jbe@1309
|
468 for (var key in object) {
|
bsw/jbe@1309
|
469 if (this.prototype[key] === undefined) {
|
bsw/jbe@1309
|
470 block.call(context, object[key], key, object);
|
bsw/jbe@1309
|
471 }
|
bsw/jbe@1309
|
472 }
|
bsw/jbe@1309
|
473 },
|
bsw/jbe@1309
|
474
|
bsw/jbe@1309
|
475 implement: function() {
|
bsw/jbe@1309
|
476 for (var i = 0; i < arguments.length; i++) {
|
bsw/jbe@1309
|
477 if (typeof arguments[i] == "function") {
|
bsw/jbe@1309
|
478 // if it's a function, call it
|
bsw/jbe@1309
|
479 arguments[i](this.prototype);
|
bsw/jbe@1309
|
480 } else {
|
bsw/jbe@1309
|
481 // add the interface using the extend method
|
bsw/jbe@1309
|
482 this.prototype.extend(arguments[i]);
|
bsw/jbe@1309
|
483 }
|
bsw/jbe@1309
|
484 }
|
bsw/jbe@1309
|
485 return this;
|
bsw/jbe@1309
|
486 },
|
bsw/jbe@1309
|
487
|
bsw/jbe@1309
|
488 toString: function() {
|
bsw/jbe@1309
|
489 return String(this.valueOf());
|
bsw/jbe@1309
|
490 }
|
bsw/jbe@1309
|
491 });
|
bsw/jbe@1309
|
492 /**
|
bsw/jbe@1309
|
493 * Rangy, a cross-browser JavaScript range and selection library
|
bsw/jbe@1309
|
494 * https://github.com/timdown/rangy
|
bsw/jbe@1309
|
495 *
|
bsw/jbe@1309
|
496 * Copyright 2015, Tim Down
|
bsw/jbe@1309
|
497 * Licensed under the MIT license.
|
bsw/jbe@1309
|
498 * Version: 1.3.1-dev
|
bsw/jbe@1309
|
499 * Build date: 20 May 2015
|
bsw/jbe@1309
|
500 *
|
bsw/jbe@1309
|
501 * NOTE: UMD wrapper removed manually for bundling (Oliver Pulges)
|
bsw/jbe@1309
|
502 */
|
bsw/jbe@1309
|
503 var rangy;
|
bsw/jbe@1309
|
504
|
bsw/jbe@1309
|
505 (function() {
|
bsw/jbe@1309
|
506 var OBJECT = "object", FUNCTION = "function", UNDEFINED = "undefined";
|
bsw/jbe@1309
|
507
|
bsw/jbe@1309
|
508 // Minimal set of properties required for DOM Level 2 Range compliance. Comparison constants such as START_TO_START
|
bsw/jbe@1309
|
509 // are omitted because ranges in KHTML do not have them but otherwise work perfectly well. See issue 113.
|
bsw/jbe@1309
|
510 var domRangeProperties = ["startContainer", "startOffset", "endContainer", "endOffset", "collapsed",
|
bsw/jbe@1309
|
511 "commonAncestorContainer"];
|
bsw/jbe@1309
|
512
|
bsw/jbe@1309
|
513 // Minimal set of methods required for DOM Level 2 Range compliance
|
bsw/jbe@1309
|
514 var domRangeMethods = ["setStart", "setStartBefore", "setStartAfter", "setEnd", "setEndBefore",
|
bsw/jbe@1309
|
515 "setEndAfter", "collapse", "selectNode", "selectNodeContents", "compareBoundaryPoints", "deleteContents",
|
bsw/jbe@1309
|
516 "extractContents", "cloneContents", "insertNode", "surroundContents", "cloneRange", "toString", "detach"];
|
bsw/jbe@1309
|
517
|
bsw/jbe@1309
|
518 var textRangeProperties = ["boundingHeight", "boundingLeft", "boundingTop", "boundingWidth", "htmlText", "text"];
|
bsw/jbe@1309
|
519
|
bsw/jbe@1309
|
520 // Subset of TextRange's full set of methods that we're interested in
|
bsw/jbe@1309
|
521 var textRangeMethods = ["collapse", "compareEndPoints", "duplicate", "moveToElementText", "parentElement", "select",
|
bsw/jbe@1309
|
522 "setEndPoint", "getBoundingClientRect"];
|
bsw/jbe@1309
|
523
|
bsw/jbe@1309
|
524 /*----------------------------------------------------------------------------------------------------------------*/
|
bsw/jbe@1309
|
525
|
bsw/jbe@1309
|
526 // Trio of functions taken from Peter Michaux's article:
|
bsw/jbe@1309
|
527 // http://peter.michaux.ca/articles/feature-detection-state-of-the-art-browser-scripting
|
bsw/jbe@1309
|
528 function isHostMethod(o, p) {
|
bsw/jbe@1309
|
529 var t = typeof o[p];
|
bsw/jbe@1309
|
530 return t == FUNCTION || (!!(t == OBJECT && o[p])) || t == "unknown";
|
bsw/jbe@1309
|
531 }
|
bsw/jbe@1309
|
532
|
bsw/jbe@1309
|
533 function isHostObject(o, p) {
|
bsw/jbe@1309
|
534 return !!(typeof o[p] == OBJECT && o[p]);
|
bsw/jbe@1309
|
535 }
|
bsw/jbe@1309
|
536
|
bsw/jbe@1309
|
537 function isHostProperty(o, p) {
|
bsw/jbe@1309
|
538 return typeof o[p] != UNDEFINED;
|
bsw/jbe@1309
|
539 }
|
bsw/jbe@1309
|
540
|
bsw/jbe@1309
|
541 // Creates a convenience function to save verbose repeated calls to tests functions
|
bsw/jbe@1309
|
542 function createMultiplePropertyTest(testFunc) {
|
bsw/jbe@1309
|
543 return function(o, props) {
|
bsw/jbe@1309
|
544 var i = props.length;
|
bsw/jbe@1309
|
545 while (i--) {
|
bsw/jbe@1309
|
546 if (!testFunc(o, props[i])) {
|
bsw/jbe@1309
|
547 return false;
|
bsw/jbe@1309
|
548 }
|
bsw/jbe@1309
|
549 }
|
bsw/jbe@1309
|
550 return true;
|
bsw/jbe@1309
|
551 };
|
bsw/jbe@1309
|
552 }
|
bsw/jbe@1309
|
553
|
bsw/jbe@1309
|
554 // Next trio of functions are a convenience to save verbose repeated calls to previous two functions
|
bsw/jbe@1309
|
555 var areHostMethods = createMultiplePropertyTest(isHostMethod);
|
bsw/jbe@1309
|
556 var areHostObjects = createMultiplePropertyTest(isHostObject);
|
bsw/jbe@1309
|
557 var areHostProperties = createMultiplePropertyTest(isHostProperty);
|
bsw/jbe@1309
|
558
|
bsw/jbe@1309
|
559 function isTextRange(range) {
|
bsw/jbe@1309
|
560 return range && areHostMethods(range, textRangeMethods) && areHostProperties(range, textRangeProperties);
|
bsw/jbe@1309
|
561 }
|
bsw/jbe@1309
|
562
|
bsw/jbe@1309
|
563 function getBody(doc) {
|
bsw/jbe@1309
|
564 return isHostObject(doc, "body") ? doc.body : doc.getElementsByTagName("body")[0];
|
bsw/jbe@1309
|
565 }
|
bsw/jbe@1309
|
566
|
bsw/jbe@1309
|
567 var forEach = [].forEach ?
|
bsw/jbe@1309
|
568 function(arr, func) {
|
bsw/jbe@1309
|
569 arr.forEach(func);
|
bsw/jbe@1309
|
570 } :
|
bsw/jbe@1309
|
571 function(arr, func) {
|
bsw/jbe@1309
|
572 for (var i = 0, len = arr.length; i < len; ++i) {
|
bsw/jbe@1309
|
573 func(arr[i], i);
|
bsw/jbe@1309
|
574 }
|
bsw/jbe@1309
|
575 };
|
bsw/jbe@1309
|
576
|
bsw/jbe@1309
|
577 var modules = {};
|
bsw/jbe@1309
|
578
|
bsw/jbe@1309
|
579 var isBrowser = (typeof window != UNDEFINED && typeof document != UNDEFINED);
|
bsw/jbe@1309
|
580
|
bsw/jbe@1309
|
581 var util = {
|
bsw/jbe@1309
|
582 isHostMethod: isHostMethod,
|
bsw/jbe@1309
|
583 isHostObject: isHostObject,
|
bsw/jbe@1309
|
584 isHostProperty: isHostProperty,
|
bsw/jbe@1309
|
585 areHostMethods: areHostMethods,
|
bsw/jbe@1309
|
586 areHostObjects: areHostObjects,
|
bsw/jbe@1309
|
587 areHostProperties: areHostProperties,
|
bsw/jbe@1309
|
588 isTextRange: isTextRange,
|
bsw/jbe@1309
|
589 getBody: getBody,
|
bsw/jbe@1309
|
590 forEach: forEach
|
bsw/jbe@1309
|
591 };
|
bsw/jbe@1309
|
592
|
bsw/jbe@1309
|
593 var api = {
|
bsw/jbe@1309
|
594 version: "1.3.1-dev",
|
bsw/jbe@1309
|
595 initialized: false,
|
bsw/jbe@1309
|
596 isBrowser: isBrowser,
|
bsw/jbe@1309
|
597 supported: true,
|
bsw/jbe@1309
|
598 util: util,
|
bsw/jbe@1309
|
599 features: {},
|
bsw/jbe@1309
|
600 modules: modules,
|
bsw/jbe@1309
|
601 config: {
|
bsw/jbe@1309
|
602 alertOnFail: false,
|
bsw/jbe@1309
|
603 alertOnWarn: false,
|
bsw/jbe@1309
|
604 preferTextRange: false,
|
bsw/jbe@1309
|
605 autoInitialize: (typeof rangyAutoInitialize == UNDEFINED) ? true : rangyAutoInitialize
|
bsw/jbe@1309
|
606 }
|
bsw/jbe@1309
|
607 };
|
bsw/jbe@1309
|
608
|
bsw/jbe@1309
|
609 function consoleLog(msg) {
|
bsw/jbe@1309
|
610 if (typeof console != UNDEFINED && isHostMethod(console, "log")) {
|
bsw/jbe@1309
|
611 console.log(msg);
|
bsw/jbe@1309
|
612 }
|
bsw/jbe@1309
|
613 }
|
bsw/jbe@1309
|
614
|
bsw/jbe@1309
|
615 function alertOrLog(msg, shouldAlert) {
|
bsw/jbe@1309
|
616 if (isBrowser && shouldAlert) {
|
bsw/jbe@1309
|
617 alert(msg);
|
bsw/jbe@1309
|
618 } else {
|
bsw/jbe@1309
|
619 consoleLog(msg);
|
bsw/jbe@1309
|
620 }
|
bsw/jbe@1309
|
621 }
|
bsw/jbe@1309
|
622
|
bsw/jbe@1309
|
623 function fail(reason) {
|
bsw/jbe@1309
|
624 api.initialized = true;
|
bsw/jbe@1309
|
625 api.supported = false;
|
bsw/jbe@1309
|
626 alertOrLog("Rangy is not supported in this environment. Reason: " + reason, api.config.alertOnFail);
|
bsw/jbe@1309
|
627 }
|
bsw/jbe@1309
|
628
|
bsw/jbe@1309
|
629 api.fail = fail;
|
bsw/jbe@1309
|
630
|
bsw/jbe@1309
|
631 function warn(msg) {
|
bsw/jbe@1309
|
632 alertOrLog("Rangy warning: " + msg, api.config.alertOnWarn);
|
bsw/jbe@1309
|
633 }
|
bsw/jbe@1309
|
634
|
bsw/jbe@1309
|
635 api.warn = warn;
|
bsw/jbe@1309
|
636
|
bsw/jbe@1309
|
637 // Add utility extend() method
|
bsw/jbe@1309
|
638 var extend;
|
bsw/jbe@1309
|
639 if ({}.hasOwnProperty) {
|
bsw/jbe@1309
|
640 util.extend = extend = function(obj, props, deep) {
|
bsw/jbe@1309
|
641 var o, p;
|
bsw/jbe@1309
|
642 for (var i in props) {
|
bsw/jbe@1309
|
643 if (props.hasOwnProperty(i)) {
|
bsw/jbe@1309
|
644 o = obj[i];
|
bsw/jbe@1309
|
645 p = props[i];
|
bsw/jbe@1309
|
646 if (deep && o !== null && typeof o == "object" && p !== null && typeof p == "object") {
|
bsw/jbe@1309
|
647 extend(o, p, true);
|
bsw/jbe@1309
|
648 }
|
bsw/jbe@1309
|
649 obj[i] = p;
|
bsw/jbe@1309
|
650 }
|
bsw/jbe@1309
|
651 }
|
bsw/jbe@1309
|
652 // Special case for toString, which does not show up in for...in loops in IE <= 8
|
bsw/jbe@1309
|
653 if (props.hasOwnProperty("toString")) {
|
bsw/jbe@1309
|
654 obj.toString = props.toString;
|
bsw/jbe@1309
|
655 }
|
bsw/jbe@1309
|
656 return obj;
|
bsw/jbe@1309
|
657 };
|
bsw/jbe@1309
|
658
|
bsw/jbe@1309
|
659 util.createOptions = function(optionsParam, defaults) {
|
bsw/jbe@1309
|
660 var options = {};
|
bsw/jbe@1309
|
661 extend(options, defaults);
|
bsw/jbe@1309
|
662 if (optionsParam) {
|
bsw/jbe@1309
|
663 extend(options, optionsParam);
|
bsw/jbe@1309
|
664 }
|
bsw/jbe@1309
|
665 return options;
|
bsw/jbe@1309
|
666 };
|
bsw/jbe@1309
|
667 } else {
|
bsw/jbe@1309
|
668 fail("hasOwnProperty not supported");
|
bsw/jbe@1309
|
669 }
|
bsw/jbe@1309
|
670
|
bsw/jbe@1309
|
671 // Test whether we're in a browser and bail out if not
|
bsw/jbe@1309
|
672 if (!isBrowser) {
|
bsw/jbe@1309
|
673 fail("Rangy can only run in a browser");
|
bsw/jbe@1309
|
674 }
|
bsw/jbe@1309
|
675
|
bsw/jbe@1309
|
676 // Test whether Array.prototype.slice can be relied on for NodeLists and use an alternative toArray() if not
|
bsw/jbe@1309
|
677 (function() {
|
bsw/jbe@1309
|
678 var toArray;
|
bsw/jbe@1309
|
679
|
bsw/jbe@1309
|
680 if (isBrowser) {
|
bsw/jbe@1309
|
681 var el = document.createElement("div");
|
bsw/jbe@1309
|
682 el.appendChild(document.createElement("span"));
|
bsw/jbe@1309
|
683 var slice = [].slice;
|
bsw/jbe@1309
|
684 try {
|
bsw/jbe@1309
|
685 if (slice.call(el.childNodes, 0)[0].nodeType == 1) {
|
bsw/jbe@1309
|
686 toArray = function(arrayLike) {
|
bsw/jbe@1309
|
687 return slice.call(arrayLike, 0);
|
bsw/jbe@1309
|
688 };
|
bsw/jbe@1309
|
689 }
|
bsw/jbe@1309
|
690 } catch (e) {}
|
bsw/jbe@1309
|
691 }
|
bsw/jbe@1309
|
692
|
bsw/jbe@1309
|
693 if (!toArray) {
|
bsw/jbe@1309
|
694 toArray = function(arrayLike) {
|
bsw/jbe@1309
|
695 var arr = [];
|
bsw/jbe@1309
|
696 for (var i = 0, len = arrayLike.length; i < len; ++i) {
|
bsw/jbe@1309
|
697 arr[i] = arrayLike[i];
|
bsw/jbe@1309
|
698 }
|
bsw/jbe@1309
|
699 return arr;
|
bsw/jbe@1309
|
700 };
|
bsw/jbe@1309
|
701 }
|
bsw/jbe@1309
|
702
|
bsw/jbe@1309
|
703 util.toArray = toArray;
|
bsw/jbe@1309
|
704 })();
|
bsw/jbe@1309
|
705
|
bsw/jbe@1309
|
706 // Very simple event handler wrapper function that doesn't attempt to solve issues such as "this" handling or
|
bsw/jbe@1309
|
707 // normalization of event properties
|
bsw/jbe@1309
|
708 var addListener;
|
bsw/jbe@1309
|
709 if (isBrowser) {
|
bsw/jbe@1309
|
710 if (isHostMethod(document, "addEventListener")) {
|
bsw/jbe@1309
|
711 addListener = function(obj, eventType, listener) {
|
bsw/jbe@1309
|
712 obj.addEventListener(eventType, listener, false);
|
bsw/jbe@1309
|
713 };
|
bsw/jbe@1309
|
714 } else if (isHostMethod(document, "attachEvent")) {
|
bsw/jbe@1309
|
715 addListener = function(obj, eventType, listener) {
|
bsw/jbe@1309
|
716 obj.attachEvent("on" + eventType, listener);
|
bsw/jbe@1309
|
717 };
|
bsw/jbe@1309
|
718 } else {
|
bsw/jbe@1309
|
719 fail("Document does not have required addEventListener or attachEvent method");
|
bsw/jbe@1309
|
720 }
|
bsw/jbe@1309
|
721
|
bsw/jbe@1309
|
722 util.addListener = addListener;
|
bsw/jbe@1309
|
723 }
|
bsw/jbe@1309
|
724
|
bsw/jbe@1309
|
725 var initListeners = [];
|
bsw/jbe@1309
|
726
|
bsw/jbe@1309
|
727 function getErrorDesc(ex) {
|
bsw/jbe@1309
|
728 return ex.message || ex.description || String(ex);
|
bsw/jbe@1309
|
729 }
|
bsw/jbe@1309
|
730
|
bsw/jbe@1309
|
731 // Initialization
|
bsw/jbe@1309
|
732 function init() {
|
bsw/jbe@1309
|
733 if (!isBrowser || api.initialized) {
|
bsw/jbe@1309
|
734 return;
|
bsw/jbe@1309
|
735 }
|
bsw/jbe@1309
|
736 var testRange;
|
bsw/jbe@1309
|
737 var implementsDomRange = false, implementsTextRange = false;
|
bsw/jbe@1309
|
738
|
bsw/jbe@1309
|
739 // First, perform basic feature tests
|
bsw/jbe@1309
|
740
|
bsw/jbe@1309
|
741 if (isHostMethod(document, "createRange")) {
|
bsw/jbe@1309
|
742 testRange = document.createRange();
|
bsw/jbe@1309
|
743 if (areHostMethods(testRange, domRangeMethods) && areHostProperties(testRange, domRangeProperties)) {
|
bsw/jbe@1309
|
744 implementsDomRange = true;
|
bsw/jbe@1309
|
745 }
|
bsw/jbe@1309
|
746 }
|
bsw/jbe@1309
|
747
|
bsw/jbe@1309
|
748 var body = getBody(document);
|
bsw/jbe@1309
|
749 if (!body || body.nodeName.toLowerCase() != "body") {
|
bsw/jbe@1309
|
750 fail("No body element found");
|
bsw/jbe@1309
|
751 return;
|
bsw/jbe@1309
|
752 }
|
bsw/jbe@1309
|
753
|
bsw/jbe@1309
|
754 if (body && isHostMethod(body, "createTextRange")) {
|
bsw/jbe@1309
|
755 testRange = body.createTextRange();
|
bsw/jbe@1309
|
756 if (isTextRange(testRange)) {
|
bsw/jbe@1309
|
757 implementsTextRange = true;
|
bsw/jbe@1309
|
758 }
|
bsw/jbe@1309
|
759 }
|
bsw/jbe@1309
|
760
|
bsw/jbe@1309
|
761 if (!implementsDomRange && !implementsTextRange) {
|
bsw/jbe@1309
|
762 fail("Neither Range nor TextRange are available");
|
bsw/jbe@1309
|
763 return;
|
bsw/jbe@1309
|
764 }
|
bsw/jbe@1309
|
765
|
bsw/jbe@1309
|
766 api.initialized = true;
|
bsw/jbe@1309
|
767 api.features = {
|
bsw/jbe@1309
|
768 implementsDomRange: implementsDomRange,
|
bsw/jbe@1309
|
769 implementsTextRange: implementsTextRange
|
bsw/jbe@1309
|
770 };
|
bsw/jbe@1309
|
771
|
bsw/jbe@1309
|
772 // Initialize modules
|
bsw/jbe@1309
|
773 var module, errorMessage;
|
bsw/jbe@1309
|
774 for (var moduleName in modules) {
|
bsw/jbe@1309
|
775 if ( (module = modules[moduleName]) instanceof Module ) {
|
bsw/jbe@1309
|
776 module.init(module, api);
|
bsw/jbe@1309
|
777 }
|
bsw/jbe@1309
|
778 }
|
bsw/jbe@1309
|
779
|
bsw/jbe@1309
|
780 // Call init listeners
|
bsw/jbe@1309
|
781 for (var i = 0, len = initListeners.length; i < len; ++i) {
|
bsw/jbe@1309
|
782 try {
|
bsw/jbe@1309
|
783 initListeners[i](api);
|
bsw/jbe@1309
|
784 } catch (ex) {
|
bsw/jbe@1309
|
785 errorMessage = "Rangy init listener threw an exception. Continuing. Detail: " + getErrorDesc(ex);
|
bsw/jbe@1309
|
786 consoleLog(errorMessage);
|
bsw/jbe@1309
|
787 }
|
bsw/jbe@1309
|
788 }
|
bsw/jbe@1309
|
789 }
|
bsw/jbe@1309
|
790
|
bsw/jbe@1309
|
791 function deprecationNotice(deprecated, replacement, module) {
|
bsw/jbe@1309
|
792 if (module) {
|
bsw/jbe@1309
|
793 deprecated += " in module " + module.name;
|
bsw/jbe@1309
|
794 }
|
bsw/jbe@1309
|
795 api.warn("DEPRECATED: " + deprecated + " is deprecated. Please use " +
|
bsw/jbe@1309
|
796 replacement + " instead.");
|
bsw/jbe@1309
|
797 }
|
bsw/jbe@1309
|
798
|
bsw/jbe@1309
|
799 function createAliasForDeprecatedMethod(owner, deprecated, replacement, module) {
|
bsw/jbe@1309
|
800 owner[deprecated] = function() {
|
bsw/jbe@1309
|
801 deprecationNotice(deprecated, replacement, module);
|
bsw/jbe@1309
|
802 return owner[replacement].apply(owner, util.toArray(arguments));
|
bsw/jbe@1309
|
803 };
|
bsw/jbe@1309
|
804 }
|
bsw/jbe@1309
|
805
|
bsw/jbe@1309
|
806 util.deprecationNotice = deprecationNotice;
|
bsw/jbe@1309
|
807 util.createAliasForDeprecatedMethod = createAliasForDeprecatedMethod;
|
bsw/jbe@1309
|
808
|
bsw/jbe@1309
|
809 // Allow external scripts to initialize this library in case it's loaded after the document has loaded
|
bsw/jbe@1309
|
810 api.init = init;
|
bsw/jbe@1309
|
811
|
bsw/jbe@1309
|
812 // Execute listener immediately if already initialized
|
bsw/jbe@1309
|
813 api.addInitListener = function(listener) {
|
bsw/jbe@1309
|
814 if (api.initialized) {
|
bsw/jbe@1309
|
815 listener(api);
|
bsw/jbe@1309
|
816 } else {
|
bsw/jbe@1309
|
817 initListeners.push(listener);
|
bsw/jbe@1309
|
818 }
|
bsw/jbe@1309
|
819 };
|
bsw/jbe@1309
|
820
|
bsw/jbe@1309
|
821 var shimListeners = [];
|
bsw/jbe@1309
|
822
|
bsw/jbe@1309
|
823 api.addShimListener = function(listener) {
|
bsw/jbe@1309
|
824 shimListeners.push(listener);
|
bsw/jbe@1309
|
825 };
|
bsw/jbe@1309
|
826
|
bsw/jbe@1309
|
827 function shim(win) {
|
bsw/jbe@1309
|
828 win = win || window;
|
bsw/jbe@1309
|
829 init();
|
bsw/jbe@1309
|
830
|
bsw/jbe@1309
|
831 // Notify listeners
|
bsw/jbe@1309
|
832 for (var i = 0, len = shimListeners.length; i < len; ++i) {
|
bsw/jbe@1309
|
833 shimListeners[i](win);
|
bsw/jbe@1309
|
834 }
|
bsw/jbe@1309
|
835 }
|
bsw/jbe@1309
|
836
|
bsw/jbe@1309
|
837 if (isBrowser) {
|
bsw/jbe@1309
|
838 api.shim = api.createMissingNativeApi = shim;
|
bsw/jbe@1309
|
839 createAliasForDeprecatedMethod(api, "createMissingNativeApi", "shim");
|
bsw/jbe@1309
|
840 }
|
bsw/jbe@1309
|
841
|
bsw/jbe@1309
|
842 function Module(name, dependencies, initializer) {
|
bsw/jbe@1309
|
843 this.name = name;
|
bsw/jbe@1309
|
844 this.dependencies = dependencies;
|
bsw/jbe@1309
|
845 this.initialized = false;
|
bsw/jbe@1309
|
846 this.supported = false;
|
bsw/jbe@1309
|
847 this.initializer = initializer;
|
bsw/jbe@1309
|
848 }
|
bsw/jbe@1309
|
849
|
bsw/jbe@1309
|
850 Module.prototype = {
|
bsw/jbe@1309
|
851 init: function() {
|
bsw/jbe@1309
|
852 var requiredModuleNames = this.dependencies || [];
|
bsw/jbe@1309
|
853 for (var i = 0, len = requiredModuleNames.length, requiredModule, moduleName; i < len; ++i) {
|
bsw/jbe@1309
|
854 moduleName = requiredModuleNames[i];
|
bsw/jbe@1309
|
855
|
bsw/jbe@1309
|
856 requiredModule = modules[moduleName];
|
bsw/jbe@1309
|
857 if (!requiredModule || !(requiredModule instanceof Module)) {
|
bsw/jbe@1309
|
858 throw new Error("required module '" + moduleName + "' not found");
|
bsw/jbe@1309
|
859 }
|
bsw/jbe@1309
|
860
|
bsw/jbe@1309
|
861 requiredModule.init();
|
bsw/jbe@1309
|
862
|
bsw/jbe@1309
|
863 if (!requiredModule.supported) {
|
bsw/jbe@1309
|
864 throw new Error("required module '" + moduleName + "' not supported");
|
bsw/jbe@1309
|
865 }
|
bsw/jbe@1309
|
866 }
|
bsw/jbe@1309
|
867
|
bsw/jbe@1309
|
868 // Now run initializer
|
bsw/jbe@1309
|
869 this.initializer(this);
|
bsw/jbe@1309
|
870 },
|
bsw/jbe@1309
|
871
|
bsw/jbe@1309
|
872 fail: function(reason) {
|
bsw/jbe@1309
|
873 this.initialized = true;
|
bsw/jbe@1309
|
874 this.supported = false;
|
bsw/jbe@1309
|
875 throw new Error(reason);
|
bsw/jbe@1309
|
876 },
|
bsw/jbe@1309
|
877
|
bsw/jbe@1309
|
878 warn: function(msg) {
|
bsw/jbe@1309
|
879 api.warn("Module " + this.name + ": " + msg);
|
bsw/jbe@1309
|
880 },
|
bsw/jbe@1309
|
881
|
bsw/jbe@1309
|
882 deprecationNotice: function(deprecated, replacement) {
|
bsw/jbe@1309
|
883 api.warn("DEPRECATED: " + deprecated + " in module " + this.name + " is deprecated. Please use " +
|
bsw/jbe@1309
|
884 replacement + " instead");
|
bsw/jbe@1309
|
885 },
|
bsw/jbe@1309
|
886
|
bsw/jbe@1309
|
887 createError: function(msg) {
|
bsw/jbe@1309
|
888 return new Error("Error in Rangy " + this.name + " module: " + msg);
|
bsw/jbe@1309
|
889 }
|
bsw/jbe@1309
|
890 };
|
bsw/jbe@1309
|
891
|
bsw/jbe@1309
|
892 function createModule(name, dependencies, initFunc) {
|
bsw/jbe@1309
|
893 var newModule = new Module(name, dependencies, function(module) {
|
bsw/jbe@1309
|
894 if (!module.initialized) {
|
bsw/jbe@1309
|
895 module.initialized = true;
|
bsw/jbe@1309
|
896 try {
|
bsw/jbe@1309
|
897 initFunc(api, module);
|
bsw/jbe@1309
|
898 module.supported = true;
|
bsw/jbe@1309
|
899 } catch (ex) {
|
bsw/jbe@1309
|
900 var errorMessage = "Module '" + name + "' failed to load: " + getErrorDesc(ex);
|
bsw/jbe@1309
|
901 consoleLog(errorMessage);
|
bsw/jbe@1309
|
902 if (ex.stack) {
|
bsw/jbe@1309
|
903 consoleLog(ex.stack);
|
bsw/jbe@1309
|
904 }
|
bsw/jbe@1309
|
905 }
|
bsw/jbe@1309
|
906 }
|
bsw/jbe@1309
|
907 });
|
bsw/jbe@1309
|
908 modules[name] = newModule;
|
bsw/jbe@1309
|
909 return newModule;
|
bsw/jbe@1309
|
910 }
|
bsw/jbe@1309
|
911
|
bsw/jbe@1309
|
912 api.createModule = function(name) {
|
bsw/jbe@1309
|
913 // Allow 2 or 3 arguments (second argument is an optional array of dependencies)
|
bsw/jbe@1309
|
914 var initFunc, dependencies;
|
bsw/jbe@1309
|
915 if (arguments.length == 2) {
|
bsw/jbe@1309
|
916 initFunc = arguments[1];
|
bsw/jbe@1309
|
917 dependencies = [];
|
bsw/jbe@1309
|
918 } else {
|
bsw/jbe@1309
|
919 initFunc = arguments[2];
|
bsw/jbe@1309
|
920 dependencies = arguments[1];
|
bsw/jbe@1309
|
921 }
|
bsw/jbe@1309
|
922
|
bsw/jbe@1309
|
923 var module = createModule(name, dependencies, initFunc);
|
bsw/jbe@1309
|
924
|
bsw/jbe@1309
|
925 // Initialize the module immediately if the core is already initialized
|
bsw/jbe@1309
|
926 if (api.initialized && api.supported) {
|
bsw/jbe@1309
|
927 module.init();
|
bsw/jbe@1309
|
928 }
|
bsw/jbe@1309
|
929 };
|
bsw/jbe@1309
|
930
|
bsw/jbe@1309
|
931 api.createCoreModule = function(name, dependencies, initFunc) {
|
bsw/jbe@1309
|
932 createModule(name, dependencies, initFunc);
|
bsw/jbe@1309
|
933 };
|
bsw/jbe@1309
|
934
|
bsw/jbe@1309
|
935 /*----------------------------------------------------------------------------------------------------------------*/
|
bsw/jbe@1309
|
936
|
bsw/jbe@1309
|
937 // Ensure rangy.rangePrototype and rangy.selectionPrototype are available immediately
|
bsw/jbe@1309
|
938
|
bsw/jbe@1309
|
939 function RangePrototype() {}
|
bsw/jbe@1309
|
940 api.RangePrototype = RangePrototype;
|
bsw/jbe@1309
|
941 api.rangePrototype = new RangePrototype();
|
bsw/jbe@1309
|
942
|
bsw/jbe@1309
|
943 function SelectionPrototype() {}
|
bsw/jbe@1309
|
944 api.selectionPrototype = new SelectionPrototype();
|
bsw/jbe@1309
|
945
|
bsw/jbe@1309
|
946 /*----------------------------------------------------------------------------------------------------------------*/
|
bsw/jbe@1309
|
947
|
bsw/jbe@1309
|
948 // DOM utility methods used by Rangy
|
bsw/jbe@1309
|
949 api.createCoreModule("DomUtil", [], function(api, module) {
|
bsw/jbe@1309
|
950 var UNDEF = "undefined";
|
bsw/jbe@1309
|
951 var util = api.util;
|
bsw/jbe@1309
|
952 var getBody = util.getBody;
|
bsw/jbe@1309
|
953
|
bsw/jbe@1309
|
954 // Perform feature tests
|
bsw/jbe@1309
|
955 if (!util.areHostMethods(document, ["createDocumentFragment", "createElement", "createTextNode"])) {
|
bsw/jbe@1309
|
956 module.fail("document missing a Node creation method");
|
bsw/jbe@1309
|
957 }
|
bsw/jbe@1309
|
958
|
bsw/jbe@1309
|
959 if (!util.isHostMethod(document, "getElementsByTagName")) {
|
bsw/jbe@1309
|
960 module.fail("document missing getElementsByTagName method");
|
bsw/jbe@1309
|
961 }
|
bsw/jbe@1309
|
962
|
bsw/jbe@1309
|
963 var el = document.createElement("div");
|
bsw/jbe@1309
|
964 if (!util.areHostMethods(el, ["insertBefore", "appendChild", "cloneNode"] ||
|
bsw/jbe@1309
|
965 !util.areHostObjects(el, ["previousSibling", "nextSibling", "childNodes", "parentNode"]))) {
|
bsw/jbe@1309
|
966 module.fail("Incomplete Element implementation");
|
bsw/jbe@1309
|
967 }
|
bsw/jbe@1309
|
968
|
bsw/jbe@1309
|
969 // innerHTML is required for Range's createContextualFragment method
|
bsw/jbe@1309
|
970 if (!util.isHostProperty(el, "innerHTML")) {
|
bsw/jbe@1309
|
971 module.fail("Element is missing innerHTML property");
|
bsw/jbe@1309
|
972 }
|
bsw/jbe@1309
|
973
|
bsw/jbe@1309
|
974 var textNode = document.createTextNode("test");
|
bsw/jbe@1309
|
975 if (!util.areHostMethods(textNode, ["splitText", "deleteData", "insertData", "appendData", "cloneNode"] ||
|
bsw/jbe@1309
|
976 !util.areHostObjects(el, ["previousSibling", "nextSibling", "childNodes", "parentNode"]) ||
|
bsw/jbe@1309
|
977 !util.areHostProperties(textNode, ["data"]))) {
|
bsw/jbe@1309
|
978 module.fail("Incomplete Text Node implementation");
|
bsw/jbe@1309
|
979 }
|
bsw/jbe@1309
|
980
|
bsw/jbe@1309
|
981 /*----------------------------------------------------------------------------------------------------------------*/
|
bsw/jbe@1309
|
982
|
bsw/jbe@1309
|
983 // Removed use of indexOf because of a bizarre bug in Opera that is thrown in one of the Acid3 tests. I haven't been
|
bsw/jbe@1309
|
984 // able to replicate it outside of the test. The bug is that indexOf returns -1 when called on an Array that
|
bsw/jbe@1309
|
985 // contains just the document as a single element and the value searched for is the document.
|
bsw/jbe@1309
|
986 var arrayContains = /*Array.prototype.indexOf ?
|
bsw/jbe@1309
|
987 function(arr, val) {
|
bsw/jbe@1309
|
988 return arr.indexOf(val) > -1;
|
bsw/jbe@1309
|
989 }:*/
|
bsw/jbe@1309
|
990
|
bsw/jbe@1309
|
991 function(arr, val) {
|
bsw/jbe@1309
|
992 var i = arr.length;
|
bsw/jbe@1309
|
993 while (i--) {
|
bsw/jbe@1309
|
994 if (arr[i] === val) {
|
bsw/jbe@1309
|
995 return true;
|
bsw/jbe@1309
|
996 }
|
bsw/jbe@1309
|
997 }
|
bsw/jbe@1309
|
998 return false;
|
bsw/jbe@1309
|
999 };
|
bsw/jbe@1309
|
1000
|
bsw/jbe@1309
|
1001 // Opera 11 puts HTML elements in the null namespace, it seems, and IE 7 has undefined namespaceURI
|
bsw/jbe@1309
|
1002 function isHtmlNamespace(node) {
|
bsw/jbe@1309
|
1003 var ns;
|
bsw/jbe@1309
|
1004 return typeof node.namespaceURI == UNDEF || ((ns = node.namespaceURI) === null || ns == "http://www.w3.org/1999/xhtml");
|
bsw/jbe@1309
|
1005 }
|
bsw/jbe@1309
|
1006
|
bsw/jbe@1309
|
1007 function parentElement(node) {
|
bsw/jbe@1309
|
1008 var parent = node.parentNode;
|
bsw/jbe@1309
|
1009 return (parent.nodeType == 1) ? parent : null;
|
bsw/jbe@1309
|
1010 }
|
bsw/jbe@1309
|
1011
|
bsw/jbe@1309
|
1012 function getNodeIndex(node) {
|
bsw/jbe@1309
|
1013 var i = 0;
|
bsw/jbe@1309
|
1014 while( (node = node.previousSibling) ) {
|
bsw/jbe@1309
|
1015 ++i;
|
bsw/jbe@1309
|
1016 }
|
bsw/jbe@1309
|
1017 return i;
|
bsw/jbe@1309
|
1018 }
|
bsw/jbe@1309
|
1019
|
bsw/jbe@1309
|
1020 function getNodeLength(node) {
|
bsw/jbe@1309
|
1021 switch (node.nodeType) {
|
bsw/jbe@1309
|
1022 case 7:
|
bsw/jbe@1309
|
1023 case 10:
|
bsw/jbe@1309
|
1024 return 0;
|
bsw/jbe@1309
|
1025 case 3:
|
bsw/jbe@1309
|
1026 case 8:
|
bsw/jbe@1309
|
1027 return node.length;
|
bsw/jbe@1309
|
1028 default:
|
bsw/jbe@1309
|
1029 return node.childNodes.length;
|
bsw/jbe@1309
|
1030 }
|
bsw/jbe@1309
|
1031 }
|
bsw/jbe@1309
|
1032
|
bsw/jbe@1309
|
1033 function getCommonAncestor(node1, node2) {
|
bsw/jbe@1309
|
1034 var ancestors = [], n;
|
bsw/jbe@1309
|
1035 for (n = node1; n; n = n.parentNode) {
|
bsw/jbe@1309
|
1036 ancestors.push(n);
|
bsw/jbe@1309
|
1037 }
|
bsw/jbe@1309
|
1038
|
bsw/jbe@1309
|
1039 for (n = node2; n; n = n.parentNode) {
|
bsw/jbe@1309
|
1040 if (arrayContains(ancestors, n)) {
|
bsw/jbe@1309
|
1041 return n;
|
bsw/jbe@1309
|
1042 }
|
bsw/jbe@1309
|
1043 }
|
bsw/jbe@1309
|
1044
|
bsw/jbe@1309
|
1045 return null;
|
bsw/jbe@1309
|
1046 }
|
bsw/jbe@1309
|
1047
|
bsw/jbe@1309
|
1048 function isAncestorOf(ancestor, descendant, selfIsAncestor) {
|
bsw/jbe@1309
|
1049 var n = selfIsAncestor ? descendant : descendant.parentNode;
|
bsw/jbe@1309
|
1050 while (n) {
|
bsw/jbe@1309
|
1051 if (n === ancestor) {
|
bsw/jbe@1309
|
1052 return true;
|
bsw/jbe@1309
|
1053 } else {
|
bsw/jbe@1309
|
1054 n = n.parentNode;
|
bsw/jbe@1309
|
1055 }
|
bsw/jbe@1309
|
1056 }
|
bsw/jbe@1309
|
1057 return false;
|
bsw/jbe@1309
|
1058 }
|
bsw/jbe@1309
|
1059
|
bsw/jbe@1309
|
1060 function isOrIsAncestorOf(ancestor, descendant) {
|
bsw/jbe@1309
|
1061 return isAncestorOf(ancestor, descendant, true);
|
bsw/jbe@1309
|
1062 }
|
bsw/jbe@1309
|
1063
|
bsw/jbe@1309
|
1064 function getClosestAncestorIn(node, ancestor, selfIsAncestor) {
|
bsw/jbe@1309
|
1065 var p, n = selfIsAncestor ? node : node.parentNode;
|
bsw/jbe@1309
|
1066 while (n) {
|
bsw/jbe@1309
|
1067 p = n.parentNode;
|
bsw/jbe@1309
|
1068 if (p === ancestor) {
|
bsw/jbe@1309
|
1069 return n;
|
bsw/jbe@1309
|
1070 }
|
bsw/jbe@1309
|
1071 n = p;
|
bsw/jbe@1309
|
1072 }
|
bsw/jbe@1309
|
1073 return null;
|
bsw/jbe@1309
|
1074 }
|
bsw/jbe@1309
|
1075
|
bsw/jbe@1309
|
1076 function isCharacterDataNode(node) {
|
bsw/jbe@1309
|
1077 var t = node.nodeType;
|
bsw/jbe@1309
|
1078 return t == 3 || t == 4 || t == 8 ; // Text, CDataSection or Comment
|
bsw/jbe@1309
|
1079 }
|
bsw/jbe@1309
|
1080
|
bsw/jbe@1309
|
1081 function isTextOrCommentNode(node) {
|
bsw/jbe@1309
|
1082 if (!node) {
|
bsw/jbe@1309
|
1083 return false;
|
bsw/jbe@1309
|
1084 }
|
bsw/jbe@1309
|
1085 var t = node.nodeType;
|
bsw/jbe@1309
|
1086 return t == 3 || t == 8 ; // Text or Comment
|
bsw/jbe@1309
|
1087 }
|
bsw/jbe@1309
|
1088
|
bsw/jbe@1309
|
1089 function insertAfter(node, precedingNode) {
|
bsw/jbe@1309
|
1090 var nextNode = precedingNode.nextSibling, parent = precedingNode.parentNode;
|
bsw/jbe@1309
|
1091 if (nextNode) {
|
bsw/jbe@1309
|
1092 parent.insertBefore(node, nextNode);
|
bsw/jbe@1309
|
1093 } else {
|
bsw/jbe@1309
|
1094 parent.appendChild(node);
|
bsw/jbe@1309
|
1095 }
|
bsw/jbe@1309
|
1096 return node;
|
bsw/jbe@1309
|
1097 }
|
bsw/jbe@1309
|
1098
|
bsw/jbe@1309
|
1099 // Note that we cannot use splitText() because it is bugridden in IE 9.
|
bsw/jbe@1309
|
1100 function splitDataNode(node, index, positionsToPreserve) {
|
bsw/jbe@1309
|
1101 var newNode = node.cloneNode(false);
|
bsw/jbe@1309
|
1102 newNode.deleteData(0, index);
|
bsw/jbe@1309
|
1103 node.deleteData(index, node.length - index);
|
bsw/jbe@1309
|
1104 insertAfter(newNode, node);
|
bsw/jbe@1309
|
1105
|
bsw/jbe@1309
|
1106 // Preserve positions
|
bsw/jbe@1309
|
1107 if (positionsToPreserve) {
|
bsw/jbe@1309
|
1108 for (var i = 0, position; position = positionsToPreserve[i++]; ) {
|
bsw/jbe@1309
|
1109 // Handle case where position was inside the portion of node after the split point
|
bsw/jbe@1309
|
1110 if (position.node == node && position.offset > index) {
|
bsw/jbe@1309
|
1111 position.node = newNode;
|
bsw/jbe@1309
|
1112 position.offset -= index;
|
bsw/jbe@1309
|
1113 }
|
bsw/jbe@1309
|
1114 // Handle the case where the position is a node offset within node's parent
|
bsw/jbe@1309
|
1115 else if (position.node == node.parentNode && position.offset > getNodeIndex(node)) {
|
bsw/jbe@1309
|
1116 ++position.offset;
|
bsw/jbe@1309
|
1117 }
|
bsw/jbe@1309
|
1118 }
|
bsw/jbe@1309
|
1119 }
|
bsw/jbe@1309
|
1120 return newNode;
|
bsw/jbe@1309
|
1121 }
|
bsw/jbe@1309
|
1122
|
bsw/jbe@1309
|
1123 function getDocument(node) {
|
bsw/jbe@1309
|
1124 if (node.nodeType == 9) {
|
bsw/jbe@1309
|
1125 return node;
|
bsw/jbe@1309
|
1126 } else if (typeof node.ownerDocument != UNDEF) {
|
bsw/jbe@1309
|
1127 return node.ownerDocument;
|
bsw/jbe@1309
|
1128 } else if (typeof node.document != UNDEF) {
|
bsw/jbe@1309
|
1129 return node.document;
|
bsw/jbe@1309
|
1130 } else if (node.parentNode) {
|
bsw/jbe@1309
|
1131 return getDocument(node.parentNode);
|
bsw/jbe@1309
|
1132 } else {
|
bsw/jbe@1309
|
1133 throw module.createError("getDocument: no document found for node");
|
bsw/jbe@1309
|
1134 }
|
bsw/jbe@1309
|
1135 }
|
bsw/jbe@1309
|
1136
|
bsw/jbe@1309
|
1137 function getWindow(node) {
|
bsw/jbe@1309
|
1138 var doc = getDocument(node);
|
bsw/jbe@1309
|
1139 if (typeof doc.defaultView != UNDEF) {
|
bsw/jbe@1309
|
1140 return doc.defaultView;
|
bsw/jbe@1309
|
1141 } else if (typeof doc.parentWindow != UNDEF) {
|
bsw/jbe@1309
|
1142 return doc.parentWindow;
|
bsw/jbe@1309
|
1143 } else {
|
bsw/jbe@1309
|
1144 throw module.createError("Cannot get a window object for node");
|
bsw/jbe@1309
|
1145 }
|
bsw/jbe@1309
|
1146 }
|
bsw/jbe@1309
|
1147
|
bsw/jbe@1309
|
1148 function getIframeDocument(iframeEl) {
|
bsw/jbe@1309
|
1149 if (typeof iframeEl.contentDocument != UNDEF) {
|
bsw/jbe@1309
|
1150 return iframeEl.contentDocument;
|
bsw/jbe@1309
|
1151 } else if (typeof iframeEl.contentWindow != UNDEF) {
|
bsw/jbe@1309
|
1152 return iframeEl.contentWindow.document;
|
bsw/jbe@1309
|
1153 } else {
|
bsw/jbe@1309
|
1154 throw module.createError("getIframeDocument: No Document object found for iframe element");
|
bsw/jbe@1309
|
1155 }
|
bsw/jbe@1309
|
1156 }
|
bsw/jbe@1309
|
1157
|
bsw/jbe@1309
|
1158 function getIframeWindow(iframeEl) {
|
bsw/jbe@1309
|
1159 if (typeof iframeEl.contentWindow != UNDEF) {
|
bsw/jbe@1309
|
1160 return iframeEl.contentWindow;
|
bsw/jbe@1309
|
1161 } else if (typeof iframeEl.contentDocument != UNDEF) {
|
bsw/jbe@1309
|
1162 return iframeEl.contentDocument.defaultView;
|
bsw/jbe@1309
|
1163 } else {
|
bsw/jbe@1309
|
1164 throw module.createError("getIframeWindow: No Window object found for iframe element");
|
bsw/jbe@1309
|
1165 }
|
bsw/jbe@1309
|
1166 }
|
bsw/jbe@1309
|
1167
|
bsw/jbe@1309
|
1168 // This looks bad. Is it worth it?
|
bsw/jbe@1309
|
1169 function isWindow(obj) {
|
bsw/jbe@1309
|
1170 return obj && util.isHostMethod(obj, "setTimeout") && util.isHostObject(obj, "document");
|
bsw/jbe@1309
|
1171 }
|
bsw/jbe@1309
|
1172
|
bsw/jbe@1309
|
1173 function getContentDocument(obj, module, methodName) {
|
bsw/jbe@1309
|
1174 var doc;
|
bsw/jbe@1309
|
1175
|
bsw/jbe@1309
|
1176 if (!obj) {
|
bsw/jbe@1309
|
1177 doc = document;
|
bsw/jbe@1309
|
1178 }
|
bsw/jbe@1309
|
1179
|
bsw/jbe@1309
|
1180 // Test if a DOM node has been passed and obtain a document object for it if so
|
bsw/jbe@1309
|
1181 else if (util.isHostProperty(obj, "nodeType")) {
|
bsw/jbe@1309
|
1182 doc = (obj.nodeType == 1 && obj.tagName.toLowerCase() == "iframe") ?
|
bsw/jbe@1309
|
1183 getIframeDocument(obj) : getDocument(obj);
|
bsw/jbe@1309
|
1184 }
|
bsw/jbe@1309
|
1185
|
bsw/jbe@1309
|
1186 // Test if the doc parameter appears to be a Window object
|
bsw/jbe@1309
|
1187 else if (isWindow(obj)) {
|
bsw/jbe@1309
|
1188 doc = obj.document;
|
bsw/jbe@1309
|
1189 }
|
bsw/jbe@1309
|
1190
|
bsw/jbe@1309
|
1191 if (!doc) {
|
bsw/jbe@1309
|
1192 throw module.createError(methodName + "(): Parameter must be a Window object or DOM node");
|
bsw/jbe@1309
|
1193 }
|
bsw/jbe@1309
|
1194
|
bsw/jbe@1309
|
1195 return doc;
|
bsw/jbe@1309
|
1196 }
|
bsw/jbe@1309
|
1197
|
bsw/jbe@1309
|
1198 function getRootContainer(node) {
|
bsw/jbe@1309
|
1199 var parent;
|
bsw/jbe@1309
|
1200 while ( (parent = node.parentNode) ) {
|
bsw/jbe@1309
|
1201 node = parent;
|
bsw/jbe@1309
|
1202 }
|
bsw/jbe@1309
|
1203 return node;
|
bsw/jbe@1309
|
1204 }
|
bsw/jbe@1309
|
1205
|
bsw/jbe@1309
|
1206 function comparePoints(nodeA, offsetA, nodeB, offsetB) {
|
bsw/jbe@1309
|
1207 // See http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level-2-Range-Comparing
|
bsw/jbe@1309
|
1208 var nodeC, root, childA, childB, n;
|
bsw/jbe@1309
|
1209 if (nodeA == nodeB) {
|
bsw/jbe@1309
|
1210 // Case 1: nodes are the same
|
bsw/jbe@1309
|
1211 return offsetA === offsetB ? 0 : (offsetA < offsetB) ? -1 : 1;
|
bsw/jbe@1309
|
1212 } else if ( (nodeC = getClosestAncestorIn(nodeB, nodeA, true)) ) {
|
bsw/jbe@1309
|
1213 // Case 2: node C (container B or an ancestor) is a child node of A
|
bsw/jbe@1309
|
1214 return offsetA <= getNodeIndex(nodeC) ? -1 : 1;
|
bsw/jbe@1309
|
1215 } else if ( (nodeC = getClosestAncestorIn(nodeA, nodeB, true)) ) {
|
bsw/jbe@1309
|
1216 // Case 3: node C (container A or an ancestor) is a child node of B
|
bsw/jbe@1309
|
1217 return getNodeIndex(nodeC) < offsetB ? -1 : 1;
|
bsw/jbe@1309
|
1218 } else {
|
bsw/jbe@1309
|
1219 root = getCommonAncestor(nodeA, nodeB);
|
bsw/jbe@1309
|
1220 if (!root) {
|
bsw/jbe@1309
|
1221 throw new Error("comparePoints error: nodes have no common ancestor");
|
bsw/jbe@1309
|
1222 }
|
bsw/jbe@1309
|
1223
|
bsw/jbe@1309
|
1224 // Case 4: containers are siblings or descendants of siblings
|
bsw/jbe@1309
|
1225 childA = (nodeA === root) ? root : getClosestAncestorIn(nodeA, root, true);
|
bsw/jbe@1309
|
1226 childB = (nodeB === root) ? root : getClosestAncestorIn(nodeB, root, true);
|
bsw/jbe@1309
|
1227
|
bsw/jbe@1309
|
1228 if (childA === childB) {
|
bsw/jbe@1309
|
1229 // This shouldn't be possible
|
bsw/jbe@1309
|
1230 throw module.createError("comparePoints got to case 4 and childA and childB are the same!");
|
bsw/jbe@1309
|
1231 } else {
|
bsw/jbe@1309
|
1232 n = root.firstChild;
|
bsw/jbe@1309
|
1233 while (n) {
|
bsw/jbe@1309
|
1234 if (n === childA) {
|
bsw/jbe@1309
|
1235 return -1;
|
bsw/jbe@1309
|
1236 } else if (n === childB) {
|
bsw/jbe@1309
|
1237 return 1;
|
bsw/jbe@1309
|
1238 }
|
bsw/jbe@1309
|
1239 n = n.nextSibling;
|
bsw/jbe@1309
|
1240 }
|
bsw/jbe@1309
|
1241 }
|
bsw/jbe@1309
|
1242 }
|
bsw/jbe@1309
|
1243 }
|
bsw/jbe@1309
|
1244
|
bsw/jbe@1309
|
1245 /*----------------------------------------------------------------------------------------------------------------*/
|
bsw/jbe@1309
|
1246
|
bsw/jbe@1309
|
1247 // Test for IE's crash (IE 6/7) or exception (IE >= 8) when a reference to garbage-collected text node is queried
|
bsw/jbe@1309
|
1248 var crashyTextNodes = false;
|
bsw/jbe@1309
|
1249
|
bsw/jbe@1309
|
1250 function isBrokenNode(node) {
|
bsw/jbe@1309
|
1251 var n;
|
bsw/jbe@1309
|
1252 try {
|
bsw/jbe@1309
|
1253 n = node.parentNode;
|
bsw/jbe@1309
|
1254 return false;
|
bsw/jbe@1309
|
1255 } catch (e) {
|
bsw/jbe@1309
|
1256 return true;
|
bsw/jbe@1309
|
1257 }
|
bsw/jbe@1309
|
1258 }
|
bsw/jbe@1309
|
1259
|
bsw/jbe@1309
|
1260 (function() {
|
bsw/jbe@1309
|
1261 var el = document.createElement("b");
|
bsw/jbe@1309
|
1262 el.innerHTML = "1";
|
bsw/jbe@1309
|
1263 var textNode = el.firstChild;
|
bsw/jbe@1309
|
1264 el.innerHTML = "<br />";
|
bsw/jbe@1309
|
1265 crashyTextNodes = isBrokenNode(textNode);
|
bsw/jbe@1309
|
1266
|
bsw/jbe@1309
|
1267 api.features.crashyTextNodes = crashyTextNodes;
|
bsw/jbe@1309
|
1268 })();
|
bsw/jbe@1309
|
1269
|
bsw/jbe@1309
|
1270 /*----------------------------------------------------------------------------------------------------------------*/
|
bsw/jbe@1309
|
1271
|
bsw/jbe@1309
|
1272 function inspectNode(node) {
|
bsw/jbe@1309
|
1273 if (!node) {
|
bsw/jbe@1309
|
1274 return "[No node]";
|
bsw/jbe@1309
|
1275 }
|
bsw/jbe@1309
|
1276 if (crashyTextNodes && isBrokenNode(node)) {
|
bsw/jbe@1309
|
1277 return "[Broken node]";
|
bsw/jbe@1309
|
1278 }
|
bsw/jbe@1309
|
1279 if (isCharacterDataNode(node)) {
|
bsw/jbe@1309
|
1280 return '"' + node.data + '"';
|
bsw/jbe@1309
|
1281 }
|
bsw/jbe@1309
|
1282 if (node.nodeType == 1) {
|
bsw/jbe@1309
|
1283 var idAttr = node.id ? ' id="' + node.id + '"' : "";
|
bsw/jbe@1309
|
1284 return "<" + node.nodeName + idAttr + ">[index:" + getNodeIndex(node) + ",length:" + node.childNodes.length + "][" + (node.innerHTML || "[innerHTML not supported]").slice(0, 25) + "]";
|
bsw/jbe@1309
|
1285 }
|
bsw/jbe@1309
|
1286 return node.nodeName;
|
bsw/jbe@1309
|
1287 }
|
bsw/jbe@1309
|
1288
|
bsw/jbe@1309
|
1289 function fragmentFromNodeChildren(node) {
|
bsw/jbe@1309
|
1290 var fragment = getDocument(node).createDocumentFragment(), child;
|
bsw/jbe@1309
|
1291 while ( (child = node.firstChild) ) {
|
bsw/jbe@1309
|
1292 fragment.appendChild(child);
|
bsw/jbe@1309
|
1293 }
|
bsw/jbe@1309
|
1294 return fragment;
|
bsw/jbe@1309
|
1295 }
|
bsw/jbe@1309
|
1296
|
bsw/jbe@1309
|
1297 var getComputedStyleProperty;
|
bsw/jbe@1309
|
1298 if (typeof window.getComputedStyle != UNDEF) {
|
bsw/jbe@1309
|
1299 getComputedStyleProperty = function(el, propName) {
|
bsw/jbe@1309
|
1300 return getWindow(el).getComputedStyle(el, null)[propName];
|
bsw/jbe@1309
|
1301 };
|
bsw/jbe@1309
|
1302 } else if (typeof document.documentElement.currentStyle != UNDEF) {
|
bsw/jbe@1309
|
1303 getComputedStyleProperty = function(el, propName) {
|
bsw/jbe@1309
|
1304 return el.currentStyle ? el.currentStyle[propName] : "";
|
bsw/jbe@1309
|
1305 };
|
bsw/jbe@1309
|
1306 } else {
|
bsw/jbe@1309
|
1307 module.fail("No means of obtaining computed style properties found");
|
bsw/jbe@1309
|
1308 }
|
bsw/jbe@1309
|
1309
|
bsw/jbe@1309
|
1310 function createTestElement(doc, html, contentEditable) {
|
bsw/jbe@1309
|
1311 var body = getBody(doc);
|
bsw/jbe@1309
|
1312 var el = doc.createElement("div");
|
bsw/jbe@1309
|
1313 el.contentEditable = "" + !!contentEditable;
|
bsw/jbe@1309
|
1314 if (html) {
|
bsw/jbe@1309
|
1315 el.innerHTML = html;
|
bsw/jbe@1309
|
1316 }
|
bsw/jbe@1309
|
1317
|
bsw/jbe@1309
|
1318 // Insert the test element at the start of the body to prevent scrolling to the bottom in iOS (issue #292)
|
bsw/jbe@1309
|
1319 var bodyFirstChild = body.firstChild;
|
bsw/jbe@1309
|
1320 if (bodyFirstChild) {
|
bsw/jbe@1309
|
1321 body.insertBefore(el, bodyFirstChild);
|
bsw/jbe@1309
|
1322 } else {
|
bsw/jbe@1309
|
1323 body.appendChild(el);
|
bsw/jbe@1309
|
1324 }
|
bsw/jbe@1309
|
1325
|
bsw/jbe@1309
|
1326 return el;
|
bsw/jbe@1309
|
1327 }
|
bsw/jbe@1309
|
1328
|
bsw/jbe@1309
|
1329 function removeNode(node) {
|
bsw/jbe@1309
|
1330 return node.parentNode.removeChild(node);
|
bsw/jbe@1309
|
1331 }
|
bsw/jbe@1309
|
1332
|
bsw/jbe@1309
|
1333 function NodeIterator(root) {
|
bsw/jbe@1309
|
1334 this.root = root;
|
bsw/jbe@1309
|
1335 this._next = root;
|
bsw/jbe@1309
|
1336 }
|
bsw/jbe@1309
|
1337
|
bsw/jbe@1309
|
1338 NodeIterator.prototype = {
|
bsw/jbe@1309
|
1339 _current: null,
|
bsw/jbe@1309
|
1340
|
bsw/jbe@1309
|
1341 hasNext: function() {
|
bsw/jbe@1309
|
1342 return !!this._next;
|
bsw/jbe@1309
|
1343 },
|
bsw/jbe@1309
|
1344
|
bsw/jbe@1309
|
1345 next: function() {
|
bsw/jbe@1309
|
1346 var n = this._current = this._next;
|
bsw/jbe@1309
|
1347 var child, next;
|
bsw/jbe@1309
|
1348 if (this._current) {
|
bsw/jbe@1309
|
1349 child = n.firstChild;
|
bsw/jbe@1309
|
1350 if (child) {
|
bsw/jbe@1309
|
1351 this._next = child;
|
bsw/jbe@1309
|
1352 } else {
|
bsw/jbe@1309
|
1353 next = null;
|
bsw/jbe@1309
|
1354 while ((n !== this.root) && !(next = n.nextSibling)) {
|
bsw/jbe@1309
|
1355 n = n.parentNode;
|
bsw/jbe@1309
|
1356 }
|
bsw/jbe@1309
|
1357 this._next = next;
|
bsw/jbe@1309
|
1358 }
|
bsw/jbe@1309
|
1359 }
|
bsw/jbe@1309
|
1360 return this._current;
|
bsw/jbe@1309
|
1361 },
|
bsw/jbe@1309
|
1362
|
bsw/jbe@1309
|
1363 detach: function() {
|
bsw/jbe@1309
|
1364 this._current = this._next = this.root = null;
|
bsw/jbe@1309
|
1365 }
|
bsw/jbe@1309
|
1366 };
|
bsw/jbe@1309
|
1367
|
bsw/jbe@1309
|
1368 function createIterator(root) {
|
bsw/jbe@1309
|
1369 return new NodeIterator(root);
|
bsw/jbe@1309
|
1370 }
|
bsw/jbe@1309
|
1371
|
bsw/jbe@1309
|
1372 function DomPosition(node, offset) {
|
bsw/jbe@1309
|
1373 this.node = node;
|
bsw/jbe@1309
|
1374 this.offset = offset;
|
bsw/jbe@1309
|
1375 }
|
bsw/jbe@1309
|
1376
|
bsw/jbe@1309
|
1377 DomPosition.prototype = {
|
bsw/jbe@1309
|
1378 equals: function(pos) {
|
bsw/jbe@1309
|
1379 return !!pos && this.node === pos.node && this.offset == pos.offset;
|
bsw/jbe@1309
|
1380 },
|
bsw/jbe@1309
|
1381
|
bsw/jbe@1309
|
1382 inspect: function() {
|
bsw/jbe@1309
|
1383 return "[DomPosition(" + inspectNode(this.node) + ":" + this.offset + ")]";
|
bsw/jbe@1309
|
1384 },
|
bsw/jbe@1309
|
1385
|
bsw/jbe@1309
|
1386 toString: function() {
|
bsw/jbe@1309
|
1387 return this.inspect();
|
bsw/jbe@1309
|
1388 }
|
bsw/jbe@1309
|
1389 };
|
bsw/jbe@1309
|
1390
|
bsw/jbe@1309
|
1391 function DOMException(codeName) {
|
bsw/jbe@1309
|
1392 this.code = this[codeName];
|
bsw/jbe@1309
|
1393 this.codeName = codeName;
|
bsw/jbe@1309
|
1394 this.message = "DOMException: " + this.codeName;
|
bsw/jbe@1309
|
1395 }
|
bsw/jbe@1309
|
1396
|
bsw/jbe@1309
|
1397 DOMException.prototype = {
|
bsw/jbe@1309
|
1398 INDEX_SIZE_ERR: 1,
|
bsw/jbe@1309
|
1399 HIERARCHY_REQUEST_ERR: 3,
|
bsw/jbe@1309
|
1400 WRONG_DOCUMENT_ERR: 4,
|
bsw/jbe@1309
|
1401 NO_MODIFICATION_ALLOWED_ERR: 7,
|
bsw/jbe@1309
|
1402 NOT_FOUND_ERR: 8,
|
bsw/jbe@1309
|
1403 NOT_SUPPORTED_ERR: 9,
|
bsw/jbe@1309
|
1404 INVALID_STATE_ERR: 11,
|
bsw/jbe@1309
|
1405 INVALID_NODE_TYPE_ERR: 24
|
bsw/jbe@1309
|
1406 };
|
bsw/jbe@1309
|
1407
|
bsw/jbe@1309
|
1408 DOMException.prototype.toString = function() {
|
bsw/jbe@1309
|
1409 return this.message;
|
bsw/jbe@1309
|
1410 };
|
bsw/jbe@1309
|
1411
|
bsw/jbe@1309
|
1412 api.dom = {
|
bsw/jbe@1309
|
1413 arrayContains: arrayContains,
|
bsw/jbe@1309
|
1414 isHtmlNamespace: isHtmlNamespace,
|
bsw/jbe@1309
|
1415 parentElement: parentElement,
|
bsw/jbe@1309
|
1416 getNodeIndex: getNodeIndex,
|
bsw/jbe@1309
|
1417 getNodeLength: getNodeLength,
|
bsw/jbe@1309
|
1418 getCommonAncestor: getCommonAncestor,
|
bsw/jbe@1309
|
1419 isAncestorOf: isAncestorOf,
|
bsw/jbe@1309
|
1420 isOrIsAncestorOf: isOrIsAncestorOf,
|
bsw/jbe@1309
|
1421 getClosestAncestorIn: getClosestAncestorIn,
|
bsw/jbe@1309
|
1422 isCharacterDataNode: isCharacterDataNode,
|
bsw/jbe@1309
|
1423 isTextOrCommentNode: isTextOrCommentNode,
|
bsw/jbe@1309
|
1424 insertAfter: insertAfter,
|
bsw/jbe@1309
|
1425 splitDataNode: splitDataNode,
|
bsw/jbe@1309
|
1426 getDocument: getDocument,
|
bsw/jbe@1309
|
1427 getWindow: getWindow,
|
bsw/jbe@1309
|
1428 getIframeWindow: getIframeWindow,
|
bsw/jbe@1309
|
1429 getIframeDocument: getIframeDocument,
|
bsw/jbe@1309
|
1430 getBody: getBody,
|
bsw/jbe@1309
|
1431 isWindow: isWindow,
|
bsw/jbe@1309
|
1432 getContentDocument: getContentDocument,
|
bsw/jbe@1309
|
1433 getRootContainer: getRootContainer,
|
bsw/jbe@1309
|
1434 comparePoints: comparePoints,
|
bsw/jbe@1309
|
1435 isBrokenNode: isBrokenNode,
|
bsw/jbe@1309
|
1436 inspectNode: inspectNode,
|
bsw/jbe@1309
|
1437 getComputedStyleProperty: getComputedStyleProperty,
|
bsw/jbe@1309
|
1438 createTestElement: createTestElement,
|
bsw/jbe@1309
|
1439 removeNode: removeNode,
|
bsw/jbe@1309
|
1440 fragmentFromNodeChildren: fragmentFromNodeChildren,
|
bsw/jbe@1309
|
1441 createIterator: createIterator,
|
bsw/jbe@1309
|
1442 DomPosition: DomPosition
|
bsw/jbe@1309
|
1443 };
|
bsw/jbe@1309
|
1444
|
bsw/jbe@1309
|
1445 api.DOMException = DOMException;
|
bsw/jbe@1309
|
1446 });
|
bsw/jbe@1309
|
1447
|
bsw/jbe@1309
|
1448 /*----------------------------------------------------------------------------------------------------------------*/
|
bsw/jbe@1309
|
1449
|
bsw/jbe@1309
|
1450 // Pure JavaScript implementation of DOM Range
|
bsw/jbe@1309
|
1451 api.createCoreModule("DomRange", ["DomUtil"], function(api, module) {
|
bsw/jbe@1309
|
1452 var dom = api.dom;
|
bsw/jbe@1309
|
1453 var util = api.util;
|
bsw/jbe@1309
|
1454 var DomPosition = dom.DomPosition;
|
bsw/jbe@1309
|
1455 var DOMException = api.DOMException;
|
bsw/jbe@1309
|
1456
|
bsw/jbe@1309
|
1457 var isCharacterDataNode = dom.isCharacterDataNode;
|
bsw/jbe@1309
|
1458 var getNodeIndex = dom.getNodeIndex;
|
bsw/jbe@1309
|
1459 var isOrIsAncestorOf = dom.isOrIsAncestorOf;
|
bsw/jbe@1309
|
1460 var getDocument = dom.getDocument;
|
bsw/jbe@1309
|
1461 var comparePoints = dom.comparePoints;
|
bsw/jbe@1309
|
1462 var splitDataNode = dom.splitDataNode;
|
bsw/jbe@1309
|
1463 var getClosestAncestorIn = dom.getClosestAncestorIn;
|
bsw/jbe@1309
|
1464 var getNodeLength = dom.getNodeLength;
|
bsw/jbe@1309
|
1465 var arrayContains = dom.arrayContains;
|
bsw/jbe@1309
|
1466 var getRootContainer = dom.getRootContainer;
|
bsw/jbe@1309
|
1467 var crashyTextNodes = api.features.crashyTextNodes;
|
bsw/jbe@1309
|
1468
|
bsw/jbe@1309
|
1469 var removeNode = dom.removeNode;
|
bsw/jbe@1309
|
1470
|
bsw/jbe@1309
|
1471 /*----------------------------------------------------------------------------------------------------------------*/
|
bsw/jbe@1309
|
1472
|
bsw/jbe@1309
|
1473 // Utility functions
|
bsw/jbe@1309
|
1474
|
bsw/jbe@1309
|
1475 function isNonTextPartiallySelected(node, range) {
|
bsw/jbe@1309
|
1476 return (node.nodeType != 3) &&
|
bsw/jbe@1309
|
1477 (isOrIsAncestorOf(node, range.startContainer) || isOrIsAncestorOf(node, range.endContainer));
|
bsw/jbe@1309
|
1478 }
|
bsw/jbe@1309
|
1479
|
bsw/jbe@1309
|
1480 function getRangeDocument(range) {
|
bsw/jbe@1309
|
1481 return range.document || getDocument(range.startContainer);
|
bsw/jbe@1309
|
1482 }
|
bsw/jbe@1309
|
1483
|
bsw/jbe@1309
|
1484 function getRangeRoot(range) {
|
bsw/jbe@1309
|
1485 return getRootContainer(range.startContainer);
|
bsw/jbe@1309
|
1486 }
|
bsw/jbe@1309
|
1487
|
bsw/jbe@1309
|
1488 function getBoundaryBeforeNode(node) {
|
bsw/jbe@1309
|
1489 return new DomPosition(node.parentNode, getNodeIndex(node));
|
bsw/jbe@1309
|
1490 }
|
bsw/jbe@1309
|
1491
|
bsw/jbe@1309
|
1492 function getBoundaryAfterNode(node) {
|
bsw/jbe@1309
|
1493 return new DomPosition(node.parentNode, getNodeIndex(node) + 1);
|
bsw/jbe@1309
|
1494 }
|
bsw/jbe@1309
|
1495
|
bsw/jbe@1309
|
1496 function insertNodeAtPosition(node, n, o) {
|
bsw/jbe@1309
|
1497 var firstNodeInserted = node.nodeType == 11 ? node.firstChild : node;
|
bsw/jbe@1309
|
1498 if (isCharacterDataNode(n)) {
|
bsw/jbe@1309
|
1499 if (o == n.length) {
|
bsw/jbe@1309
|
1500 dom.insertAfter(node, n);
|
bsw/jbe@1309
|
1501 } else {
|
bsw/jbe@1309
|
1502 n.parentNode.insertBefore(node, o == 0 ? n : splitDataNode(n, o));
|
bsw/jbe@1309
|
1503 }
|
bsw/jbe@1309
|
1504 } else if (o >= n.childNodes.length) {
|
bsw/jbe@1309
|
1505 n.appendChild(node);
|
bsw/jbe@1309
|
1506 } else {
|
bsw/jbe@1309
|
1507 n.insertBefore(node, n.childNodes[o]);
|
bsw/jbe@1309
|
1508 }
|
bsw/jbe@1309
|
1509 return firstNodeInserted;
|
bsw/jbe@1309
|
1510 }
|
bsw/jbe@1309
|
1511
|
bsw/jbe@1309
|
1512 function rangesIntersect(rangeA, rangeB, touchingIsIntersecting) {
|
bsw/jbe@1309
|
1513 assertRangeValid(rangeA);
|
bsw/jbe@1309
|
1514 assertRangeValid(rangeB);
|
bsw/jbe@1309
|
1515
|
bsw/jbe@1309
|
1516 if (getRangeDocument(rangeB) != getRangeDocument(rangeA)) {
|
bsw/jbe@1309
|
1517 throw new DOMException("WRONG_DOCUMENT_ERR");
|
bsw/jbe@1309
|
1518 }
|
bsw/jbe@1309
|
1519
|
bsw/jbe@1309
|
1520 var startComparison = comparePoints(rangeA.startContainer, rangeA.startOffset, rangeB.endContainer, rangeB.endOffset),
|
bsw/jbe@1309
|
1521 endComparison = comparePoints(rangeA.endContainer, rangeA.endOffset, rangeB.startContainer, rangeB.startOffset);
|
bsw/jbe@1309
|
1522
|
bsw/jbe@1309
|
1523 return touchingIsIntersecting ? startComparison <= 0 && endComparison >= 0 : startComparison < 0 && endComparison > 0;
|
bsw/jbe@1309
|
1524 }
|
bsw/jbe@1309
|
1525
|
bsw/jbe@1309
|
1526 function cloneSubtree(iterator) {
|
bsw/jbe@1309
|
1527 var partiallySelected;
|
bsw/jbe@1309
|
1528 for (var node, frag = getRangeDocument(iterator.range).createDocumentFragment(), subIterator; node = iterator.next(); ) {
|
bsw/jbe@1309
|
1529 partiallySelected = iterator.isPartiallySelectedSubtree();
|
bsw/jbe@1309
|
1530 node = node.cloneNode(!partiallySelected);
|
bsw/jbe@1309
|
1531 if (partiallySelected) {
|
bsw/jbe@1309
|
1532 subIterator = iterator.getSubtreeIterator();
|
bsw/jbe@1309
|
1533 node.appendChild(cloneSubtree(subIterator));
|
bsw/jbe@1309
|
1534 subIterator.detach();
|
bsw/jbe@1309
|
1535 }
|
bsw/jbe@1309
|
1536
|
bsw/jbe@1309
|
1537 if (node.nodeType == 10) { // DocumentType
|
bsw/jbe@1309
|
1538 throw new DOMException("HIERARCHY_REQUEST_ERR");
|
bsw/jbe@1309
|
1539 }
|
bsw/jbe@1309
|
1540 frag.appendChild(node);
|
bsw/jbe@1309
|
1541 }
|
bsw/jbe@1309
|
1542 return frag;
|
bsw/jbe@1309
|
1543 }
|
bsw/jbe@1309
|
1544
|
bsw/jbe@1309
|
1545 function iterateSubtree(rangeIterator, func, iteratorState) {
|
bsw/jbe@1309
|
1546 var it, n;
|
bsw/jbe@1309
|
1547 iteratorState = iteratorState || { stop: false };
|
bsw/jbe@1309
|
1548 for (var node, subRangeIterator; node = rangeIterator.next(); ) {
|
bsw/jbe@1309
|
1549 if (rangeIterator.isPartiallySelectedSubtree()) {
|
bsw/jbe@1309
|
1550 if (func(node) === false) {
|
bsw/jbe@1309
|
1551 iteratorState.stop = true;
|
bsw/jbe@1309
|
1552 return;
|
bsw/jbe@1309
|
1553 } else {
|
bsw/jbe@1309
|
1554 // The node is partially selected by the Range, so we can use a new RangeIterator on the portion of
|
bsw/jbe@1309
|
1555 // the node selected by the Range.
|
bsw/jbe@1309
|
1556 subRangeIterator = rangeIterator.getSubtreeIterator();
|
bsw/jbe@1309
|
1557 iterateSubtree(subRangeIterator, func, iteratorState);
|
bsw/jbe@1309
|
1558 subRangeIterator.detach();
|
bsw/jbe@1309
|
1559 if (iteratorState.stop) {
|
bsw/jbe@1309
|
1560 return;
|
bsw/jbe@1309
|
1561 }
|
bsw/jbe@1309
|
1562 }
|
bsw/jbe@1309
|
1563 } else {
|
bsw/jbe@1309
|
1564 // The whole node is selected, so we can use efficient DOM iteration to iterate over the node and its
|
bsw/jbe@1309
|
1565 // descendants
|
bsw/jbe@1309
|
1566 it = dom.createIterator(node);
|
bsw/jbe@1309
|
1567 while ( (n = it.next()) ) {
|
bsw/jbe@1309
|
1568 if (func(n) === false) {
|
bsw/jbe@1309
|
1569 iteratorState.stop = true;
|
bsw/jbe@1309
|
1570 return;
|
bsw/jbe@1309
|
1571 }
|
bsw/jbe@1309
|
1572 }
|
bsw/jbe@1309
|
1573 }
|
bsw/jbe@1309
|
1574 }
|
bsw/jbe@1309
|
1575 }
|
bsw/jbe@1309
|
1576
|
bsw/jbe@1309
|
1577 function deleteSubtree(iterator) {
|
bsw/jbe@1309
|
1578 var subIterator;
|
bsw/jbe@1309
|
1579 while (iterator.next()) {
|
bsw/jbe@1309
|
1580 if (iterator.isPartiallySelectedSubtree()) {
|
bsw/jbe@1309
|
1581 subIterator = iterator.getSubtreeIterator();
|
bsw/jbe@1309
|
1582 deleteSubtree(subIterator);
|
bsw/jbe@1309
|
1583 subIterator.detach();
|
bsw/jbe@1309
|
1584 } else {
|
bsw/jbe@1309
|
1585 iterator.remove();
|
bsw/jbe@1309
|
1586 }
|
bsw/jbe@1309
|
1587 }
|
bsw/jbe@1309
|
1588 }
|
bsw/jbe@1309
|
1589
|
bsw/jbe@1309
|
1590 function extractSubtree(iterator) {
|
bsw/jbe@1309
|
1591 for (var node, frag = getRangeDocument(iterator.range).createDocumentFragment(), subIterator; node = iterator.next(); ) {
|
bsw/jbe@1309
|
1592
|
bsw/jbe@1309
|
1593 if (iterator.isPartiallySelectedSubtree()) {
|
bsw/jbe@1309
|
1594 node = node.cloneNode(false);
|
bsw/jbe@1309
|
1595 subIterator = iterator.getSubtreeIterator();
|
bsw/jbe@1309
|
1596 node.appendChild(extractSubtree(subIterator));
|
bsw/jbe@1309
|
1597 subIterator.detach();
|
bsw/jbe@1309
|
1598 } else {
|
bsw/jbe@1309
|
1599 iterator.remove();
|
bsw/jbe@1309
|
1600 }
|
bsw/jbe@1309
|
1601 if (node.nodeType == 10) { // DocumentType
|
bsw/jbe@1309
|
1602 throw new DOMException("HIERARCHY_REQUEST_ERR");
|
bsw/jbe@1309
|
1603 }
|
bsw/jbe@1309
|
1604 frag.appendChild(node);
|
bsw/jbe@1309
|
1605 }
|
bsw/jbe@1309
|
1606 return frag;
|
bsw/jbe@1309
|
1607 }
|
bsw/jbe@1309
|
1608
|
bsw/jbe@1309
|
1609 function getNodesInRange(range, nodeTypes, filter) {
|
bsw/jbe@1309
|
1610 var filterNodeTypes = !!(nodeTypes && nodeTypes.length), regex;
|
bsw/jbe@1309
|
1611 var filterExists = !!filter;
|
bsw/jbe@1309
|
1612 if (filterNodeTypes) {
|
bsw/jbe@1309
|
1613 regex = new RegExp("^(" + nodeTypes.join("|") + ")$");
|
bsw/jbe@1309
|
1614 }
|
bsw/jbe@1309
|
1615
|
bsw/jbe@1309
|
1616 var nodes = [];
|
bsw/jbe@1309
|
1617 iterateSubtree(new RangeIterator(range, false), function(node) {
|
bsw/jbe@1309
|
1618 if (filterNodeTypes && !regex.test(node.nodeType)) {
|
bsw/jbe@1309
|
1619 return;
|
bsw/jbe@1309
|
1620 }
|
bsw/jbe@1309
|
1621 if (filterExists && !filter(node)) {
|
bsw/jbe@1309
|
1622 return;
|
bsw/jbe@1309
|
1623 }
|
bsw/jbe@1309
|
1624 // Don't include a boundary container if it is a character data node and the range does not contain any
|
bsw/jbe@1309
|
1625 // of its character data. See issue 190.
|
bsw/jbe@1309
|
1626 var sc = range.startContainer;
|
bsw/jbe@1309
|
1627 if (node == sc && isCharacterDataNode(sc) && range.startOffset == sc.length) {
|
bsw/jbe@1309
|
1628 return;
|
bsw/jbe@1309
|
1629 }
|
bsw/jbe@1309
|
1630
|
bsw/jbe@1309
|
1631 var ec = range.endContainer;
|
bsw/jbe@1309
|
1632 if (node == ec && isCharacterDataNode(ec) && range.endOffset == 0) {
|
bsw/jbe@1309
|
1633 return;
|
bsw/jbe@1309
|
1634 }
|
bsw/jbe@1309
|
1635
|
bsw/jbe@1309
|
1636 nodes.push(node);
|
bsw/jbe@1309
|
1637 });
|
bsw/jbe@1309
|
1638 return nodes;
|
bsw/jbe@1309
|
1639 }
|
bsw/jbe@1309
|
1640
|
bsw/jbe@1309
|
1641 function inspect(range) {
|
bsw/jbe@1309
|
1642 var name = (typeof range.getName == "undefined") ? "Range" : range.getName();
|
bsw/jbe@1309
|
1643 return "[" + name + "(" + dom.inspectNode(range.startContainer) + ":" + range.startOffset + ", " +
|
bsw/jbe@1309
|
1644 dom.inspectNode(range.endContainer) + ":" + range.endOffset + ")]";
|
bsw/jbe@1309
|
1645 }
|
bsw/jbe@1309
|
1646
|
bsw/jbe@1309
|
1647 /*----------------------------------------------------------------------------------------------------------------*/
|
bsw/jbe@1309
|
1648
|
bsw/jbe@1309
|
1649 // RangeIterator code partially borrows from IERange by Tim Ryan (http://github.com/timcameronryan/IERange)
|
bsw/jbe@1309
|
1650
|
bsw/jbe@1309
|
1651 function RangeIterator(range, clonePartiallySelectedTextNodes) {
|
bsw/jbe@1309
|
1652 this.range = range;
|
bsw/jbe@1309
|
1653 this.clonePartiallySelectedTextNodes = clonePartiallySelectedTextNodes;
|
bsw/jbe@1309
|
1654
|
bsw/jbe@1309
|
1655
|
bsw/jbe@1309
|
1656 if (!range.collapsed) {
|
bsw/jbe@1309
|
1657 this.sc = range.startContainer;
|
bsw/jbe@1309
|
1658 this.so = range.startOffset;
|
bsw/jbe@1309
|
1659 this.ec = range.endContainer;
|
bsw/jbe@1309
|
1660 this.eo = range.endOffset;
|
bsw/jbe@1309
|
1661 var root = range.commonAncestorContainer;
|
bsw/jbe@1309
|
1662
|
bsw/jbe@1309
|
1663 if (this.sc === this.ec && isCharacterDataNode(this.sc)) {
|
bsw/jbe@1309
|
1664 this.isSingleCharacterDataNode = true;
|
bsw/jbe@1309
|
1665 this._first = this._last = this._next = this.sc;
|
bsw/jbe@1309
|
1666 } else {
|
bsw/jbe@1309
|
1667 this._first = this._next = (this.sc === root && !isCharacterDataNode(this.sc)) ?
|
bsw/jbe@1309
|
1668 this.sc.childNodes[this.so] : getClosestAncestorIn(this.sc, root, true);
|
bsw/jbe@1309
|
1669 this._last = (this.ec === root && !isCharacterDataNode(this.ec)) ?
|
bsw/jbe@1309
|
1670 this.ec.childNodes[this.eo - 1] : getClosestAncestorIn(this.ec, root, true);
|
bsw/jbe@1309
|
1671 }
|
bsw/jbe@1309
|
1672 }
|
bsw/jbe@1309
|
1673 }
|
bsw/jbe@1309
|
1674
|
bsw/jbe@1309
|
1675 RangeIterator.prototype = {
|
bsw/jbe@1309
|
1676 _current: null,
|
bsw/jbe@1309
|
1677 _next: null,
|
bsw/jbe@1309
|
1678 _first: null,
|
bsw/jbe@1309
|
1679 _last: null,
|
bsw/jbe@1309
|
1680 isSingleCharacterDataNode: false,
|
bsw/jbe@1309
|
1681
|
bsw/jbe@1309
|
1682 reset: function() {
|
bsw/jbe@1309
|
1683 this._current = null;
|
bsw/jbe@1309
|
1684 this._next = this._first;
|
bsw/jbe@1309
|
1685 },
|
bsw/jbe@1309
|
1686
|
bsw/jbe@1309
|
1687 hasNext: function() {
|
bsw/jbe@1309
|
1688 return !!this._next;
|
bsw/jbe@1309
|
1689 },
|
bsw/jbe@1309
|
1690
|
bsw/jbe@1309
|
1691 next: function() {
|
bsw/jbe@1309
|
1692 // Move to next node
|
bsw/jbe@1309
|
1693 var current = this._current = this._next;
|
bsw/jbe@1309
|
1694 if (current) {
|
bsw/jbe@1309
|
1695 this._next = (current !== this._last) ? current.nextSibling : null;
|
bsw/jbe@1309
|
1696
|
bsw/jbe@1309
|
1697 // Check for partially selected text nodes
|
bsw/jbe@1309
|
1698 if (isCharacterDataNode(current) && this.clonePartiallySelectedTextNodes) {
|
bsw/jbe@1309
|
1699 if (current === this.ec) {
|
bsw/jbe@1309
|
1700 (current = current.cloneNode(true)).deleteData(this.eo, current.length - this.eo);
|
bsw/jbe@1309
|
1701 }
|
bsw/jbe@1309
|
1702 if (this._current === this.sc) {
|
bsw/jbe@1309
|
1703 (current = current.cloneNode(true)).deleteData(0, this.so);
|
bsw/jbe@1309
|
1704 }
|
bsw/jbe@1309
|
1705 }
|
bsw/jbe@1309
|
1706 }
|
bsw/jbe@1309
|
1707
|
bsw/jbe@1309
|
1708 return current;
|
bsw/jbe@1309
|
1709 },
|
bsw/jbe@1309
|
1710
|
bsw/jbe@1309
|
1711 remove: function() {
|
bsw/jbe@1309
|
1712 var current = this._current, start, end;
|
bsw/jbe@1309
|
1713
|
bsw/jbe@1309
|
1714 if (isCharacterDataNode(current) && (current === this.sc || current === this.ec)) {
|
bsw/jbe@1309
|
1715 start = (current === this.sc) ? this.so : 0;
|
bsw/jbe@1309
|
1716 end = (current === this.ec) ? this.eo : current.length;
|
bsw/jbe@1309
|
1717 if (start != end) {
|
bsw/jbe@1309
|
1718 current.deleteData(start, end - start);
|
bsw/jbe@1309
|
1719 }
|
bsw/jbe@1309
|
1720 } else {
|
bsw/jbe@1309
|
1721 if (current.parentNode) {
|
bsw/jbe@1309
|
1722 removeNode(current);
|
bsw/jbe@1309
|
1723 } else {
|
bsw/jbe@1309
|
1724 }
|
bsw/jbe@1309
|
1725 }
|
bsw/jbe@1309
|
1726 },
|
bsw/jbe@1309
|
1727
|
bsw/jbe@1309
|
1728 // Checks if the current node is partially selected
|
bsw/jbe@1309
|
1729 isPartiallySelectedSubtree: function() {
|
bsw/jbe@1309
|
1730 var current = this._current;
|
bsw/jbe@1309
|
1731 return isNonTextPartiallySelected(current, this.range);
|
bsw/jbe@1309
|
1732 },
|
bsw/jbe@1309
|
1733
|
bsw/jbe@1309
|
1734 getSubtreeIterator: function() {
|
bsw/jbe@1309
|
1735 var subRange;
|
bsw/jbe@1309
|
1736 if (this.isSingleCharacterDataNode) {
|
bsw/jbe@1309
|
1737 subRange = this.range.cloneRange();
|
bsw/jbe@1309
|
1738 subRange.collapse(false);
|
bsw/jbe@1309
|
1739 } else {
|
bsw/jbe@1309
|
1740 subRange = new Range(getRangeDocument(this.range));
|
bsw/jbe@1309
|
1741 var current = this._current;
|
bsw/jbe@1309
|
1742 var startContainer = current, startOffset = 0, endContainer = current, endOffset = getNodeLength(current);
|
bsw/jbe@1309
|
1743
|
bsw/jbe@1309
|
1744 if (isOrIsAncestorOf(current, this.sc)) {
|
bsw/jbe@1309
|
1745 startContainer = this.sc;
|
bsw/jbe@1309
|
1746 startOffset = this.so;
|
bsw/jbe@1309
|
1747 }
|
bsw/jbe@1309
|
1748 if (isOrIsAncestorOf(current, this.ec)) {
|
bsw/jbe@1309
|
1749 endContainer = this.ec;
|
bsw/jbe@1309
|
1750 endOffset = this.eo;
|
bsw/jbe@1309
|
1751 }
|
bsw/jbe@1309
|
1752
|
bsw/jbe@1309
|
1753 updateBoundaries(subRange, startContainer, startOffset, endContainer, endOffset);
|
bsw/jbe@1309
|
1754 }
|
bsw/jbe@1309
|
1755 return new RangeIterator(subRange, this.clonePartiallySelectedTextNodes);
|
bsw/jbe@1309
|
1756 },
|
bsw/jbe@1309
|
1757
|
bsw/jbe@1309
|
1758 detach: function() {
|
bsw/jbe@1309
|
1759 this.range = this._current = this._next = this._first = this._last = this.sc = this.so = this.ec = this.eo = null;
|
bsw/jbe@1309
|
1760 }
|
bsw/jbe@1309
|
1761 };
|
bsw/jbe@1309
|
1762
|
bsw/jbe@1309
|
1763 /*----------------------------------------------------------------------------------------------------------------*/
|
bsw/jbe@1309
|
1764
|
bsw/jbe@1309
|
1765 var beforeAfterNodeTypes = [1, 3, 4, 5, 7, 8, 10];
|
bsw/jbe@1309
|
1766 var rootContainerNodeTypes = [2, 9, 11];
|
bsw/jbe@1309
|
1767 var readonlyNodeTypes = [5, 6, 10, 12];
|
bsw/jbe@1309
|
1768 var insertableNodeTypes = [1, 3, 4, 5, 7, 8, 10, 11];
|
bsw/jbe@1309
|
1769 var surroundNodeTypes = [1, 3, 4, 5, 7, 8];
|
bsw/jbe@1309
|
1770
|
bsw/jbe@1309
|
1771 function createAncestorFinder(nodeTypes) {
|
bsw/jbe@1309
|
1772 return function(node, selfIsAncestor) {
|
bsw/jbe@1309
|
1773 var t, n = selfIsAncestor ? node : node.parentNode;
|
bsw/jbe@1309
|
1774 while (n) {
|
bsw/jbe@1309
|
1775 t = n.nodeType;
|
bsw/jbe@1309
|
1776 if (arrayContains(nodeTypes, t)) {
|
bsw/jbe@1309
|
1777 return n;
|
bsw/jbe@1309
|
1778 }
|
bsw/jbe@1309
|
1779 n = n.parentNode;
|
bsw/jbe@1309
|
1780 }
|
bsw/jbe@1309
|
1781 return null;
|
bsw/jbe@1309
|
1782 };
|
bsw/jbe@1309
|
1783 }
|
bsw/jbe@1309
|
1784
|
bsw/jbe@1309
|
1785 var getDocumentOrFragmentContainer = createAncestorFinder( [9, 11] );
|
bsw/jbe@1309
|
1786 var getReadonlyAncestor = createAncestorFinder(readonlyNodeTypes);
|
bsw/jbe@1309
|
1787 var getDocTypeNotationEntityAncestor = createAncestorFinder( [6, 10, 12] );
|
bsw/jbe@1309
|
1788
|
bsw/jbe@1309
|
1789 function assertNoDocTypeNotationEntityAncestor(node, allowSelf) {
|
bsw/jbe@1309
|
1790 if (getDocTypeNotationEntityAncestor(node, allowSelf)) {
|
bsw/jbe@1309
|
1791 throw new DOMException("INVALID_NODE_TYPE_ERR");
|
bsw/jbe@1309
|
1792 }
|
bsw/jbe@1309
|
1793 }
|
bsw/jbe@1309
|
1794
|
bsw/jbe@1309
|
1795 function assertValidNodeType(node, invalidTypes) {
|
bsw/jbe@1309
|
1796 if (!arrayContains(invalidTypes, node.nodeType)) {
|
bsw/jbe@1309
|
1797 throw new DOMException("INVALID_NODE_TYPE_ERR");
|
bsw/jbe@1309
|
1798 }
|
bsw/jbe@1309
|
1799 }
|
bsw/jbe@1309
|
1800
|
bsw/jbe@1309
|
1801 function assertValidOffset(node, offset) {
|
bsw/jbe@1309
|
1802 if (offset < 0 || offset > (isCharacterDataNode(node) ? node.length : node.childNodes.length)) {
|
bsw/jbe@1309
|
1803 throw new DOMException("INDEX_SIZE_ERR");
|
bsw/jbe@1309
|
1804 }
|
bsw/jbe@1309
|
1805 }
|
bsw/jbe@1309
|
1806
|
bsw/jbe@1309
|
1807 function assertSameDocumentOrFragment(node1, node2) {
|
bsw/jbe@1309
|
1808 if (getDocumentOrFragmentContainer(node1, true) !== getDocumentOrFragmentContainer(node2, true)) {
|
bsw/jbe@1309
|
1809 throw new DOMException("WRONG_DOCUMENT_ERR");
|
bsw/jbe@1309
|
1810 }
|
bsw/jbe@1309
|
1811 }
|
bsw/jbe@1309
|
1812
|
bsw/jbe@1309
|
1813 function assertNodeNotReadOnly(node) {
|
bsw/jbe@1309
|
1814 if (getReadonlyAncestor(node, true)) {
|
bsw/jbe@1309
|
1815 throw new DOMException("NO_MODIFICATION_ALLOWED_ERR");
|
bsw/jbe@1309
|
1816 }
|
bsw/jbe@1309
|
1817 }
|
bsw/jbe@1309
|
1818
|
bsw/jbe@1309
|
1819 function assertNode(node, codeName) {
|
bsw/jbe@1309
|
1820 if (!node) {
|
bsw/jbe@1309
|
1821 throw new DOMException(codeName);
|
bsw/jbe@1309
|
1822 }
|
bsw/jbe@1309
|
1823 }
|
bsw/jbe@1309
|
1824
|
bsw/jbe@1309
|
1825 function isValidOffset(node, offset) {
|
bsw/jbe@1309
|
1826 return offset <= (isCharacterDataNode(node) ? node.length : node.childNodes.length);
|
bsw/jbe@1309
|
1827 }
|
bsw/jbe@1309
|
1828
|
bsw/jbe@1309
|
1829 function isRangeValid(range) {
|
bsw/jbe@1309
|
1830 return (!!range.startContainer && !!range.endContainer &&
|
bsw/jbe@1309
|
1831 !(crashyTextNodes && (dom.isBrokenNode(range.startContainer) || dom.isBrokenNode(range.endContainer))) &&
|
bsw/jbe@1309
|
1832 getRootContainer(range.startContainer) == getRootContainer(range.endContainer) &&
|
bsw/jbe@1309
|
1833 isValidOffset(range.startContainer, range.startOffset) &&
|
bsw/jbe@1309
|
1834 isValidOffset(range.endContainer, range.endOffset));
|
bsw/jbe@1309
|
1835 }
|
bsw/jbe@1309
|
1836
|
bsw/jbe@1309
|
1837 function assertRangeValid(range) {
|
bsw/jbe@1309
|
1838 if (!isRangeValid(range)) {
|
bsw/jbe@1309
|
1839 throw new Error("Range error: Range is not valid. This usually happens after DOM mutation. Range: (" + range.inspect() + ")");
|
bsw/jbe@1309
|
1840 }
|
bsw/jbe@1309
|
1841 }
|
bsw/jbe@1309
|
1842
|
bsw/jbe@1309
|
1843 /*----------------------------------------------------------------------------------------------------------------*/
|
bsw/jbe@1309
|
1844
|
bsw/jbe@1309
|
1845 // Test the browser's innerHTML support to decide how to implement createContextualFragment
|
bsw/jbe@1309
|
1846 var styleEl = document.createElement("style");
|
bsw/jbe@1309
|
1847 var htmlParsingConforms = false;
|
bsw/jbe@1309
|
1848 try {
|
bsw/jbe@1309
|
1849 styleEl.innerHTML = "<b>x</b>";
|
bsw/jbe@1309
|
1850 htmlParsingConforms = (styleEl.firstChild.nodeType == 3); // Opera incorrectly creates an element node
|
bsw/jbe@1309
|
1851 } catch (e) {
|
bsw/jbe@1309
|
1852 // IE 6 and 7 throw
|
bsw/jbe@1309
|
1853 }
|
bsw/jbe@1309
|
1854
|
bsw/jbe@1309
|
1855 api.features.htmlParsingConforms = htmlParsingConforms;
|
bsw/jbe@1309
|
1856
|
bsw/jbe@1309
|
1857 var createContextualFragment = htmlParsingConforms ?
|
bsw/jbe@1309
|
1858
|
bsw/jbe@1309
|
1859 // Implementation as per HTML parsing spec, trusting in the browser's implementation of innerHTML. See
|
bsw/jbe@1309
|
1860 // discussion and base code for this implementation at issue 67.
|
bsw/jbe@1309
|
1861 // Spec: http://html5.org/specs/dom-parsing.html#extensions-to-the-range-interface
|
bsw/jbe@1309
|
1862 // Thanks to Aleks Williams.
|
bsw/jbe@1309
|
1863 function(fragmentStr) {
|
bsw/jbe@1309
|
1864 // "Let node the context object's start's node."
|
bsw/jbe@1309
|
1865 var node = this.startContainer;
|
bsw/jbe@1309
|
1866 var doc = getDocument(node);
|
bsw/jbe@1309
|
1867
|
bsw/jbe@1309
|
1868 // "If the context object's start's node is null, raise an INVALID_STATE_ERR
|
bsw/jbe@1309
|
1869 // exception and abort these steps."
|
bsw/jbe@1309
|
1870 if (!node) {
|
bsw/jbe@1309
|
1871 throw new DOMException("INVALID_STATE_ERR");
|
bsw/jbe@1309
|
1872 }
|
bsw/jbe@1309
|
1873
|
bsw/jbe@1309
|
1874 // "Let element be as follows, depending on node's interface:"
|
bsw/jbe@1309
|
1875 // Document, Document Fragment: null
|
bsw/jbe@1309
|
1876 var el = null;
|
bsw/jbe@1309
|
1877
|
bsw/jbe@1309
|
1878 // "Element: node"
|
bsw/jbe@1309
|
1879 if (node.nodeType == 1) {
|
bsw/jbe@1309
|
1880 el = node;
|
bsw/jbe@1309
|
1881
|
bsw/jbe@1309
|
1882 // "Text, Comment: node's parentElement"
|
bsw/jbe@1309
|
1883 } else if (isCharacterDataNode(node)) {
|
bsw/jbe@1309
|
1884 el = dom.parentElement(node);
|
bsw/jbe@1309
|
1885 }
|
bsw/jbe@1309
|
1886
|
bsw/jbe@1309
|
1887 // "If either element is null or element's ownerDocument is an HTML document
|
bsw/jbe@1309
|
1888 // and element's local name is "html" and element's namespace is the HTML
|
bsw/jbe@1309
|
1889 // namespace"
|
bsw/jbe@1309
|
1890 if (el === null || (
|
bsw/jbe@1309
|
1891 el.nodeName == "HTML" &&
|
bsw/jbe@1309
|
1892 dom.isHtmlNamespace(getDocument(el).documentElement) &&
|
bsw/jbe@1309
|
1893 dom.isHtmlNamespace(el)
|
bsw/jbe@1309
|
1894 )) {
|
bsw/jbe@1309
|
1895
|
bsw/jbe@1309
|
1896 // "let element be a new Element with "body" as its local name and the HTML
|
bsw/jbe@1309
|
1897 // namespace as its namespace.""
|
bsw/jbe@1309
|
1898 el = doc.createElement("body");
|
bsw/jbe@1309
|
1899 } else {
|
bsw/jbe@1309
|
1900 el = el.cloneNode(false);
|
bsw/jbe@1309
|
1901 }
|
bsw/jbe@1309
|
1902
|
bsw/jbe@1309
|
1903 // "If the node's document is an HTML document: Invoke the HTML fragment parsing algorithm."
|
bsw/jbe@1309
|
1904 // "If the node's document is an XML document: Invoke the XML fragment parsing algorithm."
|
bsw/jbe@1309
|
1905 // "In either case, the algorithm must be invoked with fragment as the input
|
bsw/jbe@1309
|
1906 // and element as the context element."
|
bsw/jbe@1309
|
1907 el.innerHTML = fragmentStr;
|
bsw/jbe@1309
|
1908
|
bsw/jbe@1309
|
1909 // "If this raises an exception, then abort these steps. Otherwise, let new
|
bsw/jbe@1309
|
1910 // children be the nodes returned."
|
bsw/jbe@1309
|
1911
|
bsw/jbe@1309
|
1912 // "Let fragment be a new DocumentFragment."
|
bsw/jbe@1309
|
1913 // "Append all new children to fragment."
|
bsw/jbe@1309
|
1914 // "Return fragment."
|
bsw/jbe@1309
|
1915 return dom.fragmentFromNodeChildren(el);
|
bsw/jbe@1309
|
1916 } :
|
bsw/jbe@1309
|
1917
|
bsw/jbe@1309
|
1918 // In this case, innerHTML cannot be trusted, so fall back to a simpler, non-conformant implementation that
|
bsw/jbe@1309
|
1919 // previous versions of Rangy used (with the exception of using a body element rather than a div)
|
bsw/jbe@1309
|
1920 function(fragmentStr) {
|
bsw/jbe@1309
|
1921 var doc = getRangeDocument(this);
|
bsw/jbe@1309
|
1922 var el = doc.createElement("body");
|
bsw/jbe@1309
|
1923 el.innerHTML = fragmentStr;
|
bsw/jbe@1309
|
1924
|
bsw/jbe@1309
|
1925 return dom.fragmentFromNodeChildren(el);
|
bsw/jbe@1309
|
1926 };
|
bsw/jbe@1309
|
1927
|
bsw/jbe@1309
|
1928 function splitRangeBoundaries(range, positionsToPreserve) {
|
bsw/jbe@1309
|
1929 assertRangeValid(range);
|
bsw/jbe@1309
|
1930
|
bsw/jbe@1309
|
1931 var sc = range.startContainer, so = range.startOffset, ec = range.endContainer, eo = range.endOffset;
|
bsw/jbe@1309
|
1932 var startEndSame = (sc === ec);
|
bsw/jbe@1309
|
1933
|
bsw/jbe@1309
|
1934 if (isCharacterDataNode(ec) && eo > 0 && eo < ec.length) {
|
bsw/jbe@1309
|
1935 splitDataNode(ec, eo, positionsToPreserve);
|
bsw/jbe@1309
|
1936 }
|
bsw/jbe@1309
|
1937
|
bsw/jbe@1309
|
1938 if (isCharacterDataNode(sc) && so > 0 && so < sc.length) {
|
bsw/jbe@1309
|
1939 sc = splitDataNode(sc, so, positionsToPreserve);
|
bsw/jbe@1309
|
1940 if (startEndSame) {
|
bsw/jbe@1309
|
1941 eo -= so;
|
bsw/jbe@1309
|
1942 ec = sc;
|
bsw/jbe@1309
|
1943 } else if (ec == sc.parentNode && eo >= getNodeIndex(sc)) {
|
bsw/jbe@1309
|
1944 eo++;
|
bsw/jbe@1309
|
1945 }
|
bsw/jbe@1309
|
1946 so = 0;
|
bsw/jbe@1309
|
1947 }
|
bsw/jbe@1309
|
1948 range.setStartAndEnd(sc, so, ec, eo);
|
bsw/jbe@1309
|
1949 }
|
bsw/jbe@1309
|
1950
|
bsw/jbe@1309
|
1951 function rangeToHtml(range) {
|
bsw/jbe@1309
|
1952 assertRangeValid(range);
|
bsw/jbe@1309
|
1953 var container = range.commonAncestorContainer.parentNode.cloneNode(false);
|
bsw/jbe@1309
|
1954 container.appendChild( range.cloneContents() );
|
bsw/jbe@1309
|
1955 return container.innerHTML;
|
bsw/jbe@1309
|
1956 }
|
bsw/jbe@1309
|
1957
|
bsw/jbe@1309
|
1958 /*----------------------------------------------------------------------------------------------------------------*/
|
bsw/jbe@1309
|
1959
|
bsw/jbe@1309
|
1960 var rangeProperties = ["startContainer", "startOffset", "endContainer", "endOffset", "collapsed",
|
bsw/jbe@1309
|
1961 "commonAncestorContainer"];
|
bsw/jbe@1309
|
1962
|
bsw/jbe@1309
|
1963 var s2s = 0, s2e = 1, e2e = 2, e2s = 3;
|
bsw/jbe@1309
|
1964 var n_b = 0, n_a = 1, n_b_a = 2, n_i = 3;
|
bsw/jbe@1309
|
1965
|
bsw/jbe@1309
|
1966 util.extend(api.rangePrototype, {
|
bsw/jbe@1309
|
1967 compareBoundaryPoints: function(how, range) {
|
bsw/jbe@1309
|
1968 assertRangeValid(this);
|
bsw/jbe@1309
|
1969 assertSameDocumentOrFragment(this.startContainer, range.startContainer);
|
bsw/jbe@1309
|
1970
|
bsw/jbe@1309
|
1971 var nodeA, offsetA, nodeB, offsetB;
|
bsw/jbe@1309
|
1972 var prefixA = (how == e2s || how == s2s) ? "start" : "end";
|
bsw/jbe@1309
|
1973 var prefixB = (how == s2e || how == s2s) ? "start" : "end";
|
bsw/jbe@1309
|
1974 nodeA = this[prefixA + "Container"];
|
bsw/jbe@1309
|
1975 offsetA = this[prefixA + "Offset"];
|
bsw/jbe@1309
|
1976 nodeB = range[prefixB + "Container"];
|
bsw/jbe@1309
|
1977 offsetB = range[prefixB + "Offset"];
|
bsw/jbe@1309
|
1978 return comparePoints(nodeA, offsetA, nodeB, offsetB);
|
bsw/jbe@1309
|
1979 },
|
bsw/jbe@1309
|
1980
|
bsw/jbe@1309
|
1981 insertNode: function(node) {
|
bsw/jbe@1309
|
1982 assertRangeValid(this);
|
bsw/jbe@1309
|
1983 assertValidNodeType(node, insertableNodeTypes);
|
bsw/jbe@1309
|
1984 assertNodeNotReadOnly(this.startContainer);
|
bsw/jbe@1309
|
1985
|
bsw/jbe@1309
|
1986 if (isOrIsAncestorOf(node, this.startContainer)) {
|
bsw/jbe@1309
|
1987 throw new DOMException("HIERARCHY_REQUEST_ERR");
|
bsw/jbe@1309
|
1988 }
|
bsw/jbe@1309
|
1989
|
bsw/jbe@1309
|
1990 // No check for whether the container of the start of the Range is of a type that does not allow
|
bsw/jbe@1309
|
1991 // children of the type of node: the browser's DOM implementation should do this for us when we attempt
|
bsw/jbe@1309
|
1992 // to add the node
|
bsw/jbe@1309
|
1993
|
bsw/jbe@1309
|
1994 var firstNodeInserted = insertNodeAtPosition(node, this.startContainer, this.startOffset);
|
bsw/jbe@1309
|
1995 this.setStartBefore(firstNodeInserted);
|
bsw/jbe@1309
|
1996 },
|
bsw/jbe@1309
|
1997
|
bsw/jbe@1309
|
1998 cloneContents: function() {
|
bsw/jbe@1309
|
1999 assertRangeValid(this);
|
bsw/jbe@1309
|
2000
|
bsw/jbe@1309
|
2001 var clone, frag;
|
bsw/jbe@1309
|
2002 if (this.collapsed) {
|
bsw/jbe@1309
|
2003 return getRangeDocument(this).createDocumentFragment();
|
bsw/jbe@1309
|
2004 } else {
|
bsw/jbe@1309
|
2005 if (this.startContainer === this.endContainer && isCharacterDataNode(this.startContainer)) {
|
bsw/jbe@1309
|
2006 clone = this.startContainer.cloneNode(true);
|
bsw/jbe@1309
|
2007 clone.data = clone.data.slice(this.startOffset, this.endOffset);
|
bsw/jbe@1309
|
2008 frag = getRangeDocument(this).createDocumentFragment();
|
bsw/jbe@1309
|
2009 frag.appendChild(clone);
|
bsw/jbe@1309
|
2010 return frag;
|
bsw/jbe@1309
|
2011 } else {
|
bsw/jbe@1309
|
2012 var iterator = new RangeIterator(this, true);
|
bsw/jbe@1309
|
2013 clone = cloneSubtree(iterator);
|
bsw/jbe@1309
|
2014 iterator.detach();
|
bsw/jbe@1309
|
2015 }
|
bsw/jbe@1309
|
2016 return clone;
|
bsw/jbe@1309
|
2017 }
|
bsw/jbe@1309
|
2018 },
|
bsw/jbe@1309
|
2019
|
bsw/jbe@1309
|
2020 canSurroundContents: function() {
|
bsw/jbe@1309
|
2021 assertRangeValid(this);
|
bsw/jbe@1309
|
2022 assertNodeNotReadOnly(this.startContainer);
|
bsw/jbe@1309
|
2023 assertNodeNotReadOnly(this.endContainer);
|
bsw/jbe@1309
|
2024
|
bsw/jbe@1309
|
2025 // Check if the contents can be surrounded. Specifically, this means whether the range partially selects
|
bsw/jbe@1309
|
2026 // no non-text nodes.
|
bsw/jbe@1309
|
2027 var iterator = new RangeIterator(this, true);
|
bsw/jbe@1309
|
2028 var boundariesInvalid = (iterator._first && (isNonTextPartiallySelected(iterator._first, this)) ||
|
bsw/jbe@1309
|
2029 (iterator._last && isNonTextPartiallySelected(iterator._last, this)));
|
bsw/jbe@1309
|
2030 iterator.detach();
|
bsw/jbe@1309
|
2031 return !boundariesInvalid;
|
bsw/jbe@1309
|
2032 },
|
bsw/jbe@1309
|
2033
|
bsw/jbe@1309
|
2034 surroundContents: function(node) {
|
bsw/jbe@1309
|
2035 assertValidNodeType(node, surroundNodeTypes);
|
bsw/jbe@1309
|
2036
|
bsw/jbe@1309
|
2037 if (!this.canSurroundContents()) {
|
bsw/jbe@1309
|
2038 throw new DOMException("INVALID_STATE_ERR");
|
bsw/jbe@1309
|
2039 }
|
bsw/jbe@1309
|
2040
|
bsw/jbe@1309
|
2041 // Extract the contents
|
bsw/jbe@1309
|
2042 var content = this.extractContents();
|
bsw/jbe@1309
|
2043
|
bsw/jbe@1309
|
2044 // Clear the children of the node
|
bsw/jbe@1309
|
2045 if (node.hasChildNodes()) {
|
bsw/jbe@1309
|
2046 while (node.lastChild) {
|
bsw/jbe@1309
|
2047 node.removeChild(node.lastChild);
|
bsw/jbe@1309
|
2048 }
|
bsw/jbe@1309
|
2049 }
|
bsw/jbe@1309
|
2050
|
bsw/jbe@1309
|
2051 // Insert the new node and add the extracted contents
|
bsw/jbe@1309
|
2052 insertNodeAtPosition(node, this.startContainer, this.startOffset);
|
bsw/jbe@1309
|
2053 node.appendChild(content);
|
bsw/jbe@1309
|
2054
|
bsw/jbe@1309
|
2055 this.selectNode(node);
|
bsw/jbe@1309
|
2056 },
|
bsw/jbe@1309
|
2057
|
bsw/jbe@1309
|
2058 cloneRange: function() {
|
bsw/jbe@1309
|
2059 assertRangeValid(this);
|
bsw/jbe@1309
|
2060 var range = new Range(getRangeDocument(this));
|
bsw/jbe@1309
|
2061 var i = rangeProperties.length, prop;
|
bsw/jbe@1309
|
2062 while (i--) {
|
bsw/jbe@1309
|
2063 prop = rangeProperties[i];
|
bsw/jbe@1309
|
2064 range[prop] = this[prop];
|
bsw/jbe@1309
|
2065 }
|
bsw/jbe@1309
|
2066 return range;
|
bsw/jbe@1309
|
2067 },
|
bsw/jbe@1309
|
2068
|
bsw/jbe@1309
|
2069 toString: function() {
|
bsw/jbe@1309
|
2070 assertRangeValid(this);
|
bsw/jbe@1309
|
2071 var sc = this.startContainer;
|
bsw/jbe@1309
|
2072 if (sc === this.endContainer && isCharacterDataNode(sc)) {
|
bsw/jbe@1309
|
2073 return (sc.nodeType == 3 || sc.nodeType == 4) ? sc.data.slice(this.startOffset, this.endOffset) : "";
|
bsw/jbe@1309
|
2074 } else {
|
bsw/jbe@1309
|
2075 var textParts = [], iterator = new RangeIterator(this, true);
|
bsw/jbe@1309
|
2076 iterateSubtree(iterator, function(node) {
|
bsw/jbe@1309
|
2077 // Accept only text or CDATA nodes, not comments
|
bsw/jbe@1309
|
2078 if (node.nodeType == 3 || node.nodeType == 4) {
|
bsw/jbe@1309
|
2079 textParts.push(node.data);
|
bsw/jbe@1309
|
2080 }
|
bsw/jbe@1309
|
2081 });
|
bsw/jbe@1309
|
2082 iterator.detach();
|
bsw/jbe@1309
|
2083 return textParts.join("");
|
bsw/jbe@1309
|
2084 }
|
bsw/jbe@1309
|
2085 },
|
bsw/jbe@1309
|
2086
|
bsw/jbe@1309
|
2087 // The methods below are all non-standard. The following batch were introduced by Mozilla but have since
|
bsw/jbe@1309
|
2088 // been removed from Mozilla.
|
bsw/jbe@1309
|
2089
|
bsw/jbe@1309
|
2090 compareNode: function(node) {
|
bsw/jbe@1309
|
2091 assertRangeValid(this);
|
bsw/jbe@1309
|
2092
|
bsw/jbe@1309
|
2093 var parent = node.parentNode;
|
bsw/jbe@1309
|
2094 var nodeIndex = getNodeIndex(node);
|
bsw/jbe@1309
|
2095
|
bsw/jbe@1309
|
2096 if (!parent) {
|
bsw/jbe@1309
|
2097 throw new DOMException("NOT_FOUND_ERR");
|
bsw/jbe@1309
|
2098 }
|
bsw/jbe@1309
|
2099
|
bsw/jbe@1309
|
2100 var startComparison = this.comparePoint(parent, nodeIndex),
|
bsw/jbe@1309
|
2101 endComparison = this.comparePoint(parent, nodeIndex + 1);
|
bsw/jbe@1309
|
2102
|
bsw/jbe@1309
|
2103 if (startComparison < 0) { // Node starts before
|
bsw/jbe@1309
|
2104 return (endComparison > 0) ? n_b_a : n_b;
|
bsw/jbe@1309
|
2105 } else {
|
bsw/jbe@1309
|
2106 return (endComparison > 0) ? n_a : n_i;
|
bsw/jbe@1309
|
2107 }
|
bsw/jbe@1309
|
2108 },
|
bsw/jbe@1309
|
2109
|
bsw/jbe@1309
|
2110 comparePoint: function(node, offset) {
|
bsw/jbe@1309
|
2111 assertRangeValid(this);
|
bsw/jbe@1309
|
2112 assertNode(node, "HIERARCHY_REQUEST_ERR");
|
bsw/jbe@1309
|
2113 assertSameDocumentOrFragment(node, this.startContainer);
|
bsw/jbe@1309
|
2114
|
bsw/jbe@1309
|
2115 if (comparePoints(node, offset, this.startContainer, this.startOffset) < 0) {
|
bsw/jbe@1309
|
2116 return -1;
|
bsw/jbe@1309
|
2117 } else if (comparePoints(node, offset, this.endContainer, this.endOffset) > 0) {
|
bsw/jbe@1309
|
2118 return 1;
|
bsw/jbe@1309
|
2119 }
|
bsw/jbe@1309
|
2120 return 0;
|
bsw/jbe@1309
|
2121 },
|
bsw/jbe@1309
|
2122
|
bsw/jbe@1309
|
2123 createContextualFragment: createContextualFragment,
|
bsw/jbe@1309
|
2124
|
bsw/jbe@1309
|
2125 toHtml: function() {
|
bsw/jbe@1309
|
2126 return rangeToHtml(this);
|
bsw/jbe@1309
|
2127 },
|
bsw/jbe@1309
|
2128
|
bsw/jbe@1309
|
2129 // touchingIsIntersecting determines whether this method considers a node that borders a range intersects
|
bsw/jbe@1309
|
2130 // with it (as in WebKit) or not (as in Gecko pre-1.9, and the default)
|
bsw/jbe@1309
|
2131 intersectsNode: function(node, touchingIsIntersecting) {
|
bsw/jbe@1309
|
2132 assertRangeValid(this);
|
bsw/jbe@1309
|
2133 if (getRootContainer(node) != getRangeRoot(this)) {
|
bsw/jbe@1309
|
2134 return false;
|
bsw/jbe@1309
|
2135 }
|
bsw/jbe@1309
|
2136
|
bsw/jbe@1309
|
2137 var parent = node.parentNode, offset = getNodeIndex(node);
|
bsw/jbe@1309
|
2138 if (!parent) {
|
bsw/jbe@1309
|
2139 return true;
|
bsw/jbe@1309
|
2140 }
|
bsw/jbe@1309
|
2141
|
bsw/jbe@1309
|
2142 var startComparison = comparePoints(parent, offset, this.endContainer, this.endOffset),
|
bsw/jbe@1309
|
2143 endComparison = comparePoints(parent, offset + 1, this.startContainer, this.startOffset);
|
bsw/jbe@1309
|
2144
|
bsw/jbe@1309
|
2145 return touchingIsIntersecting ? startComparison <= 0 && endComparison >= 0 : startComparison < 0 && endComparison > 0;
|
bsw/jbe@1309
|
2146 },
|
bsw/jbe@1309
|
2147
|
bsw/jbe@1309
|
2148 isPointInRange: function(node, offset) {
|
bsw/jbe@1309
|
2149 assertRangeValid(this);
|
bsw/jbe@1309
|
2150 assertNode(node, "HIERARCHY_REQUEST_ERR");
|
bsw/jbe@1309
|
2151 assertSameDocumentOrFragment(node, this.startContainer);
|
bsw/jbe@1309
|
2152
|
bsw/jbe@1309
|
2153 return (comparePoints(node, offset, this.startContainer, this.startOffset) >= 0) &&
|
bsw/jbe@1309
|
2154 (comparePoints(node, offset, this.endContainer, this.endOffset) <= 0);
|
bsw/jbe@1309
|
2155 },
|
bsw/jbe@1309
|
2156
|
bsw/jbe@1309
|
2157 // The methods below are non-standard and invented by me.
|
bsw/jbe@1309
|
2158
|
bsw/jbe@1309
|
2159 // Sharing a boundary start-to-end or end-to-start does not count as intersection.
|
bsw/jbe@1309
|
2160 intersectsRange: function(range) {
|
bsw/jbe@1309
|
2161 return rangesIntersect(this, range, false);
|
bsw/jbe@1309
|
2162 },
|
bsw/jbe@1309
|
2163
|
bsw/jbe@1309
|
2164 // Sharing a boundary start-to-end or end-to-start does count as intersection.
|
bsw/jbe@1309
|
2165 intersectsOrTouchesRange: function(range) {
|
bsw/jbe@1309
|
2166 return rangesIntersect(this, range, true);
|
bsw/jbe@1309
|
2167 },
|
bsw/jbe@1309
|
2168
|
bsw/jbe@1309
|
2169 intersection: function(range) {
|
bsw/jbe@1309
|
2170 if (this.intersectsRange(range)) {
|
bsw/jbe@1309
|
2171 var startComparison = comparePoints(this.startContainer, this.startOffset, range.startContainer, range.startOffset),
|
bsw/jbe@1309
|
2172 endComparison = comparePoints(this.endContainer, this.endOffset, range.endContainer, range.endOffset);
|
bsw/jbe@1309
|
2173
|
bsw/jbe@1309
|
2174 var intersectionRange = this.cloneRange();
|
bsw/jbe@1309
|
2175 if (startComparison == -1) {
|
bsw/jbe@1309
|
2176 intersectionRange.setStart(range.startContainer, range.startOffset);
|
bsw/jbe@1309
|
2177 }
|
bsw/jbe@1309
|
2178 if (endComparison == 1) {
|
bsw/jbe@1309
|
2179 intersectionRange.setEnd(range.endContainer, range.endOffset);
|
bsw/jbe@1309
|
2180 }
|
bsw/jbe@1309
|
2181 return intersectionRange;
|
bsw/jbe@1309
|
2182 }
|
bsw/jbe@1309
|
2183 return null;
|
bsw/jbe@1309
|
2184 },
|
bsw/jbe@1309
|
2185
|
bsw/jbe@1309
|
2186 union: function(range) {
|
bsw/jbe@1309
|
2187 if (this.intersectsOrTouchesRange(range)) {
|
bsw/jbe@1309
|
2188 var unionRange = this.cloneRange();
|
bsw/jbe@1309
|
2189 if (comparePoints(range.startContainer, range.startOffset, this.startContainer, this.startOffset) == -1) {
|
bsw/jbe@1309
|
2190 unionRange.setStart(range.startContainer, range.startOffset);
|
bsw/jbe@1309
|
2191 }
|
bsw/jbe@1309
|
2192 if (comparePoints(range.endContainer, range.endOffset, this.endContainer, this.endOffset) == 1) {
|
bsw/jbe@1309
|
2193 unionRange.setEnd(range.endContainer, range.endOffset);
|
bsw/jbe@1309
|
2194 }
|
bsw/jbe@1309
|
2195 return unionRange;
|
bsw/jbe@1309
|
2196 } else {
|
bsw/jbe@1309
|
2197 throw new DOMException("Ranges do not intersect");
|
bsw/jbe@1309
|
2198 }
|
bsw/jbe@1309
|
2199 },
|
bsw/jbe@1309
|
2200
|
bsw/jbe@1309
|
2201 containsNode: function(node, allowPartial) {
|
bsw/jbe@1309
|
2202 if (allowPartial) {
|
bsw/jbe@1309
|
2203 return this.intersectsNode(node, false);
|
bsw/jbe@1309
|
2204 } else {
|
bsw/jbe@1309
|
2205 return this.compareNode(node) == n_i;
|
bsw/jbe@1309
|
2206 }
|
bsw/jbe@1309
|
2207 },
|
bsw/jbe@1309
|
2208
|
bsw/jbe@1309
|
2209 containsNodeContents: function(node) {
|
bsw/jbe@1309
|
2210 return this.comparePoint(node, 0) >= 0 && this.comparePoint(node, getNodeLength(node)) <= 0;
|
bsw/jbe@1309
|
2211 },
|
bsw/jbe@1309
|
2212
|
bsw/jbe@1309
|
2213 containsRange: function(range) {
|
bsw/jbe@1309
|
2214 var intersection = this.intersection(range);
|
bsw/jbe@1309
|
2215 return intersection !== null && range.equals(intersection);
|
bsw/jbe@1309
|
2216 },
|
bsw/jbe@1309
|
2217
|
bsw/jbe@1309
|
2218 containsNodeText: function(node) {
|
bsw/jbe@1309
|
2219 var nodeRange = this.cloneRange();
|
bsw/jbe@1309
|
2220 nodeRange.selectNode(node);
|
bsw/jbe@1309
|
2221 var textNodes = nodeRange.getNodes([3]);
|
bsw/jbe@1309
|
2222 if (textNodes.length > 0) {
|
bsw/jbe@1309
|
2223 nodeRange.setStart(textNodes[0], 0);
|
bsw/jbe@1309
|
2224 var lastTextNode = textNodes.pop();
|
bsw/jbe@1309
|
2225 nodeRange.setEnd(lastTextNode, lastTextNode.length);
|
bsw/jbe@1309
|
2226 return this.containsRange(nodeRange);
|
bsw/jbe@1309
|
2227 } else {
|
bsw/jbe@1309
|
2228 return this.containsNodeContents(node);
|
bsw/jbe@1309
|
2229 }
|
bsw/jbe@1309
|
2230 },
|
bsw/jbe@1309
|
2231
|
bsw/jbe@1309
|
2232 getNodes: function(nodeTypes, filter) {
|
bsw/jbe@1309
|
2233 assertRangeValid(this);
|
bsw/jbe@1309
|
2234 return getNodesInRange(this, nodeTypes, filter);
|
bsw/jbe@1309
|
2235 },
|
bsw/jbe@1309
|
2236
|
bsw/jbe@1309
|
2237 getDocument: function() {
|
bsw/jbe@1309
|
2238 return getRangeDocument(this);
|
bsw/jbe@1309
|
2239 },
|
bsw/jbe@1309
|
2240
|
bsw/jbe@1309
|
2241 collapseBefore: function(node) {
|
bsw/jbe@1309
|
2242 this.setEndBefore(node);
|
bsw/jbe@1309
|
2243 this.collapse(false);
|
bsw/jbe@1309
|
2244 },
|
bsw/jbe@1309
|
2245
|
bsw/jbe@1309
|
2246 collapseAfter: function(node) {
|
bsw/jbe@1309
|
2247 this.setStartAfter(node);
|
bsw/jbe@1309
|
2248 this.collapse(true);
|
bsw/jbe@1309
|
2249 },
|
bsw/jbe@1309
|
2250
|
bsw/jbe@1309
|
2251 getBookmark: function(containerNode) {
|
bsw/jbe@1309
|
2252 var doc = getRangeDocument(this);
|
bsw/jbe@1309
|
2253 var preSelectionRange = api.createRange(doc);
|
bsw/jbe@1309
|
2254 containerNode = containerNode || dom.getBody(doc);
|
bsw/jbe@1309
|
2255 preSelectionRange.selectNodeContents(containerNode);
|
bsw/jbe@1309
|
2256 var range = this.intersection(preSelectionRange);
|
bsw/jbe@1309
|
2257 var start = 0, end = 0;
|
bsw/jbe@1309
|
2258 if (range) {
|
bsw/jbe@1309
|
2259 preSelectionRange.setEnd(range.startContainer, range.startOffset);
|
bsw/jbe@1309
|
2260 start = preSelectionRange.toString().length;
|
bsw/jbe@1309
|
2261 end = start + range.toString().length;
|
bsw/jbe@1309
|
2262 }
|
bsw/jbe@1309
|
2263
|
bsw/jbe@1309
|
2264 return {
|
bsw/jbe@1309
|
2265 start: start,
|
bsw/jbe@1309
|
2266 end: end,
|
bsw/jbe@1309
|
2267 containerNode: containerNode
|
bsw/jbe@1309
|
2268 };
|
bsw/jbe@1309
|
2269 },
|
bsw/jbe@1309
|
2270
|
bsw/jbe@1309
|
2271 moveToBookmark: function(bookmark) {
|
bsw/jbe@1309
|
2272 var containerNode = bookmark.containerNode;
|
bsw/jbe@1309
|
2273 var charIndex = 0;
|
bsw/jbe@1309
|
2274 this.setStart(containerNode, 0);
|
bsw/jbe@1309
|
2275 this.collapse(true);
|
bsw/jbe@1309
|
2276 var nodeStack = [containerNode], node, foundStart = false, stop = false;
|
bsw/jbe@1309
|
2277 var nextCharIndex, i, childNodes;
|
bsw/jbe@1309
|
2278
|
bsw/jbe@1309
|
2279 while (!stop && (node = nodeStack.pop())) {
|
bsw/jbe@1309
|
2280 if (node.nodeType == 3) {
|
bsw/jbe@1309
|
2281 nextCharIndex = charIndex + node.length;
|
bsw/jbe@1309
|
2282 if (!foundStart && bookmark.start >= charIndex && bookmark.start <= nextCharIndex) {
|
bsw/jbe@1309
|
2283 this.setStart(node, bookmark.start - charIndex);
|
bsw/jbe@1309
|
2284 foundStart = true;
|
bsw/jbe@1309
|
2285 }
|
bsw/jbe@1309
|
2286 if (foundStart && bookmark.end >= charIndex && bookmark.end <= nextCharIndex) {
|
bsw/jbe@1309
|
2287 this.setEnd(node, bookmark.end - charIndex);
|
bsw/jbe@1309
|
2288 stop = true;
|
bsw/jbe@1309
|
2289 }
|
bsw/jbe@1309
|
2290 charIndex = nextCharIndex;
|
bsw/jbe@1309
|
2291 } else {
|
bsw/jbe@1309
|
2292 childNodes = node.childNodes;
|
bsw/jbe@1309
|
2293 i = childNodes.length;
|
bsw/jbe@1309
|
2294 while (i--) {
|
bsw/jbe@1309
|
2295 nodeStack.push(childNodes[i]);
|
bsw/jbe@1309
|
2296 }
|
bsw/jbe@1309
|
2297 }
|
bsw/jbe@1309
|
2298 }
|
bsw/jbe@1309
|
2299 },
|
bsw/jbe@1309
|
2300
|
bsw/jbe@1309
|
2301 getName: function() {
|
bsw/jbe@1309
|
2302 return "DomRange";
|
bsw/jbe@1309
|
2303 },
|
bsw/jbe@1309
|
2304
|
bsw/jbe@1309
|
2305 equals: function(range) {
|
bsw/jbe@1309
|
2306 return Range.rangesEqual(this, range);
|
bsw/jbe@1309
|
2307 },
|
bsw/jbe@1309
|
2308
|
bsw/jbe@1309
|
2309 isValid: function() {
|
bsw/jbe@1309
|
2310 return isRangeValid(this);
|
bsw/jbe@1309
|
2311 },
|
bsw/jbe@1309
|
2312
|
bsw/jbe@1309
|
2313 inspect: function() {
|
bsw/jbe@1309
|
2314 return inspect(this);
|
bsw/jbe@1309
|
2315 },
|
bsw/jbe@1309
|
2316
|
bsw/jbe@1309
|
2317 detach: function() {
|
bsw/jbe@1309
|
2318 // In DOM4, detach() is now a no-op.
|
bsw/jbe@1309
|
2319 }
|
bsw/jbe@1309
|
2320 });
|
bsw/jbe@1309
|
2321
|
bsw/jbe@1309
|
2322 function copyComparisonConstantsToObject(obj) {
|
bsw/jbe@1309
|
2323 obj.START_TO_START = s2s;
|
bsw/jbe@1309
|
2324 obj.START_TO_END = s2e;
|
bsw/jbe@1309
|
2325 obj.END_TO_END = e2e;
|
bsw/jbe@1309
|
2326 obj.END_TO_START = e2s;
|
bsw/jbe@1309
|
2327
|
bsw/jbe@1309
|
2328 obj.NODE_BEFORE = n_b;
|
bsw/jbe@1309
|
2329 obj.NODE_AFTER = n_a;
|
bsw/jbe@1309
|
2330 obj.NODE_BEFORE_AND_AFTER = n_b_a;
|
bsw/jbe@1309
|
2331 obj.NODE_INSIDE = n_i;
|
bsw/jbe@1309
|
2332 }
|
bsw/jbe@1309
|
2333
|
bsw/jbe@1309
|
2334 function copyComparisonConstants(constructor) {
|
bsw/jbe@1309
|
2335 copyComparisonConstantsToObject(constructor);
|
bsw/jbe@1309
|
2336 copyComparisonConstantsToObject(constructor.prototype);
|
bsw/jbe@1309
|
2337 }
|
bsw/jbe@1309
|
2338
|
bsw/jbe@1309
|
2339 function createRangeContentRemover(remover, boundaryUpdater) {
|
bsw/jbe@1309
|
2340 return function() {
|
bsw/jbe@1309
|
2341 assertRangeValid(this);
|
bsw/jbe@1309
|
2342
|
bsw/jbe@1309
|
2343 var sc = this.startContainer, so = this.startOffset, root = this.commonAncestorContainer;
|
bsw/jbe@1309
|
2344
|
bsw/jbe@1309
|
2345 var iterator = new RangeIterator(this, true);
|
bsw/jbe@1309
|
2346
|
bsw/jbe@1309
|
2347 // Work out where to position the range after content removal
|
bsw/jbe@1309
|
2348 var node, boundary;
|
bsw/jbe@1309
|
2349 if (sc !== root) {
|
bsw/jbe@1309
|
2350 node = getClosestAncestorIn(sc, root, true);
|
bsw/jbe@1309
|
2351 boundary = getBoundaryAfterNode(node);
|
bsw/jbe@1309
|
2352 sc = boundary.node;
|
bsw/jbe@1309
|
2353 so = boundary.offset;
|
bsw/jbe@1309
|
2354 }
|
bsw/jbe@1309
|
2355
|
bsw/jbe@1309
|
2356 // Check none of the range is read-only
|
bsw/jbe@1309
|
2357 iterateSubtree(iterator, assertNodeNotReadOnly);
|
bsw/jbe@1309
|
2358
|
bsw/jbe@1309
|
2359 iterator.reset();
|
bsw/jbe@1309
|
2360
|
bsw/jbe@1309
|
2361 // Remove the content
|
bsw/jbe@1309
|
2362 var returnValue = remover(iterator);
|
bsw/jbe@1309
|
2363 iterator.detach();
|
bsw/jbe@1309
|
2364
|
bsw/jbe@1309
|
2365 // Move to the new position
|
bsw/jbe@1309
|
2366 boundaryUpdater(this, sc, so, sc, so);
|
bsw/jbe@1309
|
2367
|
bsw/jbe@1309
|
2368 return returnValue;
|
bsw/jbe@1309
|
2369 };
|
bsw/jbe@1309
|
2370 }
|
bsw/jbe@1309
|
2371
|
bsw/jbe@1309
|
2372 function createPrototypeRange(constructor, boundaryUpdater) {
|
bsw/jbe@1309
|
2373 function createBeforeAfterNodeSetter(isBefore, isStart) {
|
bsw/jbe@1309
|
2374 return function(node) {
|
bsw/jbe@1309
|
2375 assertValidNodeType(node, beforeAfterNodeTypes);
|
bsw/jbe@1309
|
2376 assertValidNodeType(getRootContainer(node), rootContainerNodeTypes);
|
bsw/jbe@1309
|
2377
|
bsw/jbe@1309
|
2378 var boundary = (isBefore ? getBoundaryBeforeNode : getBoundaryAfterNode)(node);
|
bsw/jbe@1309
|
2379 (isStart ? setRangeStart : setRangeEnd)(this, boundary.node, boundary.offset);
|
bsw/jbe@1309
|
2380 };
|
bsw/jbe@1309
|
2381 }
|
bsw/jbe@1309
|
2382
|
bsw/jbe@1309
|
2383 function setRangeStart(range, node, offset) {
|
bsw/jbe@1309
|
2384 var ec = range.endContainer, eo = range.endOffset;
|
bsw/jbe@1309
|
2385 if (node !== range.startContainer || offset !== range.startOffset) {
|
bsw/jbe@1309
|
2386 // Check the root containers of the range and the new boundary, and also check whether the new boundary
|
bsw/jbe@1309
|
2387 // is after the current end. In either case, collapse the range to the new position
|
bsw/jbe@1309
|
2388 if (getRootContainer(node) != getRootContainer(ec) || comparePoints(node, offset, ec, eo) == 1) {
|
bsw/jbe@1309
|
2389 ec = node;
|
bsw/jbe@1309
|
2390 eo = offset;
|
bsw/jbe@1309
|
2391 }
|
bsw/jbe@1309
|
2392 boundaryUpdater(range, node, offset, ec, eo);
|
bsw/jbe@1309
|
2393 }
|
bsw/jbe@1309
|
2394 }
|
bsw/jbe@1309
|
2395
|
bsw/jbe@1309
|
2396 function setRangeEnd(range, node, offset) {
|
bsw/jbe@1309
|
2397 var sc = range.startContainer, so = range.startOffset;
|
bsw/jbe@1309
|
2398 if (node !== range.endContainer || offset !== range.endOffset) {
|
bsw/jbe@1309
|
2399 // Check the root containers of the range and the new boundary, and also check whether the new boundary
|
bsw/jbe@1309
|
2400 // is after the current end. In either case, collapse the range to the new position
|
bsw/jbe@1309
|
2401 if (getRootContainer(node) != getRootContainer(sc) || comparePoints(node, offset, sc, so) == -1) {
|
bsw/jbe@1309
|
2402 sc = node;
|
bsw/jbe@1309
|
2403 so = offset;
|
bsw/jbe@1309
|
2404 }
|
bsw/jbe@1309
|
2405 boundaryUpdater(range, sc, so, node, offset);
|
bsw/jbe@1309
|
2406 }
|
bsw/jbe@1309
|
2407 }
|
bsw/jbe@1309
|
2408
|
bsw/jbe@1309
|
2409 // Set up inheritance
|
bsw/jbe@1309
|
2410 var F = function() {};
|
bsw/jbe@1309
|
2411 F.prototype = api.rangePrototype;
|
bsw/jbe@1309
|
2412 constructor.prototype = new F();
|
bsw/jbe@1309
|
2413
|
bsw/jbe@1309
|
2414 util.extend(constructor.prototype, {
|
bsw/jbe@1309
|
2415 setStart: function(node, offset) {
|
bsw/jbe@1309
|
2416 assertNoDocTypeNotationEntityAncestor(node, true);
|
bsw/jbe@1309
|
2417 assertValidOffset(node, offset);
|
bsw/jbe@1309
|
2418
|
bsw/jbe@1309
|
2419 setRangeStart(this, node, offset);
|
bsw/jbe@1309
|
2420 },
|
bsw/jbe@1309
|
2421
|
bsw/jbe@1309
|
2422 setEnd: function(node, offset) {
|
bsw/jbe@1309
|
2423 assertNoDocTypeNotationEntityAncestor(node, true);
|
bsw/jbe@1309
|
2424 assertValidOffset(node, offset);
|
bsw/jbe@1309
|
2425
|
bsw/jbe@1309
|
2426 setRangeEnd(this, node, offset);
|
bsw/jbe@1309
|
2427 },
|
bsw/jbe@1309
|
2428
|
bsw/jbe@1309
|
2429 /**
|
bsw/jbe@1309
|
2430 * Convenience method to set a range's start and end boundaries. Overloaded as follows:
|
bsw/jbe@1309
|
2431 * - Two parameters (node, offset) creates a collapsed range at that position
|
bsw/jbe@1309
|
2432 * - Three parameters (node, startOffset, endOffset) creates a range contained with node starting at
|
bsw/jbe@1309
|
2433 * startOffset and ending at endOffset
|
bsw/jbe@1309
|
2434 * - Four parameters (startNode, startOffset, endNode, endOffset) creates a range starting at startOffset in
|
bsw/jbe@1309
|
2435 * startNode and ending at endOffset in endNode
|
bsw/jbe@1309
|
2436 */
|
bsw/jbe@1309
|
2437 setStartAndEnd: function() {
|
bsw/jbe@1309
|
2438 var args = arguments;
|
bsw/jbe@1309
|
2439 var sc = args[0], so = args[1], ec = sc, eo = so;
|
bsw/jbe@1309
|
2440
|
bsw/jbe@1309
|
2441 switch (args.length) {
|
bsw/jbe@1309
|
2442 case 3:
|
bsw/jbe@1309
|
2443 eo = args[2];
|
bsw/jbe@1309
|
2444 break;
|
bsw/jbe@1309
|
2445 case 4:
|
bsw/jbe@1309
|
2446 ec = args[2];
|
bsw/jbe@1309
|
2447 eo = args[3];
|
bsw/jbe@1309
|
2448 break;
|
bsw/jbe@1309
|
2449 }
|
bsw/jbe@1309
|
2450
|
bsw/jbe@1309
|
2451 boundaryUpdater(this, sc, so, ec, eo);
|
bsw/jbe@1309
|
2452 },
|
bsw/jbe@1309
|
2453
|
bsw/jbe@1309
|
2454 setBoundary: function(node, offset, isStart) {
|
bsw/jbe@1309
|
2455 this["set" + (isStart ? "Start" : "End")](node, offset);
|
bsw/jbe@1309
|
2456 },
|
bsw/jbe@1309
|
2457
|
bsw/jbe@1309
|
2458 setStartBefore: createBeforeAfterNodeSetter(true, true),
|
bsw/jbe@1309
|
2459 setStartAfter: createBeforeAfterNodeSetter(false, true),
|
bsw/jbe@1309
|
2460 setEndBefore: createBeforeAfterNodeSetter(true, false),
|
bsw/jbe@1309
|
2461 setEndAfter: createBeforeAfterNodeSetter(false, false),
|
bsw/jbe@1309
|
2462
|
bsw/jbe@1309
|
2463 collapse: function(isStart) {
|
bsw/jbe@1309
|
2464 assertRangeValid(this);
|
bsw/jbe@1309
|
2465 if (isStart) {
|
bsw/jbe@1309
|
2466 boundaryUpdater(this, this.startContainer, this.startOffset, this.startContainer, this.startOffset);
|
bsw/jbe@1309
|
2467 } else {
|
bsw/jbe@1309
|
2468 boundaryUpdater(this, this.endContainer, this.endOffset, this.endContainer, this.endOffset);
|
bsw/jbe@1309
|
2469 }
|
bsw/jbe@1309
|
2470 },
|
bsw/jbe@1309
|
2471
|
bsw/jbe@1309
|
2472 selectNodeContents: function(node) {
|
bsw/jbe@1309
|
2473 assertNoDocTypeNotationEntityAncestor(node, true);
|
bsw/jbe@1309
|
2474
|
bsw/jbe@1309
|
2475 boundaryUpdater(this, node, 0, node, getNodeLength(node));
|
bsw/jbe@1309
|
2476 },
|
bsw/jbe@1309
|
2477
|
bsw/jbe@1309
|
2478 selectNode: function(node) {
|
bsw/jbe@1309
|
2479 assertNoDocTypeNotationEntityAncestor(node, false);
|
bsw/jbe@1309
|
2480 assertValidNodeType(node, beforeAfterNodeTypes);
|
bsw/jbe@1309
|
2481
|
bsw/jbe@1309
|
2482 var start = getBoundaryBeforeNode(node), end = getBoundaryAfterNode(node);
|
bsw/jbe@1309
|
2483 boundaryUpdater(this, start.node, start.offset, end.node, end.offset);
|
bsw/jbe@1309
|
2484 },
|
bsw/jbe@1309
|
2485
|
bsw/jbe@1309
|
2486 extractContents: createRangeContentRemover(extractSubtree, boundaryUpdater),
|
bsw/jbe@1309
|
2487
|
bsw/jbe@1309
|
2488 deleteContents: createRangeContentRemover(deleteSubtree, boundaryUpdater),
|
bsw/jbe@1309
|
2489
|
bsw/jbe@1309
|
2490 canSurroundContents: function() {
|
bsw/jbe@1309
|
2491 assertRangeValid(this);
|
bsw/jbe@1309
|
2492 assertNodeNotReadOnly(this.startContainer);
|
bsw/jbe@1309
|
2493 assertNodeNotReadOnly(this.endContainer);
|
bsw/jbe@1309
|
2494
|
bsw/jbe@1309
|
2495 // Check if the contents can be surrounded. Specifically, this means whether the range partially selects
|
bsw/jbe@1309
|
2496 // no non-text nodes.
|
bsw/jbe@1309
|
2497 var iterator = new RangeIterator(this, true);
|
bsw/jbe@1309
|
2498 var boundariesInvalid = (iterator._first && isNonTextPartiallySelected(iterator._first, this) ||
|
bsw/jbe@1309
|
2499 (iterator._last && isNonTextPartiallySelected(iterator._last, this)));
|
bsw/jbe@1309
|
2500 iterator.detach();
|
bsw/jbe@1309
|
2501 return !boundariesInvalid;
|
bsw/jbe@1309
|
2502 },
|
bsw/jbe@1309
|
2503
|
bsw/jbe@1309
|
2504 splitBoundaries: function() {
|
bsw/jbe@1309
|
2505 splitRangeBoundaries(this);
|
bsw/jbe@1309
|
2506 },
|
bsw/jbe@1309
|
2507
|
bsw/jbe@1309
|
2508 splitBoundariesPreservingPositions: function(positionsToPreserve) {
|
bsw/jbe@1309
|
2509 splitRangeBoundaries(this, positionsToPreserve);
|
bsw/jbe@1309
|
2510 },
|
bsw/jbe@1309
|
2511
|
bsw/jbe@1309
|
2512 normalizeBoundaries: function() {
|
bsw/jbe@1309
|
2513 assertRangeValid(this);
|
bsw/jbe@1309
|
2514
|
bsw/jbe@1309
|
2515 var sc = this.startContainer, so = this.startOffset, ec = this.endContainer, eo = this.endOffset;
|
bsw/jbe@1309
|
2516
|
bsw/jbe@1309
|
2517 var mergeForward = function(node) {
|
bsw/jbe@1309
|
2518 var sibling = node.nextSibling;
|
bsw/jbe@1309
|
2519 if (sibling && sibling.nodeType == node.nodeType) {
|
bsw/jbe@1309
|
2520 ec = node;
|
bsw/jbe@1309
|
2521 eo = node.length;
|
bsw/jbe@1309
|
2522 node.appendData(sibling.data);
|
bsw/jbe@1309
|
2523 removeNode(sibling);
|
bsw/jbe@1309
|
2524 }
|
bsw/jbe@1309
|
2525 };
|
bsw/jbe@1309
|
2526
|
bsw/jbe@1309
|
2527 var mergeBackward = function(node) {
|
bsw/jbe@1309
|
2528 var sibling = node.previousSibling;
|
bsw/jbe@1309
|
2529 if (sibling && sibling.nodeType == node.nodeType) {
|
bsw/jbe@1309
|
2530 sc = node;
|
bsw/jbe@1309
|
2531 var nodeLength = node.length;
|
bsw/jbe@1309
|
2532 so = sibling.length;
|
bsw/jbe@1309
|
2533 node.insertData(0, sibling.data);
|
bsw/jbe@1309
|
2534 removeNode(sibling);
|
bsw/jbe@1309
|
2535 if (sc == ec) {
|
bsw/jbe@1309
|
2536 eo += so;
|
bsw/jbe@1309
|
2537 ec = sc;
|
bsw/jbe@1309
|
2538 } else if (ec == node.parentNode) {
|
bsw/jbe@1309
|
2539 var nodeIndex = getNodeIndex(node);
|
bsw/jbe@1309
|
2540 if (eo == nodeIndex) {
|
bsw/jbe@1309
|
2541 ec = node;
|
bsw/jbe@1309
|
2542 eo = nodeLength;
|
bsw/jbe@1309
|
2543 } else if (eo > nodeIndex) {
|
bsw/jbe@1309
|
2544 eo--;
|
bsw/jbe@1309
|
2545 }
|
bsw/jbe@1309
|
2546 }
|
bsw/jbe@1309
|
2547 }
|
bsw/jbe@1309
|
2548 };
|
bsw/jbe@1309
|
2549
|
bsw/jbe@1309
|
2550 var normalizeStart = true;
|
bsw/jbe@1309
|
2551 var sibling;
|
bsw/jbe@1309
|
2552
|
bsw/jbe@1309
|
2553 if (isCharacterDataNode(ec)) {
|
bsw/jbe@1309
|
2554 if (eo == ec.length) {
|
bsw/jbe@1309
|
2555 mergeForward(ec);
|
bsw/jbe@1309
|
2556 } else if (eo == 0) {
|
bsw/jbe@1309
|
2557 sibling = ec.previousSibling;
|
bsw/jbe@1309
|
2558 if (sibling && sibling.nodeType == ec.nodeType) {
|
bsw/jbe@1309
|
2559 eo = sibling.length;
|
bsw/jbe@1309
|
2560 if (sc == ec) {
|
bsw/jbe@1309
|
2561 normalizeStart = false;
|
bsw/jbe@1309
|
2562 }
|
bsw/jbe@1309
|
2563 sibling.appendData(ec.data);
|
bsw/jbe@1309
|
2564 removeNode(ec);
|
bsw/jbe@1309
|
2565 ec = sibling;
|
bsw/jbe@1309
|
2566 }
|
bsw/jbe@1309
|
2567 }
|
bsw/jbe@1309
|
2568 } else {
|
bsw/jbe@1309
|
2569 if (eo > 0) {
|
bsw/jbe@1309
|
2570 var endNode = ec.childNodes[eo - 1];
|
bsw/jbe@1309
|
2571 if (endNode && isCharacterDataNode(endNode)) {
|
bsw/jbe@1309
|
2572 mergeForward(endNode);
|
bsw/jbe@1309
|
2573 }
|
bsw/jbe@1309
|
2574 }
|
bsw/jbe@1309
|
2575 normalizeStart = !this.collapsed;
|
bsw/jbe@1309
|
2576 }
|
bsw/jbe@1309
|
2577
|
bsw/jbe@1309
|
2578 if (normalizeStart) {
|
bsw/jbe@1309
|
2579 if (isCharacterDataNode(sc)) {
|
bsw/jbe@1309
|
2580 if (so == 0) {
|
bsw/jbe@1309
|
2581 mergeBackward(sc);
|
bsw/jbe@1309
|
2582 } else if (so == sc.length) {
|
bsw/jbe@1309
|
2583 sibling = sc.nextSibling;
|
bsw/jbe@1309
|
2584 if (sibling && sibling.nodeType == sc.nodeType) {
|
bsw/jbe@1309
|
2585 if (ec == sibling) {
|
bsw/jbe@1309
|
2586 ec = sc;
|
bsw/jbe@1309
|
2587 eo += sc.length;
|
bsw/jbe@1309
|
2588 }
|
bsw/jbe@1309
|
2589 sc.appendData(sibling.data);
|
bsw/jbe@1309
|
2590 removeNode(sibling);
|
bsw/jbe@1309
|
2591 }
|
bsw/jbe@1309
|
2592 }
|
bsw/jbe@1309
|
2593 } else {
|
bsw/jbe@1309
|
2594 if (so < sc.childNodes.length) {
|
bsw/jbe@1309
|
2595 var startNode = sc.childNodes[so];
|
bsw/jbe@1309
|
2596 if (startNode && isCharacterDataNode(startNode)) {
|
bsw/jbe@1309
|
2597 mergeBackward(startNode);
|
bsw/jbe@1309
|
2598 }
|
bsw/jbe@1309
|
2599 }
|
bsw/jbe@1309
|
2600 }
|
bsw/jbe@1309
|
2601 } else {
|
bsw/jbe@1309
|
2602 sc = ec;
|
bsw/jbe@1309
|
2603 so = eo;
|
bsw/jbe@1309
|
2604 }
|
bsw/jbe@1309
|
2605
|
bsw/jbe@1309
|
2606 boundaryUpdater(this, sc, so, ec, eo);
|
bsw/jbe@1309
|
2607 },
|
bsw/jbe@1309
|
2608
|
bsw/jbe@1309
|
2609 collapseToPoint: function(node, offset) {
|
bsw/jbe@1309
|
2610 assertNoDocTypeNotationEntityAncestor(node, true);
|
bsw/jbe@1309
|
2611 assertValidOffset(node, offset);
|
bsw/jbe@1309
|
2612 this.setStartAndEnd(node, offset);
|
bsw/jbe@1309
|
2613 }
|
bsw/jbe@1309
|
2614 });
|
bsw/jbe@1309
|
2615
|
bsw/jbe@1309
|
2616 copyComparisonConstants(constructor);
|
bsw/jbe@1309
|
2617 }
|
bsw/jbe@1309
|
2618
|
bsw/jbe@1309
|
2619 /*----------------------------------------------------------------------------------------------------------------*/
|
bsw/jbe@1309
|
2620
|
bsw/jbe@1309
|
2621 // Updates commonAncestorContainer and collapsed after boundary change
|
bsw/jbe@1309
|
2622 function updateCollapsedAndCommonAncestor(range) {
|
bsw/jbe@1309
|
2623 range.collapsed = (range.startContainer === range.endContainer && range.startOffset === range.endOffset);
|
bsw/jbe@1309
|
2624 range.commonAncestorContainer = range.collapsed ?
|
bsw/jbe@1309
|
2625 range.startContainer : dom.getCommonAncestor(range.startContainer, range.endContainer);
|
bsw/jbe@1309
|
2626 }
|
bsw/jbe@1309
|
2627
|
bsw/jbe@1309
|
2628 function updateBoundaries(range, startContainer, startOffset, endContainer, endOffset) {
|
bsw/jbe@1309
|
2629 range.startContainer = startContainer;
|
bsw/jbe@1309
|
2630 range.startOffset = startOffset;
|
bsw/jbe@1309
|
2631 range.endContainer = endContainer;
|
bsw/jbe@1309
|
2632 range.endOffset = endOffset;
|
bsw/jbe@1309
|
2633 range.document = dom.getDocument(startContainer);
|
bsw/jbe@1309
|
2634
|
bsw/jbe@1309
|
2635 updateCollapsedAndCommonAncestor(range);
|
bsw/jbe@1309
|
2636 }
|
bsw/jbe@1309
|
2637
|
bsw/jbe@1309
|
2638 function Range(doc) {
|
bsw/jbe@1309
|
2639 this.startContainer = doc;
|
bsw/jbe@1309
|
2640 this.startOffset = 0;
|
bsw/jbe@1309
|
2641 this.endContainer = doc;
|
bsw/jbe@1309
|
2642 this.endOffset = 0;
|
bsw/jbe@1309
|
2643 this.document = doc;
|
bsw/jbe@1309
|
2644 updateCollapsedAndCommonAncestor(this);
|
bsw/jbe@1309
|
2645 }
|
bsw/jbe@1309
|
2646
|
bsw/jbe@1309
|
2647 createPrototypeRange(Range, updateBoundaries);
|
bsw/jbe@1309
|
2648
|
bsw/jbe@1309
|
2649 util.extend(Range, {
|
bsw/jbe@1309
|
2650 rangeProperties: rangeProperties,
|
bsw/jbe@1309
|
2651 RangeIterator: RangeIterator,
|
bsw/jbe@1309
|
2652 copyComparisonConstants: copyComparisonConstants,
|
bsw/jbe@1309
|
2653 createPrototypeRange: createPrototypeRange,
|
bsw/jbe@1309
|
2654 inspect: inspect,
|
bsw/jbe@1309
|
2655 toHtml: rangeToHtml,
|
bsw/jbe@1309
|
2656 getRangeDocument: getRangeDocument,
|
bsw/jbe@1309
|
2657 rangesEqual: function(r1, r2) {
|
bsw/jbe@1309
|
2658 return r1.startContainer === r2.startContainer &&
|
bsw/jbe@1309
|
2659 r1.startOffset === r2.startOffset &&
|
bsw/jbe@1309
|
2660 r1.endContainer === r2.endContainer &&
|
bsw/jbe@1309
|
2661 r1.endOffset === r2.endOffset;
|
bsw/jbe@1309
|
2662 }
|
bsw/jbe@1309
|
2663 });
|
bsw/jbe@1309
|
2664
|
bsw/jbe@1309
|
2665 api.DomRange = Range;
|
bsw/jbe@1309
|
2666 });
|
bsw/jbe@1309
|
2667
|
bsw/jbe@1309
|
2668 /*----------------------------------------------------------------------------------------------------------------*/
|
bsw/jbe@1309
|
2669
|
bsw/jbe@1309
|
2670 // Wrappers for the browser's native DOM Range and/or TextRange implementation
|
bsw/jbe@1309
|
2671 api.createCoreModule("WrappedRange", ["DomRange"], function(api, module) {
|
bsw/jbe@1309
|
2672 var WrappedRange, WrappedTextRange;
|
bsw/jbe@1309
|
2673 var dom = api.dom;
|
bsw/jbe@1309
|
2674 var util = api.util;
|
bsw/jbe@1309
|
2675 var DomPosition = dom.DomPosition;
|
bsw/jbe@1309
|
2676 var DomRange = api.DomRange;
|
bsw/jbe@1309
|
2677 var getBody = dom.getBody;
|
bsw/jbe@1309
|
2678 var getContentDocument = dom.getContentDocument;
|
bsw/jbe@1309
|
2679 var isCharacterDataNode = dom.isCharacterDataNode;
|
bsw/jbe@1309
|
2680
|
bsw/jbe@1309
|
2681
|
bsw/jbe@1309
|
2682 /*----------------------------------------------------------------------------------------------------------------*/
|
bsw/jbe@1309
|
2683
|
bsw/jbe@1309
|
2684 if (api.features.implementsDomRange) {
|
bsw/jbe@1309
|
2685 // This is a wrapper around the browser's native DOM Range. It has two aims:
|
bsw/jbe@1309
|
2686 // - Provide workarounds for specific browser bugs
|
bsw/jbe@1309
|
2687 // - provide convenient extensions, which are inherited from Rangy's DomRange
|
bsw/jbe@1309
|
2688
|
bsw/jbe@1309
|
2689 (function() {
|
bsw/jbe@1309
|
2690 var rangeProto;
|
bsw/jbe@1309
|
2691 var rangeProperties = DomRange.rangeProperties;
|
bsw/jbe@1309
|
2692
|
bsw/jbe@1309
|
2693 function updateRangeProperties(range) {
|
bsw/jbe@1309
|
2694 var i = rangeProperties.length, prop;
|
bsw/jbe@1309
|
2695 while (i--) {
|
bsw/jbe@1309
|
2696 prop = rangeProperties[i];
|
bsw/jbe@1309
|
2697 range[prop] = range.nativeRange[prop];
|
bsw/jbe@1309
|
2698 }
|
bsw/jbe@1309
|
2699 // Fix for broken collapsed property in IE 9.
|
bsw/jbe@1309
|
2700 range.collapsed = (range.startContainer === range.endContainer && range.startOffset === range.endOffset);
|
bsw/jbe@1309
|
2701 }
|
bsw/jbe@1309
|
2702
|
bsw/jbe@1309
|
2703 function updateNativeRange(range, startContainer, startOffset, endContainer, endOffset) {
|
bsw/jbe@1309
|
2704 var startMoved = (range.startContainer !== startContainer || range.startOffset != startOffset);
|
bsw/jbe@1309
|
2705 var endMoved = (range.endContainer !== endContainer || range.endOffset != endOffset);
|
bsw/jbe@1309
|
2706 var nativeRangeDifferent = !range.equals(range.nativeRange);
|
bsw/jbe@1309
|
2707
|
bsw/jbe@1309
|
2708 // Always set both boundaries for the benefit of IE9 (see issue 35)
|
bsw/jbe@1309
|
2709 if (startMoved || endMoved || nativeRangeDifferent) {
|
bsw/jbe@1309
|
2710 range.setEnd(endContainer, endOffset);
|
bsw/jbe@1309
|
2711 range.setStart(startContainer, startOffset);
|
bsw/jbe@1309
|
2712 }
|
bsw/jbe@1309
|
2713 }
|
bsw/jbe@1309
|
2714
|
bsw/jbe@1309
|
2715 var createBeforeAfterNodeSetter;
|
bsw/jbe@1309
|
2716
|
bsw/jbe@1309
|
2717 WrappedRange = function(range) {
|
bsw/jbe@1309
|
2718 if (!range) {
|
bsw/jbe@1309
|
2719 throw module.createError("WrappedRange: Range must be specified");
|
bsw/jbe@1309
|
2720 }
|
bsw/jbe@1309
|
2721 this.nativeRange = range;
|
bsw/jbe@1309
|
2722 updateRangeProperties(this);
|
bsw/jbe@1309
|
2723 };
|
bsw/jbe@1309
|
2724
|
bsw/jbe@1309
|
2725 DomRange.createPrototypeRange(WrappedRange, updateNativeRange);
|
bsw/jbe@1309
|
2726
|
bsw/jbe@1309
|
2727 rangeProto = WrappedRange.prototype;
|
bsw/jbe@1309
|
2728
|
bsw/jbe@1309
|
2729 rangeProto.selectNode = function(node) {
|
bsw/jbe@1309
|
2730 this.nativeRange.selectNode(node);
|
bsw/jbe@1309
|
2731 updateRangeProperties(this);
|
bsw/jbe@1309
|
2732 };
|
bsw/jbe@1309
|
2733
|
bsw/jbe@1309
|
2734 rangeProto.cloneContents = function() {
|
bsw/jbe@1309
|
2735 return this.nativeRange.cloneContents();
|
bsw/jbe@1309
|
2736 };
|
bsw/jbe@1309
|
2737
|
bsw/jbe@1309
|
2738 // Due to a long-standing Firefox bug that I have not been able to find a reliable way to detect,
|
bsw/jbe@1309
|
2739 // insertNode() is never delegated to the native range.
|
bsw/jbe@1309
|
2740
|
bsw/jbe@1309
|
2741 rangeProto.surroundContents = function(node) {
|
bsw/jbe@1309
|
2742 this.nativeRange.surroundContents(node);
|
bsw/jbe@1309
|
2743 updateRangeProperties(this);
|
bsw/jbe@1309
|
2744 };
|
bsw/jbe@1309
|
2745
|
bsw/jbe@1309
|
2746 rangeProto.collapse = function(isStart) {
|
bsw/jbe@1309
|
2747 this.nativeRange.collapse(isStart);
|
bsw/jbe@1309
|
2748 updateRangeProperties(this);
|
bsw/jbe@1309
|
2749 };
|
bsw/jbe@1309
|
2750
|
bsw/jbe@1309
|
2751 rangeProto.cloneRange = function() {
|
bsw/jbe@1309
|
2752 return new WrappedRange(this.nativeRange.cloneRange());
|
bsw/jbe@1309
|
2753 };
|
bsw/jbe@1309
|
2754
|
bsw/jbe@1309
|
2755 rangeProto.refresh = function() {
|
bsw/jbe@1309
|
2756 updateRangeProperties(this);
|
bsw/jbe@1309
|
2757 };
|
bsw/jbe@1309
|
2758
|
bsw/jbe@1309
|
2759 rangeProto.toString = function() {
|
bsw/jbe@1309
|
2760 return this.nativeRange.toString();
|
bsw/jbe@1309
|
2761 };
|
bsw/jbe@1309
|
2762
|
bsw/jbe@1309
|
2763 // Create test range and node for feature detection
|
bsw/jbe@1309
|
2764
|
bsw/jbe@1309
|
2765 var testTextNode = document.createTextNode("test");
|
bsw/jbe@1309
|
2766 getBody(document).appendChild(testTextNode);
|
bsw/jbe@1309
|
2767 var range = document.createRange();
|
bsw/jbe@1309
|
2768
|
bsw/jbe@1309
|
2769 /*--------------------------------------------------------------------------------------------------------*/
|
bsw/jbe@1309
|
2770
|
bsw/jbe@1309
|
2771 // Test for Firefox 2 bug that prevents moving the start of a Range to a point after its current end and
|
bsw/jbe@1309
|
2772 // correct for it
|
bsw/jbe@1309
|
2773
|
bsw/jbe@1309
|
2774 range.setStart(testTextNode, 0);
|
bsw/jbe@1309
|
2775 range.setEnd(testTextNode, 0);
|
bsw/jbe@1309
|
2776
|
bsw/jbe@1309
|
2777 try {
|
bsw/jbe@1309
|
2778 range.setStart(testTextNode, 1);
|
bsw/jbe@1309
|
2779
|
bsw/jbe@1309
|
2780 rangeProto.setStart = function(node, offset) {
|
bsw/jbe@1309
|
2781 this.nativeRange.setStart(node, offset);
|
bsw/jbe@1309
|
2782 updateRangeProperties(this);
|
bsw/jbe@1309
|
2783 };
|
bsw/jbe@1309
|
2784
|
bsw/jbe@1309
|
2785 rangeProto.setEnd = function(node, offset) {
|
bsw/jbe@1309
|
2786 this.nativeRange.setEnd(node, offset);
|
bsw/jbe@1309
|
2787 updateRangeProperties(this);
|
bsw/jbe@1309
|
2788 };
|
bsw/jbe@1309
|
2789
|
bsw/jbe@1309
|
2790 createBeforeAfterNodeSetter = function(name) {
|
bsw/jbe@1309
|
2791 return function(node) {
|
bsw/jbe@1309
|
2792 this.nativeRange[name](node);
|
bsw/jbe@1309
|
2793 updateRangeProperties(this);
|
bsw/jbe@1309
|
2794 };
|
bsw/jbe@1309
|
2795 };
|
bsw/jbe@1309
|
2796
|
bsw/jbe@1309
|
2797 } catch(ex) {
|
bsw/jbe@1309
|
2798
|
bsw/jbe@1309
|
2799 rangeProto.setStart = function(node, offset) {
|
bsw/jbe@1309
|
2800 try {
|
bsw/jbe@1309
|
2801 this.nativeRange.setStart(node, offset);
|
bsw/jbe@1309
|
2802 } catch (ex) {
|
bsw/jbe@1309
|
2803 this.nativeRange.setEnd(node, offset);
|
bsw/jbe@1309
|
2804 this.nativeRange.setStart(node, offset);
|
bsw/jbe@1309
|
2805 }
|
bsw/jbe@1309
|
2806 updateRangeProperties(this);
|
bsw/jbe@1309
|
2807 };
|
bsw/jbe@1309
|
2808
|
bsw/jbe@1309
|
2809 rangeProto.setEnd = function(node, offset) {
|
bsw/jbe@1309
|
2810 try {
|
bsw/jbe@1309
|
2811 this.nativeRange.setEnd(node, offset);
|
bsw/jbe@1309
|
2812 } catch (ex) {
|
bsw/jbe@1309
|
2813 this.nativeRange.setStart(node, offset);
|
bsw/jbe@1309
|
2814 this.nativeRange.setEnd(node, offset);
|
bsw/jbe@1309
|
2815 }
|
bsw/jbe@1309
|
2816 updateRangeProperties(this);
|
bsw/jbe@1309
|
2817 };
|
bsw/jbe@1309
|
2818
|
bsw/jbe@1309
|
2819 createBeforeAfterNodeSetter = function(name, oppositeName) {
|
bsw/jbe@1309
|
2820 return function(node) {
|
bsw/jbe@1309
|
2821 try {
|
bsw/jbe@1309
|
2822 this.nativeRange[name](node);
|
bsw/jbe@1309
|
2823 } catch (ex) {
|
bsw/jbe@1309
|
2824 this.nativeRange[oppositeName](node);
|
bsw/jbe@1309
|
2825 this.nativeRange[name](node);
|
bsw/jbe@1309
|
2826 }
|
bsw/jbe@1309
|
2827 updateRangeProperties(this);
|
bsw/jbe@1309
|
2828 };
|
bsw/jbe@1309
|
2829 };
|
bsw/jbe@1309
|
2830 }
|
bsw/jbe@1309
|
2831
|
bsw/jbe@1309
|
2832 rangeProto.setStartBefore = createBeforeAfterNodeSetter("setStartBefore", "setEndBefore");
|
bsw/jbe@1309
|
2833 rangeProto.setStartAfter = createBeforeAfterNodeSetter("setStartAfter", "setEndAfter");
|
bsw/jbe@1309
|
2834 rangeProto.setEndBefore = createBeforeAfterNodeSetter("setEndBefore", "setStartBefore");
|
bsw/jbe@1309
|
2835 rangeProto.setEndAfter = createBeforeAfterNodeSetter("setEndAfter", "setStartAfter");
|
bsw/jbe@1309
|
2836
|
bsw/jbe@1309
|
2837 /*--------------------------------------------------------------------------------------------------------*/
|
bsw/jbe@1309
|
2838
|
bsw/jbe@1309
|
2839 // Always use DOM4-compliant selectNodeContents implementation: it's simpler and less code than testing
|
bsw/jbe@1309
|
2840 // whether the native implementation can be trusted
|
bsw/jbe@1309
|
2841 rangeProto.selectNodeContents = function(node) {
|
bsw/jbe@1309
|
2842 this.setStartAndEnd(node, 0, dom.getNodeLength(node));
|
bsw/jbe@1309
|
2843 };
|
bsw/jbe@1309
|
2844
|
bsw/jbe@1309
|
2845 /*--------------------------------------------------------------------------------------------------------*/
|
bsw/jbe@1309
|
2846
|
bsw/jbe@1309
|
2847 // Test for and correct WebKit bug that has the behaviour of compareBoundaryPoints round the wrong way for
|
bsw/jbe@1309
|
2848 // constants START_TO_END and END_TO_START: https://bugs.webkit.org/show_bug.cgi?id=20738
|
bsw/jbe@1309
|
2849
|
bsw/jbe@1309
|
2850 range.selectNodeContents(testTextNode);
|
bsw/jbe@1309
|
2851 range.setEnd(testTextNode, 3);
|
bsw/jbe@1309
|
2852
|
bsw/jbe@1309
|
2853 var range2 = document.createRange();
|
bsw/jbe@1309
|
2854 range2.selectNodeContents(testTextNode);
|
bsw/jbe@1309
|
2855 range2.setEnd(testTextNode, 4);
|
bsw/jbe@1309
|
2856 range2.setStart(testTextNode, 2);
|
bsw/jbe@1309
|
2857
|
bsw/jbe@1309
|
2858 if (range.compareBoundaryPoints(range.START_TO_END, range2) == -1 &&
|
bsw/jbe@1309
|
2859 range.compareBoundaryPoints(range.END_TO_START, range2) == 1) {
|
bsw/jbe@1309
|
2860 // This is the wrong way round, so correct for it
|
bsw/jbe@1309
|
2861
|
bsw/jbe@1309
|
2862 rangeProto.compareBoundaryPoints = function(type, range) {
|
bsw/jbe@1309
|
2863 range = range.nativeRange || range;
|
bsw/jbe@1309
|
2864 if (type == range.START_TO_END) {
|
bsw/jbe@1309
|
2865 type = range.END_TO_START;
|
bsw/jbe@1309
|
2866 } else if (type == range.END_TO_START) {
|
bsw/jbe@1309
|
2867 type = range.START_TO_END;
|
bsw/jbe@1309
|
2868 }
|
bsw/jbe@1309
|
2869 return this.nativeRange.compareBoundaryPoints(type, range);
|
bsw/jbe@1309
|
2870 };
|
bsw/jbe@1309
|
2871 } else {
|
bsw/jbe@1309
|
2872 rangeProto.compareBoundaryPoints = function(type, range) {
|
bsw/jbe@1309
|
2873 return this.nativeRange.compareBoundaryPoints(type, range.nativeRange || range);
|
bsw/jbe@1309
|
2874 };
|
bsw/jbe@1309
|
2875 }
|
bsw/jbe@1309
|
2876
|
bsw/jbe@1309
|
2877 /*--------------------------------------------------------------------------------------------------------*/
|
bsw/jbe@1309
|
2878
|
bsw/jbe@1309
|
2879 // Test for IE deleteContents() and extractContents() bug and correct it. See issue 107.
|
bsw/jbe@1309
|
2880
|
bsw/jbe@1309
|
2881 var el = document.createElement("div");
|
bsw/jbe@1309
|
2882 el.innerHTML = "123";
|
bsw/jbe@1309
|
2883 var textNode = el.firstChild;
|
bsw/jbe@1309
|
2884 var body = getBody(document);
|
bsw/jbe@1309
|
2885 body.appendChild(el);
|
bsw/jbe@1309
|
2886
|
bsw/jbe@1309
|
2887 range.setStart(textNode, 1);
|
bsw/jbe@1309
|
2888 range.setEnd(textNode, 2);
|
bsw/jbe@1309
|
2889 range.deleteContents();
|
bsw/jbe@1309
|
2890
|
bsw/jbe@1309
|
2891 if (textNode.data == "13") {
|
bsw/jbe@1309
|
2892 // Behaviour is correct per DOM4 Range so wrap the browser's implementation of deleteContents() and
|
bsw/jbe@1309
|
2893 // extractContents()
|
bsw/jbe@1309
|
2894 rangeProto.deleteContents = function() {
|
bsw/jbe@1309
|
2895 this.nativeRange.deleteContents();
|
bsw/jbe@1309
|
2896 updateRangeProperties(this);
|
bsw/jbe@1309
|
2897 };
|
bsw/jbe@1309
|
2898
|
bsw/jbe@1309
|
2899 rangeProto.extractContents = function() {
|
bsw/jbe@1309
|
2900 var frag = this.nativeRange.extractContents();
|
bsw/jbe@1309
|
2901 updateRangeProperties(this);
|
bsw/jbe@1309
|
2902 return frag;
|
bsw/jbe@1309
|
2903 };
|
bsw/jbe@1309
|
2904 } else {
|
bsw/jbe@1309
|
2905 }
|
bsw/jbe@1309
|
2906
|
bsw/jbe@1309
|
2907 body.removeChild(el);
|
bsw/jbe@1309
|
2908 body = null;
|
bsw/jbe@1309
|
2909
|
bsw/jbe@1309
|
2910 /*--------------------------------------------------------------------------------------------------------*/
|
bsw/jbe@1309
|
2911
|
bsw/jbe@1309
|
2912 // Test for existence of createContextualFragment and delegate to it if it exists
|
bsw/jbe@1309
|
2913 if (util.isHostMethod(range, "createContextualFragment")) {
|
bsw/jbe@1309
|
2914 rangeProto.createContextualFragment = function(fragmentStr) {
|
bsw/jbe@1309
|
2915 return this.nativeRange.createContextualFragment(fragmentStr);
|
bsw/jbe@1309
|
2916 };
|
bsw/jbe@1309
|
2917 }
|
bsw/jbe@1309
|
2918
|
bsw/jbe@1309
|
2919 /*--------------------------------------------------------------------------------------------------------*/
|
bsw/jbe@1309
|
2920
|
bsw/jbe@1309
|
2921 // Clean up
|
bsw/jbe@1309
|
2922 getBody(document).removeChild(testTextNode);
|
bsw/jbe@1309
|
2923
|
bsw/jbe@1309
|
2924 rangeProto.getName = function() {
|
bsw/jbe@1309
|
2925 return "WrappedRange";
|
bsw/jbe@1309
|
2926 };
|
bsw/jbe@1309
|
2927
|
bsw/jbe@1309
|
2928 api.WrappedRange = WrappedRange;
|
bsw/jbe@1309
|
2929
|
bsw/jbe@1309
|
2930 api.createNativeRange = function(doc) {
|
bsw/jbe@1309
|
2931 doc = getContentDocument(doc, module, "createNativeRange");
|
bsw/jbe@1309
|
2932 return doc.createRange();
|
bsw/jbe@1309
|
2933 };
|
bsw/jbe@1309
|
2934 })();
|
bsw/jbe@1309
|
2935 }
|
bsw/jbe@1309
|
2936
|
bsw/jbe@1309
|
2937 if (api.features.implementsTextRange) {
|
bsw/jbe@1309
|
2938 /*
|
bsw/jbe@1309
|
2939 This is a workaround for a bug where IE returns the wrong container element from the TextRange's parentElement()
|
bsw/jbe@1309
|
2940 method. For example, in the following (where pipes denote the selection boundaries):
|
bsw/jbe@1309
|
2941
|
bsw/jbe@1309
|
2942 <ul id="ul"><li id="a">| a </li><li id="b"> b |</li></ul>
|
bsw/jbe@1309
|
2943
|
bsw/jbe@1309
|
2944 var range = document.selection.createRange();
|
bsw/jbe@1309
|
2945 alert(range.parentElement().id); // Should alert "ul" but alerts "b"
|
bsw/jbe@1309
|
2946
|
bsw/jbe@1309
|
2947 This method returns the common ancestor node of the following:
|
bsw/jbe@1309
|
2948 - the parentElement() of the textRange
|
bsw/jbe@1309
|
2949 - the parentElement() of the textRange after calling collapse(true)
|
bsw/jbe@1309
|
2950 - the parentElement() of the textRange after calling collapse(false)
|
bsw/jbe@1309
|
2951 */
|
bsw/jbe@1309
|
2952 var getTextRangeContainerElement = function(textRange) {
|
bsw/jbe@1309
|
2953 var parentEl = textRange.parentElement();
|
bsw/jbe@1309
|
2954 var range = textRange.duplicate();
|
bsw/jbe@1309
|
2955 range.collapse(true);
|
bsw/jbe@1309
|
2956 var startEl = range.parentElement();
|
bsw/jbe@1309
|
2957 range = textRange.duplicate();
|
bsw/jbe@1309
|
2958 range.collapse(false);
|
bsw/jbe@1309
|
2959 var endEl = range.parentElement();
|
bsw/jbe@1309
|
2960 var startEndContainer = (startEl == endEl) ? startEl : dom.getCommonAncestor(startEl, endEl);
|
bsw/jbe@1309
|
2961
|
bsw/jbe@1309
|
2962 return startEndContainer == parentEl ? startEndContainer : dom.getCommonAncestor(parentEl, startEndContainer);
|
bsw/jbe@1309
|
2963 };
|
bsw/jbe@1309
|
2964
|
bsw/jbe@1309
|
2965 var textRangeIsCollapsed = function(textRange) {
|
bsw/jbe@1309
|
2966 return textRange.compareEndPoints("StartToEnd", textRange) == 0;
|
bsw/jbe@1309
|
2967 };
|
bsw/jbe@1309
|
2968
|
bsw/jbe@1309
|
2969 // Gets the boundary of a TextRange expressed as a node and an offset within that node. This function started
|
bsw/jbe@1309
|
2970 // out as an improved version of code found in Tim Cameron Ryan's IERange (http://code.google.com/p/ierange/)
|
bsw/jbe@1309
|
2971 // but has grown, fixing problems with line breaks in preformatted text, adding workaround for IE TextRange
|
bsw/jbe@1309
|
2972 // bugs, handling for inputs and images, plus optimizations.
|
bsw/jbe@1309
|
2973 var getTextRangeBoundaryPosition = function(textRange, wholeRangeContainerElement, isStart, isCollapsed, startInfo) {
|
bsw/jbe@1309
|
2974 var workingRange = textRange.duplicate();
|
bsw/jbe@1309
|
2975 workingRange.collapse(isStart);
|
bsw/jbe@1309
|
2976 var containerElement = workingRange.parentElement();
|
bsw/jbe@1309
|
2977
|
bsw/jbe@1309
|
2978 // Sometimes collapsing a TextRange that's at the start of a text node can move it into the previous node, so
|
bsw/jbe@1309
|
2979 // check for that
|
bsw/jbe@1309
|
2980 if (!dom.isOrIsAncestorOf(wholeRangeContainerElement, containerElement)) {
|
bsw/jbe@1309
|
2981 containerElement = wholeRangeContainerElement;
|
bsw/jbe@1309
|
2982 }
|
bsw/jbe@1309
|
2983
|
bsw/jbe@1309
|
2984
|
bsw/jbe@1309
|
2985 // Deal with nodes that cannot "contain rich HTML markup". In practice, this means form inputs, images and
|
bsw/jbe@1309
|
2986 // similar. See http://msdn.microsoft.com/en-us/library/aa703950%28VS.85%29.aspx
|
bsw/jbe@1309
|
2987 if (!containerElement.canHaveHTML) {
|
bsw/jbe@1309
|
2988 var pos = new DomPosition(containerElement.parentNode, dom.getNodeIndex(containerElement));
|
bsw/jbe@1309
|
2989 return {
|
bsw/jbe@1309
|
2990 boundaryPosition: pos,
|
bsw/jbe@1309
|
2991 nodeInfo: {
|
bsw/jbe@1309
|
2992 nodeIndex: pos.offset,
|
bsw/jbe@1309
|
2993 containerElement: pos.node
|
bsw/jbe@1309
|
2994 }
|
bsw/jbe@1309
|
2995 };
|
bsw/jbe@1309
|
2996 }
|
bsw/jbe@1309
|
2997
|
bsw/jbe@1309
|
2998 var workingNode = dom.getDocument(containerElement).createElement("span");
|
bsw/jbe@1309
|
2999
|
bsw/jbe@1309
|
3000 // Workaround for HTML5 Shiv's insane violation of document.createElement(). See Rangy issue 104 and HTML5
|
bsw/jbe@1309
|
3001 // Shiv issue 64: https://github.com/aFarkas/html5shiv/issues/64
|
bsw/jbe@1309
|
3002 if (workingNode.parentNode) {
|
bsw/jbe@1309
|
3003 dom.removeNode(workingNode);
|
bsw/jbe@1309
|
3004 }
|
bsw/jbe@1309
|
3005
|
bsw/jbe@1309
|
3006 var comparison, workingComparisonType = isStart ? "StartToStart" : "StartToEnd";
|
bsw/jbe@1309
|
3007 var previousNode, nextNode, boundaryPosition, boundaryNode;
|
bsw/jbe@1309
|
3008 var start = (startInfo && startInfo.containerElement == containerElement) ? startInfo.nodeIndex : 0;
|
bsw/jbe@1309
|
3009 var childNodeCount = containerElement.childNodes.length;
|
bsw/jbe@1309
|
3010 var end = childNodeCount;
|
bsw/jbe@1309
|
3011
|
bsw/jbe@1309
|
3012 // Check end first. Code within the loop assumes that the endth child node of the container is definitely
|
bsw/jbe@1309
|
3013 // after the range boundary.
|
bsw/jbe@1309
|
3014 var nodeIndex = end;
|
bsw/jbe@1309
|
3015
|
bsw/jbe@1309
|
3016 while (true) {
|
bsw/jbe@1309
|
3017 if (nodeIndex == childNodeCount) {
|
bsw/jbe@1309
|
3018 containerElement.appendChild(workingNode);
|
bsw/jbe@1309
|
3019 } else {
|
bsw/jbe@1309
|
3020 containerElement.insertBefore(workingNode, containerElement.childNodes[nodeIndex]);
|
bsw/jbe@1309
|
3021 }
|
bsw/jbe@1309
|
3022 workingRange.moveToElementText(workingNode);
|
bsw/jbe@1309
|
3023 comparison = workingRange.compareEndPoints(workingComparisonType, textRange);
|
bsw/jbe@1309
|
3024 if (comparison == 0 || start == end) {
|
bsw/jbe@1309
|
3025 break;
|
bsw/jbe@1309
|
3026 } else if (comparison == -1) {
|
bsw/jbe@1309
|
3027 if (end == start + 1) {
|
bsw/jbe@1309
|
3028 // We know the endth child node is after the range boundary, so we must be done.
|
bsw/jbe@1309
|
3029 break;
|
bsw/jbe@1309
|
3030 } else {
|
bsw/jbe@1309
|
3031 start = nodeIndex;
|
bsw/jbe@1309
|
3032 }
|
bsw/jbe@1309
|
3033 } else {
|
bsw/jbe@1309
|
3034 end = (end == start + 1) ? start : nodeIndex;
|
bsw/jbe@1309
|
3035 }
|
bsw/jbe@1309
|
3036 nodeIndex = Math.floor((start + end) / 2);
|
bsw/jbe@1309
|
3037 containerElement.removeChild(workingNode);
|
bsw/jbe@1309
|
3038 }
|
bsw/jbe@1309
|
3039
|
bsw/jbe@1309
|
3040
|
bsw/jbe@1309
|
3041 // We've now reached or gone past the boundary of the text range we're interested in
|
bsw/jbe@1309
|
3042 // so have identified the node we want
|
bsw/jbe@1309
|
3043 boundaryNode = workingNode.nextSibling;
|
bsw/jbe@1309
|
3044
|
bsw/jbe@1309
|
3045 if (comparison == -1 && boundaryNode && isCharacterDataNode(boundaryNode)) {
|
bsw/jbe@1309
|
3046 // This is a character data node (text, comment, cdata). The working range is collapsed at the start of
|
bsw/jbe@1309
|
3047 // the node containing the text range's boundary, so we move the end of the working range to the
|
bsw/jbe@1309
|
3048 // boundary point and measure the length of its text to get the boundary's offset within the node.
|
bsw/jbe@1309
|
3049 workingRange.setEndPoint(isStart ? "EndToStart" : "EndToEnd", textRange);
|
bsw/jbe@1309
|
3050
|
bsw/jbe@1309
|
3051 var offset;
|
bsw/jbe@1309
|
3052
|
bsw/jbe@1309
|
3053 if (/[\r\n]/.test(boundaryNode.data)) {
|
bsw/jbe@1309
|
3054 /*
|
bsw/jbe@1309
|
3055 For the particular case of a boundary within a text node containing rendered line breaks (within a
|
bsw/jbe@1309
|
3056 <pre> element, for example), we need a slightly complicated approach to get the boundary's offset in
|
bsw/jbe@1309
|
3057 IE. The facts:
|
bsw/jbe@1309
|
3058
|
bsw/jbe@1309
|
3059 - Each line break is represented as \r in the text node's data/nodeValue properties
|
bsw/jbe@1309
|
3060 - Each line break is represented as \r\n in the TextRange's 'text' property
|
bsw/jbe@1309
|
3061 - The 'text' property of the TextRange does not contain trailing line breaks
|
bsw/jbe@1309
|
3062
|
bsw/jbe@1309
|
3063 To get round the problem presented by the final fact above, we can use the fact that TextRange's
|
bsw/jbe@1309
|
3064 moveStart() and moveEnd() methods return the actual number of characters moved, which is not
|
bsw/jbe@1309
|
3065 necessarily the same as the number of characters it was instructed to move. The simplest approach is
|
bsw/jbe@1309
|
3066 to use this to store the characters moved when moving both the start and end of the range to the
|
bsw/jbe@1309
|
3067 start of the document body and subtracting the start offset from the end offset (the
|
bsw/jbe@1309
|
3068 "move-negative-gazillion" method). However, this is extremely slow when the document is large and
|
bsw/jbe@1309
|
3069 the range is near the end of it. Clearly doing the mirror image (i.e. moving the range boundaries to
|
bsw/jbe@1309
|
3070 the end of the document) has the same problem.
|
bsw/jbe@1309
|
3071
|
bsw/jbe@1309
|
3072 Another approach that works is to use moveStart() to move the start boundary of the range up to the
|
bsw/jbe@1309
|
3073 end boundary one character at a time and incrementing a counter with the value returned by the
|
bsw/jbe@1309
|
3074 moveStart() call. However, the check for whether the start boundary has reached the end boundary is
|
bsw/jbe@1309
|
3075 expensive, so this method is slow (although unlike "move-negative-gazillion" is largely unaffected
|
bsw/jbe@1309
|
3076 by the location of the range within the document).
|
bsw/jbe@1309
|
3077
|
bsw/jbe@1309
|
3078 The approach used below is a hybrid of the two methods above. It uses the fact that a string
|
bsw/jbe@1309
|
3079 containing the TextRange's 'text' property with each \r\n converted to a single \r character cannot
|
bsw/jbe@1309
|
3080 be longer than the text of the TextRange, so the start of the range is moved that length initially
|
bsw/jbe@1309
|
3081 and then a character at a time to make up for any trailing line breaks not contained in the 'text'
|
bsw/jbe@1309
|
3082 property. This has good performance in most situations compared to the previous two methods.
|
bsw/jbe@1309
|
3083 */
|
bsw/jbe@1309
|
3084 var tempRange = workingRange.duplicate();
|
bsw/jbe@1309
|
3085 var rangeLength = tempRange.text.replace(/\r\n/g, "\r").length;
|
bsw/jbe@1309
|
3086
|
bsw/jbe@1309
|
3087 offset = tempRange.moveStart("character", rangeLength);
|
bsw/jbe@1309
|
3088 while ( (comparison = tempRange.compareEndPoints("StartToEnd", tempRange)) == -1) {
|
bsw/jbe@1309
|
3089 offset++;
|
bsw/jbe@1309
|
3090 tempRange.moveStart("character", 1);
|
bsw/jbe@1309
|
3091 }
|
bsw/jbe@1309
|
3092 } else {
|
bsw/jbe@1309
|
3093 offset = workingRange.text.length;
|
bsw/jbe@1309
|
3094 }
|
bsw/jbe@1309
|
3095 boundaryPosition = new DomPosition(boundaryNode, offset);
|
bsw/jbe@1309
|
3096 } else {
|
bsw/jbe@1309
|
3097
|
bsw/jbe@1309
|
3098 // If the boundary immediately follows a character data node and this is the end boundary, we should favour
|
bsw/jbe@1309
|
3099 // a position within that, and likewise for a start boundary preceding a character data node
|
bsw/jbe@1309
|
3100 previousNode = (isCollapsed || !isStart) && workingNode.previousSibling;
|
bsw/jbe@1309
|
3101 nextNode = (isCollapsed || isStart) && workingNode.nextSibling;
|
bsw/jbe@1309
|
3102 if (nextNode && isCharacterDataNode(nextNode)) {
|
bsw/jbe@1309
|
3103 boundaryPosition = new DomPosition(nextNode, 0);
|
bsw/jbe@1309
|
3104 } else if (previousNode && isCharacterDataNode(previousNode)) {
|
bsw/jbe@1309
|
3105 boundaryPosition = new DomPosition(previousNode, previousNode.data.length);
|
bsw/jbe@1309
|
3106 } else {
|
bsw/jbe@1309
|
3107 boundaryPosition = new DomPosition(containerElement, dom.getNodeIndex(workingNode));
|
bsw/jbe@1309
|
3108 }
|
bsw/jbe@1309
|
3109 }
|
bsw/jbe@1309
|
3110
|
bsw/jbe@1309
|
3111 // Clean up
|
bsw/jbe@1309
|
3112 dom.removeNode(workingNode);
|
bsw/jbe@1309
|
3113
|
bsw/jbe@1309
|
3114 return {
|
bsw/jbe@1309
|
3115 boundaryPosition: boundaryPosition,
|
bsw/jbe@1309
|
3116 nodeInfo: {
|
bsw/jbe@1309
|
3117 nodeIndex: nodeIndex,
|
bsw/jbe@1309
|
3118 containerElement: containerElement
|
bsw/jbe@1309
|
3119 }
|
bsw/jbe@1309
|
3120 };
|
bsw/jbe@1309
|
3121 };
|
bsw/jbe@1309
|
3122
|
bsw/jbe@1309
|
3123 // Returns a TextRange representing the boundary of a TextRange expressed as a node and an offset within that
|
bsw/jbe@1309
|
3124 // node. This function started out as an optimized version of code found in Tim Cameron Ryan's IERange
|
bsw/jbe@1309
|
3125 // (http://code.google.com/p/ierange/)
|
bsw/jbe@1309
|
3126 var createBoundaryTextRange = function(boundaryPosition, isStart) {
|
bsw/jbe@1309
|
3127 var boundaryNode, boundaryParent, boundaryOffset = boundaryPosition.offset;
|
bsw/jbe@1309
|
3128 var doc = dom.getDocument(boundaryPosition.node);
|
bsw/jbe@1309
|
3129 var workingNode, childNodes, workingRange = getBody(doc).createTextRange();
|
bsw/jbe@1309
|
3130 var nodeIsDataNode = isCharacterDataNode(boundaryPosition.node);
|
bsw/jbe@1309
|
3131
|
bsw/jbe@1309
|
3132 if (nodeIsDataNode) {
|
bsw/jbe@1309
|
3133 boundaryNode = boundaryPosition.node;
|
bsw/jbe@1309
|
3134 boundaryParent = boundaryNode.parentNode;
|
bsw/jbe@1309
|
3135 } else {
|
bsw/jbe@1309
|
3136 childNodes = boundaryPosition.node.childNodes;
|
bsw/jbe@1309
|
3137 boundaryNode = (boundaryOffset < childNodes.length) ? childNodes[boundaryOffset] : null;
|
bsw/jbe@1309
|
3138 boundaryParent = boundaryPosition.node;
|
bsw/jbe@1309
|
3139 }
|
bsw/jbe@1309
|
3140
|
bsw/jbe@1309
|
3141 // Position the range immediately before the node containing the boundary
|
bsw/jbe@1309
|
3142 workingNode = doc.createElement("span");
|
bsw/jbe@1309
|
3143
|
bsw/jbe@1309
|
3144 // Making the working element non-empty element persuades IE to consider the TextRange boundary to be within
|
bsw/jbe@1309
|
3145 // the element rather than immediately before or after it
|
bsw/jbe@1309
|
3146 workingNode.innerHTML = "&#feff;";
|
bsw/jbe@1309
|
3147
|
bsw/jbe@1309
|
3148 // insertBefore is supposed to work like appendChild if the second parameter is null. However, a bug report
|
bsw/jbe@1309
|
3149 // for IERange suggests that it can crash the browser: http://code.google.com/p/ierange/issues/detail?id=12
|
bsw/jbe@1309
|
3150 if (boundaryNode) {
|
bsw/jbe@1309
|
3151 boundaryParent.insertBefore(workingNode, boundaryNode);
|
bsw/jbe@1309
|
3152 } else {
|
bsw/jbe@1309
|
3153 boundaryParent.appendChild(workingNode);
|
bsw/jbe@1309
|
3154 }
|
bsw/jbe@1309
|
3155
|
bsw/jbe@1309
|
3156 workingRange.moveToElementText(workingNode);
|
bsw/jbe@1309
|
3157 workingRange.collapse(!isStart);
|
bsw/jbe@1309
|
3158
|
bsw/jbe@1309
|
3159 // Clean up
|
bsw/jbe@1309
|
3160 boundaryParent.removeChild(workingNode);
|
bsw/jbe@1309
|
3161
|
bsw/jbe@1309
|
3162 // Move the working range to the text offset, if required
|
bsw/jbe@1309
|
3163 if (nodeIsDataNode) {
|
bsw/jbe@1309
|
3164 workingRange[isStart ? "moveStart" : "moveEnd"]("character", boundaryOffset);
|
bsw/jbe@1309
|
3165 }
|
bsw/jbe@1309
|
3166
|
bsw/jbe@1309
|
3167 return workingRange;
|
bsw/jbe@1309
|
3168 };
|
bsw/jbe@1309
|
3169
|
bsw/jbe@1309
|
3170 /*------------------------------------------------------------------------------------------------------------*/
|
bsw/jbe@1309
|
3171
|
bsw/jbe@1309
|
3172 // This is a wrapper around a TextRange, providing full DOM Range functionality using rangy's DomRange as a
|
bsw/jbe@1309
|
3173 // prototype
|
bsw/jbe@1309
|
3174
|
bsw/jbe@1309
|
3175 WrappedTextRange = function(textRange) {
|
bsw/jbe@1309
|
3176 this.textRange = textRange;
|
bsw/jbe@1309
|
3177 this.refresh();
|
bsw/jbe@1309
|
3178 };
|
bsw/jbe@1309
|
3179
|
bsw/jbe@1309
|
3180 WrappedTextRange.prototype = new DomRange(document);
|
bsw/jbe@1309
|
3181
|
bsw/jbe@1309
|
3182 WrappedTextRange.prototype.refresh = function() {
|
bsw/jbe@1309
|
3183 var start, end, startBoundary;
|
bsw/jbe@1309
|
3184
|
bsw/jbe@1309
|
3185 // TextRange's parentElement() method cannot be trusted. getTextRangeContainerElement() works around that.
|
bsw/jbe@1309
|
3186 var rangeContainerElement = getTextRangeContainerElement(this.textRange);
|
bsw/jbe@1309
|
3187
|
bsw/jbe@1309
|
3188 if (textRangeIsCollapsed(this.textRange)) {
|
bsw/jbe@1309
|
3189 end = start = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, true,
|
bsw/jbe@1309
|
3190 true).boundaryPosition;
|
bsw/jbe@1309
|
3191 } else {
|
bsw/jbe@1309
|
3192 startBoundary = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, true, false);
|
bsw/jbe@1309
|
3193 start = startBoundary.boundaryPosition;
|
bsw/jbe@1309
|
3194
|
bsw/jbe@1309
|
3195 // An optimization used here is that if the start and end boundaries have the same parent element, the
|
bsw/jbe@1309
|
3196 // search scope for the end boundary can be limited to exclude the portion of the element that precedes
|
bsw/jbe@1309
|
3197 // the start boundary
|
bsw/jbe@1309
|
3198 end = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, false, false,
|
bsw/jbe@1309
|
3199 startBoundary.nodeInfo).boundaryPosition;
|
bsw/jbe@1309
|
3200 }
|
bsw/jbe@1309
|
3201
|
bsw/jbe@1309
|
3202 this.setStart(start.node, start.offset);
|
bsw/jbe@1309
|
3203 this.setEnd(end.node, end.offset);
|
bsw/jbe@1309
|
3204 };
|
bsw/jbe@1309
|
3205
|
bsw/jbe@1309
|
3206 WrappedTextRange.prototype.getName = function() {
|
bsw/jbe@1309
|
3207 return "WrappedTextRange";
|
bsw/jbe@1309
|
3208 };
|
bsw/jbe@1309
|
3209
|
bsw/jbe@1309
|
3210 DomRange.copyComparisonConstants(WrappedTextRange);
|
bsw/jbe@1309
|
3211
|
bsw/jbe@1309
|
3212 var rangeToTextRange = function(range) {
|
bsw/jbe@1309
|
3213 if (range.collapsed) {
|
bsw/jbe@1309
|
3214 return createBoundaryTextRange(new DomPosition(range.startContainer, range.startOffset), true);
|
bsw/jbe@1309
|
3215 } else {
|
bsw/jbe@1309
|
3216 var startRange = createBoundaryTextRange(new DomPosition(range.startContainer, range.startOffset), true);
|
bsw/jbe@1309
|
3217 var endRange = createBoundaryTextRange(new DomPosition(range.endContainer, range.endOffset), false);
|
bsw/jbe@1309
|
3218 var textRange = getBody( DomRange.getRangeDocument(range) ).createTextRange();
|
bsw/jbe@1309
|
3219 textRange.setEndPoint("StartToStart", startRange);
|
bsw/jbe@1309
|
3220 textRange.setEndPoint("EndToEnd", endRange);
|
bsw/jbe@1309
|
3221 return textRange;
|
bsw/jbe@1309
|
3222 }
|
bsw/jbe@1309
|
3223 };
|
bsw/jbe@1309
|
3224
|
bsw/jbe@1309
|
3225 WrappedTextRange.rangeToTextRange = rangeToTextRange;
|
bsw/jbe@1309
|
3226
|
bsw/jbe@1309
|
3227 WrappedTextRange.prototype.toTextRange = function() {
|
bsw/jbe@1309
|
3228 return rangeToTextRange(this);
|
bsw/jbe@1309
|
3229 };
|
bsw/jbe@1309
|
3230
|
bsw/jbe@1309
|
3231 api.WrappedTextRange = WrappedTextRange;
|
bsw/jbe@1309
|
3232
|
bsw/jbe@1309
|
3233 // IE 9 and above have both implementations and Rangy makes both available. The next few lines sets which
|
bsw/jbe@1309
|
3234 // implementation to use by default.
|
bsw/jbe@1309
|
3235 if (!api.features.implementsDomRange || api.config.preferTextRange) {
|
bsw/jbe@1309
|
3236 // Add WrappedTextRange as the Range property of the global object to allow expression like Range.END_TO_END to work
|
bsw/jbe@1309
|
3237 var globalObj = (function(f) { return f("return this;")(); })(Function);
|
bsw/jbe@1309
|
3238 if (typeof globalObj.Range == "undefined") {
|
bsw/jbe@1309
|
3239 globalObj.Range = WrappedTextRange;
|
bsw/jbe@1309
|
3240 }
|
bsw/jbe@1309
|
3241
|
bsw/jbe@1309
|
3242 api.createNativeRange = function(doc) {
|
bsw/jbe@1309
|
3243 doc = getContentDocument(doc, module, "createNativeRange");
|
bsw/jbe@1309
|
3244 return getBody(doc).createTextRange();
|
bsw/jbe@1309
|
3245 };
|
bsw/jbe@1309
|
3246
|
bsw/jbe@1309
|
3247 api.WrappedRange = WrappedTextRange;
|
bsw/jbe@1309
|
3248 }
|
bsw/jbe@1309
|
3249 }
|
bsw/jbe@1309
|
3250
|
bsw/jbe@1309
|
3251 api.createRange = function(doc) {
|
bsw/jbe@1309
|
3252 doc = getContentDocument(doc, module, "createRange");
|
bsw/jbe@1309
|
3253 return new api.WrappedRange(api.createNativeRange(doc));
|
bsw/jbe@1309
|
3254 };
|
bsw/jbe@1309
|
3255
|
bsw/jbe@1309
|
3256 api.createRangyRange = function(doc) {
|
bsw/jbe@1309
|
3257 doc = getContentDocument(doc, module, "createRangyRange");
|
bsw/jbe@1309
|
3258 return new DomRange(doc);
|
bsw/jbe@1309
|
3259 };
|
bsw/jbe@1309
|
3260
|
bsw/jbe@1309
|
3261 util.createAliasForDeprecatedMethod(api, "createIframeRange", "createRange");
|
bsw/jbe@1309
|
3262 util.createAliasForDeprecatedMethod(api, "createIframeRangyRange", "createRangyRange");
|
bsw/jbe@1309
|
3263
|
bsw/jbe@1309
|
3264 api.addShimListener(function(win) {
|
bsw/jbe@1309
|
3265 var doc = win.document;
|
bsw/jbe@1309
|
3266 if (typeof doc.createRange == "undefined") {
|
bsw/jbe@1309
|
3267 doc.createRange = function() {
|
bsw/jbe@1309
|
3268 return api.createRange(doc);
|
bsw/jbe@1309
|
3269 };
|
bsw/jbe@1309
|
3270 }
|
bsw/jbe@1309
|
3271 doc = win = null;
|
bsw/jbe@1309
|
3272 });
|
bsw/jbe@1309
|
3273 });
|
bsw/jbe@1309
|
3274
|
bsw/jbe@1309
|
3275 /*----------------------------------------------------------------------------------------------------------------*/
|
bsw/jbe@1309
|
3276
|
bsw/jbe@1309
|
3277 // This module creates a selection object wrapper that conforms as closely as possible to the Selection specification
|
bsw/jbe@1309
|
3278 // in the HTML Editing spec (http://dvcs.w3.org/hg/editing/raw-file/tip/editing.html#selections)
|
bsw/jbe@1309
|
3279 api.createCoreModule("WrappedSelection", ["DomRange", "WrappedRange"], function(api, module) {
|
bsw/jbe@1309
|
3280 api.config.checkSelectionRanges = true;
|
bsw/jbe@1309
|
3281
|
bsw/jbe@1309
|
3282 var BOOLEAN = "boolean";
|
bsw/jbe@1309
|
3283 var NUMBER = "number";
|
bsw/jbe@1309
|
3284 var dom = api.dom;
|
bsw/jbe@1309
|
3285 var util = api.util;
|
bsw/jbe@1309
|
3286 var isHostMethod = util.isHostMethod;
|
bsw/jbe@1309
|
3287 var DomRange = api.DomRange;
|
bsw/jbe@1309
|
3288 var WrappedRange = api.WrappedRange;
|
bsw/jbe@1309
|
3289 var DOMException = api.DOMException;
|
bsw/jbe@1309
|
3290 var DomPosition = dom.DomPosition;
|
bsw/jbe@1309
|
3291 var getNativeSelection;
|
bsw/jbe@1309
|
3292 var selectionIsCollapsed;
|
bsw/jbe@1309
|
3293 var features = api.features;
|
bsw/jbe@1309
|
3294 var CONTROL = "Control";
|
bsw/jbe@1309
|
3295 var getDocument = dom.getDocument;
|
bsw/jbe@1309
|
3296 var getBody = dom.getBody;
|
bsw/jbe@1309
|
3297 var rangesEqual = DomRange.rangesEqual;
|
bsw/jbe@1309
|
3298
|
bsw/jbe@1309
|
3299
|
bsw/jbe@1309
|
3300 // Utility function to support direction parameters in the API that may be a string ("backward", "backwards",
|
bsw/jbe@1309
|
3301 // "forward" or "forwards") or a Boolean (true for backwards).
|
bsw/jbe@1309
|
3302 function isDirectionBackward(dir) {
|
bsw/jbe@1309
|
3303 return (typeof dir == "string") ? /^backward(s)?$/i.test(dir) : !!dir;
|
bsw/jbe@1309
|
3304 }
|
bsw/jbe@1309
|
3305
|
bsw/jbe@1309
|
3306 function getWindow(win, methodName) {
|
bsw/jbe@1309
|
3307 if (!win) {
|
bsw/jbe@1309
|
3308 return window;
|
bsw/jbe@1309
|
3309 } else if (dom.isWindow(win)) {
|
bsw/jbe@1309
|
3310 return win;
|
bsw/jbe@1309
|
3311 } else if (win instanceof WrappedSelection) {
|
bsw/jbe@1309
|
3312 return win.win;
|
bsw/jbe@1309
|
3313 } else {
|
bsw/jbe@1309
|
3314 var doc = dom.getContentDocument(win, module, methodName);
|
bsw/jbe@1309
|
3315 return dom.getWindow(doc);
|
bsw/jbe@1309
|
3316 }
|
bsw/jbe@1309
|
3317 }
|
bsw/jbe@1309
|
3318
|
bsw/jbe@1309
|
3319 function getWinSelection(winParam) {
|
bsw/jbe@1309
|
3320 return getWindow(winParam, "getWinSelection").getSelection();
|
bsw/jbe@1309
|
3321 }
|
bsw/jbe@1309
|
3322
|
bsw/jbe@1309
|
3323 function getDocSelection(winParam) {
|
bsw/jbe@1309
|
3324 return getWindow(winParam, "getDocSelection").document.selection;
|
bsw/jbe@1309
|
3325 }
|
bsw/jbe@1309
|
3326
|
bsw/jbe@1309
|
3327 function winSelectionIsBackward(sel) {
|
bsw/jbe@1309
|
3328 var backward = false;
|
bsw/jbe@1309
|
3329 if (sel.anchorNode) {
|
bsw/jbe@1309
|
3330 backward = (dom.comparePoints(sel.anchorNode, sel.anchorOffset, sel.focusNode, sel.focusOffset) == 1);
|
bsw/jbe@1309
|
3331 }
|
bsw/jbe@1309
|
3332 return backward;
|
bsw/jbe@1309
|
3333 }
|
bsw/jbe@1309
|
3334
|
bsw/jbe@1309
|
3335 // Test for the Range/TextRange and Selection features required
|
bsw/jbe@1309
|
3336 // Test for ability to retrieve selection
|
bsw/jbe@1309
|
3337 var implementsWinGetSelection = isHostMethod(window, "getSelection"),
|
bsw/jbe@1309
|
3338 implementsDocSelection = util.isHostObject(document, "selection");
|
bsw/jbe@1309
|
3339
|
bsw/jbe@1309
|
3340 features.implementsWinGetSelection = implementsWinGetSelection;
|
bsw/jbe@1309
|
3341 features.implementsDocSelection = implementsDocSelection;
|
bsw/jbe@1309
|
3342
|
bsw/jbe@1309
|
3343 var useDocumentSelection = implementsDocSelection && (!implementsWinGetSelection || api.config.preferTextRange);
|
bsw/jbe@1309
|
3344
|
bsw/jbe@1309
|
3345 if (useDocumentSelection) {
|
bsw/jbe@1309
|
3346 getNativeSelection = getDocSelection;
|
bsw/jbe@1309
|
3347 api.isSelectionValid = function(winParam) {
|
bsw/jbe@1309
|
3348 var doc = getWindow(winParam, "isSelectionValid").document, nativeSel = doc.selection;
|
bsw/jbe@1309
|
3349
|
bsw/jbe@1309
|
3350 // Check whether the selection TextRange is actually contained within the correct document
|
bsw/jbe@1309
|
3351 return (nativeSel.type != "None" || getDocument(nativeSel.createRange().parentElement()) == doc);
|
bsw/jbe@1309
|
3352 };
|
bsw/jbe@1309
|
3353 } else if (implementsWinGetSelection) {
|
bsw/jbe@1309
|
3354 getNativeSelection = getWinSelection;
|
bsw/jbe@1309
|
3355 api.isSelectionValid = function() {
|
bsw/jbe@1309
|
3356 return true;
|
bsw/jbe@1309
|
3357 };
|
bsw/jbe@1309
|
3358 } else {
|
bsw/jbe@1309
|
3359 module.fail("Neither document.selection or window.getSelection() detected.");
|
bsw/jbe@1309
|
3360 return false;
|
bsw/jbe@1309
|
3361 }
|
bsw/jbe@1309
|
3362
|
bsw/jbe@1309
|
3363 api.getNativeSelection = getNativeSelection;
|
bsw/jbe@1309
|
3364
|
bsw/jbe@1309
|
3365 var testSelection = getNativeSelection();
|
bsw/jbe@1309
|
3366
|
bsw/jbe@1309
|
3367 // In Firefox, the selection is null in an iframe with display: none. See issue #138.
|
bsw/jbe@1309
|
3368 if (!testSelection) {
|
bsw/jbe@1309
|
3369 module.fail("Native selection was null (possibly issue 138?)");
|
bsw/jbe@1309
|
3370 return false;
|
bsw/jbe@1309
|
3371 }
|
bsw/jbe@1309
|
3372
|
bsw/jbe@1309
|
3373 var testRange = api.createNativeRange(document);
|
bsw/jbe@1309
|
3374 var body = getBody(document);
|
bsw/jbe@1309
|
3375
|
bsw/jbe@1309
|
3376 // Obtaining a range from a selection
|
bsw/jbe@1309
|
3377 var selectionHasAnchorAndFocus = util.areHostProperties(testSelection,
|
bsw/jbe@1309
|
3378 ["anchorNode", "focusNode", "anchorOffset", "focusOffset"]);
|
bsw/jbe@1309
|
3379
|
bsw/jbe@1309
|
3380 features.selectionHasAnchorAndFocus = selectionHasAnchorAndFocus;
|
bsw/jbe@1309
|
3381
|
bsw/jbe@1309
|
3382 // Test for existence of native selection extend() method
|
bsw/jbe@1309
|
3383 var selectionHasExtend = isHostMethod(testSelection, "extend");
|
bsw/jbe@1309
|
3384 features.selectionHasExtend = selectionHasExtend;
|
bsw/jbe@1309
|
3385
|
bsw/jbe@1309
|
3386 // Test if rangeCount exists
|
bsw/jbe@1309
|
3387 var selectionHasRangeCount = (typeof testSelection.rangeCount == NUMBER);
|
bsw/jbe@1309
|
3388 features.selectionHasRangeCount = selectionHasRangeCount;
|
bsw/jbe@1309
|
3389
|
bsw/jbe@1309
|
3390 var selectionSupportsMultipleRanges = false;
|
bsw/jbe@1309
|
3391 var collapsedNonEditableSelectionsSupported = true;
|
bsw/jbe@1309
|
3392
|
bsw/jbe@1309
|
3393 var addRangeBackwardToNative = selectionHasExtend ?
|
bsw/jbe@1309
|
3394 function(nativeSelection, range) {
|
bsw/jbe@1309
|
3395 var doc = DomRange.getRangeDocument(range);
|
bsw/jbe@1309
|
3396 var endRange = api.createRange(doc);
|
bsw/jbe@1309
|
3397 endRange.collapseToPoint(range.endContainer, range.endOffset);
|
bsw/jbe@1309
|
3398 nativeSelection.addRange(getNativeRange(endRange));
|
bsw/jbe@1309
|
3399 nativeSelection.extend(range.startContainer, range.startOffset);
|
bsw/jbe@1309
|
3400 } : null;
|
bsw/jbe@1309
|
3401
|
bsw/jbe@1309
|
3402 if (util.areHostMethods(testSelection, ["addRange", "getRangeAt", "removeAllRanges"]) &&
|
bsw/jbe@1309
|
3403 typeof testSelection.rangeCount == NUMBER && features.implementsDomRange) {
|
bsw/jbe@1309
|
3404
|
bsw/jbe@1309
|
3405 (function() {
|
bsw/jbe@1309
|
3406 // Previously an iframe was used but this caused problems in some circumstances in IE, so tests are
|
bsw/jbe@1309
|
3407 // performed on the current document's selection. See issue 109.
|
bsw/jbe@1309
|
3408
|
bsw/jbe@1309
|
3409 // Note also that if a selection previously existed, it is wiped and later restored by these tests. This
|
bsw/jbe@1309
|
3410 // will result in the selection direction begin reversed if the original selection was backwards and the
|
bsw/jbe@1309
|
3411 // browser does not support setting backwards selections (Internet Explorer, I'm looking at you).
|
bsw/jbe@1309
|
3412 var sel = window.getSelection();
|
bsw/jbe@1309
|
3413 if (sel) {
|
bsw/jbe@1309
|
3414 // Store the current selection
|
bsw/jbe@1309
|
3415 var originalSelectionRangeCount = sel.rangeCount;
|
bsw/jbe@1309
|
3416 var selectionHasMultipleRanges = (originalSelectionRangeCount > 1);
|
bsw/jbe@1309
|
3417 var originalSelectionRanges = [];
|
bsw/jbe@1309
|
3418 var originalSelectionBackward = winSelectionIsBackward(sel);
|
bsw/jbe@1309
|
3419 for (var i = 0; i < originalSelectionRangeCount; ++i) {
|
bsw/jbe@1309
|
3420 originalSelectionRanges[i] = sel.getRangeAt(i);
|
bsw/jbe@1309
|
3421 }
|
bsw/jbe@1309
|
3422
|
bsw/jbe@1309
|
3423 // Create some test elements
|
bsw/jbe@1309
|
3424 var testEl = dom.createTestElement(document, "", false);
|
bsw/jbe@1309
|
3425 var textNode = testEl.appendChild( document.createTextNode("\u00a0\u00a0\u00a0") );
|
bsw/jbe@1309
|
3426
|
bsw/jbe@1309
|
3427 // Test whether the native selection will allow a collapsed selection within a non-editable element
|
bsw/jbe@1309
|
3428 var r1 = document.createRange();
|
bsw/jbe@1309
|
3429
|
bsw/jbe@1309
|
3430 r1.setStart(textNode, 1);
|
bsw/jbe@1309
|
3431 r1.collapse(true);
|
bsw/jbe@1309
|
3432 sel.removeAllRanges();
|
bsw/jbe@1309
|
3433 sel.addRange(r1);
|
bsw/jbe@1309
|
3434 collapsedNonEditableSelectionsSupported = (sel.rangeCount == 1);
|
bsw/jbe@1309
|
3435 sel.removeAllRanges();
|
bsw/jbe@1309
|
3436
|
bsw/jbe@1309
|
3437 // Test whether the native selection is capable of supporting multiple ranges.
|
bsw/jbe@1309
|
3438 if (!selectionHasMultipleRanges) {
|
bsw/jbe@1309
|
3439 // Doing the original feature test here in Chrome 36 (and presumably later versions) prints a
|
bsw/jbe@1309
|
3440 // console error of "Discontiguous selection is not supported." that cannot be suppressed. There's
|
bsw/jbe@1309
|
3441 // nothing we can do about this while retaining the feature test so we have to resort to a browser
|
bsw/jbe@1309
|
3442 // sniff. I'm not happy about it. See
|
bsw/jbe@1309
|
3443 // https://code.google.com/p/chromium/issues/detail?id=399791
|
bsw/jbe@1309
|
3444 var chromeMatch = window.navigator.appVersion.match(/Chrome\/(.*?) /);
|
bsw/jbe@1309
|
3445 if (chromeMatch && parseInt(chromeMatch[1]) >= 36) {
|
bsw/jbe@1309
|
3446 selectionSupportsMultipleRanges = false;
|
bsw/jbe@1309
|
3447 } else {
|
bsw/jbe@1309
|
3448 var r2 = r1.cloneRange();
|
bsw/jbe@1309
|
3449 r1.setStart(textNode, 0);
|
bsw/jbe@1309
|
3450 r2.setEnd(textNode, 3);
|
bsw/jbe@1309
|
3451 r2.setStart(textNode, 2);
|
bsw/jbe@1309
|
3452 sel.addRange(r1);
|
bsw/jbe@1309
|
3453 sel.addRange(r2);
|
bsw/jbe@1309
|
3454 selectionSupportsMultipleRanges = (sel.rangeCount == 2);
|
bsw/jbe@1309
|
3455 }
|
bsw/jbe@1309
|
3456 }
|
bsw/jbe@1309
|
3457
|
bsw/jbe@1309
|
3458 // Clean up
|
bsw/jbe@1309
|
3459 dom.removeNode(testEl);
|
bsw/jbe@1309
|
3460 sel.removeAllRanges();
|
bsw/jbe@1309
|
3461
|
bsw/jbe@1309
|
3462 for (i = 0; i < originalSelectionRangeCount; ++i) {
|
bsw/jbe@1309
|
3463 if (i == 0 && originalSelectionBackward) {
|
bsw/jbe@1309
|
3464 if (addRangeBackwardToNative) {
|
bsw/jbe@1309
|
3465 addRangeBackwardToNative(sel, originalSelectionRanges[i]);
|
bsw/jbe@1309
|
3466 } else {
|
bsw/jbe@1309
|
3467 api.warn("Rangy initialization: original selection was backwards but selection has been restored forwards because the browser does not support Selection.extend");
|
bsw/jbe@1309
|
3468 sel.addRange(originalSelectionRanges[i]);
|
bsw/jbe@1309
|
3469 }
|
bsw/jbe@1309
|
3470 } else {
|
bsw/jbe@1309
|
3471 sel.addRange(originalSelectionRanges[i]);
|
bsw/jbe@1309
|
3472 }
|
bsw/jbe@1309
|
3473 }
|
bsw/jbe@1309
|
3474 }
|
bsw/jbe@1309
|
3475 })();
|
bsw/jbe@1309
|
3476 }
|
bsw/jbe@1309
|
3477
|
bsw/jbe@1309
|
3478 features.selectionSupportsMultipleRanges = selectionSupportsMultipleRanges;
|
bsw/jbe@1309
|
3479 features.collapsedNonEditableSelectionsSupported = collapsedNonEditableSelectionsSupported;
|
bsw/jbe@1309
|
3480
|
bsw/jbe@1309
|
3481 // ControlRanges
|
bsw/jbe@1309
|
3482 var implementsControlRange = false, testControlRange;
|
bsw/jbe@1309
|
3483
|
bsw/jbe@1309
|
3484 if (body && isHostMethod(body, "createControlRange")) {
|
bsw/jbe@1309
|
3485 testControlRange = body.createControlRange();
|
bsw/jbe@1309
|
3486 if (util.areHostProperties(testControlRange, ["item", "add"])) {
|
bsw/jbe@1309
|
3487 implementsControlRange = true;
|
bsw/jbe@1309
|
3488 }
|
bsw/jbe@1309
|
3489 }
|
bsw/jbe@1309
|
3490 features.implementsControlRange = implementsControlRange;
|
bsw/jbe@1309
|
3491
|
bsw/jbe@1309
|
3492 // Selection collapsedness
|
bsw/jbe@1309
|
3493 if (selectionHasAnchorAndFocus) {
|
bsw/jbe@1309
|
3494 selectionIsCollapsed = function(sel) {
|
bsw/jbe@1309
|
3495 return sel.anchorNode === sel.focusNode && sel.anchorOffset === sel.focusOffset;
|
bsw/jbe@1309
|
3496 };
|
bsw/jbe@1309
|
3497 } else {
|
bsw/jbe@1309
|
3498 selectionIsCollapsed = function(sel) {
|
bsw/jbe@1309
|
3499 return sel.rangeCount ? sel.getRangeAt(sel.rangeCount - 1).collapsed : false;
|
bsw/jbe@1309
|
3500 };
|
bsw/jbe@1309
|
3501 }
|
bsw/jbe@1309
|
3502
|
bsw/jbe@1309
|
3503 function updateAnchorAndFocusFromRange(sel, range, backward) {
|
bsw/jbe@1309
|
3504 var anchorPrefix = backward ? "end" : "start", focusPrefix = backward ? "start" : "end";
|
bsw/jbe@1309
|
3505 sel.anchorNode = range[anchorPrefix + "Container"];
|
bsw/jbe@1309
|
3506 sel.anchorOffset = range[anchorPrefix + "Offset"];
|
bsw/jbe@1309
|
3507 sel.focusNode = range[focusPrefix + "Container"];
|
bsw/jbe@1309
|
3508 sel.focusOffset = range[focusPrefix + "Offset"];
|
bsw/jbe@1309
|
3509 }
|
bsw/jbe@1309
|
3510
|
bsw/jbe@1309
|
3511 function updateAnchorAndFocusFromNativeSelection(sel) {
|
bsw/jbe@1309
|
3512 var nativeSel = sel.nativeSelection;
|
bsw/jbe@1309
|
3513 sel.anchorNode = nativeSel.anchorNode;
|
bsw/jbe@1309
|
3514 sel.anchorOffset = nativeSel.anchorOffset;
|
bsw/jbe@1309
|
3515 sel.focusNode = nativeSel.focusNode;
|
bsw/jbe@1309
|
3516 sel.focusOffset = nativeSel.focusOffset;
|
bsw/jbe@1309
|
3517 }
|
bsw/jbe@1309
|
3518
|
bsw/jbe@1309
|
3519 function updateEmptySelection(sel) {
|
bsw/jbe@1309
|
3520 sel.anchorNode = sel.focusNode = null;
|
bsw/jbe@1309
|
3521 sel.anchorOffset = sel.focusOffset = 0;
|
bsw/jbe@1309
|
3522 sel.rangeCount = 0;
|
bsw/jbe@1309
|
3523 sel.isCollapsed = true;
|
bsw/jbe@1309
|
3524 sel._ranges.length = 0;
|
bsw/jbe@1309
|
3525 }
|
bsw/jbe@1309
|
3526
|
bsw/jbe@1309
|
3527 function getNativeRange(range) {
|
bsw/jbe@1309
|
3528 var nativeRange;
|
bsw/jbe@1309
|
3529 if (range instanceof DomRange) {
|
bsw/jbe@1309
|
3530 nativeRange = api.createNativeRange(range.getDocument());
|
bsw/jbe@1309
|
3531 nativeRange.setEnd(range.endContainer, range.endOffset);
|
bsw/jbe@1309
|
3532 nativeRange.setStart(range.startContainer, range.startOffset);
|
bsw/jbe@1309
|
3533 } else if (range instanceof WrappedRange) {
|
bsw/jbe@1309
|
3534 nativeRange = range.nativeRange;
|
bsw/jbe@1309
|
3535 } else if (features.implementsDomRange && (range instanceof dom.getWindow(range.startContainer).Range)) {
|
bsw/jbe@1309
|
3536 nativeRange = range;
|
bsw/jbe@1309
|
3537 }
|
bsw/jbe@1309
|
3538 return nativeRange;
|
bsw/jbe@1309
|
3539 }
|
bsw/jbe@1309
|
3540
|
bsw/jbe@1309
|
3541 function rangeContainsSingleElement(rangeNodes) {
|
bsw/jbe@1309
|
3542 if (!rangeNodes.length || rangeNodes[0].nodeType != 1) {
|
bsw/jbe@1309
|
3543 return false;
|
bsw/jbe@1309
|
3544 }
|
bsw/jbe@1309
|
3545 for (var i = 1, len = rangeNodes.length; i < len; ++i) {
|
bsw/jbe@1309
|
3546 if (!dom.isAncestorOf(rangeNodes[0], rangeNodes[i])) {
|
bsw/jbe@1309
|
3547 return false;
|
bsw/jbe@1309
|
3548 }
|
bsw/jbe@1309
|
3549 }
|
bsw/jbe@1309
|
3550 return true;
|
bsw/jbe@1309
|
3551 }
|
bsw/jbe@1309
|
3552
|
bsw/jbe@1309
|
3553 function getSingleElementFromRange(range) {
|
bsw/jbe@1309
|
3554 var nodes = range.getNodes();
|
bsw/jbe@1309
|
3555 if (!rangeContainsSingleElement(nodes)) {
|
bsw/jbe@1309
|
3556 throw module.createError("getSingleElementFromRange: range " + range.inspect() + " did not consist of a single element");
|
bsw/jbe@1309
|
3557 }
|
bsw/jbe@1309
|
3558 return nodes[0];
|
bsw/jbe@1309
|
3559 }
|
bsw/jbe@1309
|
3560
|
bsw/jbe@1309
|
3561 // Simple, quick test which only needs to distinguish between a TextRange and a ControlRange
|
bsw/jbe@1309
|
3562 function isTextRange(range) {
|
bsw/jbe@1309
|
3563 return !!range && typeof range.text != "undefined";
|
bsw/jbe@1309
|
3564 }
|
bsw/jbe@1309
|
3565
|
bsw/jbe@1309
|
3566 function updateFromTextRange(sel, range) {
|
bsw/jbe@1309
|
3567 // Create a Range from the selected TextRange
|
bsw/jbe@1309
|
3568 var wrappedRange = new WrappedRange(range);
|
bsw/jbe@1309
|
3569 sel._ranges = [wrappedRange];
|
bsw/jbe@1309
|
3570
|
bsw/jbe@1309
|
3571 updateAnchorAndFocusFromRange(sel, wrappedRange, false);
|
bsw/jbe@1309
|
3572 sel.rangeCount = 1;
|
bsw/jbe@1309
|
3573 sel.isCollapsed = wrappedRange.collapsed;
|
bsw/jbe@1309
|
3574 }
|
bsw/jbe@1309
|
3575
|
bsw/jbe@1309
|
3576 function updateControlSelection(sel) {
|
bsw/jbe@1309
|
3577 // Update the wrapped selection based on what's now in the native selection
|
bsw/jbe@1309
|
3578 sel._ranges.length = 0;
|
bsw/jbe@1309
|
3579 if (sel.docSelection.type == "None") {
|
bsw/jbe@1309
|
3580 updateEmptySelection(sel);
|
bsw/jbe@1309
|
3581 } else {
|
bsw/jbe@1309
|
3582 var controlRange = sel.docSelection.createRange();
|
bsw/jbe@1309
|
3583 if (isTextRange(controlRange)) {
|
bsw/jbe@1309
|
3584 // This case (where the selection type is "Control" and calling createRange() on the selection returns
|
bsw/jbe@1309
|
3585 // a TextRange) can happen in IE 9. It happens, for example, when all elements in the selected
|
bsw/jbe@1309
|
3586 // ControlRange have been removed from the ControlRange and removed from the document.
|
bsw/jbe@1309
|
3587 updateFromTextRange(sel, controlRange);
|
bsw/jbe@1309
|
3588 } else {
|
bsw/jbe@1309
|
3589 sel.rangeCount = controlRange.length;
|
bsw/jbe@1309
|
3590 var range, doc = getDocument(controlRange.item(0));
|
bsw/jbe@1309
|
3591 for (var i = 0; i < sel.rangeCount; ++i) {
|
bsw/jbe@1309
|
3592 range = api.createRange(doc);
|
bsw/jbe@1309
|
3593 range.selectNode(controlRange.item(i));
|
bsw/jbe@1309
|
3594 sel._ranges.push(range);
|
bsw/jbe@1309
|
3595 }
|
bsw/jbe@1309
|
3596 sel.isCollapsed = sel.rangeCount == 1 && sel._ranges[0].collapsed;
|
bsw/jbe@1309
|
3597 updateAnchorAndFocusFromRange(sel, sel._ranges[sel.rangeCount - 1], false);
|
bsw/jbe@1309
|
3598 }
|
bsw/jbe@1309
|
3599 }
|
bsw/jbe@1309
|
3600 }
|
bsw/jbe@1309
|
3601
|
bsw/jbe@1309
|
3602 function addRangeToControlSelection(sel, range) {
|
bsw/jbe@1309
|
3603 var controlRange = sel.docSelection.createRange();
|
bsw/jbe@1309
|
3604 var rangeElement = getSingleElementFromRange(range);
|
bsw/jbe@1309
|
3605
|
bsw/jbe@1309
|
3606 // Create a new ControlRange containing all the elements in the selected ControlRange plus the element
|
bsw/jbe@1309
|
3607 // contained by the supplied range
|
bsw/jbe@1309
|
3608 var doc = getDocument(controlRange.item(0));
|
bsw/jbe@1309
|
3609 var newControlRange = getBody(doc).createControlRange();
|
bsw/jbe@1309
|
3610 for (var i = 0, len = controlRange.length; i < len; ++i) {
|
bsw/jbe@1309
|
3611 newControlRange.add(controlRange.item(i));
|
bsw/jbe@1309
|
3612 }
|
bsw/jbe@1309
|
3613 try {
|
bsw/jbe@1309
|
3614 newControlRange.add(rangeElement);
|
bsw/jbe@1309
|
3615 } catch (ex) {
|
bsw/jbe@1309
|
3616 throw module.createError("addRange(): Element within the specified Range could not be added to control selection (does it have layout?)");
|
bsw/jbe@1309
|
3617 }
|
bsw/jbe@1309
|
3618 newControlRange.select();
|
bsw/jbe@1309
|
3619
|
bsw/jbe@1309
|
3620 // Update the wrapped selection based on what's now in the native selection
|
bsw/jbe@1309
|
3621 updateControlSelection(sel);
|
bsw/jbe@1309
|
3622 }
|
bsw/jbe@1309
|
3623
|
bsw/jbe@1309
|
3624 var getSelectionRangeAt;
|
bsw/jbe@1309
|
3625
|
bsw/jbe@1309
|
3626 if (isHostMethod(testSelection, "getRangeAt")) {
|
bsw/jbe@1309
|
3627 // try/catch is present because getRangeAt() must have thrown an error in some browser and some situation.
|
bsw/jbe@1309
|
3628 // Unfortunately, I didn't write a comment about the specifics and am now scared to take it out. Let that be a
|
bsw/jbe@1309
|
3629 // lesson to us all, especially me.
|
bsw/jbe@1309
|
3630 getSelectionRangeAt = function(sel, index) {
|
bsw/jbe@1309
|
3631 try {
|
bsw/jbe@1309
|
3632 return sel.getRangeAt(index);
|
bsw/jbe@1309
|
3633 } catch (ex) {
|
bsw/jbe@1309
|
3634 return null;
|
bsw/jbe@1309
|
3635 }
|
bsw/jbe@1309
|
3636 };
|
bsw/jbe@1309
|
3637 } else if (selectionHasAnchorAndFocus) {
|
bsw/jbe@1309
|
3638 getSelectionRangeAt = function(sel) {
|
bsw/jbe@1309
|
3639 var doc = getDocument(sel.anchorNode);
|
bsw/jbe@1309
|
3640 var range = api.createRange(doc);
|
bsw/jbe@1309
|
3641 range.setStartAndEnd(sel.anchorNode, sel.anchorOffset, sel.focusNode, sel.focusOffset);
|
bsw/jbe@1309
|
3642
|
bsw/jbe@1309
|
3643 // Handle the case when the selection was selected backwards (from the end to the start in the
|
bsw/jbe@1309
|
3644 // document)
|
bsw/jbe@1309
|
3645 if (range.collapsed !== this.isCollapsed) {
|
bsw/jbe@1309
|
3646 range.setStartAndEnd(sel.focusNode, sel.focusOffset, sel.anchorNode, sel.anchorOffset);
|
bsw/jbe@1309
|
3647 }
|
bsw/jbe@1309
|
3648
|
bsw/jbe@1309
|
3649 return range;
|
bsw/jbe@1309
|
3650 };
|
bsw/jbe@1309
|
3651 }
|
bsw/jbe@1309
|
3652
|
bsw/jbe@1309
|
3653 function WrappedSelection(selection, docSelection, win) {
|
bsw/jbe@1309
|
3654 this.nativeSelection = selection;
|
bsw/jbe@1309
|
3655 this.docSelection = docSelection;
|
bsw/jbe@1309
|
3656 this._ranges = [];
|
bsw/jbe@1309
|
3657 this.win = win;
|
bsw/jbe@1309
|
3658 this.refresh();
|
bsw/jbe@1309
|
3659 }
|
bsw/jbe@1309
|
3660
|
bsw/jbe@1309
|
3661 WrappedSelection.prototype = api.selectionPrototype;
|
bsw/jbe@1309
|
3662
|
bsw/jbe@1309
|
3663 function deleteProperties(sel) {
|
bsw/jbe@1309
|
3664 sel.win = sel.anchorNode = sel.focusNode = sel._ranges = null;
|
bsw/jbe@1309
|
3665 sel.rangeCount = sel.anchorOffset = sel.focusOffset = 0;
|
bsw/jbe@1309
|
3666 sel.detached = true;
|
bsw/jbe@1309
|
3667 }
|
bsw/jbe@1309
|
3668
|
bsw/jbe@1309
|
3669 var cachedRangySelections = [];
|
bsw/jbe@1309
|
3670
|
bsw/jbe@1309
|
3671 function actOnCachedSelection(win, action) {
|
bsw/jbe@1309
|
3672 var i = cachedRangySelections.length, cached, sel;
|
bsw/jbe@1309
|
3673 while (i--) {
|
bsw/jbe@1309
|
3674 cached = cachedRangySelections[i];
|
bsw/jbe@1309
|
3675 sel = cached.selection;
|
bsw/jbe@1309
|
3676 if (action == "deleteAll") {
|
bsw/jbe@1309
|
3677 deleteProperties(sel);
|
bsw/jbe@1309
|
3678 } else if (cached.win == win) {
|
bsw/jbe@1309
|
3679 if (action == "delete") {
|
bsw/jbe@1309
|
3680 cachedRangySelections.splice(i, 1);
|
bsw/jbe@1309
|
3681 return true;
|
bsw/jbe@1309
|
3682 } else {
|
bsw/jbe@1309
|
3683 return sel;
|
bsw/jbe@1309
|
3684 }
|
bsw/jbe@1309
|
3685 }
|
bsw/jbe@1309
|
3686 }
|
bsw/jbe@1309
|
3687 if (action == "deleteAll") {
|
bsw/jbe@1309
|
3688 cachedRangySelections.length = 0;
|
bsw/jbe@1309
|
3689 }
|
bsw/jbe@1309
|
3690 return null;
|
bsw/jbe@1309
|
3691 }
|
bsw/jbe@1309
|
3692
|
bsw/jbe@1309
|
3693 var getSelection = function(win) {
|
bsw/jbe@1309
|
3694 // Check if the parameter is a Rangy Selection object
|
bsw/jbe@1309
|
3695 if (win && win instanceof WrappedSelection) {
|
bsw/jbe@1309
|
3696 win.refresh();
|
bsw/jbe@1309
|
3697 return win;
|
bsw/jbe@1309
|
3698 }
|
bsw/jbe@1309
|
3699
|
bsw/jbe@1309
|
3700 win = getWindow(win, "getNativeSelection");
|
bsw/jbe@1309
|
3701
|
bsw/jbe@1309
|
3702 var sel = actOnCachedSelection(win);
|
bsw/jbe@1309
|
3703 var nativeSel = getNativeSelection(win), docSel = implementsDocSelection ? getDocSelection(win) : null;
|
bsw/jbe@1309
|
3704 if (sel) {
|
bsw/jbe@1309
|
3705 sel.nativeSelection = nativeSel;
|
bsw/jbe@1309
|
3706 sel.docSelection = docSel;
|
bsw/jbe@1309
|
3707 sel.refresh();
|
bsw/jbe@1309
|
3708 } else {
|
bsw/jbe@1309
|
3709 sel = new WrappedSelection(nativeSel, docSel, win);
|
bsw/jbe@1309
|
3710 cachedRangySelections.push( { win: win, selection: sel } );
|
bsw/jbe@1309
|
3711 }
|
bsw/jbe@1309
|
3712 return sel;
|
bsw/jbe@1309
|
3713 };
|
bsw/jbe@1309
|
3714
|
bsw/jbe@1309
|
3715 api.getSelection = getSelection;
|
bsw/jbe@1309
|
3716
|
bsw/jbe@1309
|
3717 util.createAliasForDeprecatedMethod(api, "getIframeSelection", "getSelection");
|
bsw/jbe@1309
|
3718
|
bsw/jbe@1309
|
3719 var selProto = WrappedSelection.prototype;
|
bsw/jbe@1309
|
3720
|
bsw/jbe@1309
|
3721 function createControlSelection(sel, ranges) {
|
bsw/jbe@1309
|
3722 // Ensure that the selection becomes of type "Control"
|
bsw/jbe@1309
|
3723 var doc = getDocument(ranges[0].startContainer);
|
bsw/jbe@1309
|
3724 var controlRange = getBody(doc).createControlRange();
|
bsw/jbe@1309
|
3725 for (var i = 0, el, len = ranges.length; i < len; ++i) {
|
bsw/jbe@1309
|
3726 el = getSingleElementFromRange(ranges[i]);
|
bsw/jbe@1309
|
3727 try {
|
bsw/jbe@1309
|
3728 controlRange.add(el);
|
bsw/jbe@1309
|
3729 } catch (ex) {
|
bsw/jbe@1309
|
3730 throw module.createError("setRanges(): Element within one of the specified Ranges could not be added to control selection (does it have layout?)");
|
bsw/jbe@1309
|
3731 }
|
bsw/jbe@1309
|
3732 }
|
bsw/jbe@1309
|
3733 controlRange.select();
|
bsw/jbe@1309
|
3734
|
bsw/jbe@1309
|
3735 // Update the wrapped selection based on what's now in the native selection
|
bsw/jbe@1309
|
3736 updateControlSelection(sel);
|
bsw/jbe@1309
|
3737 }
|
bsw/jbe@1309
|
3738
|
bsw/jbe@1309
|
3739 // Selecting a range
|
bsw/jbe@1309
|
3740 if (!useDocumentSelection && selectionHasAnchorAndFocus && util.areHostMethods(testSelection, ["removeAllRanges", "addRange"])) {
|
bsw/jbe@1309
|
3741 selProto.removeAllRanges = function() {
|
bsw/jbe@1309
|
3742 this.nativeSelection.removeAllRanges();
|
bsw/jbe@1309
|
3743 updateEmptySelection(this);
|
bsw/jbe@1309
|
3744 };
|
bsw/jbe@1309
|
3745
|
bsw/jbe@1309
|
3746 var addRangeBackward = function(sel, range) {
|
bsw/jbe@1309
|
3747 addRangeBackwardToNative(sel.nativeSelection, range);
|
bsw/jbe@1309
|
3748 sel.refresh();
|
bsw/jbe@1309
|
3749 };
|
bsw/jbe@1309
|
3750
|
bsw/jbe@1309
|
3751 if (selectionHasRangeCount) {
|
bsw/jbe@1309
|
3752 selProto.addRange = function(range, direction) {
|
bsw/jbe@1309
|
3753 if (implementsControlRange && implementsDocSelection && this.docSelection.type == CONTROL) {
|
bsw/jbe@1309
|
3754 addRangeToControlSelection(this, range);
|
bsw/jbe@1309
|
3755 } else {
|
bsw/jbe@1309
|
3756 if (isDirectionBackward(direction) && selectionHasExtend) {
|
bsw/jbe@1309
|
3757 addRangeBackward(this, range);
|
bsw/jbe@1309
|
3758 } else {
|
bsw/jbe@1309
|
3759 var previousRangeCount;
|
bsw/jbe@1309
|
3760 if (selectionSupportsMultipleRanges) {
|
bsw/jbe@1309
|
3761 previousRangeCount = this.rangeCount;
|
bsw/jbe@1309
|
3762 } else {
|
bsw/jbe@1309
|
3763 this.removeAllRanges();
|
bsw/jbe@1309
|
3764 previousRangeCount = 0;
|
bsw/jbe@1309
|
3765 }
|
bsw/jbe@1309
|
3766 // Clone the native range so that changing the selected range does not affect the selection.
|
bsw/jbe@1309
|
3767 // This is contrary to the spec but is the only way to achieve consistency between browsers. See
|
bsw/jbe@1309
|
3768 // issue 80.
|
bsw/jbe@1309
|
3769 var clonedNativeRange = getNativeRange(range).cloneRange();
|
bsw/jbe@1309
|
3770 try {
|
bsw/jbe@1309
|
3771 this.nativeSelection.addRange(clonedNativeRange);
|
bsw/jbe@1309
|
3772 } catch (ex) {
|
bsw/jbe@1309
|
3773 }
|
bsw/jbe@1309
|
3774
|
bsw/jbe@1309
|
3775 // Check whether adding the range was successful
|
bsw/jbe@1309
|
3776 this.rangeCount = this.nativeSelection.rangeCount;
|
bsw/jbe@1309
|
3777
|
bsw/jbe@1309
|
3778 if (this.rangeCount == previousRangeCount + 1) {
|
bsw/jbe@1309
|
3779 // The range was added successfully
|
bsw/jbe@1309
|
3780
|
bsw/jbe@1309
|
3781 // Check whether the range that we added to the selection is reflected in the last range extracted from
|
bsw/jbe@1309
|
3782 // the selection
|
bsw/jbe@1309
|
3783 if (api.config.checkSelectionRanges) {
|
bsw/jbe@1309
|
3784 var nativeRange = getSelectionRangeAt(this.nativeSelection, this.rangeCount - 1);
|
bsw/jbe@1309
|
3785 if (nativeRange && !rangesEqual(nativeRange, range)) {
|
bsw/jbe@1309
|
3786 // Happens in WebKit with, for example, a selection placed at the start of a text node
|
bsw/jbe@1309
|
3787 range = new WrappedRange(nativeRange);
|
bsw/jbe@1309
|
3788 }
|
bsw/jbe@1309
|
3789 }
|
bsw/jbe@1309
|
3790 this._ranges[this.rangeCount - 1] = range;
|
bsw/jbe@1309
|
3791 updateAnchorAndFocusFromRange(this, range, selectionIsBackward(this.nativeSelection));
|
bsw/jbe@1309
|
3792 this.isCollapsed = selectionIsCollapsed(this);
|
bsw/jbe@1309
|
3793 } else {
|
bsw/jbe@1309
|
3794 // The range was not added successfully. The simplest thing is to refresh
|
bsw/jbe@1309
|
3795 this.refresh();
|
bsw/jbe@1309
|
3796 }
|
bsw/jbe@1309
|
3797 }
|
bsw/jbe@1309
|
3798 }
|
bsw/jbe@1309
|
3799 };
|
bsw/jbe@1309
|
3800 } else {
|
bsw/jbe@1309
|
3801 selProto.addRange = function(range, direction) {
|
bsw/jbe@1309
|
3802 if (isDirectionBackward(direction) && selectionHasExtend) {
|
bsw/jbe@1309
|
3803 addRangeBackward(this, range);
|
bsw/jbe@1309
|
3804 } else {
|
bsw/jbe@1309
|
3805 this.nativeSelection.addRange(getNativeRange(range));
|
bsw/jbe@1309
|
3806 this.refresh();
|
bsw/jbe@1309
|
3807 }
|
bsw/jbe@1309
|
3808 };
|
bsw/jbe@1309
|
3809 }
|
bsw/jbe@1309
|
3810
|
bsw/jbe@1309
|
3811 selProto.setRanges = function(ranges) {
|
bsw/jbe@1309
|
3812 if (implementsControlRange && implementsDocSelection && ranges.length > 1) {
|
bsw/jbe@1309
|
3813 createControlSelection(this, ranges);
|
bsw/jbe@1309
|
3814 } else {
|
bsw/jbe@1309
|
3815 this.removeAllRanges();
|
bsw/jbe@1309
|
3816 for (var i = 0, len = ranges.length; i < len; ++i) {
|
bsw/jbe@1309
|
3817 this.addRange(ranges[i]);
|
bsw/jbe@1309
|
3818 }
|
bsw/jbe@1309
|
3819 }
|
bsw/jbe@1309
|
3820 };
|
bsw/jbe@1309
|
3821 } else if (isHostMethod(testSelection, "empty") && isHostMethod(testRange, "select") &&
|
bsw/jbe@1309
|
3822 implementsControlRange && useDocumentSelection) {
|
bsw/jbe@1309
|
3823
|
bsw/jbe@1309
|
3824 selProto.removeAllRanges = function() {
|
bsw/jbe@1309
|
3825 // Added try/catch as fix for issue #21
|
bsw/jbe@1309
|
3826 try {
|
bsw/jbe@1309
|
3827 this.docSelection.empty();
|
bsw/jbe@1309
|
3828
|
bsw/jbe@1309
|
3829 // Check for empty() not working (issue #24)
|
bsw/jbe@1309
|
3830 if (this.docSelection.type != "None") {
|
bsw/jbe@1309
|
3831 // Work around failure to empty a control selection by instead selecting a TextRange and then
|
bsw/jbe@1309
|
3832 // calling empty()
|
bsw/jbe@1309
|
3833 var doc;
|
bsw/jbe@1309
|
3834 if (this.anchorNode) {
|
bsw/jbe@1309
|
3835 doc = getDocument(this.anchorNode);
|
bsw/jbe@1309
|
3836 } else if (this.docSelection.type == CONTROL) {
|
bsw/jbe@1309
|
3837 var controlRange = this.docSelection.createRange();
|
bsw/jbe@1309
|
3838 if (controlRange.length) {
|
bsw/jbe@1309
|
3839 doc = getDocument( controlRange.item(0) );
|
bsw/jbe@1309
|
3840 }
|
bsw/jbe@1309
|
3841 }
|
bsw/jbe@1309
|
3842 if (doc) {
|
bsw/jbe@1309
|
3843 var textRange = getBody(doc).createTextRange();
|
bsw/jbe@1309
|
3844 textRange.select();
|
bsw/jbe@1309
|
3845 this.docSelection.empty();
|
bsw/jbe@1309
|
3846 }
|
bsw/jbe@1309
|
3847 }
|
bsw/jbe@1309
|
3848 } catch(ex) {}
|
bsw/jbe@1309
|
3849 updateEmptySelection(this);
|
bsw/jbe@1309
|
3850 };
|
bsw/jbe@1309
|
3851
|
bsw/jbe@1309
|
3852 selProto.addRange = function(range) {
|
bsw/jbe@1309
|
3853 if (this.docSelection.type == CONTROL) {
|
bsw/jbe@1309
|
3854 addRangeToControlSelection(this, range);
|
bsw/jbe@1309
|
3855 } else {
|
bsw/jbe@1309
|
3856 api.WrappedTextRange.rangeToTextRange(range).select();
|
bsw/jbe@1309
|
3857 this._ranges[0] = range;
|
bsw/jbe@1309
|
3858 this.rangeCount = 1;
|
bsw/jbe@1309
|
3859 this.isCollapsed = this._ranges[0].collapsed;
|
bsw/jbe@1309
|
3860 updateAnchorAndFocusFromRange(this, range, false);
|
bsw/jbe@1309
|
3861 }
|
bsw/jbe@1309
|
3862 };
|
bsw/jbe@1309
|
3863
|
bsw/jbe@1309
|
3864 selProto.setRanges = function(ranges) {
|
bsw/jbe@1309
|
3865 this.removeAllRanges();
|
bsw/jbe@1309
|
3866 var rangeCount = ranges.length;
|
bsw/jbe@1309
|
3867 if (rangeCount > 1) {
|
bsw/jbe@1309
|
3868 createControlSelection(this, ranges);
|
bsw/jbe@1309
|
3869 } else if (rangeCount) {
|
bsw/jbe@1309
|
3870 this.addRange(ranges[0]);
|
bsw/jbe@1309
|
3871 }
|
bsw/jbe@1309
|
3872 };
|
bsw/jbe@1309
|
3873 } else {
|
bsw/jbe@1309
|
3874 module.fail("No means of selecting a Range or TextRange was found");
|
bsw/jbe@1309
|
3875 return false;
|
bsw/jbe@1309
|
3876 }
|
bsw/jbe@1309
|
3877
|
bsw/jbe@1309
|
3878 selProto.getRangeAt = function(index) {
|
bsw/jbe@1309
|
3879 if (index < 0 || index >= this.rangeCount) {
|
bsw/jbe@1309
|
3880 throw new DOMException("INDEX_SIZE_ERR");
|
bsw/jbe@1309
|
3881 } else {
|
bsw/jbe@1309
|
3882 // Clone the range to preserve selection-range independence. See issue 80.
|
bsw/jbe@1309
|
3883 return this._ranges[index].cloneRange();
|
bsw/jbe@1309
|
3884 }
|
bsw/jbe@1309
|
3885 };
|
bsw/jbe@1309
|
3886
|
bsw/jbe@1309
|
3887 var refreshSelection;
|
bsw/jbe@1309
|
3888
|
bsw/jbe@1309
|
3889 if (useDocumentSelection) {
|
bsw/jbe@1309
|
3890 refreshSelection = function(sel) {
|
bsw/jbe@1309
|
3891 var range;
|
bsw/jbe@1309
|
3892 if (api.isSelectionValid(sel.win)) {
|
bsw/jbe@1309
|
3893 range = sel.docSelection.createRange();
|
bsw/jbe@1309
|
3894 } else {
|
bsw/jbe@1309
|
3895 range = getBody(sel.win.document).createTextRange();
|
bsw/jbe@1309
|
3896 range.collapse(true);
|
bsw/jbe@1309
|
3897 }
|
bsw/jbe@1309
|
3898
|
bsw/jbe@1309
|
3899 if (sel.docSelection.type == CONTROL) {
|
bsw/jbe@1309
|
3900 updateControlSelection(sel);
|
bsw/jbe@1309
|
3901 } else if (isTextRange(range)) {
|
bsw/jbe@1309
|
3902 updateFromTextRange(sel, range);
|
bsw/jbe@1309
|
3903 } else {
|
bsw/jbe@1309
|
3904 updateEmptySelection(sel);
|
bsw/jbe@1309
|
3905 }
|
bsw/jbe@1309
|
3906 };
|
bsw/jbe@1309
|
3907 } else if (isHostMethod(testSelection, "getRangeAt") && typeof testSelection.rangeCount == NUMBER) {
|
bsw/jbe@1309
|
3908 refreshSelection = function(sel) {
|
bsw/jbe@1309
|
3909 if (implementsControlRange && implementsDocSelection && sel.docSelection.type == CONTROL) {
|
bsw/jbe@1309
|
3910 updateControlSelection(sel);
|
bsw/jbe@1309
|
3911 } else {
|
bsw/jbe@1309
|
3912 sel._ranges.length = sel.rangeCount = sel.nativeSelection.rangeCount;
|
bsw/jbe@1309
|
3913 if (sel.rangeCount) {
|
bsw/jbe@1309
|
3914 for (var i = 0, len = sel.rangeCount; i < len; ++i) {
|
bsw/jbe@1309
|
3915 sel._ranges[i] = new api.WrappedRange(sel.nativeSelection.getRangeAt(i));
|
bsw/jbe@1309
|
3916 }
|
bsw/jbe@1309
|
3917 updateAnchorAndFocusFromRange(sel, sel._ranges[sel.rangeCount - 1], selectionIsBackward(sel.nativeSelection));
|
bsw/jbe@1309
|
3918 sel.isCollapsed = selectionIsCollapsed(sel);
|
bsw/jbe@1309
|
3919 } else {
|
bsw/jbe@1309
|
3920 updateEmptySelection(sel);
|
bsw/jbe@1309
|
3921 }
|
bsw/jbe@1309
|
3922 }
|
bsw/jbe@1309
|
3923 };
|
bsw/jbe@1309
|
3924 } else if (selectionHasAnchorAndFocus && typeof testSelection.isCollapsed == BOOLEAN && typeof testRange.collapsed == BOOLEAN && features.implementsDomRange) {
|
bsw/jbe@1309
|
3925 refreshSelection = function(sel) {
|
bsw/jbe@1309
|
3926 var range, nativeSel = sel.nativeSelection;
|
bsw/jbe@1309
|
3927 if (nativeSel.anchorNode) {
|
bsw/jbe@1309
|
3928 range = getSelectionRangeAt(nativeSel, 0);
|
bsw/jbe@1309
|
3929 sel._ranges = [range];
|
bsw/jbe@1309
|
3930 sel.rangeCount = 1;
|
bsw/jbe@1309
|
3931 updateAnchorAndFocusFromNativeSelection(sel);
|
bsw/jbe@1309
|
3932 sel.isCollapsed = selectionIsCollapsed(sel);
|
bsw/jbe@1309
|
3933 } else {
|
bsw/jbe@1309
|
3934 updateEmptySelection(sel);
|
bsw/jbe@1309
|
3935 }
|
bsw/jbe@1309
|
3936 };
|
bsw/jbe@1309
|
3937 } else {
|
bsw/jbe@1309
|
3938 module.fail("No means of obtaining a Range or TextRange from the user's selection was found");
|
bsw/jbe@1309
|
3939 return false;
|
bsw/jbe@1309
|
3940 }
|
bsw/jbe@1309
|
3941
|
bsw/jbe@1309
|
3942 selProto.refresh = function(checkForChanges) {
|
bsw/jbe@1309
|
3943 var oldRanges = checkForChanges ? this._ranges.slice(0) : null;
|
bsw/jbe@1309
|
3944 var oldAnchorNode = this.anchorNode, oldAnchorOffset = this.anchorOffset;
|
bsw/jbe@1309
|
3945
|
bsw/jbe@1309
|
3946 refreshSelection(this);
|
bsw/jbe@1309
|
3947 if (checkForChanges) {
|
bsw/jbe@1309
|
3948 // Check the range count first
|
bsw/jbe@1309
|
3949 var i = oldRanges.length;
|
bsw/jbe@1309
|
3950 if (i != this._ranges.length) {
|
bsw/jbe@1309
|
3951 return true;
|
bsw/jbe@1309
|
3952 }
|
bsw/jbe@1309
|
3953
|
bsw/jbe@1309
|
3954 // Now check the direction. Checking the anchor position is the same is enough since we're checking all the
|
bsw/jbe@1309
|
3955 // ranges after this
|
bsw/jbe@1309
|
3956 if (this.anchorNode != oldAnchorNode || this.anchorOffset != oldAnchorOffset) {
|
bsw/jbe@1309
|
3957 return true;
|
bsw/jbe@1309
|
3958 }
|
bsw/jbe@1309
|
3959
|
bsw/jbe@1309
|
3960 // Finally, compare each range in turn
|
bsw/jbe@1309
|
3961 while (i--) {
|
bsw/jbe@1309
|
3962 if (!rangesEqual(oldRanges[i], this._ranges[i])) {
|
bsw/jbe@1309
|
3963 return true;
|
bsw/jbe@1309
|
3964 }
|
bsw/jbe@1309
|
3965 }
|
bsw/jbe@1309
|
3966 return false;
|
bsw/jbe@1309
|
3967 }
|
bsw/jbe@1309
|
3968 };
|
bsw/jbe@1309
|
3969
|
bsw/jbe@1309
|
3970 // Removal of a single range
|
bsw/jbe@1309
|
3971 var removeRangeManually = function(sel, range) {
|
bsw/jbe@1309
|
3972 var ranges = sel.getAllRanges();
|
bsw/jbe@1309
|
3973 sel.removeAllRanges();
|
bsw/jbe@1309
|
3974 for (var i = 0, len = ranges.length; i < len; ++i) {
|
bsw/jbe@1309
|
3975 if (!rangesEqual(range, ranges[i])) {
|
bsw/jbe@1309
|
3976 sel.addRange(ranges[i]);
|
bsw/jbe@1309
|
3977 }
|
bsw/jbe@1309
|
3978 }
|
bsw/jbe@1309
|
3979 if (!sel.rangeCount) {
|
bsw/jbe@1309
|
3980 updateEmptySelection(sel);
|
bsw/jbe@1309
|
3981 }
|
bsw/jbe@1309
|
3982 };
|
bsw/jbe@1309
|
3983
|
bsw/jbe@1309
|
3984 if (implementsControlRange && implementsDocSelection) {
|
bsw/jbe@1309
|
3985 selProto.removeRange = function(range) {
|
bsw/jbe@1309
|
3986 if (this.docSelection.type == CONTROL) {
|
bsw/jbe@1309
|
3987 var controlRange = this.docSelection.createRange();
|
bsw/jbe@1309
|
3988 var rangeElement = getSingleElementFromRange(range);
|
bsw/jbe@1309
|
3989
|
bsw/jbe@1309
|
3990 // Create a new ControlRange containing all the elements in the selected ControlRange minus the
|
bsw/jbe@1309
|
3991 // element contained by the supplied range
|
bsw/jbe@1309
|
3992 var doc = getDocument(controlRange.item(0));
|
bsw/jbe@1309
|
3993 var newControlRange = getBody(doc).createControlRange();
|
bsw/jbe@1309
|
3994 var el, removed = false;
|
bsw/jbe@1309
|
3995 for (var i = 0, len = controlRange.length; i < len; ++i) {
|
bsw/jbe@1309
|
3996 el = controlRange.item(i);
|
bsw/jbe@1309
|
3997 if (el !== rangeElement || removed) {
|
bsw/jbe@1309
|
3998 newControlRange.add(controlRange.item(i));
|
bsw/jbe@1309
|
3999 } else {
|
bsw/jbe@1309
|
4000 removed = true;
|
bsw/jbe@1309
|
4001 }
|
bsw/jbe@1309
|
4002 }
|
bsw/jbe@1309
|
4003 newControlRange.select();
|
bsw/jbe@1309
|
4004
|
bsw/jbe@1309
|
4005 // Update the wrapped selection based on what's now in the native selection
|
bsw/jbe@1309
|
4006 updateControlSelection(this);
|
bsw/jbe@1309
|
4007 } else {
|
bsw/jbe@1309
|
4008 removeRangeManually(this, range);
|
bsw/jbe@1309
|
4009 }
|
bsw/jbe@1309
|
4010 };
|
bsw/jbe@1309
|
4011 } else {
|
bsw/jbe@1309
|
4012 selProto.removeRange = function(range) {
|
bsw/jbe@1309
|
4013 removeRangeManually(this, range);
|
bsw/jbe@1309
|
4014 };
|
bsw/jbe@1309
|
4015 }
|
bsw/jbe@1309
|
4016
|
bsw/jbe@1309
|
4017 // Detecting if a selection is backward
|
bsw/jbe@1309
|
4018 var selectionIsBackward;
|
bsw/jbe@1309
|
4019 if (!useDocumentSelection && selectionHasAnchorAndFocus && features.implementsDomRange) {
|
bsw/jbe@1309
|
4020 selectionIsBackward = winSelectionIsBackward;
|
bsw/jbe@1309
|
4021
|
bsw/jbe@1309
|
4022 selProto.isBackward = function() {
|
bsw/jbe@1309
|
4023 return selectionIsBackward(this);
|
bsw/jbe@1309
|
4024 };
|
bsw/jbe@1309
|
4025 } else {
|
bsw/jbe@1309
|
4026 selectionIsBackward = selProto.isBackward = function() {
|
bsw/jbe@1309
|
4027 return false;
|
bsw/jbe@1309
|
4028 };
|
bsw/jbe@1309
|
4029 }
|
bsw/jbe@1309
|
4030
|
bsw/jbe@1309
|
4031 // Create an alias for backwards compatibility. From 1.3, everything is "backward" rather than "backwards"
|
bsw/jbe@1309
|
4032 selProto.isBackwards = selProto.isBackward;
|
bsw/jbe@1309
|
4033
|
bsw/jbe@1309
|
4034 // Selection stringifier
|
bsw/jbe@1309
|
4035 // This is conformant to the old HTML5 selections draft spec but differs from WebKit and Mozilla's implementation.
|
bsw/jbe@1309
|
4036 // The current spec does not yet define this method.
|
bsw/jbe@1309
|
4037 selProto.toString = function() {
|
bsw/jbe@1309
|
4038 var rangeTexts = [];
|
bsw/jbe@1309
|
4039 for (var i = 0, len = this.rangeCount; i < len; ++i) {
|
bsw/jbe@1309
|
4040 rangeTexts[i] = "" + this._ranges[i];
|
bsw/jbe@1309
|
4041 }
|
bsw/jbe@1309
|
4042 return rangeTexts.join("");
|
bsw/jbe@1309
|
4043 };
|
bsw/jbe@1309
|
4044
|
bsw/jbe@1309
|
4045 function assertNodeInSameDocument(sel, node) {
|
bsw/jbe@1309
|
4046 if (sel.win.document != getDocument(node)) {
|
bsw/jbe@1309
|
4047 throw new DOMException("WRONG_DOCUMENT_ERR");
|
bsw/jbe@1309
|
4048 }
|
bsw/jbe@1309
|
4049 }
|
bsw/jbe@1309
|
4050
|
bsw/jbe@1309
|
4051 // No current browser conforms fully to the spec for this method, so Rangy's own method is always used
|
bsw/jbe@1309
|
4052 selProto.collapse = function(node, offset) {
|
bsw/jbe@1309
|
4053 assertNodeInSameDocument(this, node);
|
bsw/jbe@1309
|
4054 var range = api.createRange(node);
|
bsw/jbe@1309
|
4055 range.collapseToPoint(node, offset);
|
bsw/jbe@1309
|
4056 this.setSingleRange(range);
|
bsw/jbe@1309
|
4057 this.isCollapsed = true;
|
bsw/jbe@1309
|
4058 };
|
bsw/jbe@1309
|
4059
|
bsw/jbe@1309
|
4060 selProto.collapseToStart = function() {
|
bsw/jbe@1309
|
4061 if (this.rangeCount) {
|
bsw/jbe@1309
|
4062 var range = this._ranges[0];
|
bsw/jbe@1309
|
4063 this.collapse(range.startContainer, range.startOffset);
|
bsw/jbe@1309
|
4064 } else {
|
bsw/jbe@1309
|
4065 throw new DOMException("INVALID_STATE_ERR");
|
bsw/jbe@1309
|
4066 }
|
bsw/jbe@1309
|
4067 };
|
bsw/jbe@1309
|
4068
|
bsw/jbe@1309
|
4069 selProto.collapseToEnd = function() {
|
bsw/jbe@1309
|
4070 if (this.rangeCount) {
|
bsw/jbe@1309
|
4071 var range = this._ranges[this.rangeCount - 1];
|
bsw/jbe@1309
|
4072 this.collapse(range.endContainer, range.endOffset);
|
bsw/jbe@1309
|
4073 } else {
|
bsw/jbe@1309
|
4074 throw new DOMException("INVALID_STATE_ERR");
|
bsw/jbe@1309
|
4075 }
|
bsw/jbe@1309
|
4076 };
|
bsw/jbe@1309
|
4077
|
bsw/jbe@1309
|
4078 // The spec is very specific on how selectAllChildren should be implemented and not all browsers implement it as
|
bsw/jbe@1309
|
4079 // specified so the native implementation is never used by Rangy.
|
bsw/jbe@1309
|
4080 selProto.selectAllChildren = function(node) {
|
bsw/jbe@1309
|
4081 assertNodeInSameDocument(this, node);
|
bsw/jbe@1309
|
4082 var range = api.createRange(node);
|
bsw/jbe@1309
|
4083 range.selectNodeContents(node);
|
bsw/jbe@1309
|
4084 this.setSingleRange(range);
|
bsw/jbe@1309
|
4085 };
|
bsw/jbe@1309
|
4086
|
bsw/jbe@1309
|
4087 selProto.deleteFromDocument = function() {
|
bsw/jbe@1309
|
4088 // Sepcial behaviour required for IE's control selections
|
bsw/jbe@1309
|
4089 if (implementsControlRange && implementsDocSelection && this.docSelection.type == CONTROL) {
|
bsw/jbe@1309
|
4090 var controlRange = this.docSelection.createRange();
|
bsw/jbe@1309
|
4091 var element;
|
bsw/jbe@1309
|
4092 while (controlRange.length) {
|
bsw/jbe@1309
|
4093 element = controlRange.item(0);
|
bsw/jbe@1309
|
4094 controlRange.remove(element);
|
bsw/jbe@1309
|
4095 dom.removeNode(element);
|
bsw/jbe@1309
|
4096 }
|
bsw/jbe@1309
|
4097 this.refresh();
|
bsw/jbe@1309
|
4098 } else if (this.rangeCount) {
|
bsw/jbe@1309
|
4099 var ranges = this.getAllRanges();
|
bsw/jbe@1309
|
4100 if (ranges.length) {
|
bsw/jbe@1309
|
4101 this.removeAllRanges();
|
bsw/jbe@1309
|
4102 for (var i = 0, len = ranges.length; i < len; ++i) {
|
bsw/jbe@1309
|
4103 ranges[i].deleteContents();
|
bsw/jbe@1309
|
4104 }
|
bsw/jbe@1309
|
4105 // The spec says nothing about what the selection should contain after calling deleteContents on each
|
bsw/jbe@1309
|
4106 // range. Firefox moves the selection to where the final selected range was, so we emulate that
|
bsw/jbe@1309
|
4107 this.addRange(ranges[len - 1]);
|
bsw/jbe@1309
|
4108 }
|
bsw/jbe@1309
|
4109 }
|
bsw/jbe@1309
|
4110 };
|
bsw/jbe@1309
|
4111
|
bsw/jbe@1309
|
4112 // The following are non-standard extensions
|
bsw/jbe@1309
|
4113 selProto.eachRange = function(func, returnValue) {
|
bsw/jbe@1309
|
4114 for (var i = 0, len = this._ranges.length; i < len; ++i) {
|
bsw/jbe@1309
|
4115 if ( func( this.getRangeAt(i) ) ) {
|
bsw/jbe@1309
|
4116 return returnValue;
|
bsw/jbe@1309
|
4117 }
|
bsw/jbe@1309
|
4118 }
|
bsw/jbe@1309
|
4119 };
|
bsw/jbe@1309
|
4120
|
bsw/jbe@1309
|
4121 selProto.getAllRanges = function() {
|
bsw/jbe@1309
|
4122 var ranges = [];
|
bsw/jbe@1309
|
4123 this.eachRange(function(range) {
|
bsw/jbe@1309
|
4124 ranges.push(range);
|
bsw/jbe@1309
|
4125 });
|
bsw/jbe@1309
|
4126 return ranges;
|
bsw/jbe@1309
|
4127 };
|
bsw/jbe@1309
|
4128
|
bsw/jbe@1309
|
4129 selProto.setSingleRange = function(range, direction) {
|
bsw/jbe@1309
|
4130 this.removeAllRanges();
|
bsw/jbe@1309
|
4131 this.addRange(range, direction);
|
bsw/jbe@1309
|
4132 };
|
bsw/jbe@1309
|
4133
|
bsw/jbe@1309
|
4134 selProto.callMethodOnEachRange = function(methodName, params) {
|
bsw/jbe@1309
|
4135 var results = [];
|
bsw/jbe@1309
|
4136 this.eachRange( function(range) {
|
bsw/jbe@1309
|
4137 results.push( range[methodName].apply(range, params || []) );
|
bsw/jbe@1309
|
4138 } );
|
bsw/jbe@1309
|
4139 return results;
|
bsw/jbe@1309
|
4140 };
|
bsw/jbe@1309
|
4141
|
bsw/jbe@1309
|
4142 function createStartOrEndSetter(isStart) {
|
bsw/jbe@1309
|
4143 return function(node, offset) {
|
bsw/jbe@1309
|
4144 var range;
|
bsw/jbe@1309
|
4145 if (this.rangeCount) {
|
bsw/jbe@1309
|
4146 range = this.getRangeAt(0);
|
bsw/jbe@1309
|
4147 range["set" + (isStart ? "Start" : "End")](node, offset);
|
bsw/jbe@1309
|
4148 } else {
|
bsw/jbe@1309
|
4149 range = api.createRange(this.win.document);
|
bsw/jbe@1309
|
4150 range.setStartAndEnd(node, offset);
|
bsw/jbe@1309
|
4151 }
|
bsw/jbe@1309
|
4152 this.setSingleRange(range, this.isBackward());
|
bsw/jbe@1309
|
4153 };
|
bsw/jbe@1309
|
4154 }
|
bsw/jbe@1309
|
4155
|
bsw/jbe@1309
|
4156 selProto.setStart = createStartOrEndSetter(true);
|
bsw/jbe@1309
|
4157 selProto.setEnd = createStartOrEndSetter(false);
|
bsw/jbe@1309
|
4158
|
bsw/jbe@1309
|
4159 // Add select() method to Range prototype. Any existing selection will be removed.
|
bsw/jbe@1309
|
4160 api.rangePrototype.select = function(direction) {
|
bsw/jbe@1309
|
4161 getSelection( this.getDocument() ).setSingleRange(this, direction);
|
bsw/jbe@1309
|
4162 };
|
bsw/jbe@1309
|
4163
|
bsw/jbe@1309
|
4164 selProto.changeEachRange = function(func) {
|
bsw/jbe@1309
|
4165 var ranges = [];
|
bsw/jbe@1309
|
4166 var backward = this.isBackward();
|
bsw/jbe@1309
|
4167
|
bsw/jbe@1309
|
4168 this.eachRange(function(range) {
|
bsw/jbe@1309
|
4169 func(range);
|
bsw/jbe@1309
|
4170 ranges.push(range);
|
bsw/jbe@1309
|
4171 });
|
bsw/jbe@1309
|
4172
|
bsw/jbe@1309
|
4173 this.removeAllRanges();
|
bsw/jbe@1309
|
4174 if (backward && ranges.length == 1) {
|
bsw/jbe@1309
|
4175 this.addRange(ranges[0], "backward");
|
bsw/jbe@1309
|
4176 } else {
|
bsw/jbe@1309
|
4177 this.setRanges(ranges);
|
bsw/jbe@1309
|
4178 }
|
bsw/jbe@1309
|
4179 };
|
bsw/jbe@1309
|
4180
|
bsw/jbe@1309
|
4181 selProto.containsNode = function(node, allowPartial) {
|
bsw/jbe@1309
|
4182 return this.eachRange( function(range) {
|
bsw/jbe@1309
|
4183 return range.containsNode(node, allowPartial);
|
bsw/jbe@1309
|
4184 }, true ) || false;
|
bsw/jbe@1309
|
4185 };
|
bsw/jbe@1309
|
4186
|
bsw/jbe@1309
|
4187 selProto.getBookmark = function(containerNode) {
|
bsw/jbe@1309
|
4188 return {
|
bsw/jbe@1309
|
4189 backward: this.isBackward(),
|
bsw/jbe@1309
|
4190 rangeBookmarks: this.callMethodOnEachRange("getBookmark", [containerNode])
|
bsw/jbe@1309
|
4191 };
|
bsw/jbe@1309
|
4192 };
|
bsw/jbe@1309
|
4193
|
bsw/jbe@1309
|
4194 selProto.moveToBookmark = function(bookmark) {
|
bsw/jbe@1309
|
4195 var selRanges = [];
|
bsw/jbe@1309
|
4196 for (var i = 0, rangeBookmark, range; rangeBookmark = bookmark.rangeBookmarks[i++]; ) {
|
bsw/jbe@1309
|
4197 range = api.createRange(this.win);
|
bsw/jbe@1309
|
4198 range.moveToBookmark(rangeBookmark);
|
bsw/jbe@1309
|
4199 selRanges.push(range);
|
bsw/jbe@1309
|
4200 }
|
bsw/jbe@1309
|
4201 if (bookmark.backward) {
|
bsw/jbe@1309
|
4202 this.setSingleRange(selRanges[0], "backward");
|
bsw/jbe@1309
|
4203 } else {
|
bsw/jbe@1309
|
4204 this.setRanges(selRanges);
|
bsw/jbe@1309
|
4205 }
|
bsw/jbe@1309
|
4206 };
|
bsw/jbe@1309
|
4207
|
bsw/jbe@1309
|
4208 selProto.saveRanges = function() {
|
bsw/jbe@1309
|
4209 return {
|
bsw/jbe@1309
|
4210 backward: this.isBackward(),
|
bsw/jbe@1309
|
4211 ranges: this.callMethodOnEachRange("cloneRange")
|
bsw/jbe@1309
|
4212 };
|
bsw/jbe@1309
|
4213 };
|
bsw/jbe@1309
|
4214
|
bsw/jbe@1309
|
4215 selProto.restoreRanges = function(selRanges) {
|
bsw/jbe@1309
|
4216 this.removeAllRanges();
|
bsw/jbe@1309
|
4217 for (var i = 0, range; range = selRanges.ranges[i]; ++i) {
|
bsw/jbe@1309
|
4218 this.addRange(range, (selRanges.backward && i == 0));
|
bsw/jbe@1309
|
4219 }
|
bsw/jbe@1309
|
4220 };
|
bsw/jbe@1309
|
4221
|
bsw/jbe@1309
|
4222 selProto.toHtml = function() {
|
bsw/jbe@1309
|
4223 var rangeHtmls = [];
|
bsw/jbe@1309
|
4224 this.eachRange(function(range) {
|
bsw/jbe@1309
|
4225 rangeHtmls.push( DomRange.toHtml(range) );
|
bsw/jbe@1309
|
4226 });
|
bsw/jbe@1309
|
4227 return rangeHtmls.join("");
|
bsw/jbe@1309
|
4228 };
|
bsw/jbe@1309
|
4229
|
bsw/jbe@1309
|
4230 if (features.implementsTextRange) {
|
bsw/jbe@1309
|
4231 selProto.getNativeTextRange = function() {
|
bsw/jbe@1309
|
4232 var sel, textRange;
|
bsw/jbe@1309
|
4233 if ( (sel = this.docSelection) ) {
|
bsw/jbe@1309
|
4234 var range = sel.createRange();
|
bsw/jbe@1309
|
4235 if (isTextRange(range)) {
|
bsw/jbe@1309
|
4236 return range;
|
bsw/jbe@1309
|
4237 } else {
|
bsw/jbe@1309
|
4238 throw module.createError("getNativeTextRange: selection is a control selection");
|
bsw/jbe@1309
|
4239 }
|
bsw/jbe@1309
|
4240 } else if (this.rangeCount > 0) {
|
bsw/jbe@1309
|
4241 return api.WrappedTextRange.rangeToTextRange( this.getRangeAt(0) );
|
bsw/jbe@1309
|
4242 } else {
|
bsw/jbe@1309
|
4243 throw module.createError("getNativeTextRange: selection contains no range");
|
bsw/jbe@1309
|
4244 }
|
bsw/jbe@1309
|
4245 };
|
bsw/jbe@1309
|
4246 }
|
bsw/jbe@1309
|
4247
|
bsw/jbe@1309
|
4248 function inspect(sel) {
|
bsw/jbe@1309
|
4249 var rangeInspects = [];
|
bsw/jbe@1309
|
4250 var anchor = new DomPosition(sel.anchorNode, sel.anchorOffset);
|
bsw/jbe@1309
|
4251 var focus = new DomPosition(sel.focusNode, sel.focusOffset);
|
bsw/jbe@1309
|
4252 var name = (typeof sel.getName == "function") ? sel.getName() : "Selection";
|
bsw/jbe@1309
|
4253
|
bsw/jbe@1309
|
4254 if (typeof sel.rangeCount != "undefined") {
|
bsw/jbe@1309
|
4255 for (var i = 0, len = sel.rangeCount; i < len; ++i) {
|
bsw/jbe@1309
|
4256 rangeInspects[i] = DomRange.inspect(sel.getRangeAt(i));
|
bsw/jbe@1309
|
4257 }
|
bsw/jbe@1309
|
4258 }
|
bsw/jbe@1309
|
4259 return "[" + name + "(Ranges: " + rangeInspects.join(", ") +
|
bsw/jbe@1309
|
4260 ")(anchor: " + anchor.inspect() + ", focus: " + focus.inspect() + "]";
|
bsw/jbe@1309
|
4261 }
|
bsw/jbe@1309
|
4262
|
bsw/jbe@1309
|
4263 selProto.getName = function() {
|
bsw/jbe@1309
|
4264 return "WrappedSelection";
|
bsw/jbe@1309
|
4265 };
|
bsw/jbe@1309
|
4266
|
bsw/jbe@1309
|
4267 selProto.inspect = function() {
|
bsw/jbe@1309
|
4268 return inspect(this);
|
bsw/jbe@1309
|
4269 };
|
bsw/jbe@1309
|
4270
|
bsw/jbe@1309
|
4271 selProto.detach = function() {
|
bsw/jbe@1309
|
4272 actOnCachedSelection(this.win, "delete");
|
bsw/jbe@1309
|
4273 deleteProperties(this);
|
bsw/jbe@1309
|
4274 };
|
bsw/jbe@1309
|
4275
|
bsw/jbe@1309
|
4276 WrappedSelection.detachAll = function() {
|
bsw/jbe@1309
|
4277 actOnCachedSelection(null, "deleteAll");
|
bsw/jbe@1309
|
4278 };
|
bsw/jbe@1309
|
4279
|
bsw/jbe@1309
|
4280 WrappedSelection.inspect = inspect;
|
bsw/jbe@1309
|
4281 WrappedSelection.isDirectionBackward = isDirectionBackward;
|
bsw/jbe@1309
|
4282
|
bsw/jbe@1309
|
4283 api.Selection = WrappedSelection;
|
bsw/jbe@1309
|
4284
|
bsw/jbe@1309
|
4285 api.selectionPrototype = selProto;
|
bsw/jbe@1309
|
4286
|
bsw/jbe@1309
|
4287 api.addShimListener(function(win) {
|
bsw/jbe@1309
|
4288 if (typeof win.getSelection == "undefined") {
|
bsw/jbe@1309
|
4289 win.getSelection = function() {
|
bsw/jbe@1309
|
4290 return getSelection(win);
|
bsw/jbe@1309
|
4291 };
|
bsw/jbe@1309
|
4292 }
|
bsw/jbe@1309
|
4293 win = null;
|
bsw/jbe@1309
|
4294 });
|
bsw/jbe@1309
|
4295 });
|
bsw/jbe@1309
|
4296
|
bsw/jbe@1309
|
4297
|
bsw/jbe@1309
|
4298 /*----------------------------------------------------------------------------------------------------------------*/
|
bsw/jbe@1309
|
4299
|
bsw/jbe@1309
|
4300 // Wait for document to load before initializing
|
bsw/jbe@1309
|
4301 var docReady = false;
|
bsw/jbe@1309
|
4302
|
bsw/jbe@1309
|
4303 var loadHandler = function(e) {
|
bsw/jbe@1309
|
4304 if (!docReady) {
|
bsw/jbe@1309
|
4305 docReady = true;
|
bsw/jbe@1309
|
4306 if (!api.initialized && api.config.autoInitialize) {
|
bsw/jbe@1309
|
4307 init();
|
bsw/jbe@1309
|
4308 }
|
bsw/jbe@1309
|
4309 }
|
bsw/jbe@1309
|
4310 };
|
bsw/jbe@1309
|
4311
|
bsw/jbe@1309
|
4312 if (isBrowser) {
|
bsw/jbe@1309
|
4313 // Test whether the document has already been loaded and initialize immediately if so
|
bsw/jbe@1309
|
4314 if (document.readyState == "complete") {
|
bsw/jbe@1309
|
4315 loadHandler();
|
bsw/jbe@1309
|
4316 } else {
|
bsw/jbe@1309
|
4317 if (isHostMethod(document, "addEventListener")) {
|
bsw/jbe@1309
|
4318 document.addEventListener("DOMContentLoaded", loadHandler, false);
|
bsw/jbe@1309
|
4319 }
|
bsw/jbe@1309
|
4320
|
bsw/jbe@1309
|
4321 // Add a fallback in case the DOMContentLoaded event isn't supported
|
bsw/jbe@1309
|
4322 addListener(window, "load", loadHandler);
|
bsw/jbe@1309
|
4323 }
|
bsw/jbe@1309
|
4324 }
|
bsw/jbe@1309
|
4325
|
bsw/jbe@1309
|
4326 rangy = api;
|
bsw/jbe@1309
|
4327 })();
|
bsw/jbe@1309
|
4328
|
bsw/jbe@1309
|
4329 /**
|
bsw/jbe@1309
|
4330 * Selection save and restore module for Rangy.
|
bsw/jbe@1309
|
4331 * Saves and restores user selections using marker invisible elements in the DOM.
|
bsw/jbe@1309
|
4332 *
|
bsw/jbe@1309
|
4333 * Part of Rangy, a cross-browser JavaScript range and selection library
|
bsw/jbe@1309
|
4334 * https://github.com/timdown/rangy
|
bsw/jbe@1309
|
4335 *
|
bsw/jbe@1309
|
4336 * Depends on Rangy core.
|
bsw/jbe@1309
|
4337 *
|
bsw/jbe@1309
|
4338 * Copyright 2015, Tim Down
|
bsw/jbe@1309
|
4339 * Licensed under the MIT license.
|
bsw/jbe@1309
|
4340 * Version: 1.3.1-dev
|
bsw/jbe@1309
|
4341 * Build date: 20 May 2015
|
bsw/jbe@1309
|
4342 *
|
bsw/jbe@1309
|
4343 * NOTE: UMD wrapper removed manually for bundling (Oliver Pulges)
|
bsw/jbe@1309
|
4344 */
|
bsw/jbe@1309
|
4345 rangy.createModule("SaveRestore", ["WrappedRange"], function(api, module) {
|
bsw/jbe@1309
|
4346 var dom = api.dom;
|
bsw/jbe@1309
|
4347 var removeNode = dom.removeNode;
|
bsw/jbe@1309
|
4348 var isDirectionBackward = api.Selection.isDirectionBackward;
|
bsw/jbe@1309
|
4349 var markerTextChar = "\ufeff";
|
bsw/jbe@1309
|
4350
|
bsw/jbe@1309
|
4351 function gEBI(id, doc) {
|
bsw/jbe@1309
|
4352 return (doc || document).getElementById(id);
|
bsw/jbe@1309
|
4353 }
|
bsw/jbe@1309
|
4354
|
bsw/jbe@1309
|
4355 function insertRangeBoundaryMarker(range, atStart) {
|
bsw/jbe@1309
|
4356 var markerId = "selectionBoundary_" + (+new Date()) + "_" + ("" + Math.random()).slice(2);
|
bsw/jbe@1309
|
4357 var markerEl;
|
bsw/jbe@1309
|
4358 var doc = dom.getDocument(range.startContainer);
|
bsw/jbe@1309
|
4359
|
bsw/jbe@1309
|
4360 // Clone the Range and collapse to the appropriate boundary point
|
bsw/jbe@1309
|
4361 var boundaryRange = range.cloneRange();
|
bsw/jbe@1309
|
4362 boundaryRange.collapse(atStart);
|
bsw/jbe@1309
|
4363
|
bsw/jbe@1309
|
4364 // Create the marker element containing a single invisible character using DOM methods and insert it
|
bsw/jbe@1309
|
4365 markerEl = doc.createElement("span");
|
bsw/jbe@1309
|
4366 markerEl.id = markerId;
|
bsw/jbe@1309
|
4367 markerEl.style.lineHeight = "0";
|
bsw/jbe@1309
|
4368 markerEl.style.display = "none";
|
bsw/jbe@1309
|
4369 markerEl.className = "rangySelectionBoundary";
|
bsw/jbe@1309
|
4370 markerEl.appendChild(doc.createTextNode(markerTextChar));
|
bsw/jbe@1309
|
4371
|
bsw/jbe@1309
|
4372 boundaryRange.insertNode(markerEl);
|
bsw/jbe@1309
|
4373 return markerEl;
|
bsw/jbe@1309
|
4374 }
|
bsw/jbe@1309
|
4375
|
bsw/jbe@1309
|
4376 function setRangeBoundary(doc, range, markerId, atStart) {
|
bsw/jbe@1309
|
4377 var markerEl = gEBI(markerId, doc);
|
bsw/jbe@1309
|
4378 if (markerEl) {
|
bsw/jbe@1309
|
4379 range[atStart ? "setStartBefore" : "setEndBefore"](markerEl);
|
bsw/jbe@1309
|
4380 removeNode(markerEl);
|
bsw/jbe@1309
|
4381 } else {
|
bsw/jbe@1309
|
4382 module.warn("Marker element has been removed. Cannot restore selection.");
|
bsw/jbe@1309
|
4383 }
|
bsw/jbe@1309
|
4384 }
|
bsw/jbe@1309
|
4385
|
bsw/jbe@1309
|
4386 function compareRanges(r1, r2) {
|
bsw/jbe@1309
|
4387 return r2.compareBoundaryPoints(r1.START_TO_START, r1);
|
bsw/jbe@1309
|
4388 }
|
bsw/jbe@1309
|
4389
|
bsw/jbe@1309
|
4390 function saveRange(range, direction) {
|
bsw/jbe@1309
|
4391 var startEl, endEl, doc = api.DomRange.getRangeDocument(range), text = range.toString();
|
bsw/jbe@1309
|
4392 var backward = isDirectionBackward(direction);
|
bsw/jbe@1309
|
4393
|
bsw/jbe@1309
|
4394 if (range.collapsed) {
|
bsw/jbe@1309
|
4395 endEl = insertRangeBoundaryMarker(range, false);
|
bsw/jbe@1309
|
4396 return {
|
bsw/jbe@1309
|
4397 document: doc,
|
bsw/jbe@1309
|
4398 markerId: endEl.id,
|
bsw/jbe@1309
|
4399 collapsed: true
|
bsw/jbe@1309
|
4400 };
|
bsw/jbe@1309
|
4401 } else {
|
bsw/jbe@1309
|
4402 endEl = insertRangeBoundaryMarker(range, false);
|
bsw/jbe@1309
|
4403 startEl = insertRangeBoundaryMarker(range, true);
|
bsw/jbe@1309
|
4404
|
bsw/jbe@1309
|
4405 return {
|
bsw/jbe@1309
|
4406 document: doc,
|
bsw/jbe@1309
|
4407 startMarkerId: startEl.id,
|
bsw/jbe@1309
|
4408 endMarkerId: endEl.id,
|
bsw/jbe@1309
|
4409 collapsed: false,
|
bsw/jbe@1309
|
4410 backward: backward,
|
bsw/jbe@1309
|
4411 toString: function() {
|
bsw/jbe@1309
|
4412 return "original text: '" + text + "', new text: '" + range.toString() + "'";
|
bsw/jbe@1309
|
4413 }
|
bsw/jbe@1309
|
4414 };
|
bsw/jbe@1309
|
4415 }
|
bsw/jbe@1309
|
4416 }
|
bsw/jbe@1309
|
4417
|
bsw/jbe@1309
|
4418 function restoreRange(rangeInfo, normalize) {
|
bsw/jbe@1309
|
4419 var doc = rangeInfo.document;
|
bsw/jbe@1309
|
4420 if (typeof normalize == "undefined") {
|
bsw/jbe@1309
|
4421 normalize = true;
|
bsw/jbe@1309
|
4422 }
|
bsw/jbe@1309
|
4423 var range = api.createRange(doc);
|
bsw/jbe@1309
|
4424 if (rangeInfo.collapsed) {
|
bsw/jbe@1309
|
4425 var markerEl = gEBI(rangeInfo.markerId, doc);
|
bsw/jbe@1309
|
4426 if (markerEl) {
|
bsw/jbe@1309
|
4427 markerEl.style.display = "inline";
|
bsw/jbe@1309
|
4428 var previousNode = markerEl.previousSibling;
|
bsw/jbe@1309
|
4429
|
bsw/jbe@1309
|
4430 // Workaround for issue 17
|
bsw/jbe@1309
|
4431 if (previousNode && previousNode.nodeType == 3) {
|
bsw/jbe@1309
|
4432 removeNode(markerEl);
|
bsw/jbe@1309
|
4433 range.collapseToPoint(previousNode, previousNode.length);
|
bsw/jbe@1309
|
4434 } else {
|
bsw/jbe@1309
|
4435 range.collapseBefore(markerEl);
|
bsw/jbe@1309
|
4436 removeNode(markerEl);
|
bsw/jbe@1309
|
4437 }
|
bsw/jbe@1309
|
4438 } else {
|
bsw/jbe@1309
|
4439 module.warn("Marker element has been removed. Cannot restore selection.");
|
bsw/jbe@1309
|
4440 }
|
bsw/jbe@1309
|
4441 } else {
|
bsw/jbe@1309
|
4442 setRangeBoundary(doc, range, rangeInfo.startMarkerId, true);
|
bsw/jbe@1309
|
4443 setRangeBoundary(doc, range, rangeInfo.endMarkerId, false);
|
bsw/jbe@1309
|
4444 }
|
bsw/jbe@1309
|
4445
|
bsw/jbe@1309
|
4446 if (normalize) {
|
bsw/jbe@1309
|
4447 range.normalizeBoundaries();
|
bsw/jbe@1309
|
4448 }
|
bsw/jbe@1309
|
4449
|
bsw/jbe@1309
|
4450 return range;
|
bsw/jbe@1309
|
4451 }
|
bsw/jbe@1309
|
4452
|
bsw/jbe@1309
|
4453 function saveRanges(ranges, direction) {
|
bsw/jbe@1309
|
4454 var rangeInfos = [], range, doc;
|
bsw/jbe@1309
|
4455 var backward = isDirectionBackward(direction);
|
bsw/jbe@1309
|
4456
|
bsw/jbe@1309
|
4457 // Order the ranges by position within the DOM, latest first, cloning the array to leave the original untouched
|
bsw/jbe@1309
|
4458 ranges = ranges.slice(0);
|
bsw/jbe@1309
|
4459 ranges.sort(compareRanges);
|
bsw/jbe@1309
|
4460
|
bsw/jbe@1309
|
4461 for (var i = 0, len = ranges.length; i < len; ++i) {
|
bsw/jbe@1309
|
4462 rangeInfos[i] = saveRange(ranges[i], backward);
|
bsw/jbe@1309
|
4463 }
|
bsw/jbe@1309
|
4464
|
bsw/jbe@1309
|
4465 // Now that all the markers are in place and DOM manipulation over, adjust each range's boundaries to lie
|
bsw/jbe@1309
|
4466 // between its markers
|
bsw/jbe@1309
|
4467 for (i = len - 1; i >= 0; --i) {
|
bsw/jbe@1309
|
4468 range = ranges[i];
|
bsw/jbe@1309
|
4469 doc = api.DomRange.getRangeDocument(range);
|
bsw/jbe@1309
|
4470 if (range.collapsed) {
|
bsw/jbe@1309
|
4471 range.collapseAfter(gEBI(rangeInfos[i].markerId, doc));
|
bsw/jbe@1309
|
4472 } else {
|
bsw/jbe@1309
|
4473 range.setEndBefore(gEBI(rangeInfos[i].endMarkerId, doc));
|
bsw/jbe@1309
|
4474 range.setStartAfter(gEBI(rangeInfos[i].startMarkerId, doc));
|
bsw/jbe@1309
|
4475 }
|
bsw/jbe@1309
|
4476 }
|
bsw/jbe@1309
|
4477
|
bsw/jbe@1309
|
4478 return rangeInfos;
|
bsw/jbe@1309
|
4479 }
|
bsw/jbe@1309
|
4480
|
bsw/jbe@1309
|
4481 function saveSelection(win) {
|
bsw/jbe@1309
|
4482 if (!api.isSelectionValid(win)) {
|
bsw/jbe@1309
|
4483 module.warn("Cannot save selection. This usually happens when the selection is collapsed and the selection document has lost focus.");
|
bsw/jbe@1309
|
4484 return null;
|
bsw/jbe@1309
|
4485 }
|
bsw/jbe@1309
|
4486 var sel = api.getSelection(win);
|
bsw/jbe@1309
|
4487 var ranges = sel.getAllRanges();
|
bsw/jbe@1309
|
4488 var backward = (ranges.length == 1 && sel.isBackward());
|
bsw/jbe@1309
|
4489
|
bsw/jbe@1309
|
4490 var rangeInfos = saveRanges(ranges, backward);
|
bsw/jbe@1309
|
4491
|
bsw/jbe@1309
|
4492 // Ensure current selection is unaffected
|
bsw/jbe@1309
|
4493 if (backward) {
|
bsw/jbe@1309
|
4494 sel.setSingleRange(ranges[0], backward);
|
bsw/jbe@1309
|
4495 } else {
|
bsw/jbe@1309
|
4496 sel.setRanges(ranges);
|
bsw/jbe@1309
|
4497 }
|
bsw/jbe@1309
|
4498
|
bsw/jbe@1309
|
4499 return {
|
bsw/jbe@1309
|
4500 win: win,
|
bsw/jbe@1309
|
4501 rangeInfos: rangeInfos,
|
bsw/jbe@1309
|
4502 restored: false
|
bsw/jbe@1309
|
4503 };
|
bsw/jbe@1309
|
4504 }
|
bsw/jbe@1309
|
4505
|
bsw/jbe@1309
|
4506 function restoreRanges(rangeInfos) {
|
bsw/jbe@1309
|
4507 var ranges = [];
|
bsw/jbe@1309
|
4508
|
bsw/jbe@1309
|
4509 // Ranges are in reverse order of appearance in the DOM. We want to restore earliest first to avoid
|
bsw/jbe@1309
|
4510 // normalization affecting previously restored ranges.
|
bsw/jbe@1309
|
4511 var rangeCount = rangeInfos.length;
|
bsw/jbe@1309
|
4512
|
bsw/jbe@1309
|
4513 for (var i = rangeCount - 1; i >= 0; i--) {
|
bsw/jbe@1309
|
4514 ranges[i] = restoreRange(rangeInfos[i], true);
|
bsw/jbe@1309
|
4515 }
|
bsw/jbe@1309
|
4516
|
bsw/jbe@1309
|
4517 return ranges;
|
bsw/jbe@1309
|
4518 }
|
bsw/jbe@1309
|
4519
|
bsw/jbe@1309
|
4520 function restoreSelection(savedSelection, preserveDirection) {
|
bsw/jbe@1309
|
4521 if (!savedSelection.restored) {
|
bsw/jbe@1309
|
4522 var rangeInfos = savedSelection.rangeInfos;
|
bsw/jbe@1309
|
4523 var sel = api.getSelection(savedSelection.win);
|
bsw/jbe@1309
|
4524 var ranges = restoreRanges(rangeInfos), rangeCount = rangeInfos.length;
|
bsw/jbe@1309
|
4525
|
bsw/jbe@1309
|
4526 if (rangeCount == 1 && preserveDirection && api.features.selectionHasExtend && rangeInfos[0].backward) {
|
bsw/jbe@1309
|
4527 sel.removeAllRanges();
|
bsw/jbe@1309
|
4528 sel.addRange(ranges[0], true);
|
bsw/jbe@1309
|
4529 } else {
|
bsw/jbe@1309
|
4530 sel.setRanges(ranges);
|
bsw/jbe@1309
|
4531 }
|
bsw/jbe@1309
|
4532
|
bsw/jbe@1309
|
4533 savedSelection.restored = true;
|
bsw/jbe@1309
|
4534 }
|
bsw/jbe@1309
|
4535 }
|
bsw/jbe@1309
|
4536
|
bsw/jbe@1309
|
4537 function removeMarkerElement(doc, markerId) {
|
bsw/jbe@1309
|
4538 var markerEl = gEBI(markerId, doc);
|
bsw/jbe@1309
|
4539 if (markerEl) {
|
bsw/jbe@1309
|
4540 removeNode(markerEl);
|
bsw/jbe@1309
|
4541 }
|
bsw/jbe@1309
|
4542 }
|
bsw/jbe@1309
|
4543
|
bsw/jbe@1309
|
4544 function removeMarkers(savedSelection) {
|
bsw/jbe@1309
|
4545 var rangeInfos = savedSelection.rangeInfos;
|
bsw/jbe@1309
|
4546 for (var i = 0, len = rangeInfos.length, rangeInfo; i < len; ++i) {
|
bsw/jbe@1309
|
4547 rangeInfo = rangeInfos[i];
|
bsw/jbe@1309
|
4548 if (rangeInfo.collapsed) {
|
bsw/jbe@1309
|
4549 removeMarkerElement(savedSelection.doc, rangeInfo.markerId);
|
bsw/jbe@1309
|
4550 } else {
|
bsw/jbe@1309
|
4551 removeMarkerElement(savedSelection.doc, rangeInfo.startMarkerId);
|
bsw/jbe@1309
|
4552 removeMarkerElement(savedSelection.doc, rangeInfo.endMarkerId);
|
bsw/jbe@1309
|
4553 }
|
bsw/jbe@1309
|
4554 }
|
bsw/jbe@1309
|
4555 }
|
bsw/jbe@1309
|
4556
|
bsw/jbe@1309
|
4557 api.util.extend(api, {
|
bsw/jbe@1309
|
4558 saveRange: saveRange,
|
bsw/jbe@1309
|
4559 restoreRange: restoreRange,
|
bsw/jbe@1309
|
4560 saveRanges: saveRanges,
|
bsw/jbe@1309
|
4561 restoreRanges: restoreRanges,
|
bsw/jbe@1309
|
4562 saveSelection: saveSelection,
|
bsw/jbe@1309
|
4563 restoreSelection: restoreSelection,
|
bsw/jbe@1309
|
4564 removeMarkerElement: removeMarkerElement,
|
bsw/jbe@1309
|
4565 removeMarkers: removeMarkers
|
bsw/jbe@1309
|
4566 });
|
bsw/jbe@1309
|
4567 });
|
bsw/jbe@1309
|
4568
|
bsw/jbe@1309
|
4569 /**
|
bsw/jbe@1309
|
4570 * Text range module for Rangy.
|
bsw/jbe@1309
|
4571 * Text-based manipulation and searching of ranges and selections.
|
bsw/jbe@1309
|
4572 *
|
bsw/jbe@1309
|
4573 * Features
|
bsw/jbe@1309
|
4574 *
|
bsw/jbe@1309
|
4575 * - Ability to move range boundaries by character or word offsets
|
bsw/jbe@1309
|
4576 * - Customizable word tokenizer
|
bsw/jbe@1309
|
4577 * - Ignores text nodes inside <script> or <style> elements or those hidden by CSS display and visibility properties
|
bsw/jbe@1309
|
4578 * - Range findText method to search for text or regex within the page or within a range. Flags for whole words and case
|
bsw/jbe@1309
|
4579 * sensitivity
|
bsw/jbe@1309
|
4580 * - Selection and range save/restore as text offsets within a node
|
bsw/jbe@1309
|
4581 * - Methods to return visible text within a range or selection
|
bsw/jbe@1309
|
4582 * - innerText method for elements
|
bsw/jbe@1309
|
4583 *
|
bsw/jbe@1309
|
4584 * References
|
bsw/jbe@1309
|
4585 *
|
bsw/jbe@1309
|
4586 * https://www.w3.org/Bugs/Public/show_bug.cgi?id=13145
|
bsw/jbe@1309
|
4587 * http://aryeh.name/spec/innertext/innertext.html
|
bsw/jbe@1309
|
4588 * http://dvcs.w3.org/hg/editing/raw-file/tip/editing.html
|
bsw/jbe@1309
|
4589 *
|
bsw/jbe@1309
|
4590 * Part of Rangy, a cross-browser JavaScript range and selection library
|
bsw/jbe@1309
|
4591 * https://github.com/timdown/rangy
|
bsw/jbe@1309
|
4592 *
|
bsw/jbe@1309
|
4593 * Depends on Rangy core.
|
bsw/jbe@1309
|
4594 *
|
bsw/jbe@1309
|
4595 * Copyright 2015, Tim Down
|
bsw/jbe@1309
|
4596 * Licensed under the MIT license.
|
bsw/jbe@1309
|
4597 * Version: 1.3.1-dev
|
bsw/jbe@1309
|
4598 * Build date: 20 May 2015
|
bsw/jbe@1309
|
4599 */
|
bsw/jbe@1309
|
4600
|
bsw/jbe@1309
|
4601 /**
|
bsw/jbe@1309
|
4602 * Problem: handling of trailing spaces before line breaks is handled inconsistently between browsers.
|
bsw/jbe@1309
|
4603 *
|
bsw/jbe@1309
|
4604 * First, a <br>: this is relatively simple. For the following HTML:
|
bsw/jbe@1309
|
4605 *
|
bsw/jbe@1309
|
4606 * 1 <br>2
|
bsw/jbe@1309
|
4607 *
|
bsw/jbe@1309
|
4608 * - IE and WebKit render the space, include it in the selection (i.e. when the content is selected and pasted into a
|
bsw/jbe@1309
|
4609 * textarea, the space is present) and allow the caret to be placed after it.
|
bsw/jbe@1309
|
4610 * - Firefox does not acknowledge the space in the selection but it is possible to place the caret after it.
|
bsw/jbe@1309
|
4611 * - Opera does not render the space but has two separate caret positions on either side of the space (left and right
|
bsw/jbe@1309
|
4612 * arrow keys show this) and includes the space in the selection.
|
bsw/jbe@1309
|
4613 *
|
bsw/jbe@1309
|
4614 * The other case is the line break or breaks implied by block elements. For the following HTML:
|
bsw/jbe@1309
|
4615 *
|
bsw/jbe@1309
|
4616 * <p>1 </p><p>2<p>
|
bsw/jbe@1309
|
4617 *
|
bsw/jbe@1309
|
4618 * - WebKit does not acknowledge the space in any way
|
bsw/jbe@1309
|
4619 * - Firefox, IE and Opera as per <br>
|
bsw/jbe@1309
|
4620 *
|
bsw/jbe@1309
|
4621 * One more case is trailing spaces before line breaks in elements with white-space: pre-line. For the following HTML:
|
bsw/jbe@1309
|
4622 *
|
bsw/jbe@1309
|
4623 * <p style="white-space: pre-line">1
|
bsw/jbe@1309
|
4624 * 2</p>
|
bsw/jbe@1309
|
4625 *
|
bsw/jbe@1309
|
4626 * - Firefox and WebKit include the space in caret positions
|
bsw/jbe@1309
|
4627 * - IE does not support pre-line up to and including version 9
|
bsw/jbe@1309
|
4628 * - Opera ignores the space
|
bsw/jbe@1309
|
4629 * - Trailing space only renders if there is a non-collapsed character in the line
|
bsw/jbe@1309
|
4630 *
|
bsw/jbe@1309
|
4631 * Problem is whether Rangy should ever acknowledge the space and if so, when. Another problem is whether this can be
|
bsw/jbe@1309
|
4632 * feature-tested
|
bsw/jbe@1309
|
4633 *
|
bsw/jbe@1309
|
4634 * NOTE: UMD wrapper removed manually for bundling (Oliver Pulges)
|
bsw/jbe@1309
|
4635 */
|
bsw/jbe@1309
|
4636 rangy.createModule("TextRange", ["WrappedSelection"], function(api, module) {
|
bsw/jbe@1309
|
4637 var UNDEF = "undefined";
|
bsw/jbe@1309
|
4638 var CHARACTER = "character", WORD = "word";
|
bsw/jbe@1309
|
4639 var dom = api.dom, util = api.util;
|
bsw/jbe@1309
|
4640 var extend = util.extend;
|
bsw/jbe@1309
|
4641 var createOptions = util.createOptions;
|
bsw/jbe@1309
|
4642 var getBody = dom.getBody;
|
bsw/jbe@1309
|
4643
|
bsw/jbe@1309
|
4644
|
bsw/jbe@1309
|
4645 var spacesRegex = /^[ \t\f\r\n]+$/;
|
bsw/jbe@1309
|
4646 var spacesMinusLineBreaksRegex = /^[ \t\f\r]+$/;
|
bsw/jbe@1309
|
4647 var allWhiteSpaceRegex = /^[\t-\r \u0085\u00A0\u1680\u180E\u2000-\u200B\u2028\u2029\u202F\u205F\u3000]+$/;
|
bsw/jbe@1309
|
4648 var nonLineBreakWhiteSpaceRegex = /^[\t \u00A0\u1680\u180E\u2000-\u200B\u202F\u205F\u3000]+$/;
|
bsw/jbe@1309
|
4649 var lineBreakRegex = /^[\n-\r\u0085\u2028\u2029]$/;
|
bsw/jbe@1309
|
4650
|
bsw/jbe@1309
|
4651 var defaultLanguage = "en";
|
bsw/jbe@1309
|
4652
|
bsw/jbe@1309
|
4653 var isDirectionBackward = api.Selection.isDirectionBackward;
|
bsw/jbe@1309
|
4654
|
bsw/jbe@1309
|
4655 // Properties representing whether trailing spaces inside blocks are completely collapsed (as they are in WebKit,
|
bsw/jbe@1309
|
4656 // but not other browsers). Also test whether trailing spaces before <br> elements are collapsed.
|
bsw/jbe@1309
|
4657 var trailingSpaceInBlockCollapses = false;
|
bsw/jbe@1309
|
4658 var trailingSpaceBeforeBrCollapses = false;
|
bsw/jbe@1309
|
4659 var trailingSpaceBeforeBlockCollapses = false;
|
bsw/jbe@1309
|
4660 var trailingSpaceBeforeLineBreakInPreLineCollapses = true;
|
bsw/jbe@1309
|
4661
|
bsw/jbe@1309
|
4662 (function() {
|
bsw/jbe@1309
|
4663 var el = dom.createTestElement(document, "<p>1 </p><p></p>", true);
|
bsw/jbe@1309
|
4664 var p = el.firstChild;
|
bsw/jbe@1309
|
4665 var sel = api.getSelection();
|
bsw/jbe@1309
|
4666 sel.collapse(p.lastChild, 2);
|
bsw/jbe@1309
|
4667 sel.setStart(p.firstChild, 0);
|
bsw/jbe@1309
|
4668 trailingSpaceInBlockCollapses = ("" + sel).length == 1;
|
bsw/jbe@1309
|
4669
|
bsw/jbe@1309
|
4670 el.innerHTML = "1 <br />";
|
bsw/jbe@1309
|
4671 sel.collapse(el, 2);
|
bsw/jbe@1309
|
4672 sel.setStart(el.firstChild, 0);
|
bsw/jbe@1309
|
4673 trailingSpaceBeforeBrCollapses = ("" + sel).length == 1;
|
bsw/jbe@1309
|
4674
|
bsw/jbe@1309
|
4675 el.innerHTML = "1 <p>1</p>";
|
bsw/jbe@1309
|
4676 sel.collapse(el, 2);
|
bsw/jbe@1309
|
4677 sel.setStart(el.firstChild, 0);
|
bsw/jbe@1309
|
4678 trailingSpaceBeforeBlockCollapses = ("" + sel).length == 1;
|
bsw/jbe@1309
|
4679
|
bsw/jbe@1309
|
4680 dom.removeNode(el);
|
bsw/jbe@1309
|
4681 sel.removeAllRanges();
|
bsw/jbe@1309
|
4682 })();
|
bsw/jbe@1309
|
4683
|
bsw/jbe@1309
|
4684 /*----------------------------------------------------------------------------------------------------------------*/
|
bsw/jbe@1309
|
4685
|
bsw/jbe@1309
|
4686 // This function must create word and non-word tokens for the whole of the text supplied to it
|
bsw/jbe@1309
|
4687 function defaultTokenizer(chars, wordOptions) {
|
bsw/jbe@1309
|
4688 var word = chars.join(""), result, tokenRanges = [];
|
bsw/jbe@1309
|
4689
|
bsw/jbe@1309
|
4690 function createTokenRange(start, end, isWord) {
|
bsw/jbe@1309
|
4691 tokenRanges.push( { start: start, end: end, isWord: isWord } );
|
bsw/jbe@1309
|
4692 }
|
bsw/jbe@1309
|
4693
|
bsw/jbe@1309
|
4694 // Match words and mark characters
|
bsw/jbe@1309
|
4695 var lastWordEnd = 0, wordStart, wordEnd;
|
bsw/jbe@1309
|
4696 while ( (result = wordOptions.wordRegex.exec(word)) ) {
|
bsw/jbe@1309
|
4697 wordStart = result.index;
|
bsw/jbe@1309
|
4698 wordEnd = wordStart + result[0].length;
|
bsw/jbe@1309
|
4699
|
bsw/jbe@1309
|
4700 // Create token for non-word characters preceding this word
|
bsw/jbe@1309
|
4701 if (wordStart > lastWordEnd) {
|
bsw/jbe@1309
|
4702 createTokenRange(lastWordEnd, wordStart, false);
|
bsw/jbe@1309
|
4703 }
|
bsw/jbe@1309
|
4704
|
bsw/jbe@1309
|
4705 // Get trailing space characters for word
|
bsw/jbe@1309
|
4706 if (wordOptions.includeTrailingSpace) {
|
bsw/jbe@1309
|
4707 while ( nonLineBreakWhiteSpaceRegex.test(chars[wordEnd]) ) {
|
bsw/jbe@1309
|
4708 ++wordEnd;
|
bsw/jbe@1309
|
4709 }
|
bsw/jbe@1309
|
4710 }
|
bsw/jbe@1309
|
4711 createTokenRange(wordStart, wordEnd, true);
|
bsw/jbe@1309
|
4712 lastWordEnd = wordEnd;
|
bsw/jbe@1309
|
4713 }
|
bsw/jbe@1309
|
4714
|
bsw/jbe@1309
|
4715 // Create token for trailing non-word characters, if any exist
|
bsw/jbe@1309
|
4716 if (lastWordEnd < chars.length) {
|
bsw/jbe@1309
|
4717 createTokenRange(lastWordEnd, chars.length, false);
|
bsw/jbe@1309
|
4718 }
|
bsw/jbe@1309
|
4719
|
bsw/jbe@1309
|
4720 return tokenRanges;
|
bsw/jbe@1309
|
4721 }
|
bsw/jbe@1309
|
4722
|
bsw/jbe@1309
|
4723 function convertCharRangeToToken(chars, tokenRange) {
|
bsw/jbe@1309
|
4724 var tokenChars = chars.slice(tokenRange.start, tokenRange.end);
|
bsw/jbe@1309
|
4725 var token = {
|
bsw/jbe@1309
|
4726 isWord: tokenRange.isWord,
|
bsw/jbe@1309
|
4727 chars: tokenChars,
|
bsw/jbe@1309
|
4728 toString: function() {
|
bsw/jbe@1309
|
4729 return tokenChars.join("");
|
bsw/jbe@1309
|
4730 }
|
bsw/jbe@1309
|
4731 };
|
bsw/jbe@1309
|
4732 for (var i = 0, len = tokenChars.length; i < len; ++i) {
|
bsw/jbe@1309
|
4733 tokenChars[i].token = token;
|
bsw/jbe@1309
|
4734 }
|
bsw/jbe@1309
|
4735 return token;
|
bsw/jbe@1309
|
4736 }
|
bsw/jbe@1309
|
4737
|
bsw/jbe@1309
|
4738 function tokenize(chars, wordOptions, tokenizer) {
|
bsw/jbe@1309
|
4739 var tokenRanges = tokenizer(chars, wordOptions);
|
bsw/jbe@1309
|
4740 var tokens = [];
|
bsw/jbe@1309
|
4741 for (var i = 0, tokenRange; tokenRange = tokenRanges[i++]; ) {
|
bsw/jbe@1309
|
4742 tokens.push( convertCharRangeToToken(chars, tokenRange) );
|
bsw/jbe@1309
|
4743 }
|
bsw/jbe@1309
|
4744 return tokens;
|
bsw/jbe@1309
|
4745 }
|
bsw/jbe@1309
|
4746
|
bsw/jbe@1309
|
4747 var defaultCharacterOptions = {
|
bsw/jbe@1309
|
4748 includeBlockContentTrailingSpace: true,
|
bsw/jbe@1309
|
4749 includeSpaceBeforeBr: true,
|
bsw/jbe@1309
|
4750 includeSpaceBeforeBlock: true,
|
bsw/jbe@1309
|
4751 includePreLineTrailingSpace: true,
|
bsw/jbe@1309
|
4752 ignoreCharacters: ""
|
bsw/jbe@1309
|
4753 };
|
bsw/jbe@1309
|
4754
|
bsw/jbe@1309
|
4755 function normalizeIgnoredCharacters(ignoredCharacters) {
|
bsw/jbe@1309
|
4756 // Check if character is ignored
|
bsw/jbe@1309
|
4757 var ignoredChars = ignoredCharacters || "";
|
bsw/jbe@1309
|
4758
|
bsw/jbe@1309
|
4759 // Normalize ignored characters into a string consisting of characters in ascending order of character code
|
bsw/jbe@1309
|
4760 var ignoredCharsArray = (typeof ignoredChars == "string") ? ignoredChars.split("") : ignoredChars;
|
bsw/jbe@1309
|
4761 ignoredCharsArray.sort(function(char1, char2) {
|
bsw/jbe@1309
|
4762 return char1.charCodeAt(0) - char2.charCodeAt(0);
|
bsw/jbe@1309
|
4763 });
|
bsw/jbe@1309
|
4764
|
bsw/jbe@1309
|
4765 /// Convert back to a string and remove duplicates
|
bsw/jbe@1309
|
4766 return ignoredCharsArray.join("").replace(/(.)\1+/g, "$1");
|
bsw/jbe@1309
|
4767 }
|
bsw/jbe@1309
|
4768
|
bsw/jbe@1309
|
4769 var defaultCaretCharacterOptions = {
|
bsw/jbe@1309
|
4770 includeBlockContentTrailingSpace: !trailingSpaceBeforeLineBreakInPreLineCollapses,
|
bsw/jbe@1309
|
4771 includeSpaceBeforeBr: !trailingSpaceBeforeBrCollapses,
|
bsw/jbe@1309
|
4772 includeSpaceBeforeBlock: !trailingSpaceBeforeBlockCollapses,
|
bsw/jbe@1309
|
4773 includePreLineTrailingSpace: true
|
bsw/jbe@1309
|
4774 };
|
bsw/jbe@1309
|
4775
|
bsw/jbe@1309
|
4776 var defaultWordOptions = {
|
bsw/jbe@1309
|
4777 "en": {
|
bsw/jbe@1309
|
4778 wordRegex: /[a-z0-9]+('[a-z0-9]+)*/gi,
|
bsw/jbe@1309
|
4779 includeTrailingSpace: false,
|
bsw/jbe@1309
|
4780 tokenizer: defaultTokenizer
|
bsw/jbe@1309
|
4781 }
|
bsw/jbe@1309
|
4782 };
|
bsw/jbe@1309
|
4783
|
bsw/jbe@1309
|
4784 var defaultFindOptions = {
|
bsw/jbe@1309
|
4785 caseSensitive: false,
|
bsw/jbe@1309
|
4786 withinRange: null,
|
bsw/jbe@1309
|
4787 wholeWordsOnly: false,
|
bsw/jbe@1309
|
4788 wrap: false,
|
bsw/jbe@1309
|
4789 direction: "forward",
|
bsw/jbe@1309
|
4790 wordOptions: null,
|
bsw/jbe@1309
|
4791 characterOptions: null
|
bsw/jbe@1309
|
4792 };
|
bsw/jbe@1309
|
4793
|
bsw/jbe@1309
|
4794 var defaultMoveOptions = {
|
bsw/jbe@1309
|
4795 wordOptions: null,
|
bsw/jbe@1309
|
4796 characterOptions: null
|
bsw/jbe@1309
|
4797 };
|
bsw/jbe@1309
|
4798
|
bsw/jbe@1309
|
4799 var defaultExpandOptions = {
|
bsw/jbe@1309
|
4800 wordOptions: null,
|
bsw/jbe@1309
|
4801 characterOptions: null,
|
bsw/jbe@1309
|
4802 trim: false,
|
bsw/jbe@1309
|
4803 trimStart: true,
|
bsw/jbe@1309
|
4804 trimEnd: true
|
bsw/jbe@1309
|
4805 };
|
bsw/jbe@1309
|
4806
|
bsw/jbe@1309
|
4807 var defaultWordIteratorOptions = {
|
bsw/jbe@1309
|
4808 wordOptions: null,
|
bsw/jbe@1309
|
4809 characterOptions: null,
|
bsw/jbe@1309
|
4810 direction: "forward"
|
bsw/jbe@1309
|
4811 };
|
bsw/jbe@1309
|
4812
|
bsw/jbe@1309
|
4813 function createWordOptions(options) {
|
bsw/jbe@1309
|
4814 var lang, defaults;
|
bsw/jbe@1309
|
4815 if (!options) {
|
bsw/jbe@1309
|
4816 return defaultWordOptions[defaultLanguage];
|
bsw/jbe@1309
|
4817 } else {
|
bsw/jbe@1309
|
4818 lang = options.language || defaultLanguage;
|
bsw/jbe@1309
|
4819 defaults = {};
|
bsw/jbe@1309
|
4820 extend(defaults, defaultWordOptions[lang] || defaultWordOptions[defaultLanguage]);
|
bsw/jbe@1309
|
4821 extend(defaults, options);
|
bsw/jbe@1309
|
4822 return defaults;
|
bsw/jbe@1309
|
4823 }
|
bsw/jbe@1309
|
4824 }
|
bsw/jbe@1309
|
4825
|
bsw/jbe@1309
|
4826 function createNestedOptions(optionsParam, defaults) {
|
bsw/jbe@1309
|
4827 var options = createOptions(optionsParam, defaults);
|
bsw/jbe@1309
|
4828 if (defaults.hasOwnProperty("wordOptions")) {
|
bsw/jbe@1309
|
4829 options.wordOptions = createWordOptions(options.wordOptions);
|
bsw/jbe@1309
|
4830 }
|
bsw/jbe@1309
|
4831 if (defaults.hasOwnProperty("characterOptions")) {
|
bsw/jbe@1309
|
4832 options.characterOptions = createOptions(options.characterOptions, defaultCharacterOptions);
|
bsw/jbe@1309
|
4833 }
|
bsw/jbe@1309
|
4834 return options;
|
bsw/jbe@1309
|
4835 }
|
bsw/jbe@1309
|
4836
|
bsw/jbe@1309
|
4837 /*----------------------------------------------------------------------------------------------------------------*/
|
bsw/jbe@1309
|
4838
|
bsw/jbe@1309
|
4839 /* DOM utility functions */
|
bsw/jbe@1309
|
4840 var getComputedStyleProperty = dom.getComputedStyleProperty;
|
bsw/jbe@1309
|
4841
|
bsw/jbe@1309
|
4842 // Create cachable versions of DOM functions
|
bsw/jbe@1309
|
4843
|
bsw/jbe@1309
|
4844 // Test for old IE's incorrect display properties
|
bsw/jbe@1309
|
4845 var tableCssDisplayBlock;
|
bsw/jbe@1309
|
4846 (function() {
|
bsw/jbe@1309
|
4847 var table = document.createElement("table");
|
bsw/jbe@1309
|
4848 var body = getBody(document);
|
bsw/jbe@1309
|
4849 body.appendChild(table);
|
bsw/jbe@1309
|
4850 tableCssDisplayBlock = (getComputedStyleProperty(table, "display") == "block");
|
bsw/jbe@1309
|
4851 body.removeChild(table);
|
bsw/jbe@1309
|
4852 })();
|
bsw/jbe@1309
|
4853
|
bsw/jbe@1309
|
4854 var defaultDisplayValueForTag = {
|
bsw/jbe@1309
|
4855 table: "table",
|
bsw/jbe@1309
|
4856 caption: "table-caption",
|
bsw/jbe@1309
|
4857 colgroup: "table-column-group",
|
bsw/jbe@1309
|
4858 col: "table-column",
|
bsw/jbe@1309
|
4859 thead: "table-header-group",
|
bsw/jbe@1309
|
4860 tbody: "table-row-group",
|
bsw/jbe@1309
|
4861 tfoot: "table-footer-group",
|
bsw/jbe@1309
|
4862 tr: "table-row",
|
bsw/jbe@1309
|
4863 td: "table-cell",
|
bsw/jbe@1309
|
4864 th: "table-cell"
|
bsw/jbe@1309
|
4865 };
|
bsw/jbe@1309
|
4866
|
bsw/jbe@1309
|
4867 // Corrects IE's "block" value for table-related elements
|
bsw/jbe@1309
|
4868 function getComputedDisplay(el, win) {
|
bsw/jbe@1309
|
4869 var display = getComputedStyleProperty(el, "display", win);
|
bsw/jbe@1309
|
4870 var tagName = el.tagName.toLowerCase();
|
bsw/jbe@1309
|
4871 return (display == "block" &&
|
bsw/jbe@1309
|
4872 tableCssDisplayBlock &&
|
bsw/jbe@1309
|
4873 defaultDisplayValueForTag.hasOwnProperty(tagName)) ?
|
bsw/jbe@1309
|
4874 defaultDisplayValueForTag[tagName] : display;
|
bsw/jbe@1309
|
4875 }
|
bsw/jbe@1309
|
4876
|
bsw/jbe@1309
|
4877 function isHidden(node) {
|
bsw/jbe@1309
|
4878 var ancestors = getAncestorsAndSelf(node);
|
bsw/jbe@1309
|
4879 for (var i = 0, len = ancestors.length; i < len; ++i) {
|
bsw/jbe@1309
|
4880 if (ancestors[i].nodeType == 1 && getComputedDisplay(ancestors[i]) == "none") {
|
bsw/jbe@1309
|
4881 return true;
|
bsw/jbe@1309
|
4882 }
|
bsw/jbe@1309
|
4883 }
|
bsw/jbe@1309
|
4884
|
bsw/jbe@1309
|
4885 return false;
|
bsw/jbe@1309
|
4886 }
|
bsw/jbe@1309
|
4887
|
bsw/jbe@1309
|
4888 function isVisibilityHiddenTextNode(textNode) {
|
bsw/jbe@1309
|
4889 var el;
|
bsw/jbe@1309
|
4890 return textNode.nodeType == 3 &&
|
bsw/jbe@1309
|
4891 (el = textNode.parentNode) &&
|
bsw/jbe@1309
|
4892 getComputedStyleProperty(el, "visibility") == "hidden";
|
bsw/jbe@1309
|
4893 }
|
bsw/jbe@1309
|
4894
|
bsw/jbe@1309
|
4895 /*----------------------------------------------------------------------------------------------------------------*/
|
bsw/jbe@1309
|
4896
|
bsw/jbe@1309
|
4897
|
bsw/jbe@1309
|
4898 // "A block node is either an Element whose "display" property does not have
|
bsw/jbe@1309
|
4899 // resolved value "inline" or "inline-block" or "inline-table" or "none", or a
|
bsw/jbe@1309
|
4900 // Document, or a DocumentFragment."
|
bsw/jbe@1309
|
4901 function isBlockNode(node) {
|
bsw/jbe@1309
|
4902 return node &&
|
bsw/jbe@1309
|
4903 ((node.nodeType == 1 && !/^(inline(-block|-table)?|none)$/.test(getComputedDisplay(node))) ||
|
bsw/jbe@1309
|
4904 node.nodeType == 9 || node.nodeType == 11);
|
bsw/jbe@1309
|
4905 }
|
bsw/jbe@1309
|
4906
|
bsw/jbe@1309
|
4907 function getLastDescendantOrSelf(node) {
|
bsw/jbe@1309
|
4908 var lastChild = node.lastChild;
|
bsw/jbe@1309
|
4909 return lastChild ? getLastDescendantOrSelf(lastChild) : node;
|
bsw/jbe@1309
|
4910 }
|
bsw/jbe@1309
|
4911
|
bsw/jbe@1309
|
4912 function containsPositions(node) {
|
bsw/jbe@1309
|
4913 return dom.isCharacterDataNode(node) ||
|
bsw/jbe@1309
|
4914 !/^(area|base|basefont|br|col|frame|hr|img|input|isindex|link|meta|param)$/i.test(node.nodeName);
|
bsw/jbe@1309
|
4915 }
|
bsw/jbe@1309
|
4916
|
bsw/jbe@1309
|
4917 function getAncestors(node) {
|
bsw/jbe@1309
|
4918 var ancestors = [];
|
bsw/jbe@1309
|
4919 while (node.parentNode) {
|
bsw/jbe@1309
|
4920 ancestors.unshift(node.parentNode);
|
bsw/jbe@1309
|
4921 node = node.parentNode;
|
bsw/jbe@1309
|
4922 }
|
bsw/jbe@1309
|
4923 return ancestors;
|
bsw/jbe@1309
|
4924 }
|
bsw/jbe@1309
|
4925
|
bsw/jbe@1309
|
4926 function getAncestorsAndSelf(node) {
|
bsw/jbe@1309
|
4927 return getAncestors(node).concat([node]);
|
bsw/jbe@1309
|
4928 }
|
bsw/jbe@1309
|
4929
|
bsw/jbe@1309
|
4930 function nextNodeDescendants(node) {
|
bsw/jbe@1309
|
4931 while (node && !node.nextSibling) {
|
bsw/jbe@1309
|
4932 node = node.parentNode;
|
bsw/jbe@1309
|
4933 }
|
bsw/jbe@1309
|
4934 if (!node) {
|
bsw/jbe@1309
|
4935 return null;
|
bsw/jbe@1309
|
4936 }
|
bsw/jbe@1309
|
4937 return node.nextSibling;
|
bsw/jbe@1309
|
4938 }
|
bsw/jbe@1309
|
4939
|
bsw/jbe@1309
|
4940 function nextNode(node, excludeChildren) {
|
bsw/jbe@1309
|
4941 if (!excludeChildren && node.hasChildNodes()) {
|
bsw/jbe@1309
|
4942 return node.firstChild;
|
bsw/jbe@1309
|
4943 }
|
bsw/jbe@1309
|
4944 return nextNodeDescendants(node);
|
bsw/jbe@1309
|
4945 }
|
bsw/jbe@1309
|
4946
|
bsw/jbe@1309
|
4947 function previousNode(node) {
|
bsw/jbe@1309
|
4948 var previous = node.previousSibling;
|
bsw/jbe@1309
|
4949 if (previous) {
|
bsw/jbe@1309
|
4950 node = previous;
|
bsw/jbe@1309
|
4951 while (node.hasChildNodes()) {
|
bsw/jbe@1309
|
4952 node = node.lastChild;
|
bsw/jbe@1309
|
4953 }
|
bsw/jbe@1309
|
4954 return node;
|
bsw/jbe@1309
|
4955 }
|
bsw/jbe@1309
|
4956 var parent = node.parentNode;
|
bsw/jbe@1309
|
4957 if (parent && parent.nodeType == 1) {
|
bsw/jbe@1309
|
4958 return parent;
|
bsw/jbe@1309
|
4959 }
|
bsw/jbe@1309
|
4960 return null;
|
bsw/jbe@1309
|
4961 }
|
bsw/jbe@1309
|
4962
|
bsw/jbe@1309
|
4963 // Adpated from Aryeh's code.
|
bsw/jbe@1309
|
4964 // "A whitespace node is either a Text node whose data is the empty string; or
|
bsw/jbe@1309
|
4965 // a Text node whose data consists only of one or more tabs (0x0009), line
|
bsw/jbe@1309
|
4966 // feeds (0x000A), carriage returns (0x000D), and/or spaces (0x0020), and whose
|
bsw/jbe@1309
|
4967 // parent is an Element whose resolved value for "white-space" is "normal" or
|
bsw/jbe@1309
|
4968 // "nowrap"; or a Text node whose data consists only of one or more tabs
|
bsw/jbe@1309
|
4969 // (0x0009), carriage returns (0x000D), and/or spaces (0x0020), and whose
|
bsw/jbe@1309
|
4970 // parent is an Element whose resolved value for "white-space" is "pre-line"."
|
bsw/jbe@1309
|
4971 function isWhitespaceNode(node) {
|
bsw/jbe@1309
|
4972 if (!node || node.nodeType != 3) {
|
bsw/jbe@1309
|
4973 return false;
|
bsw/jbe@1309
|
4974 }
|
bsw/jbe@1309
|
4975 var text = node.data;
|
bsw/jbe@1309
|
4976 if (text === "") {
|
bsw/jbe@1309
|
4977 return true;
|
bsw/jbe@1309
|
4978 }
|
bsw/jbe@1309
|
4979 var parent = node.parentNode;
|
bsw/jbe@1309
|
4980 if (!parent || parent.nodeType != 1) {
|
bsw/jbe@1309
|
4981 return false;
|
bsw/jbe@1309
|
4982 }
|
bsw/jbe@1309
|
4983 var computedWhiteSpace = getComputedStyleProperty(node.parentNode, "whiteSpace");
|
bsw/jbe@1309
|
4984
|
bsw/jbe@1309
|
4985 return (/^[\t\n\r ]+$/.test(text) && /^(normal|nowrap)$/.test(computedWhiteSpace)) ||
|
bsw/jbe@1309
|
4986 (/^[\t\r ]+$/.test(text) && computedWhiteSpace == "pre-line");
|
bsw/jbe@1309
|
4987 }
|
bsw/jbe@1309
|
4988
|
bsw/jbe@1309
|
4989 // Adpated from Aryeh's code.
|
bsw/jbe@1309
|
4990 // "node is a collapsed whitespace node if the following algorithm returns
|
bsw/jbe@1309
|
4991 // true:"
|
bsw/jbe@1309
|
4992 function isCollapsedWhitespaceNode(node) {
|
bsw/jbe@1309
|
4993 // "If node's data is the empty string, return true."
|
bsw/jbe@1309
|
4994 if (node.data === "") {
|
bsw/jbe@1309
|
4995 return true;
|
bsw/jbe@1309
|
4996 }
|
bsw/jbe@1309
|
4997
|
bsw/jbe@1309
|
4998 // "If node is not a whitespace node, return false."
|
bsw/jbe@1309
|
4999 if (!isWhitespaceNode(node)) {
|
bsw/jbe@1309
|
5000 return false;
|
bsw/jbe@1309
|
5001 }
|
bsw/jbe@1309
|
5002
|
bsw/jbe@1309
|
5003 // "Let ancestor be node's parent."
|
bsw/jbe@1309
|
5004 var ancestor = node.parentNode;
|
bsw/jbe@1309
|
5005
|
bsw/jbe@1309
|
5006 // "If ancestor is null, return true."
|
bsw/jbe@1309
|
5007 if (!ancestor) {
|
bsw/jbe@1309
|
5008 return true;
|
bsw/jbe@1309
|
5009 }
|
bsw/jbe@1309
|
5010
|
bsw/jbe@1309
|
5011 // "If the "display" property of some ancestor of node has resolved value "none", return true."
|
bsw/jbe@1309
|
5012 if (isHidden(node)) {
|
bsw/jbe@1309
|
5013 return true;
|
bsw/jbe@1309
|
5014 }
|
bsw/jbe@1309
|
5015
|
bsw/jbe@1309
|
5016 return false;
|
bsw/jbe@1309
|
5017 }
|
bsw/jbe@1309
|
5018
|
bsw/jbe@1309
|
5019 function isCollapsedNode(node) {
|
bsw/jbe@1309
|
5020 var type = node.nodeType;
|
bsw/jbe@1309
|
5021 return type == 7 /* PROCESSING_INSTRUCTION */ ||
|
bsw/jbe@1309
|
5022 type == 8 /* COMMENT */ ||
|
bsw/jbe@1309
|
5023 isHidden(node) ||
|
bsw/jbe@1309
|
5024 /^(script|style)$/i.test(node.nodeName) ||
|
bsw/jbe@1309
|
5025 isVisibilityHiddenTextNode(node) ||
|
bsw/jbe@1309
|
5026 isCollapsedWhitespaceNode(node);
|
bsw/jbe@1309
|
5027 }
|
bsw/jbe@1309
|
5028
|
bsw/jbe@1309
|
5029 function isIgnoredNode(node, win) {
|
bsw/jbe@1309
|
5030 var type = node.nodeType;
|
bsw/jbe@1309
|
5031 return type == 7 /* PROCESSING_INSTRUCTION */ ||
|
bsw/jbe@1309
|
5032 type == 8 /* COMMENT */ ||
|
bsw/jbe@1309
|
5033 (type == 1 && getComputedDisplay(node, win) == "none");
|
bsw/jbe@1309
|
5034 }
|
bsw/jbe@1309
|
5035
|
bsw/jbe@1309
|
5036 /*----------------------------------------------------------------------------------------------------------------*/
|
bsw/jbe@1309
|
5037
|
bsw/jbe@1309
|
5038 // Possibly overengineered caching system to prevent repeated DOM calls slowing everything down
|
bsw/jbe@1309
|
5039
|
bsw/jbe@1309
|
5040 function Cache() {
|
bsw/jbe@1309
|
5041 this.store = {};
|
bsw/jbe@1309
|
5042 }
|
bsw/jbe@1309
|
5043
|
bsw/jbe@1309
|
5044 Cache.prototype = {
|
bsw/jbe@1309
|
5045 get: function(key) {
|
bsw/jbe@1309
|
5046 return this.store.hasOwnProperty(key) ? this.store[key] : null;
|
bsw/jbe@1309
|
5047 },
|
bsw/jbe@1309
|
5048
|
bsw/jbe@1309
|
5049 set: function(key, value) {
|
bsw/jbe@1309
|
5050 return this.store[key] = value;
|
bsw/jbe@1309
|
5051 }
|
bsw/jbe@1309
|
5052 };
|
bsw/jbe@1309
|
5053
|
bsw/jbe@1309
|
5054 var cachedCount = 0, uncachedCount = 0;
|
bsw/jbe@1309
|
5055
|
bsw/jbe@1309
|
5056 function createCachingGetter(methodName, func, objProperty) {
|
bsw/jbe@1309
|
5057 return function(args) {
|
bsw/jbe@1309
|
5058 var cache = this.cache;
|
bsw/jbe@1309
|
5059 if (cache.hasOwnProperty(methodName)) {
|
bsw/jbe@1309
|
5060 cachedCount++;
|
bsw/jbe@1309
|
5061 return cache[methodName];
|
bsw/jbe@1309
|
5062 } else {
|
bsw/jbe@1309
|
5063 uncachedCount++;
|
bsw/jbe@1309
|
5064 var value = func.call(this, objProperty ? this[objProperty] : this, args);
|
bsw/jbe@1309
|
5065 cache[methodName] = value;
|
bsw/jbe@1309
|
5066 return value;
|
bsw/jbe@1309
|
5067 }
|
bsw/jbe@1309
|
5068 };
|
bsw/jbe@1309
|
5069 }
|
bsw/jbe@1309
|
5070
|
bsw/jbe@1309
|
5071 /*----------------------------------------------------------------------------------------------------------------*/
|
bsw/jbe@1309
|
5072
|
bsw/jbe@1309
|
5073 function NodeWrapper(node, session) {
|
bsw/jbe@1309
|
5074 this.node = node;
|
bsw/jbe@1309
|
5075 this.session = session;
|
bsw/jbe@1309
|
5076 this.cache = new Cache();
|
bsw/jbe@1309
|
5077 this.positions = new Cache();
|
bsw/jbe@1309
|
5078 }
|
bsw/jbe@1309
|
5079
|
bsw/jbe@1309
|
5080 var nodeProto = {
|
bsw/jbe@1309
|
5081 getPosition: function(offset) {
|
bsw/jbe@1309
|
5082 var positions = this.positions;
|
bsw/jbe@1309
|
5083 return positions.get(offset) || positions.set(offset, new Position(this, offset));
|
bsw/jbe@1309
|
5084 },
|
bsw/jbe@1309
|
5085
|
bsw/jbe@1309
|
5086 toString: function() {
|
bsw/jbe@1309
|
5087 return "[NodeWrapper(" + dom.inspectNode(this.node) + ")]";
|
bsw/jbe@1309
|
5088 }
|
bsw/jbe@1309
|
5089 };
|
bsw/jbe@1309
|
5090
|
bsw/jbe@1309
|
5091 NodeWrapper.prototype = nodeProto;
|
bsw/jbe@1309
|
5092
|
bsw/jbe@1309
|
5093 var EMPTY = "EMPTY",
|
bsw/jbe@1309
|
5094 NON_SPACE = "NON_SPACE",
|
bsw/jbe@1309
|
5095 UNCOLLAPSIBLE_SPACE = "UNCOLLAPSIBLE_SPACE",
|
bsw/jbe@1309
|
5096 COLLAPSIBLE_SPACE = "COLLAPSIBLE_SPACE",
|
bsw/jbe@1309
|
5097 TRAILING_SPACE_BEFORE_BLOCK = "TRAILING_SPACE_BEFORE_BLOCK",
|
bsw/jbe@1309
|
5098 TRAILING_SPACE_IN_BLOCK = "TRAILING_SPACE_IN_BLOCK",
|
bsw/jbe@1309
|
5099 TRAILING_SPACE_BEFORE_BR = "TRAILING_SPACE_BEFORE_BR",
|
bsw/jbe@1309
|
5100 PRE_LINE_TRAILING_SPACE_BEFORE_LINE_BREAK = "PRE_LINE_TRAILING_SPACE_BEFORE_LINE_BREAK",
|
bsw/jbe@1309
|
5101 TRAILING_LINE_BREAK_AFTER_BR = "TRAILING_LINE_BREAK_AFTER_BR",
|
bsw/jbe@1309
|
5102 INCLUDED_TRAILING_LINE_BREAK_AFTER_BR = "INCLUDED_TRAILING_LINE_BREAK_AFTER_BR";
|
bsw/jbe@1309
|
5103
|
bsw/jbe@1309
|
5104 extend(nodeProto, {
|
bsw/jbe@1309
|
5105 isCharacterDataNode: createCachingGetter("isCharacterDataNode", dom.isCharacterDataNode, "node"),
|
bsw/jbe@1309
|
5106 getNodeIndex: createCachingGetter("nodeIndex", dom.getNodeIndex, "node"),
|
bsw/jbe@1309
|
5107 getLength: createCachingGetter("nodeLength", dom.getNodeLength, "node"),
|
bsw/jbe@1309
|
5108 containsPositions: createCachingGetter("containsPositions", containsPositions, "node"),
|
bsw/jbe@1309
|
5109 isWhitespace: createCachingGetter("isWhitespace", isWhitespaceNode, "node"),
|
bsw/jbe@1309
|
5110 isCollapsedWhitespace: createCachingGetter("isCollapsedWhitespace", isCollapsedWhitespaceNode, "node"),
|
bsw/jbe@1309
|
5111 getComputedDisplay: createCachingGetter("computedDisplay", getComputedDisplay, "node"),
|
bsw/jbe@1309
|
5112 isCollapsed: createCachingGetter("collapsed", isCollapsedNode, "node"),
|
bsw/jbe@1309
|
5113 isIgnored: createCachingGetter("ignored", isIgnoredNode, "node"),
|
bsw/jbe@1309
|
5114 next: createCachingGetter("nextPos", nextNode, "node"),
|
bsw/jbe@1309
|
5115 previous: createCachingGetter("previous", previousNode, "node"),
|
bsw/jbe@1309
|
5116
|
bsw/jbe@1309
|
5117 getTextNodeInfo: createCachingGetter("textNodeInfo", function(textNode) {
|
bsw/jbe@1309
|
5118 var spaceRegex = null, collapseSpaces = false;
|
bsw/jbe@1309
|
5119 var cssWhitespace = getComputedStyleProperty(textNode.parentNode, "whiteSpace");
|
bsw/jbe@1309
|
5120 var preLine = (cssWhitespace == "pre-line");
|
bsw/jbe@1309
|
5121 if (preLine) {
|
bsw/jbe@1309
|
5122 spaceRegex = spacesMinusLineBreaksRegex;
|
bsw/jbe@1309
|
5123 collapseSpaces = true;
|
bsw/jbe@1309
|
5124 } else if (cssWhitespace == "normal" || cssWhitespace == "nowrap") {
|
bsw/jbe@1309
|
5125 spaceRegex = spacesRegex;
|
bsw/jbe@1309
|
5126 collapseSpaces = true;
|
bsw/jbe@1309
|
5127 }
|
bsw/jbe@1309
|
5128
|
bsw/jbe@1309
|
5129 return {
|
bsw/jbe@1309
|
5130 node: textNode,
|
bsw/jbe@1309
|
5131 text: textNode.data,
|
bsw/jbe@1309
|
5132 spaceRegex: spaceRegex,
|
bsw/jbe@1309
|
5133 collapseSpaces: collapseSpaces,
|
bsw/jbe@1309
|
5134 preLine: preLine
|
bsw/jbe@1309
|
5135 };
|
bsw/jbe@1309
|
5136 }, "node"),
|
bsw/jbe@1309
|
5137
|
bsw/jbe@1309
|
5138 hasInnerText: createCachingGetter("hasInnerText", function(el, backward) {
|
bsw/jbe@1309
|
5139 var session = this.session;
|
bsw/jbe@1309
|
5140 var posAfterEl = session.getPosition(el.parentNode, this.getNodeIndex() + 1);
|
bsw/jbe@1309
|
5141 var firstPosInEl = session.getPosition(el, 0);
|
bsw/jbe@1309
|
5142
|
bsw/jbe@1309
|
5143 var pos = backward ? posAfterEl : firstPosInEl;
|
bsw/jbe@1309
|
5144 var endPos = backward ? firstPosInEl : posAfterEl;
|
bsw/jbe@1309
|
5145
|
bsw/jbe@1309
|
5146 /*
|
bsw/jbe@1309
|
5147 <body><p>X </p><p>Y</p></body>
|
bsw/jbe@1309
|
5148
|
bsw/jbe@1309
|
5149 Positions:
|
bsw/jbe@1309
|
5150
|
bsw/jbe@1309
|
5151 body:0:""
|
bsw/jbe@1309
|
5152 p:0:""
|
bsw/jbe@1309
|
5153 text:0:""
|
bsw/jbe@1309
|
5154 text:1:"X"
|
bsw/jbe@1309
|
5155 text:2:TRAILING_SPACE_IN_BLOCK
|
bsw/jbe@1309
|
5156 text:3:COLLAPSED_SPACE
|
bsw/jbe@1309
|
5157 p:1:""
|
bsw/jbe@1309
|
5158 body:1:"\n"
|
bsw/jbe@1309
|
5159 p:0:""
|
bsw/jbe@1309
|
5160 text:0:""
|
bsw/jbe@1309
|
5161 text:1:"Y"
|
bsw/jbe@1309
|
5162
|
bsw/jbe@1309
|
5163 A character is a TRAILING_SPACE_IN_BLOCK iff:
|
bsw/jbe@1309
|
5164
|
bsw/jbe@1309
|
5165 - There is no uncollapsed character after it within the visible containing block element
|
bsw/jbe@1309
|
5166
|
bsw/jbe@1309
|
5167 A character is a TRAILING_SPACE_BEFORE_BR iff:
|
bsw/jbe@1309
|
5168
|
bsw/jbe@1309
|
5169 - There is no uncollapsed character after it preceding a <br> element
|
bsw/jbe@1309
|
5170
|
bsw/jbe@1309
|
5171 An element has inner text iff
|
bsw/jbe@1309
|
5172
|
bsw/jbe@1309
|
5173 - It is not hidden
|
bsw/jbe@1309
|
5174 - It contains an uncollapsed character
|
bsw/jbe@1309
|
5175
|
bsw/jbe@1309
|
5176 All trailing spaces (pre-line, before <br>, end of block) require definite non-empty characters to render.
|
bsw/jbe@1309
|
5177 */
|
bsw/jbe@1309
|
5178
|
bsw/jbe@1309
|
5179 while (pos !== endPos) {
|
bsw/jbe@1309
|
5180 pos.prepopulateChar();
|
bsw/jbe@1309
|
5181 if (pos.isDefinitelyNonEmpty()) {
|
bsw/jbe@1309
|
5182 return true;
|
bsw/jbe@1309
|
5183 }
|
bsw/jbe@1309
|
5184 pos = backward ? pos.previousVisible() : pos.nextVisible();
|
bsw/jbe@1309
|
5185 }
|
bsw/jbe@1309
|
5186
|
bsw/jbe@1309
|
5187 return false;
|
bsw/jbe@1309
|
5188 }, "node"),
|
bsw/jbe@1309
|
5189
|
bsw/jbe@1309
|
5190 isRenderedBlock: createCachingGetter("isRenderedBlock", function(el) {
|
bsw/jbe@1309
|
5191 // Ensure that a block element containing a <br> is considered to have inner text
|
bsw/jbe@1309
|
5192 var brs = el.getElementsByTagName("br");
|
bsw/jbe@1309
|
5193 for (var i = 0, len = brs.length; i < len; ++i) {
|
bsw/jbe@1309
|
5194 if (!isCollapsedNode(brs[i])) {
|
bsw/jbe@1309
|
5195 return true;
|
bsw/jbe@1309
|
5196 }
|
bsw/jbe@1309
|
5197 }
|
bsw/jbe@1309
|
5198 return this.hasInnerText();
|
bsw/jbe@1309
|
5199 }, "node"),
|
bsw/jbe@1309
|
5200
|
bsw/jbe@1309
|
5201 getTrailingSpace: createCachingGetter("trailingSpace", function(el) {
|
bsw/jbe@1309
|
5202 if (el.tagName.toLowerCase() == "br") {
|
bsw/jbe@1309
|
5203 return "";
|
bsw/jbe@1309
|
5204 } else {
|
bsw/jbe@1309
|
5205 switch (this.getComputedDisplay()) {
|
bsw/jbe@1309
|
5206 case "inline":
|
bsw/jbe@1309
|
5207 var child = el.lastChild;
|
bsw/jbe@1309
|
5208 while (child) {
|
bsw/jbe@1309
|
5209 if (!isIgnoredNode(child)) {
|
bsw/jbe@1309
|
5210 return (child.nodeType == 1) ? this.session.getNodeWrapper(child).getTrailingSpace() : "";
|
bsw/jbe@1309
|
5211 }
|
bsw/jbe@1309
|
5212 child = child.previousSibling;
|
bsw/jbe@1309
|
5213 }
|
bsw/jbe@1309
|
5214 break;
|
bsw/jbe@1309
|
5215 case "inline-block":
|
bsw/jbe@1309
|
5216 case "inline-table":
|
bsw/jbe@1309
|
5217 case "none":
|
bsw/jbe@1309
|
5218 case "table-column":
|
bsw/jbe@1309
|
5219 case "table-column-group":
|
bsw/jbe@1309
|
5220 break;
|
bsw/jbe@1309
|
5221 case "table-cell":
|
bsw/jbe@1309
|
5222 return "\t";
|
bsw/jbe@1309
|
5223 default:
|
bsw/jbe@1309
|
5224 return this.isRenderedBlock(true) ? "\n" : "";
|
bsw/jbe@1309
|
5225 }
|
bsw/jbe@1309
|
5226 }
|
bsw/jbe@1309
|
5227 return "";
|
bsw/jbe@1309
|
5228 }, "node"),
|
bsw/jbe@1309
|
5229
|
bsw/jbe@1309
|
5230 getLeadingSpace: createCachingGetter("leadingSpace", function(el) {
|
bsw/jbe@1309
|
5231 switch (this.getComputedDisplay()) {
|
bsw/jbe@1309
|
5232 case "inline":
|
bsw/jbe@1309
|
5233 case "inline-block":
|
bsw/jbe@1309
|
5234 case "inline-table":
|
bsw/jbe@1309
|
5235 case "none":
|
bsw/jbe@1309
|
5236 case "table-column":
|
bsw/jbe@1309
|
5237 case "table-column-group":
|
bsw/jbe@1309
|
5238 case "table-cell":
|
bsw/jbe@1309
|
5239 break;
|
bsw/jbe@1309
|
5240 default:
|
bsw/jbe@1309
|
5241 return this.isRenderedBlock(false) ? "\n" : "";
|
bsw/jbe@1309
|
5242 }
|
bsw/jbe@1309
|
5243 return "";
|
bsw/jbe@1309
|
5244 }, "node")
|
bsw/jbe@1309
|
5245 });
|
bsw/jbe@1309
|
5246
|
bsw/jbe@1309
|
5247 /*----------------------------------------------------------------------------------------------------------------*/
|
bsw/jbe@1309
|
5248
|
bsw/jbe@1309
|
5249 function Position(nodeWrapper, offset) {
|
bsw/jbe@1309
|
5250 this.offset = offset;
|
bsw/jbe@1309
|
5251 this.nodeWrapper = nodeWrapper;
|
bsw/jbe@1309
|
5252 this.node = nodeWrapper.node;
|
bsw/jbe@1309
|
5253 this.session = nodeWrapper.session;
|
bsw/jbe@1309
|
5254 this.cache = new Cache();
|
bsw/jbe@1309
|
5255 }
|
bsw/jbe@1309
|
5256
|
bsw/jbe@1309
|
5257 function inspectPosition() {
|
bsw/jbe@1309
|
5258 return "[Position(" + dom.inspectNode(this.node) + ":" + this.offset + ")]";
|
bsw/jbe@1309
|
5259 }
|
bsw/jbe@1309
|
5260
|
bsw/jbe@1309
|
5261 var positionProto = {
|
bsw/jbe@1309
|
5262 character: "",
|
bsw/jbe@1309
|
5263 characterType: EMPTY,
|
bsw/jbe@1309
|
5264 isBr: false,
|
bsw/jbe@1309
|
5265
|
bsw/jbe@1309
|
5266 /*
|
bsw/jbe@1309
|
5267 This method:
|
bsw/jbe@1309
|
5268 - Fully populates positions that have characters that can be determined independently of any other characters.
|
bsw/jbe@1309
|
5269 - Populates most types of space positions with a provisional character. The character is finalized later.
|
bsw/jbe@1309
|
5270 */
|
bsw/jbe@1309
|
5271 prepopulateChar: function() {
|
bsw/jbe@1309
|
5272 var pos = this;
|
bsw/jbe@1309
|
5273 if (!pos.prepopulatedChar) {
|
bsw/jbe@1309
|
5274 var node = pos.node, offset = pos.offset;
|
bsw/jbe@1309
|
5275 var visibleChar = "", charType = EMPTY;
|
bsw/jbe@1309
|
5276 var finalizedChar = false;
|
bsw/jbe@1309
|
5277 if (offset > 0) {
|
bsw/jbe@1309
|
5278 if (node.nodeType == 3) {
|
bsw/jbe@1309
|
5279 var text = node.data;
|
bsw/jbe@1309
|
5280 var textChar = text.charAt(offset - 1);
|
bsw/jbe@1309
|
5281
|
bsw/jbe@1309
|
5282 var nodeInfo = pos.nodeWrapper.getTextNodeInfo();
|
bsw/jbe@1309
|
5283 var spaceRegex = nodeInfo.spaceRegex;
|
bsw/jbe@1309
|
5284 if (nodeInfo.collapseSpaces) {
|
bsw/jbe@1309
|
5285 if (spaceRegex.test(textChar)) {
|
bsw/jbe@1309
|
5286 // "If the character at position is from set, append a single space (U+0020) to newdata and advance
|
bsw/jbe@1309
|
5287 // position until the character at position is not from set."
|
bsw/jbe@1309
|
5288
|
bsw/jbe@1309
|
5289 // We also need to check for the case where we're in a pre-line and we have a space preceding a
|
bsw/jbe@1309
|
5290 // line break, because such spaces are collapsed in some browsers
|
bsw/jbe@1309
|
5291 if (offset > 1 && spaceRegex.test(text.charAt(offset - 2))) {
|
bsw/jbe@1309
|
5292 } else if (nodeInfo.preLine && text.charAt(offset) === "\n") {
|
bsw/jbe@1309
|
5293 visibleChar = " ";
|
bsw/jbe@1309
|
5294 charType = PRE_LINE_TRAILING_SPACE_BEFORE_LINE_BREAK;
|
bsw/jbe@1309
|
5295 } else {
|
bsw/jbe@1309
|
5296 visibleChar = " ";
|
bsw/jbe@1309
|
5297 //pos.checkForFollowingLineBreak = true;
|
bsw/jbe@1309
|
5298 charType = COLLAPSIBLE_SPACE;
|
bsw/jbe@1309
|
5299 }
|
bsw/jbe@1309
|
5300 } else {
|
bsw/jbe@1309
|
5301 visibleChar = textChar;
|
bsw/jbe@1309
|
5302 charType = NON_SPACE;
|
bsw/jbe@1309
|
5303 finalizedChar = true;
|
bsw/jbe@1309
|
5304 }
|
bsw/jbe@1309
|
5305 } else {
|
bsw/jbe@1309
|
5306 visibleChar = textChar;
|
bsw/jbe@1309
|
5307 charType = UNCOLLAPSIBLE_SPACE;
|
bsw/jbe@1309
|
5308 finalizedChar = true;
|
bsw/jbe@1309
|
5309 }
|
bsw/jbe@1309
|
5310 } else {
|
bsw/jbe@1309
|
5311 var nodePassed = node.childNodes[offset - 1];
|
bsw/jbe@1309
|
5312 if (nodePassed && nodePassed.nodeType == 1 && !isCollapsedNode(nodePassed)) {
|
bsw/jbe@1309
|
5313 if (nodePassed.tagName.toLowerCase() == "br") {
|
bsw/jbe@1309
|
5314 visibleChar = "\n";
|
bsw/jbe@1309
|
5315 pos.isBr = true;
|
bsw/jbe@1309
|
5316 charType = COLLAPSIBLE_SPACE;
|
bsw/jbe@1309
|
5317 finalizedChar = false;
|
bsw/jbe@1309
|
5318 } else {
|
bsw/jbe@1309
|
5319 pos.checkForTrailingSpace = true;
|
bsw/jbe@1309
|
5320 }
|
bsw/jbe@1309
|
5321 }
|
bsw/jbe@1309
|
5322
|
bsw/jbe@1309
|
5323 // Check the leading space of the next node for the case when a block element follows an inline
|
bsw/jbe@1309
|
5324 // element or text node. In that case, there is an implied line break between the two nodes.
|
bsw/jbe@1309
|
5325 if (!visibleChar) {
|
bsw/jbe@1309
|
5326 var nextNode = node.childNodes[offset];
|
bsw/jbe@1309
|
5327 if (nextNode && nextNode.nodeType == 1 && !isCollapsedNode(nextNode)) {
|
bsw/jbe@1309
|
5328 pos.checkForLeadingSpace = true;
|
bsw/jbe@1309
|
5329 }
|
bsw/jbe@1309
|
5330 }
|
bsw/jbe@1309
|
5331 }
|
bsw/jbe@1309
|
5332 }
|
bsw/jbe@1309
|
5333
|
bsw/jbe@1309
|
5334 pos.prepopulatedChar = true;
|
bsw/jbe@1309
|
5335 pos.character = visibleChar;
|
bsw/jbe@1309
|
5336 pos.characterType = charType;
|
bsw/jbe@1309
|
5337 pos.isCharInvariant = finalizedChar;
|
bsw/jbe@1309
|
5338 }
|
bsw/jbe@1309
|
5339 },
|
bsw/jbe@1309
|
5340
|
bsw/jbe@1309
|
5341 isDefinitelyNonEmpty: function() {
|
bsw/jbe@1309
|
5342 var charType = this.characterType;
|
bsw/jbe@1309
|
5343 return charType == NON_SPACE || charType == UNCOLLAPSIBLE_SPACE;
|
bsw/jbe@1309
|
5344 },
|
bsw/jbe@1309
|
5345
|
bsw/jbe@1309
|
5346 // Resolve leading and trailing spaces, which may involve prepopulating other positions
|
bsw/jbe@1309
|
5347 resolveLeadingAndTrailingSpaces: function() {
|
bsw/jbe@1309
|
5348 if (!this.prepopulatedChar) {
|
bsw/jbe@1309
|
5349 this.prepopulateChar();
|
bsw/jbe@1309
|
5350 }
|
bsw/jbe@1309
|
5351 if (this.checkForTrailingSpace) {
|
bsw/jbe@1309
|
5352 var trailingSpace = this.session.getNodeWrapper(this.node.childNodes[this.offset - 1]).getTrailingSpace();
|
bsw/jbe@1309
|
5353 if (trailingSpace) {
|
bsw/jbe@1309
|
5354 this.isTrailingSpace = true;
|
bsw/jbe@1309
|
5355 this.character = trailingSpace;
|
bsw/jbe@1309
|
5356 this.characterType = COLLAPSIBLE_SPACE;
|
bsw/jbe@1309
|
5357 }
|
bsw/jbe@1309
|
5358 this.checkForTrailingSpace = false;
|
bsw/jbe@1309
|
5359 }
|
bsw/jbe@1309
|
5360 if (this.checkForLeadingSpace) {
|
bsw/jbe@1309
|
5361 var leadingSpace = this.session.getNodeWrapper(this.node.childNodes[this.offset]).getLeadingSpace();
|
bsw/jbe@1309
|
5362 if (leadingSpace) {
|
bsw/jbe@1309
|
5363 this.isLeadingSpace = true;
|
bsw/jbe@1309
|
5364 this.character = leadingSpace;
|
bsw/jbe@1309
|
5365 this.characterType = COLLAPSIBLE_SPACE;
|
bsw/jbe@1309
|
5366 }
|
bsw/jbe@1309
|
5367 this.checkForLeadingSpace = false;
|
bsw/jbe@1309
|
5368 }
|
bsw/jbe@1309
|
5369 },
|
bsw/jbe@1309
|
5370
|
bsw/jbe@1309
|
5371 getPrecedingUncollapsedPosition: function(characterOptions) {
|
bsw/jbe@1309
|
5372 var pos = this, character;
|
bsw/jbe@1309
|
5373 while ( (pos = pos.previousVisible()) ) {
|
bsw/jbe@1309
|
5374 character = pos.getCharacter(characterOptions);
|
bsw/jbe@1309
|
5375 if (character !== "") {
|
bsw/jbe@1309
|
5376 return pos;
|
bsw/jbe@1309
|
5377 }
|
bsw/jbe@1309
|
5378 }
|
bsw/jbe@1309
|
5379
|
bsw/jbe@1309
|
5380 return null;
|
bsw/jbe@1309
|
5381 },
|
bsw/jbe@1309
|
5382
|
bsw/jbe@1309
|
5383 getCharacter: function(characterOptions) {
|
bsw/jbe@1309
|
5384 this.resolveLeadingAndTrailingSpaces();
|
bsw/jbe@1309
|
5385
|
bsw/jbe@1309
|
5386 var thisChar = this.character, returnChar;
|
bsw/jbe@1309
|
5387
|
bsw/jbe@1309
|
5388 // Check if character is ignored
|
bsw/jbe@1309
|
5389 var ignoredChars = normalizeIgnoredCharacters(characterOptions.ignoreCharacters);
|
bsw/jbe@1309
|
5390 var isIgnoredCharacter = (thisChar !== "" && ignoredChars.indexOf(thisChar) > -1);
|
bsw/jbe@1309
|
5391
|
bsw/jbe@1309
|
5392 // Check if this position's character is invariant (i.e. not dependent on character options) and return it
|
bsw/jbe@1309
|
5393 // if so
|
bsw/jbe@1309
|
5394 if (this.isCharInvariant) {
|
bsw/jbe@1309
|
5395 returnChar = isIgnoredCharacter ? "" : thisChar;
|
bsw/jbe@1309
|
5396 return returnChar;
|
bsw/jbe@1309
|
5397 }
|
bsw/jbe@1309
|
5398
|
bsw/jbe@1309
|
5399 var cacheKey = ["character", characterOptions.includeSpaceBeforeBr, characterOptions.includeBlockContentTrailingSpace, characterOptions.includePreLineTrailingSpace, ignoredChars].join("_");
|
bsw/jbe@1309
|
5400 var cachedChar = this.cache.get(cacheKey);
|
bsw/jbe@1309
|
5401 if (cachedChar !== null) {
|
bsw/jbe@1309
|
5402 return cachedChar;
|
bsw/jbe@1309
|
5403 }
|
bsw/jbe@1309
|
5404
|
bsw/jbe@1309
|
5405 // We need to actually get the character now
|
bsw/jbe@1309
|
5406 var character = "";
|
bsw/jbe@1309
|
5407 var collapsible = (this.characterType == COLLAPSIBLE_SPACE);
|
bsw/jbe@1309
|
5408
|
bsw/jbe@1309
|
5409 var nextPos, previousPos;
|
bsw/jbe@1309
|
5410 var gotPreviousPos = false;
|
bsw/jbe@1309
|
5411 var pos = this;
|
bsw/jbe@1309
|
5412
|
bsw/jbe@1309
|
5413 function getPreviousPos() {
|
bsw/jbe@1309
|
5414 if (!gotPreviousPos) {
|
bsw/jbe@1309
|
5415 previousPos = pos.getPrecedingUncollapsedPosition(characterOptions);
|
bsw/jbe@1309
|
5416 gotPreviousPos = true;
|
bsw/jbe@1309
|
5417 }
|
bsw/jbe@1309
|
5418 return previousPos;
|
bsw/jbe@1309
|
5419 }
|
bsw/jbe@1309
|
5420
|
bsw/jbe@1309
|
5421 // Disallow a collapsible space that is followed by a line break or is the last character
|
bsw/jbe@1309
|
5422 if (collapsible) {
|
bsw/jbe@1309
|
5423 // Allow a trailing space that we've previously determined should be included
|
bsw/jbe@1309
|
5424 if (this.type == INCLUDED_TRAILING_LINE_BREAK_AFTER_BR) {
|
bsw/jbe@1309
|
5425 character = "\n";
|
bsw/jbe@1309
|
5426 }
|
bsw/jbe@1309
|
5427 // Disallow a collapsible space that follows a trailing space or line break, or is the first character,
|
bsw/jbe@1309
|
5428 // or follows a collapsible included space
|
bsw/jbe@1309
|
5429 else if (thisChar == " " &&
|
bsw/jbe@1309
|
5430 (!getPreviousPos() || previousPos.isTrailingSpace || previousPos.character == "\n" || (previousPos.character == " " && previousPos.characterType == COLLAPSIBLE_SPACE))) {
|
bsw/jbe@1309
|
5431 }
|
bsw/jbe@1309
|
5432 // Allow a leading line break unless it follows a line break
|
bsw/jbe@1309
|
5433 else if (thisChar == "\n" && this.isLeadingSpace) {
|
bsw/jbe@1309
|
5434 if (getPreviousPos() && previousPos.character != "\n") {
|
bsw/jbe@1309
|
5435 character = "\n";
|
bsw/jbe@1309
|
5436 } else {
|
bsw/jbe@1309
|
5437 }
|
bsw/jbe@1309
|
5438 } else {
|
bsw/jbe@1309
|
5439 nextPos = this.nextUncollapsed();
|
bsw/jbe@1309
|
5440 if (nextPos) {
|
bsw/jbe@1309
|
5441 if (nextPos.isBr) {
|
bsw/jbe@1309
|
5442 this.type = TRAILING_SPACE_BEFORE_BR;
|
bsw/jbe@1309
|
5443 } else if (nextPos.isTrailingSpace && nextPos.character == "\n") {
|
bsw/jbe@1309
|
5444 this.type = TRAILING_SPACE_IN_BLOCK;
|
bsw/jbe@1309
|
5445 } else if (nextPos.isLeadingSpace && nextPos.character == "\n") {
|
bsw/jbe@1309
|
5446 this.type = TRAILING_SPACE_BEFORE_BLOCK;
|
bsw/jbe@1309
|
5447 }
|
bsw/jbe@1309
|
5448
|
bsw/jbe@1309
|
5449 if (nextPos.character == "\n") {
|
bsw/jbe@1309
|
5450 if (this.type == TRAILING_SPACE_BEFORE_BR && !characterOptions.includeSpaceBeforeBr) {
|
bsw/jbe@1309
|
5451 } else if (this.type == TRAILING_SPACE_BEFORE_BLOCK && !characterOptions.includeSpaceBeforeBlock) {
|
bsw/jbe@1309
|
5452 } else if (this.type == TRAILING_SPACE_IN_BLOCK && nextPos.isTrailingSpace && !characterOptions.includeBlockContentTrailingSpace) {
|
bsw/jbe@1309
|
5453 } else if (this.type == PRE_LINE_TRAILING_SPACE_BEFORE_LINE_BREAK && nextPos.type == NON_SPACE && !characterOptions.includePreLineTrailingSpace) {
|
bsw/jbe@1309
|
5454 } else if (thisChar == "\n") {
|
bsw/jbe@1309
|
5455 if (nextPos.isTrailingSpace) {
|
bsw/jbe@1309
|
5456 if (this.isTrailingSpace) {
|
bsw/jbe@1309
|
5457 } else if (this.isBr) {
|
bsw/jbe@1309
|
5458 nextPos.type = TRAILING_LINE_BREAK_AFTER_BR;
|
bsw/jbe@1309
|
5459
|
bsw/jbe@1309
|
5460 if (getPreviousPos() && previousPos.isLeadingSpace && !previousPos.isTrailingSpace && previousPos.character == "\n") {
|
bsw/jbe@1309
|
5461 nextPos.character = "";
|
bsw/jbe@1309
|
5462 } else {
|
bsw/jbe@1309
|
5463 nextPos.type = INCLUDED_TRAILING_LINE_BREAK_AFTER_BR;
|
bsw/jbe@1309
|
5464 }
|
bsw/jbe@1309
|
5465 }
|
bsw/jbe@1309
|
5466 } else {
|
bsw/jbe@1309
|
5467 character = "\n";
|
bsw/jbe@1309
|
5468 }
|
bsw/jbe@1309
|
5469 } else if (thisChar == " ") {
|
bsw/jbe@1309
|
5470 character = " ";
|
bsw/jbe@1309
|
5471 } else {
|
bsw/jbe@1309
|
5472 }
|
bsw/jbe@1309
|
5473 } else {
|
bsw/jbe@1309
|
5474 character = thisChar;
|
bsw/jbe@1309
|
5475 }
|
bsw/jbe@1309
|
5476 } else {
|
bsw/jbe@1309
|
5477 }
|
bsw/jbe@1309
|
5478 }
|
bsw/jbe@1309
|
5479 }
|
bsw/jbe@1309
|
5480
|
bsw/jbe@1309
|
5481 if (ignoredChars.indexOf(character) > -1) {
|
bsw/jbe@1309
|
5482 character = "";
|
bsw/jbe@1309
|
5483 }
|
bsw/jbe@1309
|
5484
|
bsw/jbe@1309
|
5485
|
bsw/jbe@1309
|
5486 this.cache.set(cacheKey, character);
|
bsw/jbe@1309
|
5487
|
bsw/jbe@1309
|
5488 return character;
|
bsw/jbe@1309
|
5489 },
|
bsw/jbe@1309
|
5490
|
bsw/jbe@1309
|
5491 equals: function(pos) {
|
bsw/jbe@1309
|
5492 return !!pos && this.node === pos.node && this.offset === pos.offset;
|
bsw/jbe@1309
|
5493 },
|
bsw/jbe@1309
|
5494
|
bsw/jbe@1309
|
5495 inspect: inspectPosition,
|
bsw/jbe@1309
|
5496
|
bsw/jbe@1309
|
5497 toString: function() {
|
bsw/jbe@1309
|
5498 return this.character;
|
bsw/jbe@1309
|
5499 }
|
bsw/jbe@1309
|
5500 };
|
bsw/jbe@1309
|
5501
|
bsw/jbe@1309
|
5502 Position.prototype = positionProto;
|
bsw/jbe@1309
|
5503
|
bsw/jbe@1309
|
5504 extend(positionProto, {
|
bsw/jbe@1309
|
5505 next: createCachingGetter("nextPos", function(pos) {
|
bsw/jbe@1309
|
5506 var nodeWrapper = pos.nodeWrapper, node = pos.node, offset = pos.offset, session = nodeWrapper.session;
|
bsw/jbe@1309
|
5507 if (!node) {
|
bsw/jbe@1309
|
5508 return null;
|
bsw/jbe@1309
|
5509 }
|
bsw/jbe@1309
|
5510 var nextNode, nextOffset, child;
|
bsw/jbe@1309
|
5511 if (offset == nodeWrapper.getLength()) {
|
bsw/jbe@1309
|
5512 // Move onto the next node
|
bsw/jbe@1309
|
5513 nextNode = node.parentNode;
|
bsw/jbe@1309
|
5514 nextOffset = nextNode ? nodeWrapper.getNodeIndex() + 1 : 0;
|
bsw/jbe@1309
|
5515 } else {
|
bsw/jbe@1309
|
5516 if (nodeWrapper.isCharacterDataNode()) {
|
bsw/jbe@1309
|
5517 nextNode = node;
|
bsw/jbe@1309
|
5518 nextOffset = offset + 1;
|
bsw/jbe@1309
|
5519 } else {
|
bsw/jbe@1309
|
5520 child = node.childNodes[offset];
|
bsw/jbe@1309
|
5521 // Go into the children next, if children there are
|
bsw/jbe@1309
|
5522 if (session.getNodeWrapper(child).containsPositions()) {
|
bsw/jbe@1309
|
5523 nextNode = child;
|
bsw/jbe@1309
|
5524 nextOffset = 0;
|
bsw/jbe@1309
|
5525 } else {
|
bsw/jbe@1309
|
5526 nextNode = node;
|
bsw/jbe@1309
|
5527 nextOffset = offset + 1;
|
bsw/jbe@1309
|
5528 }
|
bsw/jbe@1309
|
5529 }
|
bsw/jbe@1309
|
5530 }
|
bsw/jbe@1309
|
5531
|
bsw/jbe@1309
|
5532 return nextNode ? session.getPosition(nextNode, nextOffset) : null;
|
bsw/jbe@1309
|
5533 }),
|
bsw/jbe@1309
|
5534
|
bsw/jbe@1309
|
5535 previous: createCachingGetter("previous", function(pos) {
|
bsw/jbe@1309
|
5536 var nodeWrapper = pos.nodeWrapper, node = pos.node, offset = pos.offset, session = nodeWrapper.session;
|
bsw/jbe@1309
|
5537 var previousNode, previousOffset, child;
|
bsw/jbe@1309
|
5538 if (offset == 0) {
|
bsw/jbe@1309
|
5539 previousNode = node.parentNode;
|
bsw/jbe@1309
|
5540 previousOffset = previousNode ? nodeWrapper.getNodeIndex() : 0;
|
bsw/jbe@1309
|
5541 } else {
|
bsw/jbe@1309
|
5542 if (nodeWrapper.isCharacterDataNode()) {
|
bsw/jbe@1309
|
5543 previousNode = node;
|
bsw/jbe@1309
|
5544 previousOffset = offset - 1;
|
bsw/jbe@1309
|
5545 } else {
|
bsw/jbe@1309
|
5546 child = node.childNodes[offset - 1];
|
bsw/jbe@1309
|
5547 // Go into the children next, if children there are
|
bsw/jbe@1309
|
5548 if (session.getNodeWrapper(child).containsPositions()) {
|
bsw/jbe@1309
|
5549 previousNode = child;
|
bsw/jbe@1309
|
5550 previousOffset = dom.getNodeLength(child);
|
bsw/jbe@1309
|
5551 } else {
|
bsw/jbe@1309
|
5552 previousNode = node;
|
bsw/jbe@1309
|
5553 previousOffset = offset - 1;
|
bsw/jbe@1309
|
5554 }
|
bsw/jbe@1309
|
5555 }
|
bsw/jbe@1309
|
5556 }
|
bsw/jbe@1309
|
5557 return previousNode ? session.getPosition(previousNode, previousOffset) : null;
|
bsw/jbe@1309
|
5558 }),
|
bsw/jbe@1309
|
5559
|
bsw/jbe@1309
|
5560 /*
|
bsw/jbe@1309
|
5561 Next and previous position moving functions that filter out
|
bsw/jbe@1309
|
5562
|
bsw/jbe@1309
|
5563 - Hidden (CSS visibility/display) elements
|
bsw/jbe@1309
|
5564 - Script and style elements
|
bsw/jbe@1309
|
5565 */
|
bsw/jbe@1309
|
5566 nextVisible: createCachingGetter("nextVisible", function(pos) {
|
bsw/jbe@1309
|
5567 var next = pos.next();
|
bsw/jbe@1309
|
5568 if (!next) {
|
bsw/jbe@1309
|
5569 return null;
|
bsw/jbe@1309
|
5570 }
|
bsw/jbe@1309
|
5571 var nodeWrapper = next.nodeWrapper, node = next.node;
|
bsw/jbe@1309
|
5572 var newPos = next;
|
bsw/jbe@1309
|
5573 if (nodeWrapper.isCollapsed()) {
|
bsw/jbe@1309
|
5574 // We're skipping this node and all its descendants
|
bsw/jbe@1309
|
5575 newPos = nodeWrapper.session.getPosition(node.parentNode, nodeWrapper.getNodeIndex() + 1);
|
bsw/jbe@1309
|
5576 }
|
bsw/jbe@1309
|
5577 return newPos;
|
bsw/jbe@1309
|
5578 }),
|
bsw/jbe@1309
|
5579
|
bsw/jbe@1309
|
5580 nextUncollapsed: createCachingGetter("nextUncollapsed", function(pos) {
|
bsw/jbe@1309
|
5581 var nextPos = pos;
|
bsw/jbe@1309
|
5582 while ( (nextPos = nextPos.nextVisible()) ) {
|
bsw/jbe@1309
|
5583 nextPos.resolveLeadingAndTrailingSpaces();
|
bsw/jbe@1309
|
5584 if (nextPos.character !== "") {
|
bsw/jbe@1309
|
5585 return nextPos;
|
bsw/jbe@1309
|
5586 }
|
bsw/jbe@1309
|
5587 }
|
bsw/jbe@1309
|
5588 return null;
|
bsw/jbe@1309
|
5589 }),
|
bsw/jbe@1309
|
5590
|
bsw/jbe@1309
|
5591 previousVisible: createCachingGetter("previousVisible", function(pos) {
|
bsw/jbe@1309
|
5592 var previous = pos.previous();
|
bsw/jbe@1309
|
5593 if (!previous) {
|
bsw/jbe@1309
|
5594 return null;
|
bsw/jbe@1309
|
5595 }
|
bsw/jbe@1309
|
5596 var nodeWrapper = previous.nodeWrapper, node = previous.node;
|
bsw/jbe@1309
|
5597 var newPos = previous;
|
bsw/jbe@1309
|
5598 if (nodeWrapper.isCollapsed()) {
|
bsw/jbe@1309
|
5599 // We're skipping this node and all its descendants
|
bsw/jbe@1309
|
5600 newPos = nodeWrapper.session.getPosition(node.parentNode, nodeWrapper.getNodeIndex());
|
bsw/jbe@1309
|
5601 }
|
bsw/jbe@1309
|
5602 return newPos;
|
bsw/jbe@1309
|
5603 })
|
bsw/jbe@1309
|
5604 });
|
bsw/jbe@1309
|
5605
|
bsw/jbe@1309
|
5606 /*----------------------------------------------------------------------------------------------------------------*/
|
bsw/jbe@1309
|
5607
|
bsw/jbe@1309
|
5608 var currentSession = null;
|
bsw/jbe@1309
|
5609
|
bsw/jbe@1309
|
5610 var Session = (function() {
|
bsw/jbe@1309
|
5611 function createWrapperCache(nodeProperty) {
|
bsw/jbe@1309
|
5612 var cache = new Cache();
|
bsw/jbe@1309
|
5613
|
bsw/jbe@1309
|
5614 return {
|
bsw/jbe@1309
|
5615 get: function(node) {
|
bsw/jbe@1309
|
5616 var wrappersByProperty = cache.get(node[nodeProperty]);
|
bsw/jbe@1309
|
5617 if (wrappersByProperty) {
|
bsw/jbe@1309
|
5618 for (var i = 0, wrapper; wrapper = wrappersByProperty[i++]; ) {
|
bsw/jbe@1309
|
5619 if (wrapper.node === node) {
|
bsw/jbe@1309
|
5620 return wrapper;
|
bsw/jbe@1309
|
5621 }
|
bsw/jbe@1309
|
5622 }
|
bsw/jbe@1309
|
5623 }
|
bsw/jbe@1309
|
5624 return null;
|
bsw/jbe@1309
|
5625 },
|
bsw/jbe@1309
|
5626
|
bsw/jbe@1309
|
5627 set: function(nodeWrapper) {
|
bsw/jbe@1309
|
5628 var property = nodeWrapper.node[nodeProperty];
|
bsw/jbe@1309
|
5629 var wrappersByProperty = cache.get(property) || cache.set(property, []);
|
bsw/jbe@1309
|
5630 wrappersByProperty.push(nodeWrapper);
|
bsw/jbe@1309
|
5631 }
|
bsw/jbe@1309
|
5632 };
|
bsw/jbe@1309
|
5633 }
|
bsw/jbe@1309
|
5634
|
bsw/jbe@1309
|
5635 var uniqueIDSupported = util.isHostProperty(document.documentElement, "uniqueID");
|
bsw/jbe@1309
|
5636
|
bsw/jbe@1309
|
5637 function Session() {
|
bsw/jbe@1309
|
5638 this.initCaches();
|
bsw/jbe@1309
|
5639 }
|
bsw/jbe@1309
|
5640
|
bsw/jbe@1309
|
5641 Session.prototype = {
|
bsw/jbe@1309
|
5642 initCaches: function() {
|
bsw/jbe@1309
|
5643 this.elementCache = uniqueIDSupported ? (function() {
|
bsw/jbe@1309
|
5644 var elementsCache = new Cache();
|
bsw/jbe@1309
|
5645
|
bsw/jbe@1309
|
5646 return {
|
bsw/jbe@1309
|
5647 get: function(el) {
|
bsw/jbe@1309
|
5648 return elementsCache.get(el.uniqueID);
|
bsw/jbe@1309
|
5649 },
|
bsw/jbe@1309
|
5650
|
bsw/jbe@1309
|
5651 set: function(elWrapper) {
|
bsw/jbe@1309
|
5652 elementsCache.set(elWrapper.node.uniqueID, elWrapper);
|
bsw/jbe@1309
|
5653 }
|
bsw/jbe@1309
|
5654 };
|
bsw/jbe@1309
|
5655 })() : createWrapperCache("tagName");
|
bsw/jbe@1309
|
5656
|
bsw/jbe@1309
|
5657 // Store text nodes keyed by data, although we may need to truncate this
|
bsw/jbe@1309
|
5658 this.textNodeCache = createWrapperCache("data");
|
bsw/jbe@1309
|
5659 this.otherNodeCache = createWrapperCache("nodeName");
|
bsw/jbe@1309
|
5660 },
|
bsw/jbe@1309
|
5661
|
bsw/jbe@1309
|
5662 getNodeWrapper: function(node) {
|
bsw/jbe@1309
|
5663 var wrapperCache;
|
bsw/jbe@1309
|
5664 switch (node.nodeType) {
|
bsw/jbe@1309
|
5665 case 1:
|
bsw/jbe@1309
|
5666 wrapperCache = this.elementCache;
|
bsw/jbe@1309
|
5667 break;
|
bsw/jbe@1309
|
5668 case 3:
|
bsw/jbe@1309
|
5669 wrapperCache = this.textNodeCache;
|
bsw/jbe@1309
|
5670 break;
|
bsw/jbe@1309
|
5671 default:
|
bsw/jbe@1309
|
5672 wrapperCache = this.otherNodeCache;
|
bsw/jbe@1309
|
5673 break;
|
bsw/jbe@1309
|
5674 }
|
bsw/jbe@1309
|
5675
|
bsw/jbe@1309
|
5676 var wrapper = wrapperCache.get(node);
|
bsw/jbe@1309
|
5677 if (!wrapper) {
|
bsw/jbe@1309
|
5678 wrapper = new NodeWrapper(node, this);
|
bsw/jbe@1309
|
5679 wrapperCache.set(wrapper);
|
bsw/jbe@1309
|
5680 }
|
bsw/jbe@1309
|
5681 return wrapper;
|
bsw/jbe@1309
|
5682 },
|
bsw/jbe@1309
|
5683
|
bsw/jbe@1309
|
5684 getPosition: function(node, offset) {
|
bsw/jbe@1309
|
5685 return this.getNodeWrapper(node).getPosition(offset);
|
bsw/jbe@1309
|
5686 },
|
bsw/jbe@1309
|
5687
|
bsw/jbe@1309
|
5688 getRangeBoundaryPosition: function(range, isStart) {
|
bsw/jbe@1309
|
5689 var prefix = isStart ? "start" : "end";
|
bsw/jbe@1309
|
5690 return this.getPosition(range[prefix + "Container"], range[prefix + "Offset"]);
|
bsw/jbe@1309
|
5691 },
|
bsw/jbe@1309
|
5692
|
bsw/jbe@1309
|
5693 detach: function() {
|
bsw/jbe@1309
|
5694 this.elementCache = this.textNodeCache = this.otherNodeCache = null;
|
bsw/jbe@1309
|
5695 }
|
bsw/jbe@1309
|
5696 };
|
bsw/jbe@1309
|
5697
|
bsw/jbe@1309
|
5698 return Session;
|
bsw/jbe@1309
|
5699 })();
|
bsw/jbe@1309
|
5700
|
bsw/jbe@1309
|
5701 /*----------------------------------------------------------------------------------------------------------------*/
|
bsw/jbe@1309
|
5702
|
bsw/jbe@1309
|
5703 function startSession() {
|
bsw/jbe@1309
|
5704 endSession();
|
bsw/jbe@1309
|
5705 return (currentSession = new Session());
|
bsw/jbe@1309
|
5706 }
|
bsw/jbe@1309
|
5707
|
bsw/jbe@1309
|
5708 function getSession() {
|
bsw/jbe@1309
|
5709 return currentSession || startSession();
|
bsw/jbe@1309
|
5710 }
|
bsw/jbe@1309
|
5711
|
bsw/jbe@1309
|
5712 function endSession() {
|
bsw/jbe@1309
|
5713 if (currentSession) {
|
bsw/jbe@1309
|
5714 currentSession.detach();
|
bsw/jbe@1309
|
5715 }
|
bsw/jbe@1309
|
5716 currentSession = null;
|
bsw/jbe@1309
|
5717 }
|
bsw/jbe@1309
|
5718
|
bsw/jbe@1309
|
5719 /*----------------------------------------------------------------------------------------------------------------*/
|
bsw/jbe@1309
|
5720
|
bsw/jbe@1309
|
5721 // Extensions to the rangy.dom utility object
|
bsw/jbe@1309
|
5722
|
bsw/jbe@1309
|
5723 extend(dom, {
|
bsw/jbe@1309
|
5724 nextNode: nextNode,
|
bsw/jbe@1309
|
5725 previousNode: previousNode
|
bsw/jbe@1309
|
5726 });
|
bsw/jbe@1309
|
5727
|
bsw/jbe@1309
|
5728 /*----------------------------------------------------------------------------------------------------------------*/
|
bsw/jbe@1309
|
5729
|
bsw/jbe@1309
|
5730 function createCharacterIterator(startPos, backward, endPos, characterOptions) {
|
bsw/jbe@1309
|
5731
|
bsw/jbe@1309
|
5732 // Adjust the end position to ensure that it is actually reached
|
bsw/jbe@1309
|
5733 if (endPos) {
|
bsw/jbe@1309
|
5734 if (backward) {
|
bsw/jbe@1309
|
5735 if (isCollapsedNode(endPos.node)) {
|
bsw/jbe@1309
|
5736 endPos = startPos.previousVisible();
|
bsw/jbe@1309
|
5737 }
|
bsw/jbe@1309
|
5738 } else {
|
bsw/jbe@1309
|
5739 if (isCollapsedNode(endPos.node)) {
|
bsw/jbe@1309
|
5740 endPos = endPos.nextVisible();
|
bsw/jbe@1309
|
5741 }
|
bsw/jbe@1309
|
5742 }
|
bsw/jbe@1309
|
5743 }
|
bsw/jbe@1309
|
5744
|
bsw/jbe@1309
|
5745 var pos = startPos, finished = false;
|
bsw/jbe@1309
|
5746
|
bsw/jbe@1309
|
5747 function next() {
|
bsw/jbe@1309
|
5748 var charPos = null;
|
bsw/jbe@1309
|
5749 if (backward) {
|
bsw/jbe@1309
|
5750 charPos = pos;
|
bsw/jbe@1309
|
5751 if (!finished) {
|
bsw/jbe@1309
|
5752 pos = pos.previousVisible();
|
bsw/jbe@1309
|
5753 finished = !pos || (endPos && pos.equals(endPos));
|
bsw/jbe@1309
|
5754 }
|
bsw/jbe@1309
|
5755 } else {
|
bsw/jbe@1309
|
5756 if (!finished) {
|
bsw/jbe@1309
|
5757 charPos = pos = pos.nextVisible();
|
bsw/jbe@1309
|
5758 finished = !pos || (endPos && pos.equals(endPos));
|
bsw/jbe@1309
|
5759 }
|
bsw/jbe@1309
|
5760 }
|
bsw/jbe@1309
|
5761 if (finished) {
|
bsw/jbe@1309
|
5762 pos = null;
|
bsw/jbe@1309
|
5763 }
|
bsw/jbe@1309
|
5764 return charPos;
|
bsw/jbe@1309
|
5765 }
|
bsw/jbe@1309
|
5766
|
bsw/jbe@1309
|
5767 var previousTextPos, returnPreviousTextPos = false;
|
bsw/jbe@1309
|
5768
|
bsw/jbe@1309
|
5769 return {
|
bsw/jbe@1309
|
5770 next: function() {
|
bsw/jbe@1309
|
5771 if (returnPreviousTextPos) {
|
bsw/jbe@1309
|
5772 returnPreviousTextPos = false;
|
bsw/jbe@1309
|
5773 return previousTextPos;
|
bsw/jbe@1309
|
5774 } else {
|
bsw/jbe@1309
|
5775 var pos, character;
|
bsw/jbe@1309
|
5776 while ( (pos = next()) ) {
|
bsw/jbe@1309
|
5777 character = pos.getCharacter(characterOptions);
|
bsw/jbe@1309
|
5778 if (character) {
|
bsw/jbe@1309
|
5779 previousTextPos = pos;
|
bsw/jbe@1309
|
5780 return pos;
|
bsw/jbe@1309
|
5781 }
|
bsw/jbe@1309
|
5782 }
|
bsw/jbe@1309
|
5783 return null;
|
bsw/jbe@1309
|
5784 }
|
bsw/jbe@1309
|
5785 },
|
bsw/jbe@1309
|
5786
|
bsw/jbe@1309
|
5787 rewind: function() {
|
bsw/jbe@1309
|
5788 if (previousTextPos) {
|
bsw/jbe@1309
|
5789 returnPreviousTextPos = true;
|
bsw/jbe@1309
|
5790 } else {
|
bsw/jbe@1309
|
5791 throw module.createError("createCharacterIterator: cannot rewind. Only one position can be rewound.");
|
bsw/jbe@1309
|
5792 }
|
bsw/jbe@1309
|
5793 },
|
bsw/jbe@1309
|
5794
|
bsw/jbe@1309
|
5795 dispose: function() {
|
bsw/jbe@1309
|
5796 startPos = endPos = null;
|
bsw/jbe@1309
|
5797 }
|
bsw/jbe@1309
|
5798 };
|
bsw/jbe@1309
|
5799 }
|
bsw/jbe@1309
|
5800
|
bsw/jbe@1309
|
5801 var arrayIndexOf = Array.prototype.indexOf ?
|
bsw/jbe@1309
|
5802 function(arr, val) {
|
bsw/jbe@1309
|
5803 return arr.indexOf(val);
|
bsw/jbe@1309
|
5804 } :
|
bsw/jbe@1309
|
5805 function(arr, val) {
|
bsw/jbe@1309
|
5806 for (var i = 0, len = arr.length; i < len; ++i) {
|
bsw/jbe@1309
|
5807 if (arr[i] === val) {
|
bsw/jbe@1309
|
5808 return i;
|
bsw/jbe@1309
|
5809 }
|
bsw/jbe@1309
|
5810 }
|
bsw/jbe@1309
|
5811 return -1;
|
bsw/jbe@1309
|
5812 };
|
bsw/jbe@1309
|
5813
|
bsw/jbe@1309
|
5814 // Provides a pair of iterators over text positions, tokenized. Transparently requests more text when next()
|
bsw/jbe@1309
|
5815 // is called and there is no more tokenized text
|
bsw/jbe@1309
|
5816 function createTokenizedTextProvider(pos, characterOptions, wordOptions) {
|
bsw/jbe@1309
|
5817 var forwardIterator = createCharacterIterator(pos, false, null, characterOptions);
|
bsw/jbe@1309
|
5818 var backwardIterator = createCharacterIterator(pos, true, null, characterOptions);
|
bsw/jbe@1309
|
5819 var tokenizer = wordOptions.tokenizer;
|
bsw/jbe@1309
|
5820
|
bsw/jbe@1309
|
5821 // Consumes a word and the whitespace beyond it
|
bsw/jbe@1309
|
5822 function consumeWord(forward) {
|
bsw/jbe@1309
|
5823 var pos, textChar;
|
bsw/jbe@1309
|
5824 var newChars = [], it = forward ? forwardIterator : backwardIterator;
|
bsw/jbe@1309
|
5825
|
bsw/jbe@1309
|
5826 var passedWordBoundary = false, insideWord = false;
|
bsw/jbe@1309
|
5827
|
bsw/jbe@1309
|
5828 while ( (pos = it.next()) ) {
|
bsw/jbe@1309
|
5829 textChar = pos.character;
|
bsw/jbe@1309
|
5830
|
bsw/jbe@1309
|
5831
|
bsw/jbe@1309
|
5832 if (allWhiteSpaceRegex.test(textChar)) {
|
bsw/jbe@1309
|
5833 if (insideWord) {
|
bsw/jbe@1309
|
5834 insideWord = false;
|
bsw/jbe@1309
|
5835 passedWordBoundary = true;
|
bsw/jbe@1309
|
5836 }
|
bsw/jbe@1309
|
5837 } else {
|
bsw/jbe@1309
|
5838 if (passedWordBoundary) {
|
bsw/jbe@1309
|
5839 it.rewind();
|
bsw/jbe@1309
|
5840 break;
|
bsw/jbe@1309
|
5841 } else {
|
bsw/jbe@1309
|
5842 insideWord = true;
|
bsw/jbe@1309
|
5843 }
|
bsw/jbe@1309
|
5844 }
|
bsw/jbe@1309
|
5845 newChars.push(pos);
|
bsw/jbe@1309
|
5846 }
|
bsw/jbe@1309
|
5847
|
bsw/jbe@1309
|
5848
|
bsw/jbe@1309
|
5849 return newChars;
|
bsw/jbe@1309
|
5850 }
|
bsw/jbe@1309
|
5851
|
bsw/jbe@1309
|
5852 // Get initial word surrounding initial position and tokenize it
|
bsw/jbe@1309
|
5853 var forwardChars = consumeWord(true);
|
bsw/jbe@1309
|
5854 var backwardChars = consumeWord(false).reverse();
|
bsw/jbe@1309
|
5855 var tokens = tokenize(backwardChars.concat(forwardChars), wordOptions, tokenizer);
|
bsw/jbe@1309
|
5856
|
bsw/jbe@1309
|
5857 // Create initial token buffers
|
bsw/jbe@1309
|
5858 var forwardTokensBuffer = forwardChars.length ?
|
bsw/jbe@1309
|
5859 tokens.slice(arrayIndexOf(tokens, forwardChars[0].token)) : [];
|
bsw/jbe@1309
|
5860
|
bsw/jbe@1309
|
5861 var backwardTokensBuffer = backwardChars.length ?
|
bsw/jbe@1309
|
5862 tokens.slice(0, arrayIndexOf(tokens, backwardChars.pop().token) + 1) : [];
|
bsw/jbe@1309
|
5863
|
bsw/jbe@1309
|
5864 function inspectBuffer(buffer) {
|
bsw/jbe@1309
|
5865 var textPositions = ["[" + buffer.length + "]"];
|
bsw/jbe@1309
|
5866 for (var i = 0; i < buffer.length; ++i) {
|
bsw/jbe@1309
|
5867 textPositions.push("(word: " + buffer[i] + ", is word: " + buffer[i].isWord + ")");
|
bsw/jbe@1309
|
5868 }
|
bsw/jbe@1309
|
5869 return textPositions;
|
bsw/jbe@1309
|
5870 }
|
bsw/jbe@1309
|
5871
|
bsw/jbe@1309
|
5872
|
bsw/jbe@1309
|
5873 return {
|
bsw/jbe@1309
|
5874 nextEndToken: function() {
|
bsw/jbe@1309
|
5875 var lastToken, forwardChars;
|
bsw/jbe@1309
|
5876
|
bsw/jbe@1309
|
5877 // If we're down to the last token, consume character chunks until we have a word or run out of
|
bsw/jbe@1309
|
5878 // characters to consume
|
bsw/jbe@1309
|
5879 while ( forwardTokensBuffer.length == 1 &&
|
bsw/jbe@1309
|
5880 !(lastToken = forwardTokensBuffer[0]).isWord &&
|
bsw/jbe@1309
|
5881 (forwardChars = consumeWord(true)).length > 0) {
|
bsw/jbe@1309
|
5882
|
bsw/jbe@1309
|
5883 // Merge trailing non-word into next word and tokenize
|
bsw/jbe@1309
|
5884 forwardTokensBuffer = tokenize(lastToken.chars.concat(forwardChars), wordOptions, tokenizer);
|
bsw/jbe@1309
|
5885 }
|
bsw/jbe@1309
|
5886
|
bsw/jbe@1309
|
5887 return forwardTokensBuffer.shift();
|
bsw/jbe@1309
|
5888 },
|
bsw/jbe@1309
|
5889
|
bsw/jbe@1309
|
5890 previousStartToken: function() {
|
bsw/jbe@1309
|
5891 var lastToken, backwardChars;
|
bsw/jbe@1309
|
5892
|
bsw/jbe@1309
|
5893 // If we're down to the last token, consume character chunks until we have a word or run out of
|
bsw/jbe@1309
|
5894 // characters to consume
|
bsw/jbe@1309
|
5895 while ( backwardTokensBuffer.length == 1 &&
|
bsw/jbe@1309
|
5896 !(lastToken = backwardTokensBuffer[0]).isWord &&
|
bsw/jbe@1309
|
5897 (backwardChars = consumeWord(false)).length > 0) {
|
bsw/jbe@1309
|
5898
|
bsw/jbe@1309
|
5899 // Merge leading non-word into next word and tokenize
|
bsw/jbe@1309
|
5900 backwardTokensBuffer = tokenize(backwardChars.reverse().concat(lastToken.chars), wordOptions, tokenizer);
|
bsw/jbe@1309
|
5901 }
|
bsw/jbe@1309
|
5902
|
bsw/jbe@1309
|
5903 return backwardTokensBuffer.pop();
|
bsw/jbe@1309
|
5904 },
|
bsw/jbe@1309
|
5905
|
bsw/jbe@1309
|
5906 dispose: function() {
|
bsw/jbe@1309
|
5907 forwardIterator.dispose();
|
bsw/jbe@1309
|
5908 backwardIterator.dispose();
|
bsw/jbe@1309
|
5909 forwardTokensBuffer = backwardTokensBuffer = null;
|
bsw/jbe@1309
|
5910 }
|
bsw/jbe@1309
|
5911 };
|
bsw/jbe@1309
|
5912 }
|
bsw/jbe@1309
|
5913
|
bsw/jbe@1309
|
5914 function movePositionBy(pos, unit, count, characterOptions, wordOptions) {
|
bsw/jbe@1309
|
5915 var unitsMoved = 0, currentPos, newPos = pos, charIterator, nextPos, absCount = Math.abs(count), token;
|
bsw/jbe@1309
|
5916 if (count !== 0) {
|
bsw/jbe@1309
|
5917 var backward = (count < 0);
|
bsw/jbe@1309
|
5918
|
bsw/jbe@1309
|
5919 switch (unit) {
|
bsw/jbe@1309
|
5920 case CHARACTER:
|
bsw/jbe@1309
|
5921 charIterator = createCharacterIterator(pos, backward, null, characterOptions);
|
bsw/jbe@1309
|
5922 while ( (currentPos = charIterator.next()) && unitsMoved < absCount ) {
|
bsw/jbe@1309
|
5923 ++unitsMoved;
|
bsw/jbe@1309
|
5924 newPos = currentPos;
|
bsw/jbe@1309
|
5925 }
|
bsw/jbe@1309
|
5926 nextPos = currentPos;
|
bsw/jbe@1309
|
5927 charIterator.dispose();
|
bsw/jbe@1309
|
5928 break;
|
bsw/jbe@1309
|
5929 case WORD:
|
bsw/jbe@1309
|
5930 var tokenizedTextProvider = createTokenizedTextProvider(pos, characterOptions, wordOptions);
|
bsw/jbe@1309
|
5931 var next = backward ? tokenizedTextProvider.previousStartToken : tokenizedTextProvider.nextEndToken;
|
bsw/jbe@1309
|
5932
|
bsw/jbe@1309
|
5933 while ( (token = next()) && unitsMoved < absCount ) {
|
bsw/jbe@1309
|
5934 if (token.isWord) {
|
bsw/jbe@1309
|
5935 ++unitsMoved;
|
bsw/jbe@1309
|
5936 newPos = backward ? token.chars[0] : token.chars[token.chars.length - 1];
|
bsw/jbe@1309
|
5937 }
|
bsw/jbe@1309
|
5938 }
|
bsw/jbe@1309
|
5939 break;
|
bsw/jbe@1309
|
5940 default:
|
bsw/jbe@1309
|
5941 throw new Error("movePositionBy: unit '" + unit + "' not implemented");
|
bsw/jbe@1309
|
5942 }
|
bsw/jbe@1309
|
5943
|
bsw/jbe@1309
|
5944 // Perform any necessary position tweaks
|
bsw/jbe@1309
|
5945 if (backward) {
|
bsw/jbe@1309
|
5946 newPos = newPos.previousVisible();
|
bsw/jbe@1309
|
5947 unitsMoved = -unitsMoved;
|
bsw/jbe@1309
|
5948 } else if (newPos && newPos.isLeadingSpace && !newPos.isTrailingSpace) {
|
bsw/jbe@1309
|
5949 // Tweak the position for the case of a leading space. The problem is that an uncollapsed leading space
|
bsw/jbe@1309
|
5950 // before a block element (for example, the line break between "1" and "2" in the following HTML:
|
bsw/jbe@1309
|
5951 // "1<p>2</p>") is considered to be attached to the position immediately before the block element, which
|
bsw/jbe@1309
|
5952 // corresponds with a different selection position in most browsers from the one we want (i.e. at the
|
bsw/jbe@1309
|
5953 // start of the contents of the block element). We get round this by advancing the position returned to
|
bsw/jbe@1309
|
5954 // the last possible equivalent visible position.
|
bsw/jbe@1309
|
5955 if (unit == WORD) {
|
bsw/jbe@1309
|
5956 charIterator = createCharacterIterator(pos, false, null, characterOptions);
|
bsw/jbe@1309
|
5957 nextPos = charIterator.next();
|
bsw/jbe@1309
|
5958 charIterator.dispose();
|
bsw/jbe@1309
|
5959 }
|
bsw/jbe@1309
|
5960 if (nextPos) {
|
bsw/jbe@1309
|
5961 newPos = nextPos.previousVisible();
|
bsw/jbe@1309
|
5962 }
|
bsw/jbe@1309
|
5963 }
|
bsw/jbe@1309
|
5964 }
|
bsw/jbe@1309
|
5965
|
bsw/jbe@1309
|
5966
|
bsw/jbe@1309
|
5967 return {
|
bsw/jbe@1309
|
5968 position: newPos,
|
bsw/jbe@1309
|
5969 unitsMoved: unitsMoved
|
bsw/jbe@1309
|
5970 };
|
bsw/jbe@1309
|
5971 }
|
bsw/jbe@1309
|
5972
|
bsw/jbe@1309
|
5973 function createRangeCharacterIterator(session, range, characterOptions, backward) {
|
bsw/jbe@1309
|
5974 var rangeStart = session.getRangeBoundaryPosition(range, true);
|
bsw/jbe@1309
|
5975 var rangeEnd = session.getRangeBoundaryPosition(range, false);
|
bsw/jbe@1309
|
5976 var itStart = backward ? rangeEnd : rangeStart;
|
bsw/jbe@1309
|
5977 var itEnd = backward ? rangeStart : rangeEnd;
|
bsw/jbe@1309
|
5978
|
bsw/jbe@1309
|
5979 return createCharacterIterator(itStart, !!backward, itEnd, characterOptions);
|
bsw/jbe@1309
|
5980 }
|
bsw/jbe@1309
|
5981
|
bsw/jbe@1309
|
5982 function getRangeCharacters(session, range, characterOptions) {
|
bsw/jbe@1309
|
5983
|
bsw/jbe@1309
|
5984 var chars = [], it = createRangeCharacterIterator(session, range, characterOptions), pos;
|
bsw/jbe@1309
|
5985 while ( (pos = it.next()) ) {
|
bsw/jbe@1309
|
5986 chars.push(pos);
|
bsw/jbe@1309
|
5987 }
|
bsw/jbe@1309
|
5988
|
bsw/jbe@1309
|
5989 it.dispose();
|
bsw/jbe@1309
|
5990 return chars;
|
bsw/jbe@1309
|
5991 }
|
bsw/jbe@1309
|
5992
|
bsw/jbe@1309
|
5993 function isWholeWord(startPos, endPos, wordOptions) {
|
bsw/jbe@1309
|
5994 var range = api.createRange(startPos.node);
|
bsw/jbe@1309
|
5995 range.setStartAndEnd(startPos.node, startPos.offset, endPos.node, endPos.offset);
|
bsw/jbe@1309
|
5996 return !range.expand("word", { wordOptions: wordOptions });
|
bsw/jbe@1309
|
5997 }
|
bsw/jbe@1309
|
5998
|
bsw/jbe@1309
|
5999 function findTextFromPosition(initialPos, searchTerm, isRegex, searchScopeRange, findOptions) {
|
bsw/jbe@1309
|
6000 var backward = isDirectionBackward(findOptions.direction);
|
bsw/jbe@1309
|
6001 var it = createCharacterIterator(
|
bsw/jbe@1309
|
6002 initialPos,
|
bsw/jbe@1309
|
6003 backward,
|
bsw/jbe@1309
|
6004 initialPos.session.getRangeBoundaryPosition(searchScopeRange, backward),
|
bsw/jbe@1309
|
6005 findOptions.characterOptions
|
bsw/jbe@1309
|
6006 );
|
bsw/jbe@1309
|
6007 var text = "", chars = [], pos, currentChar, matchStartIndex, matchEndIndex;
|
bsw/jbe@1309
|
6008 var result, insideRegexMatch;
|
bsw/jbe@1309
|
6009 var returnValue = null;
|
bsw/jbe@1309
|
6010
|
bsw/jbe@1309
|
6011 function handleMatch(startIndex, endIndex) {
|
bsw/jbe@1309
|
6012 var startPos = chars[startIndex].previousVisible();
|
bsw/jbe@1309
|
6013 var endPos = chars[endIndex - 1];
|
bsw/jbe@1309
|
6014 var valid = (!findOptions.wholeWordsOnly || isWholeWord(startPos, endPos, findOptions.wordOptions));
|
bsw/jbe@1309
|
6015
|
bsw/jbe@1309
|
6016 return {
|
bsw/jbe@1309
|
6017 startPos: startPos,
|
bsw/jbe@1309
|
6018 endPos: endPos,
|
bsw/jbe@1309
|
6019 valid: valid
|
bsw/jbe@1309
|
6020 };
|
bsw/jbe@1309
|
6021 }
|
bsw/jbe@1309
|
6022
|
bsw/jbe@1309
|
6023 while ( (pos = it.next()) ) {
|
bsw/jbe@1309
|
6024 currentChar = pos.character;
|
bsw/jbe@1309
|
6025 if (!isRegex && !findOptions.caseSensitive) {
|
bsw/jbe@1309
|
6026 currentChar = currentChar.toLowerCase();
|
bsw/jbe@1309
|
6027 }
|
bsw/jbe@1309
|
6028
|
bsw/jbe@1309
|
6029 if (backward) {
|
bsw/jbe@1309
|
6030 chars.unshift(pos);
|
bsw/jbe@1309
|
6031 text = currentChar + text;
|
bsw/jbe@1309
|
6032 } else {
|
bsw/jbe@1309
|
6033 chars.push(pos);
|
bsw/jbe@1309
|
6034 text += currentChar;
|
bsw/jbe@1309
|
6035 }
|
bsw/jbe@1309
|
6036
|
bsw/jbe@1309
|
6037 if (isRegex) {
|
bsw/jbe@1309
|
6038 result = searchTerm.exec(text);
|
bsw/jbe@1309
|
6039 if (result) {
|
bsw/jbe@1309
|
6040 matchStartIndex = result.index;
|
bsw/jbe@1309
|
6041 matchEndIndex = matchStartIndex + result[0].length;
|
bsw/jbe@1309
|
6042 if (insideRegexMatch) {
|
bsw/jbe@1309
|
6043 // Check whether the match is now over
|
bsw/jbe@1309
|
6044 if ((!backward && matchEndIndex < text.length) || (backward && matchStartIndex > 0)) {
|
bsw/jbe@1309
|
6045 returnValue = handleMatch(matchStartIndex, matchEndIndex);
|
bsw/jbe@1309
|
6046 break;
|
bsw/jbe@1309
|
6047 }
|
bsw/jbe@1309
|
6048 } else {
|
bsw/jbe@1309
|
6049 insideRegexMatch = true;
|
bsw/jbe@1309
|
6050 }
|
bsw/jbe@1309
|
6051 }
|
bsw/jbe@1309
|
6052 } else if ( (matchStartIndex = text.indexOf(searchTerm)) != -1 ) {
|
bsw/jbe@1309
|
6053 returnValue = handleMatch(matchStartIndex, matchStartIndex + searchTerm.length);
|
bsw/jbe@1309
|
6054 break;
|
bsw/jbe@1309
|
6055 }
|
bsw/jbe@1309
|
6056 }
|
bsw/jbe@1309
|
6057
|
bsw/jbe@1309
|
6058 // Check whether regex match extends to the end of the range
|
bsw/jbe@1309
|
6059 if (insideRegexMatch) {
|
bsw/jbe@1309
|
6060 returnValue = handleMatch(matchStartIndex, matchEndIndex);
|
bsw/jbe@1309
|
6061 }
|
bsw/jbe@1309
|
6062 it.dispose();
|
bsw/jbe@1309
|
6063
|
bsw/jbe@1309
|
6064 return returnValue;
|
bsw/jbe@1309
|
6065 }
|
bsw/jbe@1309
|
6066
|
bsw/jbe@1309
|
6067 function createEntryPointFunction(func) {
|
bsw/jbe@1309
|
6068 return function() {
|
bsw/jbe@1309
|
6069 var sessionRunning = !!currentSession;
|
bsw/jbe@1309
|
6070 var session = getSession();
|
bsw/jbe@1309
|
6071 var args = [session].concat( util.toArray(arguments) );
|
bsw/jbe@1309
|
6072 var returnValue = func.apply(this, args);
|
bsw/jbe@1309
|
6073 if (!sessionRunning) {
|
bsw/jbe@1309
|
6074 endSession();
|
bsw/jbe@1309
|
6075 }
|
bsw/jbe@1309
|
6076 return returnValue;
|
bsw/jbe@1309
|
6077 };
|
bsw/jbe@1309
|
6078 }
|
bsw/jbe@1309
|
6079
|
bsw/jbe@1309
|
6080 /*----------------------------------------------------------------------------------------------------------------*/
|
bsw/jbe@1309
|
6081
|
bsw/jbe@1309
|
6082 // Extensions to the Rangy Range object
|
bsw/jbe@1309
|
6083
|
bsw/jbe@1309
|
6084 function createRangeBoundaryMover(isStart, collapse) {
|
bsw/jbe@1309
|
6085 /*
|
bsw/jbe@1309
|
6086 Unit can be "character" or "word"
|
bsw/jbe@1309
|
6087 Options:
|
bsw/jbe@1309
|
6088
|
bsw/jbe@1309
|
6089 - includeTrailingSpace
|
bsw/jbe@1309
|
6090 - wordRegex
|
bsw/jbe@1309
|
6091 - tokenizer
|
bsw/jbe@1309
|
6092 - collapseSpaceBeforeLineBreak
|
bsw/jbe@1309
|
6093 */
|
bsw/jbe@1309
|
6094 return createEntryPointFunction(
|
bsw/jbe@1309
|
6095 function(session, unit, count, moveOptions) {
|
bsw/jbe@1309
|
6096 if (typeof count == UNDEF) {
|
bsw/jbe@1309
|
6097 count = unit;
|
bsw/jbe@1309
|
6098 unit = CHARACTER;
|
bsw/jbe@1309
|
6099 }
|
bsw/jbe@1309
|
6100 moveOptions = createNestedOptions(moveOptions, defaultMoveOptions);
|
bsw/jbe@1309
|
6101
|
bsw/jbe@1309
|
6102 var boundaryIsStart = isStart;
|
bsw/jbe@1309
|
6103 if (collapse) {
|
bsw/jbe@1309
|
6104 boundaryIsStart = (count >= 0);
|
bsw/jbe@1309
|
6105 this.collapse(!boundaryIsStart);
|
bsw/jbe@1309
|
6106 }
|
bsw/jbe@1309
|
6107 var moveResult = movePositionBy(session.getRangeBoundaryPosition(this, boundaryIsStart), unit, count, moveOptions.characterOptions, moveOptions.wordOptions);
|
bsw/jbe@1309
|
6108 var newPos = moveResult.position;
|
bsw/jbe@1309
|
6109 this[boundaryIsStart ? "setStart" : "setEnd"](newPos.node, newPos.offset);
|
bsw/jbe@1309
|
6110 return moveResult.unitsMoved;
|
bsw/jbe@1309
|
6111 }
|
bsw/jbe@1309
|
6112 );
|
bsw/jbe@1309
|
6113 }
|
bsw/jbe@1309
|
6114
|
bsw/jbe@1309
|
6115 function createRangeTrimmer(isStart) {
|
bsw/jbe@1309
|
6116 return createEntryPointFunction(
|
bsw/jbe@1309
|
6117 function(session, characterOptions) {
|
bsw/jbe@1309
|
6118 characterOptions = createOptions(characterOptions, defaultCharacterOptions);
|
bsw/jbe@1309
|
6119 var pos;
|
bsw/jbe@1309
|
6120 var it = createRangeCharacterIterator(session, this, characterOptions, !isStart);
|
bsw/jbe@1309
|
6121 var trimCharCount = 0;
|
bsw/jbe@1309
|
6122 while ( (pos = it.next()) && allWhiteSpaceRegex.test(pos.character) ) {
|
bsw/jbe@1309
|
6123 ++trimCharCount;
|
bsw/jbe@1309
|
6124 }
|
bsw/jbe@1309
|
6125 it.dispose();
|
bsw/jbe@1309
|
6126 var trimmed = (trimCharCount > 0);
|
bsw/jbe@1309
|
6127 if (trimmed) {
|
bsw/jbe@1309
|
6128 this[isStart ? "moveStart" : "moveEnd"](
|
bsw/jbe@1309
|
6129 "character",
|
bsw/jbe@1309
|
6130 isStart ? trimCharCount : -trimCharCount,
|
bsw/jbe@1309
|
6131 { characterOptions: characterOptions }
|
bsw/jbe@1309
|
6132 );
|
bsw/jbe@1309
|
6133 }
|
bsw/jbe@1309
|
6134 return trimmed;
|
bsw/jbe@1309
|
6135 }
|
bsw/jbe@1309
|
6136 );
|
bsw/jbe@1309
|
6137 }
|
bsw/jbe@1309
|
6138
|
bsw/jbe@1309
|
6139 extend(api.rangePrototype, {
|
bsw/jbe@1309
|
6140 moveStart: createRangeBoundaryMover(true, false),
|
bsw/jbe@1309
|
6141
|
bsw/jbe@1309
|
6142 moveEnd: createRangeBoundaryMover(false, false),
|
bsw/jbe@1309
|
6143
|
bsw/jbe@1309
|
6144 move: createRangeBoundaryMover(true, true),
|
bsw/jbe@1309
|
6145
|
bsw/jbe@1309
|
6146 trimStart: createRangeTrimmer(true),
|
bsw/jbe@1309
|
6147
|
bsw/jbe@1309
|
6148 trimEnd: createRangeTrimmer(false),
|
bsw/jbe@1309
|
6149
|
bsw/jbe@1309
|
6150 trim: createEntryPointFunction(
|
bsw/jbe@1309
|
6151 function(session, characterOptions) {
|
bsw/jbe@1309
|
6152 var startTrimmed = this.trimStart(characterOptions), endTrimmed = this.trimEnd(characterOptions);
|
bsw/jbe@1309
|
6153 return startTrimmed || endTrimmed;
|
bsw/jbe@1309
|
6154 }
|
bsw/jbe@1309
|
6155 ),
|
bsw/jbe@1309
|
6156
|
bsw/jbe@1309
|
6157 expand: createEntryPointFunction(
|
bsw/jbe@1309
|
6158 function(session, unit, expandOptions) {
|
bsw/jbe@1309
|
6159 var moved = false;
|
bsw/jbe@1309
|
6160 expandOptions = createNestedOptions(expandOptions, defaultExpandOptions);
|
bsw/jbe@1309
|
6161 var characterOptions = expandOptions.characterOptions;
|
bsw/jbe@1309
|
6162 if (!unit) {
|
bsw/jbe@1309
|
6163 unit = CHARACTER;
|
bsw/jbe@1309
|
6164 }
|
bsw/jbe@1309
|
6165 if (unit == WORD) {
|
bsw/jbe@1309
|
6166 var wordOptions = expandOptions.wordOptions;
|
bsw/jbe@1309
|
6167 var startPos = session.getRangeBoundaryPosition(this, true);
|
bsw/jbe@1309
|
6168 var endPos = session.getRangeBoundaryPosition(this, false);
|
bsw/jbe@1309
|
6169
|
bsw/jbe@1309
|
6170 var startTokenizedTextProvider = createTokenizedTextProvider(startPos, characterOptions, wordOptions);
|
bsw/jbe@1309
|
6171 var startToken = startTokenizedTextProvider.nextEndToken();
|
bsw/jbe@1309
|
6172 var newStartPos = startToken.chars[0].previousVisible();
|
bsw/jbe@1309
|
6173 var endToken, newEndPos;
|
bsw/jbe@1309
|
6174
|
bsw/jbe@1309
|
6175 if (this.collapsed) {
|
bsw/jbe@1309
|
6176 endToken = startToken;
|
bsw/jbe@1309
|
6177 } else {
|
bsw/jbe@1309
|
6178 var endTokenizedTextProvider = createTokenizedTextProvider(endPos, characterOptions, wordOptions);
|
bsw/jbe@1309
|
6179 endToken = endTokenizedTextProvider.previousStartToken();
|
bsw/jbe@1309
|
6180 }
|
bsw/jbe@1309
|
6181 newEndPos = endToken.chars[endToken.chars.length - 1];
|
bsw/jbe@1309
|
6182
|
bsw/jbe@1309
|
6183 if (!newStartPos.equals(startPos)) {
|
bsw/jbe@1309
|
6184 this.setStart(newStartPos.node, newStartPos.offset);
|
bsw/jbe@1309
|
6185 moved = true;
|
bsw/jbe@1309
|
6186 }
|
bsw/jbe@1309
|
6187 if (newEndPos && !newEndPos.equals(endPos)) {
|
bsw/jbe@1309
|
6188 this.setEnd(newEndPos.node, newEndPos.offset);
|
bsw/jbe@1309
|
6189 moved = true;
|
bsw/jbe@1309
|
6190 }
|
bsw/jbe@1309
|
6191
|
bsw/jbe@1309
|
6192 if (expandOptions.trim) {
|
bsw/jbe@1309
|
6193 if (expandOptions.trimStart) {
|
bsw/jbe@1309
|
6194 moved = this.trimStart(characterOptions) || moved;
|
bsw/jbe@1309
|
6195 }
|
bsw/jbe@1309
|
6196 if (expandOptions.trimEnd) {
|
bsw/jbe@1309
|
6197 moved = this.trimEnd(characterOptions) || moved;
|
bsw/jbe@1309
|
6198 }
|
bsw/jbe@1309
|
6199 }
|
bsw/jbe@1309
|
6200
|
bsw/jbe@1309
|
6201 return moved;
|
bsw/jbe@1309
|
6202 } else {
|
bsw/jbe@1309
|
6203 return this.moveEnd(CHARACTER, 1, expandOptions);
|
bsw/jbe@1309
|
6204 }
|
bsw/jbe@1309
|
6205 }
|
bsw/jbe@1309
|
6206 ),
|
bsw/jbe@1309
|
6207
|
bsw/jbe@1309
|
6208 text: createEntryPointFunction(
|
bsw/jbe@1309
|
6209 function(session, characterOptions) {
|
bsw/jbe@1309
|
6210 return this.collapsed ?
|
bsw/jbe@1309
|
6211 "" : getRangeCharacters(session, this, createOptions(characterOptions, defaultCharacterOptions)).join("");
|
bsw/jbe@1309
|
6212 }
|
bsw/jbe@1309
|
6213 ),
|
bsw/jbe@1309
|
6214
|
bsw/jbe@1309
|
6215 selectCharacters: createEntryPointFunction(
|
bsw/jbe@1309
|
6216 function(session, containerNode, startIndex, endIndex, characterOptions) {
|
bsw/jbe@1309
|
6217 var moveOptions = { characterOptions: characterOptions };
|
bsw/jbe@1309
|
6218 if (!containerNode) {
|
bsw/jbe@1309
|
6219 containerNode = getBody( this.getDocument() );
|
bsw/jbe@1309
|
6220 }
|
bsw/jbe@1309
|
6221 this.selectNodeContents(containerNode);
|
bsw/jbe@1309
|
6222 this.collapse(true);
|
bsw/jbe@1309
|
6223 this.moveStart("character", startIndex, moveOptions);
|
bsw/jbe@1309
|
6224 this.collapse(true);
|
bsw/jbe@1309
|
6225 this.moveEnd("character", endIndex - startIndex, moveOptions);
|
bsw/jbe@1309
|
6226 }
|
bsw/jbe@1309
|
6227 ),
|
bsw/jbe@1309
|
6228
|
bsw/jbe@1309
|
6229 // Character indexes are relative to the start of node
|
bsw/jbe@1309
|
6230 toCharacterRange: createEntryPointFunction(
|
bsw/jbe@1309
|
6231 function(session, containerNode, characterOptions) {
|
bsw/jbe@1309
|
6232 if (!containerNode) {
|
bsw/jbe@1309
|
6233 containerNode = getBody( this.getDocument() );
|
bsw/jbe@1309
|
6234 }
|
bsw/jbe@1309
|
6235 var parent = containerNode.parentNode, nodeIndex = dom.getNodeIndex(containerNode);
|
bsw/jbe@1309
|
6236 var rangeStartsBeforeNode = (dom.comparePoints(this.startContainer, this.endContainer, parent, nodeIndex) == -1);
|
bsw/jbe@1309
|
6237 var rangeBetween = this.cloneRange();
|
bsw/jbe@1309
|
6238 var startIndex, endIndex;
|
bsw/jbe@1309
|
6239 if (rangeStartsBeforeNode) {
|
bsw/jbe@1309
|
6240 rangeBetween.setStartAndEnd(this.startContainer, this.startOffset, parent, nodeIndex);
|
bsw/jbe@1309
|
6241 startIndex = -rangeBetween.text(characterOptions).length;
|
bsw/jbe@1309
|
6242 } else {
|
bsw/jbe@1309
|
6243 rangeBetween.setStartAndEnd(parent, nodeIndex, this.startContainer, this.startOffset);
|
bsw/jbe@1309
|
6244 startIndex = rangeBetween.text(characterOptions).length;
|
bsw/jbe@1309
|
6245 }
|
bsw/jbe@1309
|
6246 endIndex = startIndex + this.text(characterOptions).length;
|
bsw/jbe@1309
|
6247
|
bsw/jbe@1309
|
6248 return {
|
bsw/jbe@1309
|
6249 start: startIndex,
|
bsw/jbe@1309
|
6250 end: endIndex
|
bsw/jbe@1309
|
6251 };
|
bsw/jbe@1309
|
6252 }
|
bsw/jbe@1309
|
6253 ),
|
bsw/jbe@1309
|
6254
|
bsw/jbe@1309
|
6255 findText: createEntryPointFunction(
|
bsw/jbe@1309
|
6256 function(session, searchTermParam, findOptions) {
|
bsw/jbe@1309
|
6257 // Set up options
|
bsw/jbe@1309
|
6258 findOptions = createNestedOptions(findOptions, defaultFindOptions);
|
bsw/jbe@1309
|
6259
|
bsw/jbe@1309
|
6260 // Create word options if we're matching whole words only
|
bsw/jbe@1309
|
6261 if (findOptions.wholeWordsOnly) {
|
bsw/jbe@1309
|
6262 // We don't ever want trailing spaces for search results
|
bsw/jbe@1309
|
6263 findOptions.wordOptions.includeTrailingSpace = false;
|
bsw/jbe@1309
|
6264 }
|
bsw/jbe@1309
|
6265
|
bsw/jbe@1309
|
6266 var backward = isDirectionBackward(findOptions.direction);
|
bsw/jbe@1309
|
6267
|
bsw/jbe@1309
|
6268 // Create a range representing the search scope if none was provided
|
bsw/jbe@1309
|
6269 var searchScopeRange = findOptions.withinRange;
|
bsw/jbe@1309
|
6270 if (!searchScopeRange) {
|
bsw/jbe@1309
|
6271 searchScopeRange = api.createRange();
|
bsw/jbe@1309
|
6272 searchScopeRange.selectNodeContents(this.getDocument());
|
bsw/jbe@1309
|
6273 }
|
bsw/jbe@1309
|
6274
|
bsw/jbe@1309
|
6275 // Examine and prepare the search term
|
bsw/jbe@1309
|
6276 var searchTerm = searchTermParam, isRegex = false;
|
bsw/jbe@1309
|
6277 if (typeof searchTerm == "string") {
|
bsw/jbe@1309
|
6278 if (!findOptions.caseSensitive) {
|
bsw/jbe@1309
|
6279 searchTerm = searchTerm.toLowerCase();
|
bsw/jbe@1309
|
6280 }
|
bsw/jbe@1309
|
6281 } else {
|
bsw/jbe@1309
|
6282 isRegex = true;
|
bsw/jbe@1309
|
6283 }
|
bsw/jbe@1309
|
6284
|
bsw/jbe@1309
|
6285 var initialPos = session.getRangeBoundaryPosition(this, !backward);
|
bsw/jbe@1309
|
6286
|
bsw/jbe@1309
|
6287 // Adjust initial position if it lies outside the search scope
|
bsw/jbe@1309
|
6288 var comparison = searchScopeRange.comparePoint(initialPos.node, initialPos.offset);
|
bsw/jbe@1309
|
6289
|
bsw/jbe@1309
|
6290 if (comparison === -1) {
|
bsw/jbe@1309
|
6291 initialPos = session.getRangeBoundaryPosition(searchScopeRange, true);
|
bsw/jbe@1309
|
6292 } else if (comparison === 1) {
|
bsw/jbe@1309
|
6293 initialPos = session.getRangeBoundaryPosition(searchScopeRange, false);
|
bsw/jbe@1309
|
6294 }
|
bsw/jbe@1309
|
6295
|
bsw/jbe@1309
|
6296 var pos = initialPos;
|
bsw/jbe@1309
|
6297 var wrappedAround = false;
|
bsw/jbe@1309
|
6298
|
bsw/jbe@1309
|
6299 // Try to find a match and ignore invalid ones
|
bsw/jbe@1309
|
6300 var findResult;
|
bsw/jbe@1309
|
6301 while (true) {
|
bsw/jbe@1309
|
6302 findResult = findTextFromPosition(pos, searchTerm, isRegex, searchScopeRange, findOptions);
|
bsw/jbe@1309
|
6303
|
bsw/jbe@1309
|
6304 if (findResult) {
|
bsw/jbe@1309
|
6305 if (findResult.valid) {
|
bsw/jbe@1309
|
6306 this.setStartAndEnd(findResult.startPos.node, findResult.startPos.offset, findResult.endPos.node, findResult.endPos.offset);
|
bsw/jbe@1309
|
6307 return true;
|
bsw/jbe@1309
|
6308 } else {
|
bsw/jbe@1309
|
6309 // We've found a match that is not a whole word, so we carry on searching from the point immediately
|
bsw/jbe@1309
|
6310 // after the match
|
bsw/jbe@1309
|
6311 pos = backward ? findResult.startPos : findResult.endPos;
|
bsw/jbe@1309
|
6312 }
|
bsw/jbe@1309
|
6313 } else if (findOptions.wrap && !wrappedAround) {
|
bsw/jbe@1309
|
6314 // No result found but we're wrapping around and limiting the scope to the unsearched part of the range
|
bsw/jbe@1309
|
6315 searchScopeRange = searchScopeRange.cloneRange();
|
bsw/jbe@1309
|
6316 pos = session.getRangeBoundaryPosition(searchScopeRange, !backward);
|
bsw/jbe@1309
|
6317 searchScopeRange.setBoundary(initialPos.node, initialPos.offset, backward);
|
bsw/jbe@1309
|
6318 wrappedAround = true;
|
bsw/jbe@1309
|
6319 } else {
|
bsw/jbe@1309
|
6320 // Nothing found and we can't wrap around, so we're done
|
bsw/jbe@1309
|
6321 return false;
|
bsw/jbe@1309
|
6322 }
|
bsw/jbe@1309
|
6323 }
|
bsw/jbe@1309
|
6324 }
|
bsw/jbe@1309
|
6325 ),
|
bsw/jbe@1309
|
6326
|
bsw/jbe@1309
|
6327 pasteHtml: function(html) {
|
bsw/jbe@1309
|
6328 this.deleteContents();
|
bsw/jbe@1309
|
6329 if (html) {
|
bsw/jbe@1309
|
6330 var frag = this.createContextualFragment(html);
|
bsw/jbe@1309
|
6331 var lastChild = frag.lastChild;
|
bsw/jbe@1309
|
6332 this.insertNode(frag);
|
bsw/jbe@1309
|
6333 this.collapseAfter(lastChild);
|
bsw/jbe@1309
|
6334 }
|
bsw/jbe@1309
|
6335 }
|
bsw/jbe@1309
|
6336 });
|
bsw/jbe@1309
|
6337
|
bsw/jbe@1309
|
6338 /*----------------------------------------------------------------------------------------------------------------*/
|
bsw/jbe@1309
|
6339
|
bsw/jbe@1309
|
6340 // Extensions to the Rangy Selection object
|
bsw/jbe@1309
|
6341
|
bsw/jbe@1309
|
6342 function createSelectionTrimmer(methodName) {
|
bsw/jbe@1309
|
6343 return createEntryPointFunction(
|
bsw/jbe@1309
|
6344 function(session, characterOptions) {
|
bsw/jbe@1309
|
6345 var trimmed = false;
|
bsw/jbe@1309
|
6346 this.changeEachRange(function(range) {
|
bsw/jbe@1309
|
6347 trimmed = range[methodName](characterOptions) || trimmed;
|
bsw/jbe@1309
|
6348 });
|
bsw/jbe@1309
|
6349 return trimmed;
|
bsw/jbe@1309
|
6350 }
|
bsw/jbe@1309
|
6351 );
|
bsw/jbe@1309
|
6352 }
|
bsw/jbe@1309
|
6353
|
bsw/jbe@1309
|
6354 extend(api.selectionPrototype, {
|
bsw/jbe@1309
|
6355 expand: createEntryPointFunction(
|
bsw/jbe@1309
|
6356 function(session, unit, expandOptions) {
|
bsw/jbe@1309
|
6357 this.changeEachRange(function(range) {
|
bsw/jbe@1309
|
6358 range.expand(unit, expandOptions);
|
bsw/jbe@1309
|
6359 });
|
bsw/jbe@1309
|
6360 }
|
bsw/jbe@1309
|
6361 ),
|
bsw/jbe@1309
|
6362
|
bsw/jbe@1309
|
6363 move: createEntryPointFunction(
|
bsw/jbe@1309
|
6364 function(session, unit, count, options) {
|
bsw/jbe@1309
|
6365 var unitsMoved = 0;
|
bsw/jbe@1309
|
6366 if (this.focusNode) {
|
bsw/jbe@1309
|
6367 this.collapse(this.focusNode, this.focusOffset);
|
bsw/jbe@1309
|
6368 var range = this.getRangeAt(0);
|
bsw/jbe@1309
|
6369 if (!options) {
|
bsw/jbe@1309
|
6370 options = {};
|
bsw/jbe@1309
|
6371 }
|
bsw/jbe@1309
|
6372 options.characterOptions = createOptions(options.characterOptions, defaultCaretCharacterOptions);
|
bsw/jbe@1309
|
6373 unitsMoved = range.move(unit, count, options);
|
bsw/jbe@1309
|
6374 this.setSingleRange(range);
|
bsw/jbe@1309
|
6375 }
|
bsw/jbe@1309
|
6376 return unitsMoved;
|
bsw/jbe@1309
|
6377 }
|
bsw/jbe@1309
|
6378 ),
|
bsw/jbe@1309
|
6379
|
bsw/jbe@1309
|
6380 trimStart: createSelectionTrimmer("trimStart"),
|
bsw/jbe@1309
|
6381 trimEnd: createSelectionTrimmer("trimEnd"),
|
bsw/jbe@1309
|
6382 trim: createSelectionTrimmer("trim"),
|
bsw/jbe@1309
|
6383
|
bsw/jbe@1309
|
6384 selectCharacters: createEntryPointFunction(
|
bsw/jbe@1309
|
6385 function(session, containerNode, startIndex, endIndex, direction, characterOptions) {
|
bsw/jbe@1309
|
6386 var range = api.createRange(containerNode);
|
bsw/jbe@1309
|
6387 range.selectCharacters(containerNode, startIndex, endIndex, characterOptions);
|
bsw/jbe@1309
|
6388 this.setSingleRange(range, direction);
|
bsw/jbe@1309
|
6389 }
|
bsw/jbe@1309
|
6390 ),
|
bsw/jbe@1309
|
6391
|
bsw/jbe@1309
|
6392 saveCharacterRanges: createEntryPointFunction(
|
bsw/jbe@1309
|
6393 function(session, containerNode, characterOptions) {
|
bsw/jbe@1309
|
6394 var ranges = this.getAllRanges(), rangeCount = ranges.length;
|
bsw/jbe@1309
|
6395 var rangeInfos = [];
|
bsw/jbe@1309
|
6396
|
bsw/jbe@1309
|
6397 var backward = rangeCount == 1 && this.isBackward();
|
bsw/jbe@1309
|
6398
|
bsw/jbe@1309
|
6399 for (var i = 0, len = ranges.length; i < len; ++i) {
|
bsw/jbe@1309
|
6400 rangeInfos[i] = {
|
bsw/jbe@1309
|
6401 characterRange: ranges[i].toCharacterRange(containerNode, characterOptions),
|
bsw/jbe@1309
|
6402 backward: backward,
|
bsw/jbe@1309
|
6403 characterOptions: characterOptions
|
bsw/jbe@1309
|
6404 };
|
bsw/jbe@1309
|
6405 }
|
bsw/jbe@1309
|
6406
|
bsw/jbe@1309
|
6407 return rangeInfos;
|
bsw/jbe@1309
|
6408 }
|
bsw/jbe@1309
|
6409 ),
|
bsw/jbe@1309
|
6410
|
bsw/jbe@1309
|
6411 restoreCharacterRanges: createEntryPointFunction(
|
bsw/jbe@1309
|
6412 function(session, containerNode, saved) {
|
bsw/jbe@1309
|
6413 this.removeAllRanges();
|
bsw/jbe@1309
|
6414 for (var i = 0, len = saved.length, range, rangeInfo, characterRange; i < len; ++i) {
|
bsw/jbe@1309
|
6415 rangeInfo = saved[i];
|
bsw/jbe@1309
|
6416 characterRange = rangeInfo.characterRange;
|
bsw/jbe@1309
|
6417 range = api.createRange(containerNode);
|
bsw/jbe@1309
|
6418 range.selectCharacters(containerNode, characterRange.start, characterRange.end, rangeInfo.characterOptions);
|
bsw/jbe@1309
|
6419 this.addRange(range, rangeInfo.backward);
|
bsw/jbe@1309
|
6420 }
|
bsw/jbe@1309
|
6421 }
|
bsw/jbe@1309
|
6422 ),
|
bsw/jbe@1309
|
6423
|
bsw/jbe@1309
|
6424 text: createEntryPointFunction(
|
bsw/jbe@1309
|
6425 function(session, characterOptions) {
|
bsw/jbe@1309
|
6426 var rangeTexts = [];
|
bsw/jbe@1309
|
6427 for (var i = 0, len = this.rangeCount; i < len; ++i) {
|
bsw/jbe@1309
|
6428 rangeTexts[i] = this.getRangeAt(i).text(characterOptions);
|
bsw/jbe@1309
|
6429 }
|
bsw/jbe@1309
|
6430 return rangeTexts.join("");
|
bsw/jbe@1309
|
6431 }
|
bsw/jbe@1309
|
6432 )
|
bsw/jbe@1309
|
6433 });
|
bsw/jbe@1309
|
6434
|
bsw/jbe@1309
|
6435 /*----------------------------------------------------------------------------------------------------------------*/
|
bsw/jbe@1309
|
6436
|
bsw/jbe@1309
|
6437 // Extensions to the core rangy object
|
bsw/jbe@1309
|
6438
|
bsw/jbe@1309
|
6439 api.innerText = function(el, characterOptions) {
|
bsw/jbe@1309
|
6440 var range = api.createRange(el);
|
bsw/jbe@1309
|
6441 range.selectNodeContents(el);
|
bsw/jbe@1309
|
6442 var text = range.text(characterOptions);
|
bsw/jbe@1309
|
6443 return text;
|
bsw/jbe@1309
|
6444 };
|
bsw/jbe@1309
|
6445
|
bsw/jbe@1309
|
6446 api.createWordIterator = function(startNode, startOffset, iteratorOptions) {
|
bsw/jbe@1309
|
6447 var session = getSession();
|
bsw/jbe@1309
|
6448 iteratorOptions = createNestedOptions(iteratorOptions, defaultWordIteratorOptions);
|
bsw/jbe@1309
|
6449 var startPos = session.getPosition(startNode, startOffset);
|
bsw/jbe@1309
|
6450 var tokenizedTextProvider = createTokenizedTextProvider(startPos, iteratorOptions.characterOptions, iteratorOptions.wordOptions);
|
bsw/jbe@1309
|
6451 var backward = isDirectionBackward(iteratorOptions.direction);
|
bsw/jbe@1309
|
6452
|
bsw/jbe@1309
|
6453 return {
|
bsw/jbe@1309
|
6454 next: function() {
|
bsw/jbe@1309
|
6455 return backward ? tokenizedTextProvider.previousStartToken() : tokenizedTextProvider.nextEndToken();
|
bsw/jbe@1309
|
6456 },
|
bsw/jbe@1309
|
6457
|
bsw/jbe@1309
|
6458 dispose: function() {
|
bsw/jbe@1309
|
6459 tokenizedTextProvider.dispose();
|
bsw/jbe@1309
|
6460 this.next = function() {};
|
bsw/jbe@1309
|
6461 }
|
bsw/jbe@1309
|
6462 };
|
bsw/jbe@1309
|
6463 };
|
bsw/jbe@1309
|
6464
|
bsw/jbe@1309
|
6465 /*----------------------------------------------------------------------------------------------------------------*/
|
bsw/jbe@1309
|
6466
|
bsw/jbe@1309
|
6467 api.noMutation = function(func) {
|
bsw/jbe@1309
|
6468 var session = getSession();
|
bsw/jbe@1309
|
6469 func(session);
|
bsw/jbe@1309
|
6470 endSession();
|
bsw/jbe@1309
|
6471 };
|
bsw/jbe@1309
|
6472
|
bsw/jbe@1309
|
6473 api.noMutation.createEntryPointFunction = createEntryPointFunction;
|
bsw/jbe@1309
|
6474
|
bsw/jbe@1309
|
6475 api.textRange = {
|
bsw/jbe@1309
|
6476 isBlockNode: isBlockNode,
|
bsw/jbe@1309
|
6477 isCollapsedWhitespaceNode: isCollapsedWhitespaceNode,
|
bsw/jbe@1309
|
6478
|
bsw/jbe@1309
|
6479 createPosition: createEntryPointFunction(
|
bsw/jbe@1309
|
6480 function(session, node, offset) {
|
bsw/jbe@1309
|
6481 return session.getPosition(node, offset);
|
bsw/jbe@1309
|
6482 }
|
bsw/jbe@1309
|
6483 )
|
bsw/jbe@1309
|
6484 };
|
bsw/jbe@1309
|
6485 });
|
bsw/jbe@1309
|
6486
|
bsw/jbe@1309
|
6487 /**
|
bsw/jbe@1309
|
6488 * Detect browser support for specific features
|
bsw/jbe@1309
|
6489 */
|
bsw/jbe@1309
|
6490 wysihtml.browser = (function() {
|
bsw/jbe@1309
|
6491 var userAgent = navigator.userAgent,
|
bsw/jbe@1309
|
6492 testElement = document.createElement("div"),
|
bsw/jbe@1309
|
6493 // Browser sniffing is unfortunately needed since some behaviors are impossible to feature detect
|
bsw/jbe@1309
|
6494 // We need to be extra careful about Microsoft as it shows increasing tendency of tainting its userAgent strings with false feathers
|
bsw/jbe@1309
|
6495 isGecko = userAgent.indexOf("Gecko") !== -1 && userAgent.indexOf("KHTML") === -1 && !isIE(),
|
bsw/jbe@1309
|
6496 isWebKit = userAgent.indexOf("AppleWebKit/") !== -1 && !isIE(),
|
bsw/jbe@1309
|
6497 isChrome = userAgent.indexOf("Chrome/") !== -1 && !isIE(),
|
bsw/jbe@1309
|
6498 isOpera = userAgent.indexOf("Opera/") !== -1 && !isIE();
|
bsw/jbe@1309
|
6499
|
bsw/jbe@1309
|
6500 function iosVersion(userAgent) {
|
bsw/jbe@1309
|
6501 return +((/ipad|iphone|ipod/.test(userAgent) && userAgent.match(/ os (\d+).+? like mac os x/)) || [undefined, 0])[1];
|
bsw/jbe@1309
|
6502 }
|
bsw/jbe@1309
|
6503
|
bsw/jbe@1309
|
6504 function androidVersion(userAgent) {
|
bsw/jbe@1309
|
6505 return +(userAgent.match(/android (\d+)/) || [undefined, 0])[1];
|
bsw/jbe@1309
|
6506 }
|
bsw/jbe@1309
|
6507
|
bsw/jbe@1309
|
6508 function isIE(version, equation) {
|
bsw/jbe@1309
|
6509 var rv = -1,
|
bsw/jbe@1309
|
6510 re;
|
bsw/jbe@1309
|
6511
|
bsw/jbe@1309
|
6512 if (navigator.appName == 'Microsoft Internet Explorer') {
|
bsw/jbe@1309
|
6513 re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})");
|
bsw/jbe@1309
|
6514 } else if (navigator.appName == 'Netscape') {
|
bsw/jbe@1309
|
6515 if (navigator.userAgent.indexOf("Trident") > -1) {
|
bsw/jbe@1309
|
6516 re = new RegExp("Trident/.*rv:([0-9]{1,}[\.0-9]{0,})");
|
bsw/jbe@1309
|
6517 } else if ((/Edge\/(\d+)./i).test(navigator.userAgent)) {
|
bsw/jbe@1309
|
6518 re = /Edge\/(\d+)./i;
|
bsw/jbe@1309
|
6519 }
|
bsw/jbe@1309
|
6520 }
|
bsw/jbe@1309
|
6521
|
bsw/jbe@1309
|
6522 if (re && re.exec(navigator.userAgent) != null) {
|
bsw/jbe@1309
|
6523 rv = parseFloat(RegExp.$1);
|
bsw/jbe@1309
|
6524 }
|
bsw/jbe@1309
|
6525
|
bsw/jbe@1309
|
6526 if (rv === -1) { return false; }
|
bsw/jbe@1309
|
6527 if (!version) { return true; }
|
bsw/jbe@1309
|
6528 if (!equation) { return version === rv; }
|
bsw/jbe@1309
|
6529 if (equation === "<") { return version < rv; }
|
bsw/jbe@1309
|
6530 if (equation === ">") { return version > rv; }
|
bsw/jbe@1309
|
6531 if (equation === "<=") { return version <= rv; }
|
bsw/jbe@1309
|
6532 if (equation === ">=") { return version >= rv; }
|
bsw/jbe@1309
|
6533 }
|
bsw/jbe@1309
|
6534
|
bsw/jbe@1309
|
6535 return {
|
bsw/jbe@1309
|
6536 // Static variable needed, publicly accessible, to be able override it in unit tests
|
bsw/jbe@1309
|
6537 USER_AGENT: userAgent,
|
bsw/jbe@1309
|
6538
|
bsw/jbe@1309
|
6539 /**
|
bsw/jbe@1309
|
6540 * Exclude browsers that are not capable of displaying and handling
|
bsw/jbe@1309
|
6541 * contentEditable as desired:
|
bsw/jbe@1309
|
6542 * - iPhone, iPad (tested iOS 4.2.2) and Android (tested 2.2) refuse to make contentEditables focusable
|
bsw/jbe@1309
|
6543 * - IE < 8 create invalid markup and crash randomly from time to time
|
bsw/jbe@1309
|
6544 *
|
bsw/jbe@1309
|
6545 * @return {Boolean}
|
bsw/jbe@1309
|
6546 */
|
bsw/jbe@1309
|
6547 supported: function() {
|
bsw/jbe@1309
|
6548 var userAgent = this.USER_AGENT.toLowerCase(),
|
bsw/jbe@1309
|
6549 // Essential for making html elements editable
|
bsw/jbe@1309
|
6550 hasContentEditableSupport = "contentEditable" in testElement,
|
bsw/jbe@1309
|
6551 // Following methods are needed in order to interact with the contentEditable area
|
bsw/jbe@1309
|
6552 hasEditingApiSupport = document.execCommand && document.queryCommandSupported && document.queryCommandState,
|
bsw/jbe@1309
|
6553 // document selector apis are only supported by IE 8+, Safari 4+, Chrome and Firefox 3.5+
|
bsw/jbe@1309
|
6554 hasQuerySelectorSupport = document.querySelector && document.querySelectorAll,
|
bsw/jbe@1309
|
6555 // contentEditable is unusable in mobile browsers (tested iOS 4.2.2, Android 2.2, Opera Mobile, WebOS 3.05)
|
bsw/jbe@1309
|
6556 isIncompatibleMobileBrowser = (this.isIos() && iosVersion(userAgent) < 5) || (this.isAndroid() && androidVersion(userAgent) < 4) || userAgent.indexOf("opera mobi") !== -1 || userAgent.indexOf("hpwos/") !== -1;
|
bsw/jbe@1309
|
6557 return hasContentEditableSupport
|
bsw/jbe@1309
|
6558 && hasEditingApiSupport
|
bsw/jbe@1309
|
6559 && hasQuerySelectorSupport
|
bsw/jbe@1309
|
6560 && !isIncompatibleMobileBrowser;
|
bsw/jbe@1309
|
6561 },
|
bsw/jbe@1309
|
6562
|
bsw/jbe@1309
|
6563 isTouchDevice: function() {
|
bsw/jbe@1309
|
6564 return this.supportsEvent("touchmove");
|
bsw/jbe@1309
|
6565 },
|
bsw/jbe@1309
|
6566
|
bsw/jbe@1309
|
6567 isIos: function() {
|
bsw/jbe@1309
|
6568 return (/ipad|iphone|ipod/i).test(this.USER_AGENT);
|
bsw/jbe@1309
|
6569 },
|
bsw/jbe@1309
|
6570
|
bsw/jbe@1309
|
6571 isAndroid: function() {
|
bsw/jbe@1309
|
6572 return this.USER_AGENT.indexOf("Android") !== -1;
|
bsw/jbe@1309
|
6573 },
|
bsw/jbe@1309
|
6574
|
bsw/jbe@1309
|
6575 /**
|
bsw/jbe@1309
|
6576 * Whether the browser supports sandboxed iframes
|
bsw/jbe@1309
|
6577 * Currently only IE 6+ offers such feature <iframe security="restricted">
|
bsw/jbe@1309
|
6578 *
|
bsw/jbe@1309
|
6579 * http://msdn.microsoft.com/en-us/library/ms534622(v=vs.85).aspx
|
bsw/jbe@1309
|
6580 * http://blogs.msdn.com/b/ie/archive/2008/01/18/using-frames-more-securely.aspx
|
bsw/jbe@1309
|
6581 *
|
bsw/jbe@1309
|
6582 * HTML5 sandboxed iframes are still buggy and their DOM is not reachable from the outside (except when using postMessage)
|
bsw/jbe@1309
|
6583 */
|
bsw/jbe@1309
|
6584 supportsSandboxedIframes: function() {
|
bsw/jbe@1309
|
6585 return isIE();
|
bsw/jbe@1309
|
6586 },
|
bsw/jbe@1309
|
6587
|
bsw/jbe@1309
|
6588 /**
|
bsw/jbe@1309
|
6589 * IE6+7 throw a mixed content warning when the src of an iframe
|
bsw/jbe@1309
|
6590 * is empty/unset or about:blank
|
bsw/jbe@1309
|
6591 * window.querySelector is implemented as of IE8
|
bsw/jbe@1309
|
6592 */
|
bsw/jbe@1309
|
6593 throwsMixedContentWarningWhenIframeSrcIsEmpty: function() {
|
bsw/jbe@1309
|
6594 return !("querySelector" in document);
|
bsw/jbe@1309
|
6595 },
|
bsw/jbe@1309
|
6596
|
bsw/jbe@1309
|
6597 /**
|
bsw/jbe@1309
|
6598 * Whether the caret is correctly displayed in contentEditable elements
|
bsw/jbe@1309
|
6599 * Firefox sometimes shows a huge caret in the beginning after focusing
|
bsw/jbe@1309
|
6600 */
|
bsw/jbe@1309
|
6601 displaysCaretInEmptyContentEditableCorrectly: function() {
|
bsw/jbe@1309
|
6602 return isIE(12, ">");
|
bsw/jbe@1309
|
6603 },
|
bsw/jbe@1309
|
6604
|
bsw/jbe@1309
|
6605 /**
|
bsw/jbe@1309
|
6606 * Opera and IE are the only browsers who offer the css value
|
bsw/jbe@1309
|
6607 * in the original unit, thx to the currentStyle object
|
bsw/jbe@1309
|
6608 * All other browsers provide the computed style in px via window.getComputedStyle
|
bsw/jbe@1309
|
6609 */
|
bsw/jbe@1309
|
6610 hasCurrentStyleProperty: function() {
|
bsw/jbe@1309
|
6611 return "currentStyle" in testElement;
|
bsw/jbe@1309
|
6612 },
|
bsw/jbe@1309
|
6613
|
bsw/jbe@1309
|
6614 /**
|
bsw/jbe@1309
|
6615 * Whether the browser inserts a <br> when pressing enter in a contentEditable element
|
bsw/jbe@1309
|
6616 */
|
bsw/jbe@1309
|
6617 insertsLineBreaksOnReturn: function() {
|
bsw/jbe@1309
|
6618 return isGecko;
|
bsw/jbe@1309
|
6619 },
|
bsw/jbe@1309
|
6620
|
bsw/jbe@1309
|
6621 supportsPlaceholderAttributeOn: function(element) {
|
bsw/jbe@1309
|
6622 return "placeholder" in element;
|
bsw/jbe@1309
|
6623 },
|
bsw/jbe@1309
|
6624
|
bsw/jbe@1309
|
6625 supportsEvent: function(eventName) {
|
bsw/jbe@1309
|
6626 return "on" + eventName in testElement || (function() {
|
bsw/jbe@1309
|
6627 testElement.setAttribute("on" + eventName, "return;");
|
bsw/jbe@1309
|
6628 return typeof(testElement["on" + eventName]) === "function";
|
bsw/jbe@1309
|
6629 })();
|
bsw/jbe@1309
|
6630 },
|
bsw/jbe@1309
|
6631
|
bsw/jbe@1309
|
6632 /**
|
bsw/jbe@1309
|
6633 * Opera doesn't correctly fire focus/blur events when clicking in- and outside of iframe
|
bsw/jbe@1309
|
6634 */
|
bsw/jbe@1309
|
6635 supportsEventsInIframeCorrectly: function() {
|
bsw/jbe@1309
|
6636 return !isOpera;
|
bsw/jbe@1309
|
6637 },
|
bsw/jbe@1309
|
6638
|
bsw/jbe@1309
|
6639 /**
|
bsw/jbe@1309
|
6640 * Everything below IE9 doesn't know how to treat HTML5 tags
|
bsw/jbe@1309
|
6641 *
|
bsw/jbe@1309
|
6642 * @param {Object} context The document object on which to check HTML5 support
|
bsw/jbe@1309
|
6643 *
|
bsw/jbe@1309
|
6644 * @example
|
bsw/jbe@1309
|
6645 * wysihtml.browser.supportsHTML5Tags(document);
|
bsw/jbe@1309
|
6646 */
|
bsw/jbe@1309
|
6647 supportsHTML5Tags: function(context) {
|
bsw/jbe@1309
|
6648 var element = context.createElement("div"),
|
bsw/jbe@1309
|
6649 html5 = "<article>foo</article>";
|
bsw/jbe@1309
|
6650 element.innerHTML = html5;
|
bsw/jbe@1309
|
6651 return element.innerHTML.toLowerCase() === html5;
|
bsw/jbe@1309
|
6652 },
|
bsw/jbe@1309
|
6653
|
bsw/jbe@1309
|
6654 /**
|
bsw/jbe@1309
|
6655 * Checks whether a document supports a certain queryCommand
|
bsw/jbe@1309
|
6656 * In particular, Opera needs a reference to a document that has a contentEditable in it's dom tree
|
bsw/jbe@1309
|
6657 * in oder to report correct results
|
bsw/jbe@1309
|
6658 *
|
bsw/jbe@1309
|
6659 * @param {Object} doc Document object on which to check for a query command
|
bsw/jbe@1309
|
6660 * @param {String} command The query command to check for
|
bsw/jbe@1309
|
6661 * @return {Boolean}
|
bsw/jbe@1309
|
6662 *
|
bsw/jbe@1309
|
6663 * @example
|
bsw/jbe@1309
|
6664 * wysihtml.browser.supportsCommand(document, "bold");
|
bsw/jbe@1309
|
6665 */
|
bsw/jbe@1309
|
6666 supportsCommand: (function() {
|
bsw/jbe@1309
|
6667 // Following commands are supported but contain bugs in some browsers
|
bsw/jbe@1309
|
6668 // TODO: investigate if some of these bugs can be tested without altering selection on page, instead of targeting browsers and versions directly
|
bsw/jbe@1309
|
6669 var buggyCommands = {
|
bsw/jbe@1309
|
6670 // formatBlock fails with some tags (eg. <blockquote>)
|
bsw/jbe@1309
|
6671 "formatBlock": isIE(10, "<="),
|
bsw/jbe@1309
|
6672 // When inserting unordered or ordered lists in Firefox, Chrome or Safari, the current selection or line gets
|
bsw/jbe@1309
|
6673 // converted into a list (<ul><li>...</li></ul>, <ol><li>...</li></ol>)
|
bsw/jbe@1309
|
6674 // IE and Opera act a bit different here as they convert the entire content of the current block element into a list
|
bsw/jbe@1309
|
6675 "insertUnorderedList": isIE(),
|
bsw/jbe@1309
|
6676 "insertOrderedList": isIE()
|
bsw/jbe@1309
|
6677 };
|
bsw/jbe@1309
|
6678
|
bsw/jbe@1309
|
6679 // Firefox throws errors for queryCommandSupported, so we have to build up our own object of supported commands
|
bsw/jbe@1309
|
6680 var supported = {
|
bsw/jbe@1309
|
6681 "insertHTML": isGecko
|
bsw/jbe@1309
|
6682 };
|
bsw/jbe@1309
|
6683
|
bsw/jbe@1309
|
6684 return function(doc, command) {
|
bsw/jbe@1309
|
6685 var isBuggy = buggyCommands[command];
|
bsw/jbe@1309
|
6686 if (!isBuggy) {
|
bsw/jbe@1309
|
6687 // Firefox throws errors when invoking queryCommandSupported or queryCommandEnabled
|
bsw/jbe@1309
|
6688 try {
|
bsw/jbe@1309
|
6689 return doc.queryCommandSupported(command);
|
bsw/jbe@1309
|
6690 } catch(e1) {}
|
bsw/jbe@1309
|
6691
|
bsw/jbe@1309
|
6692 try {
|
bsw/jbe@1309
|
6693 return doc.queryCommandEnabled(command);
|
bsw/jbe@1309
|
6694 } catch(e2) {
|
bsw/jbe@1309
|
6695 return !!supported[command];
|
bsw/jbe@1309
|
6696 }
|
bsw/jbe@1309
|
6697 }
|
bsw/jbe@1309
|
6698 return false;
|
bsw/jbe@1309
|
6699 };
|
bsw/jbe@1309
|
6700 })(),
|
bsw/jbe@1309
|
6701
|
bsw/jbe@1309
|
6702 /**
|
bsw/jbe@1309
|
6703 * IE: URLs starting with:
|
bsw/jbe@1309
|
6704 * www., http://, https://, ftp://, gopher://, mailto:, new:, snews:, telnet:, wasis:, file://,
|
bsw/jbe@1309
|
6705 * nntp://, newsrc:, ldap://, ldaps://, outlook:, mic:// and url:
|
bsw/jbe@1309
|
6706 * will automatically be auto-linked when either the user inserts them via copy&paste or presses the
|
bsw/jbe@1309
|
6707 * space bar when the caret is directly after such an url.
|
bsw/jbe@1309
|
6708 * This behavior cannot easily be avoided in IE < 9 since the logic is hardcoded in the mshtml.dll
|
bsw/jbe@1309
|
6709 * (related blog post on msdn
|
bsw/jbe@1309
|
6710 * http://blogs.msdn.com/b/ieinternals/archive/2009/09/17/prevent-automatic-hyperlinking-in-contenteditable-html.aspx).
|
bsw/jbe@1309
|
6711 */
|
bsw/jbe@1309
|
6712 doesAutoLinkingInContentEditable: function() {
|
bsw/jbe@1309
|
6713 return isIE();
|
bsw/jbe@1309
|
6714 },
|
bsw/jbe@1309
|
6715
|
bsw/jbe@1309
|
6716 /**
|
bsw/jbe@1309
|
6717 * As stated above, IE auto links urls typed into contentEditable elements
|
bsw/jbe@1309
|
6718 * Since IE9 it's possible to prevent this behavior
|
bsw/jbe@1309
|
6719 */
|
bsw/jbe@1309
|
6720 canDisableAutoLinking: function() {
|
bsw/jbe@1309
|
6721 return this.supportsCommand(document, "AutoUrlDetect");
|
bsw/jbe@1309
|
6722 },
|
bsw/jbe@1309
|
6723
|
bsw/jbe@1309
|
6724 /**
|
bsw/jbe@1309
|
6725 * IE leaves an empty paragraph in the contentEditable element after clearing it
|
bsw/jbe@1309
|
6726 * Chrome/Safari sometimes an empty <div>
|
bsw/jbe@1309
|
6727 */
|
bsw/jbe@1309
|
6728 clearsContentEditableCorrectly: function() {
|
bsw/jbe@1309
|
6729 return isGecko || isOpera || isWebKit;
|
bsw/jbe@1309
|
6730 },
|
bsw/jbe@1309
|
6731
|
bsw/jbe@1309
|
6732 /**
|
bsw/jbe@1309
|
6733 * IE gives wrong results for getAttribute
|
bsw/jbe@1309
|
6734 */
|
bsw/jbe@1309
|
6735 supportsGetAttributeCorrectly: function() {
|
bsw/jbe@1309
|
6736 var td = document.createElement("td");
|
bsw/jbe@1309
|
6737 return td.getAttribute("rowspan") != "1";
|
bsw/jbe@1309
|
6738 },
|
bsw/jbe@1309
|
6739
|
bsw/jbe@1309
|
6740 /**
|
bsw/jbe@1309
|
6741 * When clicking on images in IE, Opera and Firefox, they are selected, which makes it easy to interact with them.
|
bsw/jbe@1309
|
6742 * Chrome and Safari both don't support this
|
bsw/jbe@1309
|
6743 */
|
bsw/jbe@1309
|
6744 canSelectImagesInContentEditable: function() {
|
bsw/jbe@1309
|
6745 return isGecko || isIE() || isOpera;
|
bsw/jbe@1309
|
6746 },
|
bsw/jbe@1309
|
6747
|
bsw/jbe@1309
|
6748 /**
|
bsw/jbe@1309
|
6749 * All browsers except Safari and Chrome automatically scroll the range/caret position into view
|
bsw/jbe@1309
|
6750 */
|
bsw/jbe@1309
|
6751 autoScrollsToCaret: function() {
|
bsw/jbe@1309
|
6752 return !isWebKit;
|
bsw/jbe@1309
|
6753 },
|
bsw/jbe@1309
|
6754
|
bsw/jbe@1309
|
6755 /**
|
bsw/jbe@1309
|
6756 * Check whether the browser automatically closes tags that don't need to be opened
|
bsw/jbe@1309
|
6757 */
|
bsw/jbe@1309
|
6758 autoClosesUnclosedTags: function() {
|
bsw/jbe@1309
|
6759 var clonedTestElement = testElement.cloneNode(false),
|
bsw/jbe@1309
|
6760 returnValue,
|
bsw/jbe@1309
|
6761 innerHTML;
|
bsw/jbe@1309
|
6762
|
bsw/jbe@1309
|
6763 clonedTestElement.innerHTML = "<p><div></div>";
|
bsw/jbe@1309
|
6764 innerHTML = clonedTestElement.innerHTML.toLowerCase();
|
bsw/jbe@1309
|
6765 returnValue = innerHTML === "<p></p><div></div>" || innerHTML === "<p><div></div></p>";
|
bsw/jbe@1309
|
6766
|
bsw/jbe@1309
|
6767 // Cache result by overwriting current function
|
bsw/jbe@1309
|
6768 this.autoClosesUnclosedTags = function() { return returnValue; };
|
bsw/jbe@1309
|
6769
|
bsw/jbe@1309
|
6770 return returnValue;
|
bsw/jbe@1309
|
6771 },
|
bsw/jbe@1309
|
6772
|
bsw/jbe@1309
|
6773 /**
|
bsw/jbe@1309
|
6774 * Whether the browser supports the native document.getElementsByClassName which returns live NodeLists
|
bsw/jbe@1309
|
6775 */
|
bsw/jbe@1309
|
6776 supportsNativeGetElementsByClassName: function() {
|
bsw/jbe@1309
|
6777 return String(document.getElementsByClassName).indexOf("[native code]") !== -1;
|
bsw/jbe@1309
|
6778 },
|
bsw/jbe@1309
|
6779
|
bsw/jbe@1309
|
6780 /**
|
bsw/jbe@1309
|
6781 * As of now (19.04.2011) only supported by Firefox 4 and Chrome
|
bsw/jbe@1309
|
6782 * See https://developer.mozilla.org/en/DOM/Selection/modify
|
bsw/jbe@1309
|
6783 */
|
bsw/jbe@1309
|
6784 supportsSelectionModify: function() {
|
bsw/jbe@1309
|
6785 return "getSelection" in window && "modify" in window.getSelection();
|
bsw/jbe@1309
|
6786 },
|
bsw/jbe@1309
|
6787
|
bsw/jbe@1309
|
6788 /**
|
bsw/jbe@1309
|
6789 * Opera needs a white space after a <br> in order to position the caret correctly
|
bsw/jbe@1309
|
6790 */
|
bsw/jbe@1309
|
6791 needsSpaceAfterLineBreak: function() {
|
bsw/jbe@1309
|
6792 return isOpera;
|
bsw/jbe@1309
|
6793 },
|
bsw/jbe@1309
|
6794
|
bsw/jbe@1309
|
6795 /**
|
bsw/jbe@1309
|
6796 * Whether the browser supports the speech api on the given element
|
bsw/jbe@1309
|
6797 * See http://mikepultz.com/2011/03/accessing-google-speech-api-chrome-11/
|
bsw/jbe@1309
|
6798 *
|
bsw/jbe@1309
|
6799 * @example
|
bsw/jbe@1309
|
6800 * var input = document.createElement("input");
|
bsw/jbe@1309
|
6801 * if (wysihtml.browser.supportsSpeechApiOn(input)) {
|
bsw/jbe@1309
|
6802 * // ...
|
bsw/jbe@1309
|
6803 * }
|
bsw/jbe@1309
|
6804 */
|
bsw/jbe@1309
|
6805 supportsSpeechApiOn: function(input) {
|
bsw/jbe@1309
|
6806 var chromeVersion = userAgent.match(/Chrome\/(\d+)/) || [undefined, 0];
|
bsw/jbe@1309
|
6807 return chromeVersion[1] >= 11 && ("onwebkitspeechchange" in input || "speech" in input);
|
bsw/jbe@1309
|
6808 },
|
bsw/jbe@1309
|
6809
|
bsw/jbe@1309
|
6810 /**
|
bsw/jbe@1309
|
6811 * IE9 crashes when setting a getter via Object.defineProperty on XMLHttpRequest or XDomainRequest
|
bsw/jbe@1309
|
6812 * See https://connect.microsoft.com/ie/feedback/details/650112
|
bsw/jbe@1309
|
6813 * or try the POC http://tifftiff.de/ie9_crash/
|
bsw/jbe@1309
|
6814 */
|
bsw/jbe@1309
|
6815 crashesWhenDefineProperty: function(property) {
|
bsw/jbe@1309
|
6816 return isIE(9) && (property === "XMLHttpRequest" || property === "XDomainRequest");
|
bsw/jbe@1309
|
6817 },
|
bsw/jbe@1309
|
6818
|
bsw/jbe@1309
|
6819 /**
|
bsw/jbe@1309
|
6820 * IE is the only browser who fires the "focus" event not immediately when .focus() is called on an element
|
bsw/jbe@1309
|
6821 */
|
bsw/jbe@1309
|
6822 doesAsyncFocus: function() {
|
bsw/jbe@1309
|
6823 return isIE(12, ">");
|
bsw/jbe@1309
|
6824 },
|
bsw/jbe@1309
|
6825
|
bsw/jbe@1309
|
6826 /**
|
bsw/jbe@1309
|
6827 * In IE it's impssible for the user and for the selection library to set the caret after an <img> when it's the lastChild in the document
|
bsw/jbe@1309
|
6828 */
|
bsw/jbe@1309
|
6829 hasProblemsSettingCaretAfterImg: function() {
|
bsw/jbe@1309
|
6830 return isIE();
|
bsw/jbe@1309
|
6831 },
|
bsw/jbe@1309
|
6832
|
bsw/jbe@1309
|
6833 /* In IE when deleting with caret at the begining of LI, List get broken into half instead of merging the LI with previous */
|
bsw/jbe@1309
|
6834 hasLiDeletingProblem: function() {
|
bsw/jbe@1309
|
6835 return isIE();
|
bsw/jbe@1309
|
6836 },
|
bsw/jbe@1309
|
6837
|
bsw/jbe@1309
|
6838 hasUndoInContextMenu: function() {
|
bsw/jbe@1309
|
6839 return isGecko || isChrome || isOpera;
|
bsw/jbe@1309
|
6840 },
|
bsw/jbe@1309
|
6841
|
bsw/jbe@1309
|
6842 /**
|
bsw/jbe@1309
|
6843 * Opera sometimes doesn't insert the node at the right position when range.insertNode(someNode)
|
bsw/jbe@1309
|
6844 * is used (regardless if rangy or native)
|
bsw/jbe@1309
|
6845 * This especially happens when the caret is positioned right after a <br> because then
|
bsw/jbe@1309
|
6846 * insertNode() will insert the node right before the <br>
|
bsw/jbe@1309
|
6847 */
|
bsw/jbe@1309
|
6848 hasInsertNodeIssue: function() {
|
bsw/jbe@1309
|
6849 return isOpera;
|
bsw/jbe@1309
|
6850 },
|
bsw/jbe@1309
|
6851
|
bsw/jbe@1309
|
6852 /**
|
bsw/jbe@1309
|
6853 * IE 8+9 don't fire the focus event of the <body> when the iframe gets focused (even though the caret gets set into the <body>)
|
bsw/jbe@1309
|
6854 */
|
bsw/jbe@1309
|
6855 hasIframeFocusIssue: function() {
|
bsw/jbe@1309
|
6856 return isIE();
|
bsw/jbe@1309
|
6857 },
|
bsw/jbe@1309
|
6858
|
bsw/jbe@1309
|
6859 /**
|
bsw/jbe@1309
|
6860 * Chrome + Safari create invalid nested markup after paste
|
bsw/jbe@1309
|
6861 *
|
bsw/jbe@1309
|
6862 * <p>
|
bsw/jbe@1309
|
6863 * foo
|
bsw/jbe@1309
|
6864 * <p>bar</p> <!-- BOO! -->
|
bsw/jbe@1309
|
6865 * </p>
|
bsw/jbe@1309
|
6866 */
|
bsw/jbe@1309
|
6867 createsNestedInvalidMarkupAfterPaste: function() {
|
bsw/jbe@1309
|
6868 return isWebKit;
|
bsw/jbe@1309
|
6869 },
|
bsw/jbe@1309
|
6870
|
bsw/jbe@1309
|
6871 // In all webkit browsers there are some places where caret can not be placed at the end of blocks and directly before block level element
|
bsw/jbe@1309
|
6872 // when startContainer is element.
|
bsw/jbe@1309
|
6873 hasCaretBlockElementIssue: function() {
|
bsw/jbe@1309
|
6874 return isWebKit;
|
bsw/jbe@1309
|
6875 },
|
bsw/jbe@1309
|
6876
|
bsw/jbe@1309
|
6877 supportsMutationEvents: function() {
|
bsw/jbe@1309
|
6878 return ("MutationEvent" in window);
|
bsw/jbe@1309
|
6879 },
|
bsw/jbe@1309
|
6880
|
bsw/jbe@1309
|
6881 /**
|
bsw/jbe@1309
|
6882 IE (at least up to 11) does not support clipboardData on event.
|
bsw/jbe@1309
|
6883 It is on window but cannot return text/html
|
bsw/jbe@1309
|
6884 Should actually check for clipboardData on paste event, but cannot in firefox
|
bsw/jbe@1309
|
6885 */
|
bsw/jbe@1309
|
6886 supportsModernPaste: function () {
|
bsw/jbe@1309
|
6887 return !isIE();
|
bsw/jbe@1309
|
6888 },
|
bsw/jbe@1309
|
6889
|
bsw/jbe@1309
|
6890 // Unifies the property names of element.style by returning the suitable property name for current browser
|
bsw/jbe@1309
|
6891 // Input property key must be the standard
|
bsw/jbe@1309
|
6892 fixStyleKey: function(key) {
|
bsw/jbe@1309
|
6893 if (key === "cssFloat") {
|
bsw/jbe@1309
|
6894 return ("styleFloat" in document.createElement("div").style) ? "styleFloat" : "cssFloat";
|
bsw/jbe@1309
|
6895 }
|
bsw/jbe@1309
|
6896 return key;
|
bsw/jbe@1309
|
6897 },
|
bsw/jbe@1309
|
6898
|
bsw/jbe@1309
|
6899 usesControlRanges: function() {
|
bsw/jbe@1309
|
6900 return document.body && "createControlRange" in document.body;
|
bsw/jbe@1309
|
6901 },
|
bsw/jbe@1309
|
6902
|
bsw/jbe@1309
|
6903 // Webkit browsers have an issue that when caret is at the end of link it is moved outside of link while inserting new characters,
|
bsw/jbe@1309
|
6904 // so all inserted content will be after link. Selection before inserion is reported to be in link though.
|
bsw/jbe@1309
|
6905 // This makes changing link texts from problematic to impossible (if link is just 1 characer long) for the user.
|
bsw/jbe@1309
|
6906 // TODO: needs to be tested better than just browser as it some day might get fixed
|
bsw/jbe@1309
|
6907 hasCaretAtLinkEndInsertionProblems: function() {
|
bsw/jbe@1309
|
6908 return isWebKit;
|
bsw/jbe@1309
|
6909 }
|
bsw/jbe@1309
|
6910 };
|
bsw/jbe@1309
|
6911 })();
|
bsw/jbe@1309
|
6912
|
bsw/jbe@1309
|
6913 wysihtml.lang.array = function(arr) {
|
bsw/jbe@1309
|
6914 return {
|
bsw/jbe@1309
|
6915 /**
|
bsw/jbe@1309
|
6916 * Check whether a given object exists in an array
|
bsw/jbe@1309
|
6917 *
|
bsw/jbe@1309
|
6918 * @example
|
bsw/jbe@1309
|
6919 * wysihtml.lang.array([1, 2]).contains(1);
|
bsw/jbe@1309
|
6920 * // => true
|
bsw/jbe@1309
|
6921 *
|
bsw/jbe@1309
|
6922 * Can be used to match array with array. If intersection is found true is returned
|
bsw/jbe@1309
|
6923 */
|
bsw/jbe@1309
|
6924 contains: function(needle) {
|
bsw/jbe@1309
|
6925 if (Array.isArray(needle)) {
|
bsw/jbe@1309
|
6926 for (var i = needle.length; i--;) {
|
bsw/jbe@1309
|
6927 if (wysihtml.lang.array(arr).indexOf(needle[i]) !== -1) {
|
bsw/jbe@1309
|
6928 return true;
|
bsw/jbe@1309
|
6929 }
|
bsw/jbe@1309
|
6930 }
|
bsw/jbe@1309
|
6931 return false;
|
bsw/jbe@1309
|
6932 } else {
|
bsw/jbe@1309
|
6933 return wysihtml.lang.array(arr).indexOf(needle) !== -1;
|
bsw/jbe@1309
|
6934 }
|
bsw/jbe@1309
|
6935 },
|
bsw/jbe@1309
|
6936
|
bsw/jbe@1309
|
6937 /**
|
bsw/jbe@1309
|
6938 * Check whether a given object exists in an array and return index
|
bsw/jbe@1309
|
6939 * If no elelemt found returns -1
|
bsw/jbe@1309
|
6940 *
|
bsw/jbe@1309
|
6941 * @example
|
bsw/jbe@1309
|
6942 * wysihtml.lang.array([1, 2]).indexOf(2);
|
bsw/jbe@1309
|
6943 * // => 1
|
bsw/jbe@1309
|
6944 */
|
bsw/jbe@1309
|
6945 indexOf: function(needle) {
|
bsw/jbe@1309
|
6946 if (arr.indexOf) {
|
bsw/jbe@1309
|
6947 return arr.indexOf(needle);
|
bsw/jbe@1309
|
6948 } else {
|
bsw/jbe@1309
|
6949 for (var i=0, length=arr.length; i<length; i++) {
|
bsw/jbe@1309
|
6950 if (arr[i] === needle) { return i; }
|
bsw/jbe@1309
|
6951 }
|
bsw/jbe@1309
|
6952 return -1;
|
bsw/jbe@1309
|
6953 }
|
bsw/jbe@1309
|
6954 },
|
bsw/jbe@1309
|
6955
|
bsw/jbe@1309
|
6956 /**
|
bsw/jbe@1309
|
6957 * Substract one array from another
|
bsw/jbe@1309
|
6958 *
|
bsw/jbe@1309
|
6959 * @example
|
bsw/jbe@1309
|
6960 * wysihtml.lang.array([1, 2, 3, 4]).without([3, 4]);
|
bsw/jbe@1309
|
6961 * // => [1, 2]
|
bsw/jbe@1309
|
6962 */
|
bsw/jbe@1309
|
6963 without: function(arrayToSubstract) {
|
bsw/jbe@1309
|
6964 arrayToSubstract = wysihtml.lang.array(arrayToSubstract);
|
bsw/jbe@1309
|
6965 var newArr = [],
|
bsw/jbe@1309
|
6966 i = 0,
|
bsw/jbe@1309
|
6967 length = arr.length;
|
bsw/jbe@1309
|
6968 for (; i<length; i++) {
|
bsw/jbe@1309
|
6969 if (!arrayToSubstract.contains(arr[i])) {
|
bsw/jbe@1309
|
6970 newArr.push(arr[i]);
|
bsw/jbe@1309
|
6971 }
|
bsw/jbe@1309
|
6972 }
|
bsw/jbe@1309
|
6973 return newArr;
|
bsw/jbe@1309
|
6974 },
|
bsw/jbe@1309
|
6975
|
bsw/jbe@1309
|
6976 /**
|
bsw/jbe@1309
|
6977 * Return a clean native array
|
bsw/jbe@1309
|
6978 *
|
bsw/jbe@1309
|
6979 * Following will convert a Live NodeList to a proper Array
|
bsw/jbe@1309
|
6980 * @example
|
bsw/jbe@1309
|
6981 * var childNodes = wysihtml.lang.array(document.body.childNodes).get();
|
bsw/jbe@1309
|
6982 */
|
bsw/jbe@1309
|
6983 get: function() {
|
bsw/jbe@1309
|
6984 var i = 0,
|
bsw/jbe@1309
|
6985 length = arr.length,
|
bsw/jbe@1309
|
6986 newArray = [];
|
bsw/jbe@1309
|
6987 for (; i<length; i++) {
|
bsw/jbe@1309
|
6988 newArray.push(arr[i]);
|
bsw/jbe@1309
|
6989 }
|
bsw/jbe@1309
|
6990 return newArray;
|
bsw/jbe@1309
|
6991 },
|
bsw/jbe@1309
|
6992
|
bsw/jbe@1309
|
6993 /**
|
bsw/jbe@1309
|
6994 * Creates a new array with the results of calling a provided function on every element in this array.
|
bsw/jbe@1309
|
6995 * optionally this can be provided as second argument
|
bsw/jbe@1309
|
6996 *
|
bsw/jbe@1309
|
6997 * @example
|
bsw/jbe@1309
|
6998 * var childNodes = wysihtml.lang.array([1,2,3,4]).map(function (value, index, array) {
|
bsw/jbe@1309
|
6999 return value * 2;
|
bsw/jbe@1309
|
7000 * });
|
bsw/jbe@1309
|
7001 * // => [2,4,6,8]
|
bsw/jbe@1309
|
7002 */
|
bsw/jbe@1309
|
7003 map: function(callback, thisArg) {
|
bsw/jbe@1309
|
7004 if (Array.prototype.map) {
|
bsw/jbe@1309
|
7005 return arr.map(callback, thisArg);
|
bsw/jbe@1309
|
7006 } else {
|
bsw/jbe@1309
|
7007 var len = arr.length >>> 0,
|
bsw/jbe@1309
|
7008 A = new Array(len),
|
bsw/jbe@1309
|
7009 i = 0;
|
bsw/jbe@1309
|
7010 for (; i < len; i++) {
|
bsw/jbe@1309
|
7011 A[i] = callback.call(thisArg, arr[i], i, arr);
|
bsw/jbe@1309
|
7012 }
|
bsw/jbe@1309
|
7013 return A;
|
bsw/jbe@1309
|
7014 }
|
bsw/jbe@1309
|
7015 },
|
bsw/jbe@1309
|
7016
|
bsw/jbe@1309
|
7017 /* ReturnS new array without duplicate entries
|
bsw/jbe@1309
|
7018 *
|
bsw/jbe@1309
|
7019 * @example
|
bsw/jbe@1309
|
7020 * var uniq = wysihtml.lang.array([1,2,3,2,1,4]).unique();
|
bsw/jbe@1309
|
7021 * // => [1,2,3,4]
|
bsw/jbe@1309
|
7022 */
|
bsw/jbe@1309
|
7023 unique: function() {
|
bsw/jbe@1309
|
7024 var vals = [],
|
bsw/jbe@1309
|
7025 max = arr.length,
|
bsw/jbe@1309
|
7026 idx = 0;
|
bsw/jbe@1309
|
7027
|
bsw/jbe@1309
|
7028 while (idx < max) {
|
bsw/jbe@1309
|
7029 if (!wysihtml.lang.array(vals).contains(arr[idx])) {
|
bsw/jbe@1309
|
7030 vals.push(arr[idx]);
|
bsw/jbe@1309
|
7031 }
|
bsw/jbe@1309
|
7032 idx++;
|
bsw/jbe@1309
|
7033 }
|
bsw/jbe@1309
|
7034 return vals;
|
bsw/jbe@1309
|
7035 }
|
bsw/jbe@1309
|
7036
|
bsw/jbe@1309
|
7037 };
|
bsw/jbe@1309
|
7038 };
|
bsw/jbe@1309
|
7039
|
bsw/jbe@1309
|
7040 wysihtml.lang.Dispatcher = Base.extend(
|
bsw/jbe@1309
|
7041 /** @scope wysihtml.lang.Dialog.prototype */ {
|
bsw/jbe@1309
|
7042 on: function(eventName, handler) {
|
bsw/jbe@1309
|
7043 this.events = this.events || {};
|
bsw/jbe@1309
|
7044 this.events[eventName] = this.events[eventName] || [];
|
bsw/jbe@1309
|
7045 this.events[eventName].push(handler);
|
bsw/jbe@1309
|
7046 return this;
|
bsw/jbe@1309
|
7047 },
|
bsw/jbe@1309
|
7048
|
bsw/jbe@1309
|
7049 off: function(eventName, handler) {
|
bsw/jbe@1309
|
7050 this.events = this.events || {};
|
bsw/jbe@1309
|
7051 var i = 0,
|
bsw/jbe@1309
|
7052 handlers,
|
bsw/jbe@1309
|
7053 newHandlers;
|
bsw/jbe@1309
|
7054 if (eventName) {
|
bsw/jbe@1309
|
7055 handlers = this.events[eventName] || [],
|
bsw/jbe@1309
|
7056 newHandlers = [];
|
bsw/jbe@1309
|
7057 for (; i<handlers.length; i++) {
|
bsw/jbe@1309
|
7058 if (handlers[i] !== handler && handler) {
|
bsw/jbe@1309
|
7059 newHandlers.push(handlers[i]);
|
bsw/jbe@1309
|
7060 }
|
bsw/jbe@1309
|
7061 }
|
bsw/jbe@1309
|
7062 this.events[eventName] = newHandlers;
|
bsw/jbe@1309
|
7063 } else {
|
bsw/jbe@1309
|
7064 // Clean up all events
|
bsw/jbe@1309
|
7065 this.events = {};
|
bsw/jbe@1309
|
7066 }
|
bsw/jbe@1309
|
7067 return this;
|
bsw/jbe@1309
|
7068 },
|
bsw/jbe@1309
|
7069
|
bsw/jbe@1309
|
7070 fire: function(eventName, payload) {
|
bsw/jbe@1309
|
7071 this.events = this.events || {};
|
bsw/jbe@1309
|
7072 var handlers = this.events[eventName] || [],
|
bsw/jbe@1309
|
7073 i = 0;
|
bsw/jbe@1309
|
7074 for (; i<handlers.length; i++) {
|
bsw/jbe@1309
|
7075 handlers[i].call(this, payload);
|
bsw/jbe@1309
|
7076 }
|
bsw/jbe@1309
|
7077 return this;
|
bsw/jbe@1309
|
7078 },
|
bsw/jbe@1309
|
7079
|
bsw/jbe@1309
|
7080 // deprecated, use .on()
|
bsw/jbe@1309
|
7081 observe: function() {
|
bsw/jbe@1309
|
7082 return this.on.apply(this, arguments);
|
bsw/jbe@1309
|
7083 },
|
bsw/jbe@1309
|
7084
|
bsw/jbe@1309
|
7085 // deprecated, use .off()
|
bsw/jbe@1309
|
7086 stopObserving: function() {
|
bsw/jbe@1309
|
7087 return this.off.apply(this, arguments);
|
bsw/jbe@1309
|
7088 }
|
bsw/jbe@1309
|
7089 });
|
bsw/jbe@1309
|
7090
|
bsw/jbe@1309
|
7091 wysihtml.lang.object = function(obj) {
|
bsw/jbe@1309
|
7092 return {
|
bsw/jbe@1309
|
7093 /**
|
bsw/jbe@1309
|
7094 * @example
|
bsw/jbe@1309
|
7095 * wysihtml.lang.object({ foo: 1, bar: 1 }).merge({ bar: 2, baz: 3 }).get();
|
bsw/jbe@1309
|
7096 * // => { foo: 1, bar: 2, baz: 3 }
|
bsw/jbe@1309
|
7097 */
|
bsw/jbe@1309
|
7098 merge: function(otherObj, deep) {
|
bsw/jbe@1309
|
7099 for (var i in otherObj) {
|
bsw/jbe@1309
|
7100 if (deep && wysihtml.lang.object(otherObj[i]).isPlainObject() && (typeof obj[i] === "undefined" || wysihtml.lang.object(obj[i]).isPlainObject())) {
|
bsw/jbe@1309
|
7101 if (typeof obj[i] === "undefined") {
|
bsw/jbe@1309
|
7102 obj[i] = wysihtml.lang.object(otherObj[i]).clone(true);
|
bsw/jbe@1309
|
7103 } else {
|
bsw/jbe@1309
|
7104 wysihtml.lang.object(obj[i]).merge(wysihtml.lang.object(otherObj[i]).clone(true));
|
bsw/jbe@1309
|
7105 }
|
bsw/jbe@1309
|
7106 } else {
|
bsw/jbe@1309
|
7107 obj[i] = wysihtml.lang.object(otherObj[i]).isPlainObject() ? wysihtml.lang.object(otherObj[i]).clone(true) : otherObj[i];
|
bsw/jbe@1309
|
7108 }
|
bsw/jbe@1309
|
7109 }
|
bsw/jbe@1309
|
7110 return this;
|
bsw/jbe@1309
|
7111 },
|
bsw/jbe@1309
|
7112
|
bsw/jbe@1309
|
7113 difference: function (otherObj) {
|
bsw/jbe@1309
|
7114 var diffObj = {};
|
bsw/jbe@1309
|
7115
|
bsw/jbe@1309
|
7116 // Get old values not in comparing object
|
bsw/jbe@1309
|
7117 for (var i in obj) {
|
bsw/jbe@1309
|
7118 if (obj.hasOwnProperty(i)) {
|
bsw/jbe@1309
|
7119 if (!otherObj.hasOwnProperty(i)) {
|
bsw/jbe@1309
|
7120 diffObj[i] = obj[i];
|
bsw/jbe@1309
|
7121 }
|
bsw/jbe@1309
|
7122 }
|
bsw/jbe@1309
|
7123 }
|
bsw/jbe@1309
|
7124
|
bsw/jbe@1309
|
7125 // Get new and different values in comparing object
|
bsw/jbe@1309
|
7126 for (var o in otherObj) {
|
bsw/jbe@1309
|
7127 if (otherObj.hasOwnProperty(o)) {
|
bsw/jbe@1309
|
7128 if (!obj.hasOwnProperty(o) || obj[o] !== otherObj[o]) {
|
bsw/jbe@1309
|
7129 diffObj[0] = obj[0];
|
bsw/jbe@1309
|
7130 }
|
bsw/jbe@1309
|
7131 }
|
bsw/jbe@1309
|
7132 }
|
bsw/jbe@1309
|
7133 return diffObj;
|
bsw/jbe@1309
|
7134 },
|
bsw/jbe@1309
|
7135
|
bsw/jbe@1309
|
7136 get: function() {
|
bsw/jbe@1309
|
7137 return obj;
|
bsw/jbe@1309
|
7138 },
|
bsw/jbe@1309
|
7139
|
bsw/jbe@1309
|
7140 /**
|
bsw/jbe@1309
|
7141 * @example
|
bsw/jbe@1309
|
7142 * wysihtml.lang.object({ foo: 1 }).clone();
|
bsw/jbe@1309
|
7143 * // => { foo: 1 }
|
bsw/jbe@1309
|
7144 *
|
bsw/jbe@1309
|
7145 * v0.4.14 adds options for deep clone : wysihtml.lang.object({ foo: 1 }).clone(true);
|
bsw/jbe@1309
|
7146 */
|
bsw/jbe@1309
|
7147 clone: function(deep) {
|
bsw/jbe@1309
|
7148 var newObj = {},
|
bsw/jbe@1309
|
7149 i;
|
bsw/jbe@1309
|
7150
|
bsw/jbe@1309
|
7151 if (obj === null || !wysihtml.lang.object(obj).isPlainObject()) {
|
bsw/jbe@1309
|
7152 return obj;
|
bsw/jbe@1309
|
7153 }
|
bsw/jbe@1309
|
7154
|
bsw/jbe@1309
|
7155 for (i in obj) {
|
bsw/jbe@1309
|
7156 if(obj.hasOwnProperty(i)) {
|
bsw/jbe@1309
|
7157 if (deep) {
|
bsw/jbe@1309
|
7158 newObj[i] = wysihtml.lang.object(obj[i]).clone(deep);
|
bsw/jbe@1309
|
7159 } else {
|
bsw/jbe@1309
|
7160 newObj[i] = obj[i];
|
bsw/jbe@1309
|
7161 }
|
bsw/jbe@1309
|
7162 }
|
bsw/jbe@1309
|
7163 }
|
bsw/jbe@1309
|
7164 return newObj;
|
bsw/jbe@1309
|
7165 },
|
bsw/jbe@1309
|
7166
|
bsw/jbe@1309
|
7167 /**
|
bsw/jbe@1309
|
7168 * @example
|
bsw/jbe@1309
|
7169 * wysihtml.lang.object([]).isArray();
|
bsw/jbe@1309
|
7170 * // => true
|
bsw/jbe@1309
|
7171 */
|
bsw/jbe@1309
|
7172 isArray: function() {
|
bsw/jbe@1309
|
7173 return Object.prototype.toString.call(obj) === "[object Array]";
|
bsw/jbe@1309
|
7174 },
|
bsw/jbe@1309
|
7175
|
bsw/jbe@1309
|
7176 /**
|
bsw/jbe@1309
|
7177 * @example
|
bsw/jbe@1309
|
7178 * wysihtml.lang.object(function() {}).isFunction();
|
bsw/jbe@1309
|
7179 * // => true
|
bsw/jbe@1309
|
7180 */
|
bsw/jbe@1309
|
7181 isFunction: function() {
|
bsw/jbe@1309
|
7182 return Object.prototype.toString.call(obj) === '[object Function]';
|
bsw/jbe@1309
|
7183 },
|
bsw/jbe@1309
|
7184
|
bsw/jbe@1309
|
7185 isPlainObject: function () {
|
bsw/jbe@1309
|
7186 return obj && Object.prototype.toString.call(obj) === '[object Object]' && !(("Node" in window) ? obj instanceof Node : obj instanceof Element || obj instanceof Text);
|
bsw/jbe@1309
|
7187 },
|
bsw/jbe@1309
|
7188
|
bsw/jbe@1309
|
7189 /**
|
bsw/jbe@1309
|
7190 * @example
|
bsw/jbe@1309
|
7191 * wysihtml.lang.object({}).isEmpty();
|
bsw/jbe@1309
|
7192 * // => true
|
bsw/jbe@1309
|
7193 */
|
bsw/jbe@1309
|
7194 isEmpty: function() {
|
bsw/jbe@1309
|
7195 for (var i in obj) {
|
bsw/jbe@1309
|
7196 if (obj.hasOwnProperty(i)) {
|
bsw/jbe@1309
|
7197 return false;
|
bsw/jbe@1309
|
7198 }
|
bsw/jbe@1309
|
7199 }
|
bsw/jbe@1309
|
7200 return true;
|
bsw/jbe@1309
|
7201 }
|
bsw/jbe@1309
|
7202 };
|
bsw/jbe@1309
|
7203 };
|
bsw/jbe@1309
|
7204
|
bsw/jbe@1309
|
7205 (function() {
|
bsw/jbe@1309
|
7206 var WHITE_SPACE_START = /^\s+/,
|
bsw/jbe@1309
|
7207 WHITE_SPACE_END = /\s+$/,
|
bsw/jbe@1309
|
7208 ENTITY_REG_EXP = /[&<>\t"]/g,
|
bsw/jbe@1309
|
7209 ENTITY_MAP = {
|
bsw/jbe@1309
|
7210 '&': '&',
|
bsw/jbe@1309
|
7211 '<': '<',
|
bsw/jbe@1309
|
7212 '>': '>',
|
bsw/jbe@1309
|
7213 '"': """,
|
bsw/jbe@1309
|
7214 '\t':" "
|
bsw/jbe@1309
|
7215 };
|
bsw/jbe@1309
|
7216 wysihtml.lang.string = function(str) {
|
bsw/jbe@1309
|
7217 str = String(str);
|
bsw/jbe@1309
|
7218 return {
|
bsw/jbe@1309
|
7219 /**
|
bsw/jbe@1309
|
7220 * @example
|
bsw/jbe@1309
|
7221 * wysihtml.lang.string(" foo ").trim();
|
bsw/jbe@1309
|
7222 * // => "foo"
|
bsw/jbe@1309
|
7223 */
|
bsw/jbe@1309
|
7224 trim: function() {
|
bsw/jbe@1309
|
7225 return str.replace(WHITE_SPACE_START, "").replace(WHITE_SPACE_END, "");
|
bsw/jbe@1309
|
7226 },
|
bsw/jbe@1309
|
7227
|
bsw/jbe@1309
|
7228 /**
|
bsw/jbe@1309
|
7229 * @example
|
bsw/jbe@1309
|
7230 * wysihtml.lang.string("Hello #{name}").interpolate({ name: "Christopher" });
|
bsw/jbe@1309
|
7231 * // => "Hello Christopher"
|
bsw/jbe@1309
|
7232 */
|
bsw/jbe@1309
|
7233 interpolate: function(vars) {
|
bsw/jbe@1309
|
7234 for (var i in vars) {
|
bsw/jbe@1309
|
7235 str = this.replace("#{" + i + "}").by(vars[i]);
|
bsw/jbe@1309
|
7236 }
|
bsw/jbe@1309
|
7237 return str;
|
bsw/jbe@1309
|
7238 },
|
bsw/jbe@1309
|
7239
|
bsw/jbe@1309
|
7240 /**
|
bsw/jbe@1309
|
7241 * @example
|
bsw/jbe@1309
|
7242 * wysihtml.lang.string("Hello Tom").replace("Tom").with("Hans");
|
bsw/jbe@1309
|
7243 * // => "Hello Hans"
|
bsw/jbe@1309
|
7244 */
|
bsw/jbe@1309
|
7245 replace: function(search) {
|
bsw/jbe@1309
|
7246 return {
|
bsw/jbe@1309
|
7247 by: function(replace) {
|
bsw/jbe@1309
|
7248 return str.split(search).join(replace);
|
bsw/jbe@1309
|
7249 }
|
bsw/jbe@1309
|
7250 };
|
bsw/jbe@1309
|
7251 },
|
bsw/jbe@1309
|
7252
|
bsw/jbe@1309
|
7253 /**
|
bsw/jbe@1309
|
7254 * @example
|
bsw/jbe@1309
|
7255 * wysihtml.lang.string("hello<br>").escapeHTML();
|
bsw/jbe@1309
|
7256 * // => "hello<br>"
|
bsw/jbe@1309
|
7257 */
|
bsw/jbe@1309
|
7258 escapeHTML: function(linebreaks, convertSpaces) {
|
bsw/jbe@1309
|
7259 var html = str.replace(ENTITY_REG_EXP, function(c) { return ENTITY_MAP[c]; });
|
bsw/jbe@1309
|
7260 if (linebreaks) {
|
bsw/jbe@1309
|
7261 html = html.replace(/(?:\r\n|\r|\n)/g, '<br />');
|
bsw/jbe@1309
|
7262 }
|
bsw/jbe@1309
|
7263 if (convertSpaces) {
|
bsw/jbe@1309
|
7264 html = html.replace(/ /gi, " ");
|
bsw/jbe@1309
|
7265 }
|
bsw/jbe@1309
|
7266 return html;
|
bsw/jbe@1309
|
7267 }
|
bsw/jbe@1309
|
7268 };
|
bsw/jbe@1309
|
7269 };
|
bsw/jbe@1309
|
7270 })();
|
bsw/jbe@1309
|
7271
|
bsw/jbe@1309
|
7272 /**
|
bsw/jbe@1309
|
7273 * Find urls in descendant text nodes of an element and auto-links them
|
bsw/jbe@1309
|
7274 * Inspired by http://james.padolsey.com/javascript/find-and-replace-text-with-javascript/
|
bsw/jbe@1309
|
7275 *
|
bsw/jbe@1309
|
7276 * @param {Element} element Container element in which to search for urls
|
bsw/jbe@1309
|
7277 *
|
bsw/jbe@1309
|
7278 * @example
|
bsw/jbe@1309
|
7279 * <div id="text-container">Please click here: www.google.com</div>
|
bsw/jbe@1309
|
7280 * <script>wysihtml.dom.autoLink(document.getElementById("text-container"));</script>
|
bsw/jbe@1309
|
7281 */
|
bsw/jbe@1309
|
7282 (function(wysihtml) {
|
bsw/jbe@1309
|
7283 var /**
|
bsw/jbe@1309
|
7284 * Don't auto-link urls that are contained in the following elements:
|
bsw/jbe@1309
|
7285 */
|
bsw/jbe@1309
|
7286 IGNORE_URLS_IN = wysihtml.lang.array(["CODE", "PRE", "A", "SCRIPT", "HEAD", "TITLE", "STYLE"]),
|
bsw/jbe@1309
|
7287 /**
|
bsw/jbe@1309
|
7288 * revision 1:
|
bsw/jbe@1309
|
7289 * /(\S+\.{1}[^\s\,\.\!]+)/g
|
bsw/jbe@1309
|
7290 *
|
bsw/jbe@1309
|
7291 * revision 2:
|
bsw/jbe@1309
|
7292 * /(\b(((https?|ftp):\/\/)|(www\.))[-A-Z0-9+&@#\/%?=~_|!:,.;\[\]]*[-A-Z0-9+&@#\/%=~_|])/gim
|
bsw/jbe@1309
|
7293 *
|
bsw/jbe@1309
|
7294 * put this in the beginning if you don't wan't to match within a word
|
bsw/jbe@1309
|
7295 * (^|[\>\(\{\[\s\>])
|
bsw/jbe@1309
|
7296 */
|
bsw/jbe@1309
|
7297 URL_REG_EXP = /((https?:\/\/|www\.)[^\s<]{3,})/gi,
|
bsw/jbe@1309
|
7298 TRAILING_CHAR_REG_EXP = /([^\w\/\-](,?))$/i,
|
bsw/jbe@1309
|
7299 MAX_DISPLAY_LENGTH = 100,
|
bsw/jbe@1309
|
7300 BRACKETS = { ")": "(", "]": "[", "}": "{" };
|
bsw/jbe@1309
|
7301
|
bsw/jbe@1309
|
7302 function autoLink(element, ignoreInClasses) {
|
bsw/jbe@1309
|
7303 if (_hasParentThatShouldBeIgnored(element, ignoreInClasses)) {
|
bsw/jbe@1309
|
7304 return element;
|
bsw/jbe@1309
|
7305 }
|
bsw/jbe@1309
|
7306
|
bsw/jbe@1309
|
7307 if (element === element.ownerDocument.documentElement) {
|
bsw/jbe@1309
|
7308 element = element.ownerDocument.body;
|
bsw/jbe@1309
|
7309 }
|
bsw/jbe@1309
|
7310
|
bsw/jbe@1309
|
7311 return _parseNode(element, ignoreInClasses);
|
bsw/jbe@1309
|
7312 }
|
bsw/jbe@1309
|
7313
|
bsw/jbe@1309
|
7314 /**
|
bsw/jbe@1309
|
7315 * This is basically a rebuild of
|
bsw/jbe@1309
|
7316 * the rails auto_link_urls text helper
|
bsw/jbe@1309
|
7317 */
|
bsw/jbe@1309
|
7318 function _convertUrlsToLinks(str) {
|
bsw/jbe@1309
|
7319 return str.replace(URL_REG_EXP, function(match, url) {
|
bsw/jbe@1309
|
7320 var punctuation = (url.match(TRAILING_CHAR_REG_EXP) || [])[1] || "",
|
bsw/jbe@1309
|
7321 opening = BRACKETS[punctuation];
|
bsw/jbe@1309
|
7322 url = url.replace(TRAILING_CHAR_REG_EXP, "");
|
bsw/jbe@1309
|
7323
|
bsw/jbe@1309
|
7324 if (url.split(opening).length > url.split(punctuation).length) {
|
bsw/jbe@1309
|
7325 url = url + punctuation;
|
bsw/jbe@1309
|
7326 punctuation = "";
|
bsw/jbe@1309
|
7327 }
|
bsw/jbe@1309
|
7328 var realUrl = url,
|
bsw/jbe@1309
|
7329 displayUrl = url;
|
bsw/jbe@1309
|
7330 if (url.length > MAX_DISPLAY_LENGTH) {
|
bsw/jbe@1309
|
7331 displayUrl = displayUrl.substr(0, MAX_DISPLAY_LENGTH) + "...";
|
bsw/jbe@1309
|
7332 }
|
bsw/jbe@1309
|
7333 // Add http prefix if necessary
|
bsw/jbe@1309
|
7334 if (realUrl.substr(0, 4) === "www.") {
|
bsw/jbe@1309
|
7335 realUrl = "http://" + realUrl;
|
bsw/jbe@1309
|
7336 }
|
bsw/jbe@1309
|
7337
|
bsw/jbe@1309
|
7338 return '<a href="' + realUrl + '">' + displayUrl + '</a>' + punctuation;
|
bsw/jbe@1309
|
7339 });
|
bsw/jbe@1309
|
7340 }
|
bsw/jbe@1309
|
7341
|
bsw/jbe@1309
|
7342 /**
|
bsw/jbe@1309
|
7343 * Creates or (if already cached) returns a temp element
|
bsw/jbe@1309
|
7344 * for the given document object
|
bsw/jbe@1309
|
7345 */
|
bsw/jbe@1309
|
7346 function _getTempElement(context) {
|
bsw/jbe@1309
|
7347 var tempElement = context._wysihtml_tempElement;
|
bsw/jbe@1309
|
7348 if (!tempElement) {
|
bsw/jbe@1309
|
7349 tempElement = context._wysihtml_tempElement = context.createElement("div");
|
bsw/jbe@1309
|
7350 }
|
bsw/jbe@1309
|
7351 return tempElement;
|
bsw/jbe@1309
|
7352 }
|
bsw/jbe@1309
|
7353
|
bsw/jbe@1309
|
7354 /**
|
bsw/jbe@1309
|
7355 * Replaces the original text nodes with the newly auto-linked dom tree
|
bsw/jbe@1309
|
7356 */
|
bsw/jbe@1309
|
7357 function _wrapMatchesInNode(textNode) {
|
bsw/jbe@1309
|
7358 var parentNode = textNode.parentNode,
|
bsw/jbe@1309
|
7359 nodeValue = wysihtml.lang.string(textNode.data).escapeHTML(),
|
bsw/jbe@1309
|
7360 tempElement = _getTempElement(parentNode.ownerDocument);
|
bsw/jbe@1309
|
7361
|
bsw/jbe@1309
|
7362 // We need to insert an empty/temporary <span /> to fix IE quirks
|
bsw/jbe@1309
|
7363 // Elsewise IE would strip white space in the beginning
|
bsw/jbe@1309
|
7364 tempElement.innerHTML = "<span></span>" + _convertUrlsToLinks(nodeValue);
|
bsw/jbe@1309
|
7365 tempElement.removeChild(tempElement.firstChild);
|
bsw/jbe@1309
|
7366
|
bsw/jbe@1309
|
7367 while (tempElement.firstChild) {
|
bsw/jbe@1309
|
7368 // inserts tempElement.firstChild before textNode
|
bsw/jbe@1309
|
7369 parentNode.insertBefore(tempElement.firstChild, textNode);
|
bsw/jbe@1309
|
7370 }
|
bsw/jbe@1309
|
7371 parentNode.removeChild(textNode);
|
bsw/jbe@1309
|
7372 }
|
bsw/jbe@1309
|
7373
|
bsw/jbe@1309
|
7374 function _hasParentThatShouldBeIgnored(node, ignoreInClasses) {
|
bsw/jbe@1309
|
7375 var nodeName;
|
bsw/jbe@1309
|
7376 while (node.parentNode) {
|
bsw/jbe@1309
|
7377 node = node.parentNode;
|
bsw/jbe@1309
|
7378 nodeName = node.nodeName;
|
bsw/jbe@1309
|
7379 if (node.className && wysihtml.lang.array(node.className.split(' ')).contains(ignoreInClasses)) {
|
bsw/jbe@1309
|
7380 return true;
|
bsw/jbe@1309
|
7381 }
|
bsw/jbe@1309
|
7382 if (IGNORE_URLS_IN.contains(nodeName)) {
|
bsw/jbe@1309
|
7383 return true;
|
bsw/jbe@1309
|
7384 } else if (nodeName === "body") {
|
bsw/jbe@1309
|
7385 return false;
|
bsw/jbe@1309
|
7386 }
|
bsw/jbe@1309
|
7387 }
|
bsw/jbe@1309
|
7388 return false;
|
bsw/jbe@1309
|
7389 }
|
bsw/jbe@1309
|
7390
|
bsw/jbe@1309
|
7391 function _parseNode(element, ignoreInClasses) {
|
bsw/jbe@1309
|
7392 if (IGNORE_URLS_IN.contains(element.nodeName)) {
|
bsw/jbe@1309
|
7393 return;
|
bsw/jbe@1309
|
7394 }
|
bsw/jbe@1309
|
7395
|
bsw/jbe@1309
|
7396 if (element.className && wysihtml.lang.array(element.className.split(' ')).contains(ignoreInClasses)) {
|
bsw/jbe@1309
|
7397 return;
|
bsw/jbe@1309
|
7398 }
|
bsw/jbe@1309
|
7399
|
bsw/jbe@1309
|
7400 if (element.nodeType === wysihtml.TEXT_NODE && element.data.match(URL_REG_EXP)) {
|
bsw/jbe@1309
|
7401 _wrapMatchesInNode(element);
|
bsw/jbe@1309
|
7402 return;
|
bsw/jbe@1309
|
7403 }
|
bsw/jbe@1309
|
7404
|
bsw/jbe@1309
|
7405 var childNodes = wysihtml.lang.array(element.childNodes).get(),
|
bsw/jbe@1309
|
7406 childNodesLength = childNodes.length,
|
bsw/jbe@1309
|
7407 i = 0;
|
bsw/jbe@1309
|
7408
|
bsw/jbe@1309
|
7409 for (; i<childNodesLength; i++) {
|
bsw/jbe@1309
|
7410 _parseNode(childNodes[i], ignoreInClasses);
|
bsw/jbe@1309
|
7411 }
|
bsw/jbe@1309
|
7412
|
bsw/jbe@1309
|
7413 return element;
|
bsw/jbe@1309
|
7414 }
|
bsw/jbe@1309
|
7415
|
bsw/jbe@1309
|
7416 wysihtml.dom.autoLink = autoLink;
|
bsw/jbe@1309
|
7417
|
bsw/jbe@1309
|
7418 // Reveal url reg exp to the outside
|
bsw/jbe@1309
|
7419 wysihtml.dom.autoLink.URL_REG_EXP = URL_REG_EXP;
|
bsw/jbe@1309
|
7420 })(wysihtml);
|
bsw/jbe@1309
|
7421
|
bsw/jbe@1309
|
7422 (function(wysihtml) {
|
bsw/jbe@1309
|
7423 var api = wysihtml.dom;
|
bsw/jbe@1309
|
7424
|
bsw/jbe@1309
|
7425 api.addClass = function(element, className) {
|
bsw/jbe@1309
|
7426 var classList = element.classList;
|
bsw/jbe@1309
|
7427 if (classList) {
|
bsw/jbe@1309
|
7428 return classList.add(className);
|
bsw/jbe@1309
|
7429 }
|
bsw/jbe@1309
|
7430 if (api.hasClass(element, className)) {
|
bsw/jbe@1309
|
7431 return;
|
bsw/jbe@1309
|
7432 }
|
bsw/jbe@1309
|
7433 element.className += " " + className;
|
bsw/jbe@1309
|
7434 };
|
bsw/jbe@1309
|
7435
|
bsw/jbe@1309
|
7436 api.removeClass = function(element, className) {
|
bsw/jbe@1309
|
7437 var classList = element.classList;
|
bsw/jbe@1309
|
7438 if (classList) {
|
bsw/jbe@1309
|
7439 return classList.remove(className);
|
bsw/jbe@1309
|
7440 }
|
bsw/jbe@1309
|
7441
|
bsw/jbe@1309
|
7442 element.className = element.className.replace(new RegExp("(^|\\s+)" + className + "(\\s+|$)"), " ");
|
bsw/jbe@1309
|
7443 };
|
bsw/jbe@1309
|
7444
|
bsw/jbe@1309
|
7445 api.hasClass = function(element, className) {
|
bsw/jbe@1309
|
7446 var classList = element.classList;
|
bsw/jbe@1309
|
7447 if (classList) {
|
bsw/jbe@1309
|
7448 return classList.contains(className);
|
bsw/jbe@1309
|
7449 }
|
bsw/jbe@1309
|
7450
|
bsw/jbe@1309
|
7451 var elementClassName = element.className;
|
bsw/jbe@1309
|
7452 return (elementClassName.length > 0 && (elementClassName == className || new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName)));
|
bsw/jbe@1309
|
7453 };
|
bsw/jbe@1309
|
7454 })(wysihtml);
|
bsw/jbe@1309
|
7455
|
bsw/jbe@1309
|
7456 wysihtml.dom.compareDocumentPosition = (function() {
|
bsw/jbe@1309
|
7457 var documentElement = document.documentElement;
|
bsw/jbe@1309
|
7458 if (documentElement.compareDocumentPosition) {
|
bsw/jbe@1309
|
7459 return function(container, element) {
|
bsw/jbe@1309
|
7460 return container.compareDocumentPosition(element);
|
bsw/jbe@1309
|
7461 };
|
bsw/jbe@1309
|
7462 } else {
|
bsw/jbe@1309
|
7463 return function( container, element ) {
|
bsw/jbe@1309
|
7464 // implementation borrowed from https://github.com/tmpvar/jsdom/blob/681a8524b663281a0f58348c6129c8c184efc62c/lib/jsdom/level3/core.js // MIT license
|
bsw/jbe@1309
|
7465 var thisOwner, otherOwner;
|
bsw/jbe@1309
|
7466
|
bsw/jbe@1309
|
7467 if( container.nodeType === 9) // Node.DOCUMENT_NODE
|
bsw/jbe@1309
|
7468 thisOwner = container;
|
bsw/jbe@1309
|
7469 else
|
bsw/jbe@1309
|
7470 thisOwner = container.ownerDocument;
|
bsw/jbe@1309
|
7471
|
bsw/jbe@1309
|
7472 if( element.nodeType === 9) // Node.DOCUMENT_NODE
|
bsw/jbe@1309
|
7473 otherOwner = element;
|
bsw/jbe@1309
|
7474 else
|
bsw/jbe@1309
|
7475 otherOwner = element.ownerDocument;
|
bsw/jbe@1309
|
7476
|
bsw/jbe@1309
|
7477 if( container === element ) return 0;
|
bsw/jbe@1309
|
7478 if( container === element.ownerDocument ) return 4 + 16; //Node.DOCUMENT_POSITION_FOLLOWING + Node.DOCUMENT_POSITION_CONTAINED_BY;
|
bsw/jbe@1309
|
7479 if( container.ownerDocument === element ) return 2 + 8; //Node.DOCUMENT_POSITION_PRECEDING + Node.DOCUMENT_POSITION_CONTAINS;
|
bsw/jbe@1309
|
7480 if( thisOwner !== otherOwner ) return 1; // Node.DOCUMENT_POSITION_DISCONNECTED;
|
bsw/jbe@1309
|
7481
|
bsw/jbe@1309
|
7482 // Text nodes for attributes does not have a _parentNode. So we need to find them as attribute child.
|
bsw/jbe@1309
|
7483 if( container.nodeType === 2 /*Node.ATTRIBUTE_NODE*/ && container.childNodes && wysihtml.lang.array(container.childNodes).indexOf( element ) !== -1)
|
bsw/jbe@1309
|
7484 return 4 + 16; //Node.DOCUMENT_POSITION_FOLLOWING + Node.DOCUMENT_POSITION_CONTAINED_BY;
|
bsw/jbe@1309
|
7485
|
bsw/jbe@1309
|
7486 if( element.nodeType === 2 /*Node.ATTRIBUTE_NODE*/ && element.childNodes && wysihtml.lang.array(element.childNodes).indexOf( container ) !== -1)
|
bsw/jbe@1309
|
7487 return 2 + 8; //Node.DOCUMENT_POSITION_PRECEDING + Node.DOCUMENT_POSITION_CONTAINS;
|
bsw/jbe@1309
|
7488
|
bsw/jbe@1309
|
7489 var point = container;
|
bsw/jbe@1309
|
7490 var parents = [ ];
|
bsw/jbe@1309
|
7491 var previous = null;
|
bsw/jbe@1309
|
7492 while( point ) {
|
bsw/jbe@1309
|
7493 if( point == element ) return 2 + 8; //Node.DOCUMENT_POSITION_PRECEDING + Node.DOCUMENT_POSITION_CONTAINS;
|
bsw/jbe@1309
|
7494 parents.push( point );
|
bsw/jbe@1309
|
7495 point = point.parentNode;
|
bsw/jbe@1309
|
7496 }
|
bsw/jbe@1309
|
7497 point = element;
|
bsw/jbe@1309
|
7498 previous = null;
|
bsw/jbe@1309
|
7499 while( point ) {
|
bsw/jbe@1309
|
7500 if( point == container ) return 4 + 16; //Node.DOCUMENT_POSITION_FOLLOWING + Node.DOCUMENT_POSITION_CONTAINED_BY;
|
bsw/jbe@1309
|
7501 var location_index = wysihtml.lang.array(parents).indexOf( point );
|
bsw/jbe@1309
|
7502 if( location_index !== -1) {
|
bsw/jbe@1309
|
7503 var smallest_common_ancestor = parents[ location_index ];
|
bsw/jbe@1309
|
7504 var this_index = wysihtml.lang.array(smallest_common_ancestor.childNodes).indexOf( parents[location_index - 1]);//smallest_common_ancestor.childNodes.toArray().indexOf( parents[location_index - 1] );
|
bsw/jbe@1309
|
7505 var other_index = wysihtml.lang.array(smallest_common_ancestor.childNodes).indexOf( previous ); //smallest_common_ancestor.childNodes.toArray().indexOf( previous );
|
bsw/jbe@1309
|
7506 if( this_index > other_index ) {
|
bsw/jbe@1309
|
7507 return 2; //Node.DOCUMENT_POSITION_PRECEDING;
|
bsw/jbe@1309
|
7508 }
|
bsw/jbe@1309
|
7509 else {
|
bsw/jbe@1309
|
7510 return 4; //Node.DOCUMENT_POSITION_FOLLOWING;
|
bsw/jbe@1309
|
7511 }
|
bsw/jbe@1309
|
7512 }
|
bsw/jbe@1309
|
7513 previous = point;
|
bsw/jbe@1309
|
7514 point = point.parentNode;
|
bsw/jbe@1309
|
7515 }
|
bsw/jbe@1309
|
7516 return 1; //Node.DOCUMENT_POSITION_DISCONNECTED;
|
bsw/jbe@1309
|
7517 };
|
bsw/jbe@1309
|
7518 }
|
bsw/jbe@1309
|
7519 })();
|
bsw/jbe@1309
|
7520
|
bsw/jbe@1309
|
7521 wysihtml.dom.contains = (function() {
|
bsw/jbe@1309
|
7522 var documentElement = document.documentElement;
|
bsw/jbe@1309
|
7523 if (documentElement.contains) {
|
bsw/jbe@1309
|
7524 return function(container, element) {
|
bsw/jbe@1309
|
7525 if (element.nodeType !== wysihtml.ELEMENT_NODE) {
|
bsw/jbe@1309
|
7526 if (element.parentNode === container) {
|
bsw/jbe@1309
|
7527 return true;
|
bsw/jbe@1309
|
7528 }
|
bsw/jbe@1309
|
7529 element = element.parentNode;
|
bsw/jbe@1309
|
7530 }
|
bsw/jbe@1309
|
7531 return container !== element && container.contains(element);
|
bsw/jbe@1309
|
7532 };
|
bsw/jbe@1309
|
7533 } else if (documentElement.compareDocumentPosition) {
|
bsw/jbe@1309
|
7534 return function(container, element) {
|
bsw/jbe@1309
|
7535 // https://developer.mozilla.org/en/DOM/Node.compareDocumentPosition
|
bsw/jbe@1309
|
7536 return !!(container.compareDocumentPosition(element) & 16);
|
bsw/jbe@1309
|
7537 };
|
bsw/jbe@1309
|
7538 }
|
bsw/jbe@1309
|
7539 })();
|
bsw/jbe@1309
|
7540
|
bsw/jbe@1309
|
7541 (function(wysihtml) {
|
bsw/jbe@1309
|
7542 var doc = document;
|
bsw/jbe@1309
|
7543 wysihtml.dom.ContentEditableArea = Base.extend({
|
bsw/jbe@1309
|
7544 getContentEditable: function() {
|
bsw/jbe@1309
|
7545 return this.element;
|
bsw/jbe@1309
|
7546 },
|
bsw/jbe@1309
|
7547
|
bsw/jbe@1309
|
7548 getWindow: function() {
|
bsw/jbe@1309
|
7549 return this.element.ownerDocument.defaultView || this.element.ownerDocument.parentWindow;
|
bsw/jbe@1309
|
7550 },
|
bsw/jbe@1309
|
7551
|
bsw/jbe@1309
|
7552 getDocument: function() {
|
bsw/jbe@1309
|
7553 return this.element.ownerDocument;
|
bsw/jbe@1309
|
7554 },
|
bsw/jbe@1309
|
7555
|
bsw/jbe@1309
|
7556 constructor: function(readyCallback, config, contentEditable) {
|
bsw/jbe@1309
|
7557 this.callback = readyCallback || wysihtml.EMPTY_FUNCTION;
|
bsw/jbe@1309
|
7558 this.config = wysihtml.lang.object({}).merge(config).get();
|
bsw/jbe@1309
|
7559 if (!this.config.className) {
|
bsw/jbe@1309
|
7560 this.config.className = "wysihtml-sandbox";
|
bsw/jbe@1309
|
7561 }
|
bsw/jbe@1309
|
7562 if (contentEditable) {
|
bsw/jbe@1309
|
7563 this.element = this._bindElement(contentEditable);
|
bsw/jbe@1309
|
7564 } else {
|
bsw/jbe@1309
|
7565 this.element = this._createElement();
|
bsw/jbe@1309
|
7566 }
|
bsw/jbe@1309
|
7567 },
|
bsw/jbe@1309
|
7568
|
bsw/jbe@1309
|
7569 destroy: function() {
|
bsw/jbe@1309
|
7570
|
bsw/jbe@1309
|
7571 },
|
bsw/jbe@1309
|
7572
|
bsw/jbe@1309
|
7573 // creates a new contenteditable and initiates it
|
bsw/jbe@1309
|
7574 _createElement: function() {
|
bsw/jbe@1309
|
7575 var element = doc.createElement("div");
|
bsw/jbe@1309
|
7576 element.className = this.config.className;
|
bsw/jbe@1309
|
7577 this._loadElement(element);
|
bsw/jbe@1309
|
7578 return element;
|
bsw/jbe@1309
|
7579 },
|
bsw/jbe@1309
|
7580
|
bsw/jbe@1309
|
7581 // initiates an allready existent contenteditable
|
bsw/jbe@1309
|
7582 _bindElement: function(contentEditable) {
|
bsw/jbe@1309
|
7583 contentEditable.className = contentEditable.className ? contentEditable.className + " wysihtml-sandbox" : "wysihtml-sandbox";
|
bsw/jbe@1309
|
7584 this._loadElement(contentEditable, true);
|
bsw/jbe@1309
|
7585 return contentEditable;
|
bsw/jbe@1309
|
7586 },
|
bsw/jbe@1309
|
7587
|
bsw/jbe@1309
|
7588 _loadElement: function(element, contentExists) {
|
bsw/jbe@1309
|
7589 var that = this;
|
bsw/jbe@1309
|
7590
|
bsw/jbe@1309
|
7591 if (!contentExists) {
|
bsw/jbe@1309
|
7592 var innerHtml = this._getHtml();
|
bsw/jbe@1309
|
7593 element.innerHTML = innerHtml;
|
bsw/jbe@1309
|
7594 }
|
bsw/jbe@1309
|
7595
|
bsw/jbe@1309
|
7596 this.loaded = true;
|
bsw/jbe@1309
|
7597 // Trigger the callback
|
bsw/jbe@1309
|
7598 setTimeout(function() { that.callback(that); }, 0);
|
bsw/jbe@1309
|
7599 },
|
bsw/jbe@1309
|
7600
|
bsw/jbe@1309
|
7601 _getHtml: function(templateVars) {
|
bsw/jbe@1309
|
7602 return '';
|
bsw/jbe@1309
|
7603 }
|
bsw/jbe@1309
|
7604
|
bsw/jbe@1309
|
7605 });
|
bsw/jbe@1309
|
7606 })(wysihtml);
|
bsw/jbe@1309
|
7607
|
bsw/jbe@1309
|
7608 /**
|
bsw/jbe@1309
|
7609 * Converts an HTML fragment/element into a unordered/ordered list
|
bsw/jbe@1309
|
7610 *
|
bsw/jbe@1309
|
7611 * @param {Element} element The element which should be turned into a list
|
bsw/jbe@1309
|
7612 * @param {String} listType The list type in which to convert the tree (either "ul" or "ol")
|
bsw/jbe@1309
|
7613 * @return {Element} The created list
|
bsw/jbe@1309
|
7614 *
|
bsw/jbe@1309
|
7615 * @example
|
bsw/jbe@1309
|
7616 * <!-- Assume the following dom: -->
|
bsw/jbe@1309
|
7617 * <span id="pseudo-list">
|
bsw/jbe@1309
|
7618 * eminem<br>
|
bsw/jbe@1309
|
7619 * dr. dre
|
bsw/jbe@1309
|
7620 * <div>50 Cent</div>
|
bsw/jbe@1309
|
7621 * </span>
|
bsw/jbe@1309
|
7622 *
|
bsw/jbe@1309
|
7623 * <script>
|
bsw/jbe@1309
|
7624 * wysihtml.dom.convertToList(document.getElementById("pseudo-list"), "ul");
|
bsw/jbe@1309
|
7625 * </script>
|
bsw/jbe@1309
|
7626 *
|
bsw/jbe@1309
|
7627 * <!-- Will result in: -->
|
bsw/jbe@1309
|
7628 * <ul>
|
bsw/jbe@1309
|
7629 * <li>eminem</li>
|
bsw/jbe@1309
|
7630 * <li>dr. dre</li>
|
bsw/jbe@1309
|
7631 * <li>50 Cent</li>
|
bsw/jbe@1309
|
7632 * </ul>
|
bsw/jbe@1309
|
7633 */
|
bsw/jbe@1309
|
7634 wysihtml.dom.convertToList = (function() {
|
bsw/jbe@1309
|
7635 function _createListItem(doc, list) {
|
bsw/jbe@1309
|
7636 var listItem = doc.createElement("li");
|
bsw/jbe@1309
|
7637 list.appendChild(listItem);
|
bsw/jbe@1309
|
7638 return listItem;
|
bsw/jbe@1309
|
7639 }
|
bsw/jbe@1309
|
7640
|
bsw/jbe@1309
|
7641 function _createList(doc, type) {
|
bsw/jbe@1309
|
7642 return doc.createElement(type);
|
bsw/jbe@1309
|
7643 }
|
bsw/jbe@1309
|
7644
|
bsw/jbe@1309
|
7645 function convertToList(element, listType, uneditableClass) {
|
bsw/jbe@1309
|
7646 if (element.nodeName === "UL" || element.nodeName === "OL" || element.nodeName === "MENU") {
|
bsw/jbe@1309
|
7647 // Already a list
|
bsw/jbe@1309
|
7648 return element;
|
bsw/jbe@1309
|
7649 }
|
bsw/jbe@1309
|
7650
|
bsw/jbe@1309
|
7651 var doc = element.ownerDocument,
|
bsw/jbe@1309
|
7652 list = _createList(doc, listType),
|
bsw/jbe@1309
|
7653 lineBreaks = element.querySelectorAll("br"),
|
bsw/jbe@1309
|
7654 lineBreaksLength = lineBreaks.length,
|
bsw/jbe@1309
|
7655 childNodes,
|
bsw/jbe@1309
|
7656 childNodesLength,
|
bsw/jbe@1309
|
7657 childNode,
|
bsw/jbe@1309
|
7658 lineBreak,
|
bsw/jbe@1309
|
7659 parentNode,
|
bsw/jbe@1309
|
7660 isBlockElement,
|
bsw/jbe@1309
|
7661 isLineBreak,
|
bsw/jbe@1309
|
7662 currentListItem,
|
bsw/jbe@1309
|
7663 i;
|
bsw/jbe@1309
|
7664
|
bsw/jbe@1309
|
7665 // First find <br> at the end of inline elements and move them behind them
|
bsw/jbe@1309
|
7666 for (i=0; i<lineBreaksLength; i++) {
|
bsw/jbe@1309
|
7667 lineBreak = lineBreaks[i];
|
bsw/jbe@1309
|
7668 while ((parentNode = lineBreak.parentNode) && parentNode !== element && parentNode.lastChild === lineBreak) {
|
bsw/jbe@1309
|
7669 if (wysihtml.dom.getStyle("display").from(parentNode) === "block") {
|
bsw/jbe@1309
|
7670 parentNode.removeChild(lineBreak);
|
bsw/jbe@1309
|
7671 break;
|
bsw/jbe@1309
|
7672 }
|
bsw/jbe@1309
|
7673 wysihtml.dom.insert(lineBreak).after(lineBreak.parentNode);
|
bsw/jbe@1309
|
7674 }
|
bsw/jbe@1309
|
7675 }
|
bsw/jbe@1309
|
7676
|
bsw/jbe@1309
|
7677 childNodes = wysihtml.lang.array(element.childNodes).get();
|
bsw/jbe@1309
|
7678 childNodesLength = childNodes.length;
|
bsw/jbe@1309
|
7679
|
bsw/jbe@1309
|
7680 for (i=0; i<childNodesLength; i++) {
|
bsw/jbe@1309
|
7681 currentListItem = currentListItem || _createListItem(doc, list);
|
bsw/jbe@1309
|
7682 childNode = childNodes[i];
|
bsw/jbe@1309
|
7683 isBlockElement = wysihtml.dom.getStyle("display").from(childNode) === "block";
|
bsw/jbe@1309
|
7684 isLineBreak = childNode.nodeName === "BR";
|
bsw/jbe@1309
|
7685
|
bsw/jbe@1309
|
7686 // consider uneditable as an inline element
|
bsw/jbe@1309
|
7687 if (isBlockElement && (!uneditableClass || !wysihtml.dom.hasClass(childNode, uneditableClass))) {
|
bsw/jbe@1309
|
7688 // Append blockElement to current <li> if empty, otherwise create a new one
|
bsw/jbe@1309
|
7689 currentListItem = currentListItem.firstChild ? _createListItem(doc, list) : currentListItem;
|
bsw/jbe@1309
|
7690 currentListItem.appendChild(childNode);
|
bsw/jbe@1309
|
7691 currentListItem = null;
|
bsw/jbe@1309
|
7692 continue;
|
bsw/jbe@1309
|
7693 }
|
bsw/jbe@1309
|
7694
|
bsw/jbe@1309
|
7695 if (isLineBreak) {
|
bsw/jbe@1309
|
7696 // Only create a new list item in the next iteration when the current one has already content
|
bsw/jbe@1309
|
7697 currentListItem = currentListItem.firstChild ? null : currentListItem;
|
bsw/jbe@1309
|
7698 continue;
|
bsw/jbe@1309
|
7699 }
|
bsw/jbe@1309
|
7700
|
bsw/jbe@1309
|
7701 currentListItem.appendChild(childNode);
|
bsw/jbe@1309
|
7702 }
|
bsw/jbe@1309
|
7703
|
bsw/jbe@1309
|
7704 if (childNodes.length === 0) {
|
bsw/jbe@1309
|
7705 _createListItem(doc, list);
|
bsw/jbe@1309
|
7706 }
|
bsw/jbe@1309
|
7707
|
bsw/jbe@1309
|
7708 element.parentNode.replaceChild(list, element);
|
bsw/jbe@1309
|
7709 return list;
|
bsw/jbe@1309
|
7710 }
|
bsw/jbe@1309
|
7711
|
bsw/jbe@1309
|
7712 return convertToList;
|
bsw/jbe@1309
|
7713 })();
|
bsw/jbe@1309
|
7714
|
bsw/jbe@1309
|
7715 /**
|
bsw/jbe@1309
|
7716 * Copy a set of attributes from one element to another
|
bsw/jbe@1309
|
7717 *
|
bsw/jbe@1309
|
7718 * @param {Array} attributesToCopy List of attributes which should be copied
|
bsw/jbe@1309
|
7719 * @return {Object} Returns an object which offers the "from" method which can be invoked with the element where to
|
bsw/jbe@1309
|
7720 * copy the attributes from., this again returns an object which provides a method named "to" which can be invoked
|
bsw/jbe@1309
|
7721 * with the element where to copy the attributes to (see example)
|
bsw/jbe@1309
|
7722 *
|
bsw/jbe@1309
|
7723 * @example
|
bsw/jbe@1309
|
7724 * var textarea = document.querySelector("textarea"),
|
bsw/jbe@1309
|
7725 * div = document.querySelector("div[contenteditable=true]"),
|
bsw/jbe@1309
|
7726 * anotherDiv = document.querySelector("div.preview");
|
bsw/jbe@1309
|
7727 * wysihtml.dom.copyAttributes(["spellcheck", "value", "placeholder"]).from(textarea).to(div).andTo(anotherDiv);
|
bsw/jbe@1309
|
7728 *
|
bsw/jbe@1309
|
7729 */
|
bsw/jbe@1309
|
7730 wysihtml.dom.copyAttributes = function(attributesToCopy) {
|
bsw/jbe@1309
|
7731 return {
|
bsw/jbe@1309
|
7732 from: function(elementToCopyFrom) {
|
bsw/jbe@1309
|
7733 return {
|
bsw/jbe@1309
|
7734 to: function pasteElementAttributesTo(elementToCopyTo) {
|
bsw/jbe@1309
|
7735 var attribute,
|
bsw/jbe@1309
|
7736 i = 0,
|
bsw/jbe@1309
|
7737 length = attributesToCopy.length;
|
bsw/jbe@1309
|
7738 for (; i<length; i++) {
|
bsw/jbe@1309
|
7739 attribute = attributesToCopy[i];
|
bsw/jbe@1309
|
7740 if (typeof(elementToCopyFrom[attribute]) !== "undefined" && elementToCopyFrom[attribute] !== "") {
|
bsw/jbe@1309
|
7741 elementToCopyTo[attribute] = elementToCopyFrom[attribute];
|
bsw/jbe@1309
|
7742 }
|
bsw/jbe@1309
|
7743 }
|
bsw/jbe@1309
|
7744 return { andTo: pasteElementAttributesTo };
|
bsw/jbe@1309
|
7745 }
|
bsw/jbe@1309
|
7746 };
|
bsw/jbe@1309
|
7747 }
|
bsw/jbe@1309
|
7748 };
|
bsw/jbe@1309
|
7749 };
|
bsw/jbe@1309
|
7750
|
bsw/jbe@1309
|
7751 /**
|
bsw/jbe@1309
|
7752 * Copy a set of styles from one element to another
|
bsw/jbe@1309
|
7753 * Please note that this only works properly across browsers when the element from which to copy the styles
|
bsw/jbe@1309
|
7754 * is in the dom
|
bsw/jbe@1309
|
7755 *
|
bsw/jbe@1309
|
7756 * Interesting article on how to copy styles
|
bsw/jbe@1309
|
7757 *
|
bsw/jbe@1309
|
7758 * @param {Array} stylesToCopy List of styles which should be copied
|
bsw/jbe@1309
|
7759 * @return {Object} Returns an object which offers the "from" method which can be invoked with the element where to
|
bsw/jbe@1309
|
7760 * copy the styles from., this again returns an object which provides a method named "to" which can be invoked
|
bsw/jbe@1309
|
7761 * with the element where to copy the styles to (see example)
|
bsw/jbe@1309
|
7762 *
|
bsw/jbe@1309
|
7763 * @example
|
bsw/jbe@1309
|
7764 * var textarea = document.querySelector("textarea"),
|
bsw/jbe@1309
|
7765 * div = document.querySelector("div[contenteditable=true]"),
|
bsw/jbe@1309
|
7766 * anotherDiv = document.querySelector("div.preview");
|
bsw/jbe@1309
|
7767 * wysihtml.dom.copyStyles(["overflow-y", "width", "height"]).from(textarea).to(div).andTo(anotherDiv);
|
bsw/jbe@1309
|
7768 *
|
bsw/jbe@1309
|
7769 */
|
bsw/jbe@1309
|
7770 (function(dom) {
|
bsw/jbe@1309
|
7771
|
bsw/jbe@1309
|
7772 /**
|
bsw/jbe@1309
|
7773 * Mozilla, WebKit and Opera recalculate the computed width when box-sizing: boder-box; is set
|
bsw/jbe@1309
|
7774 * So if an element has "width: 200px; -moz-box-sizing: border-box; border: 1px;" then
|
bsw/jbe@1309
|
7775 * its computed css width will be 198px
|
bsw/jbe@1309
|
7776 *
|
bsw/jbe@1309
|
7777 * See https://bugzilla.mozilla.org/show_bug.cgi?id=520992
|
bsw/jbe@1309
|
7778 */
|
bsw/jbe@1309
|
7779 var BOX_SIZING_PROPERTIES = ["-webkit-box-sizing", "-moz-box-sizing", "-ms-box-sizing", "box-sizing"];
|
bsw/jbe@1309
|
7780
|
bsw/jbe@1309
|
7781 var shouldIgnoreBoxSizingBorderBox = function(element) {
|
bsw/jbe@1309
|
7782 if (hasBoxSizingBorderBox(element)) {
|
bsw/jbe@1309
|
7783 return parseInt(dom.getStyle("width").from(element), 10) < element.offsetWidth;
|
bsw/jbe@1309
|
7784 }
|
bsw/jbe@1309
|
7785 return false;
|
bsw/jbe@1309
|
7786 };
|
bsw/jbe@1309
|
7787
|
bsw/jbe@1309
|
7788 var hasBoxSizingBorderBox = function(element) {
|
bsw/jbe@1309
|
7789 var i = 0,
|
bsw/jbe@1309
|
7790 length = BOX_SIZING_PROPERTIES.length;
|
bsw/jbe@1309
|
7791 for (; i<length; i++) {
|
bsw/jbe@1309
|
7792 if (dom.getStyle(BOX_SIZING_PROPERTIES[i]).from(element) === "border-box") {
|
bsw/jbe@1309
|
7793 return BOX_SIZING_PROPERTIES[i];
|
bsw/jbe@1309
|
7794 }
|
bsw/jbe@1309
|
7795 }
|
bsw/jbe@1309
|
7796 };
|
bsw/jbe@1309
|
7797
|
bsw/jbe@1309
|
7798 dom.copyStyles = function(stylesToCopy) {
|
bsw/jbe@1309
|
7799 return {
|
bsw/jbe@1309
|
7800 from: function(element) {
|
bsw/jbe@1309
|
7801 if (shouldIgnoreBoxSizingBorderBox(element)) {
|
bsw/jbe@1309
|
7802 stylesToCopy = wysihtml.lang.array(stylesToCopy).without(BOX_SIZING_PROPERTIES);
|
bsw/jbe@1309
|
7803 }
|
bsw/jbe@1309
|
7804
|
bsw/jbe@1309
|
7805 var cssText = "",
|
bsw/jbe@1309
|
7806 length = stylesToCopy.length,
|
bsw/jbe@1309
|
7807 i = 0,
|
bsw/jbe@1309
|
7808 property;
|
bsw/jbe@1309
|
7809 for (; i<length; i++) {
|
bsw/jbe@1309
|
7810 property = stylesToCopy[i];
|
bsw/jbe@1309
|
7811 cssText += property + ":" + dom.getStyle(property).from(element) + ";";
|
bsw/jbe@1309
|
7812 }
|
bsw/jbe@1309
|
7813
|
bsw/jbe@1309
|
7814 return {
|
bsw/jbe@1309
|
7815 to: function pasteStylesTo(element) {
|
bsw/jbe@1309
|
7816 dom.setStyles(cssText).on(element);
|
bsw/jbe@1309
|
7817 return { andTo: pasteStylesTo };
|
bsw/jbe@1309
|
7818 }
|
bsw/jbe@1309
|
7819 };
|
bsw/jbe@1309
|
7820 }
|
bsw/jbe@1309
|
7821 };
|
bsw/jbe@1309
|
7822 };
|
bsw/jbe@1309
|
7823 })(wysihtml.dom);
|
bsw/jbe@1309
|
7824
|
bsw/jbe@1309
|
7825 /**
|
bsw/jbe@1309
|
7826 * Event Delegation
|
bsw/jbe@1309
|
7827 *
|
bsw/jbe@1309
|
7828 * @example
|
bsw/jbe@1309
|
7829 * wysihtml.dom.delegate(document.body, "a", "click", function() {
|
bsw/jbe@1309
|
7830 * // foo
|
bsw/jbe@1309
|
7831 * });
|
bsw/jbe@1309
|
7832 */
|
bsw/jbe@1309
|
7833 (function(wysihtml) {
|
bsw/jbe@1309
|
7834 wysihtml.dom.delegate = function(container, selector, eventName, handler) {
|
bsw/jbe@1309
|
7835 var callback = function(event) {
|
bsw/jbe@1309
|
7836 var target = event.target,
|
bsw/jbe@1309
|
7837 element = (target.nodeType === 3) ? target.parentNode : target, // IE has .contains only seeing elements not textnodes
|
bsw/jbe@1309
|
7838 matches = container.querySelectorAll(selector);
|
bsw/jbe@1309
|
7839
|
bsw/jbe@1309
|
7840 for (var i = 0, max = matches.length; i < max; i++) {
|
bsw/jbe@1309
|
7841 if (matches[i].contains(element)) {
|
bsw/jbe@1309
|
7842 handler.call(matches[i], event);
|
bsw/jbe@1309
|
7843 }
|
bsw/jbe@1309
|
7844 }
|
bsw/jbe@1309
|
7845 };
|
bsw/jbe@1309
|
7846
|
bsw/jbe@1309
|
7847 container.addEventListener(eventName, callback, false);
|
bsw/jbe@1309
|
7848 return {
|
bsw/jbe@1309
|
7849 stop: function() {
|
bsw/jbe@1309
|
7850 container.removeEventListener(eventName, callback, false);
|
bsw/jbe@1309
|
7851 }
|
bsw/jbe@1309
|
7852 };
|
bsw/jbe@1309
|
7853 };
|
bsw/jbe@1309
|
7854 })(wysihtml);
|
bsw/jbe@1309
|
7855
|
bsw/jbe@1309
|
7856 // TODO: Refactor dom tree traversing here
|
bsw/jbe@1309
|
7857 (function(wysihtml) {
|
bsw/jbe@1309
|
7858
|
bsw/jbe@1309
|
7859 // Finds parents of a node, returning the outermost node first in Array
|
bsw/jbe@1309
|
7860 // if contain node is given parents search is stopped at the container
|
bsw/jbe@1309
|
7861 function parents(node, container) {
|
bsw/jbe@1309
|
7862 var nodes = [node], n = node;
|
bsw/jbe@1309
|
7863
|
bsw/jbe@1309
|
7864 // iterate parents while parent exists and it is not container element
|
bsw/jbe@1309
|
7865 while((container && n && n !== container) || (!container && n)) {
|
bsw/jbe@1309
|
7866 nodes.unshift(n);
|
bsw/jbe@1309
|
7867 n = n.parentNode;
|
bsw/jbe@1309
|
7868 }
|
bsw/jbe@1309
|
7869 return nodes;
|
bsw/jbe@1309
|
7870 }
|
bsw/jbe@1309
|
7871
|
bsw/jbe@1309
|
7872 wysihtml.dom.domNode = function(node) {
|
bsw/jbe@1309
|
7873 var defaultNodeTypes = [wysihtml.ELEMENT_NODE, wysihtml.TEXT_NODE];
|
bsw/jbe@1309
|
7874
|
bsw/jbe@1309
|
7875 return {
|
bsw/jbe@1309
|
7876
|
bsw/jbe@1309
|
7877 is: {
|
bsw/jbe@1309
|
7878 emptyTextNode: function(ignoreWhitespace) {
|
bsw/jbe@1309
|
7879 var regx = ignoreWhitespace ? (/^\s*$/g) : (/^[\r\n]*$/g);
|
bsw/jbe@1309
|
7880 return node && node.nodeType === wysihtml.TEXT_NODE && (regx).test(node.data);
|
bsw/jbe@1309
|
7881 },
|
bsw/jbe@1309
|
7882
|
bsw/jbe@1309
|
7883 // Returns if node is the rangy selection bookmark element (that must not be taken into account in most situatons and is removed on selection restoring)
|
bsw/jbe@1309
|
7884 rangyBookmark: function() {
|
bsw/jbe@1309
|
7885 return node && node.nodeType === 1 && node.classList.contains('rangySelectionBoundary');
|
bsw/jbe@1309
|
7886 },
|
bsw/jbe@1309
|
7887
|
bsw/jbe@1309
|
7888 visible: function() {
|
bsw/jbe@1309
|
7889 var isVisible = !(/^\s*$/g).test(wysihtml.dom.getTextContent(node));
|
bsw/jbe@1309
|
7890
|
bsw/jbe@1309
|
7891 if (!isVisible) {
|
bsw/jbe@1309
|
7892 if (node.nodeType === 1 && node.querySelector('img, br, hr, object, embed, canvas, input, textarea')) {
|
bsw/jbe@1309
|
7893 isVisible = true;
|
bsw/jbe@1309
|
7894 }
|
bsw/jbe@1309
|
7895 }
|
bsw/jbe@1309
|
7896 return isVisible;
|
bsw/jbe@1309
|
7897 },
|
bsw/jbe@1309
|
7898 lineBreak: function() {
|
bsw/jbe@1309
|
7899 return node && node.nodeType === 1 && node.nodeName === "BR";
|
bsw/jbe@1309
|
7900 },
|
bsw/jbe@1309
|
7901 block: function() {
|
bsw/jbe@1309
|
7902 return node && node.nodeType === 1 && node.ownerDocument.defaultView.getComputedStyle(node).display === "block";
|
bsw/jbe@1309
|
7903 },
|
bsw/jbe@1309
|
7904 // Void elements are elemens that can not have content
|
bsw/jbe@1309
|
7905 // In most cases browsers should solve the cases for you when you try to insert content into those,
|
bsw/jbe@1309
|
7906 // but IE does not and it is not nice to do so anyway.
|
bsw/jbe@1309
|
7907 voidElement: function() {
|
bsw/jbe@1309
|
7908 return wysihtml.dom.domNode(node).test({
|
bsw/jbe@1309
|
7909 query: wysihtml.VOID_ELEMENTS
|
bsw/jbe@1309
|
7910 });
|
bsw/jbe@1309
|
7911 }
|
bsw/jbe@1309
|
7912 },
|
bsw/jbe@1309
|
7913
|
bsw/jbe@1309
|
7914 // var node = wysihtml.dom.domNode(element).prev({nodeTypes: [1,3], ignoreBlankTexts: true});
|
bsw/jbe@1309
|
7915 prev: function(options) {
|
bsw/jbe@1309
|
7916 var prevNode = node.previousSibling,
|
bsw/jbe@1309
|
7917 types = (options && options.nodeTypes) ? options.nodeTypes : defaultNodeTypes;
|
bsw/jbe@1309
|
7918
|
bsw/jbe@1309
|
7919 if (!prevNode) {
|
bsw/jbe@1309
|
7920 return null;
|
bsw/jbe@1309
|
7921 }
|
bsw/jbe@1309
|
7922
|
bsw/jbe@1309
|
7923 if (
|
bsw/jbe@1309
|
7924 wysihtml.dom.domNode(prevNode).is.rangyBookmark() || // is Rangy temporary boomark element (bypass)
|
bsw/jbe@1309
|
7925 (!wysihtml.lang.array(types).contains(prevNode.nodeType)) || // nodeTypes check.
|
bsw/jbe@1309
|
7926 (options && options.ignoreBlankTexts && wysihtml.dom.domNode(prevNode).is.emptyTextNode(true)) // Blank text nodes bypassed if set
|
bsw/jbe@1309
|
7927 ) {
|
bsw/jbe@1309
|
7928 return wysihtml.dom.domNode(prevNode).prev(options);
|
bsw/jbe@1309
|
7929 }
|
bsw/jbe@1309
|
7930
|
bsw/jbe@1309
|
7931 return prevNode;
|
bsw/jbe@1309
|
7932 },
|
bsw/jbe@1309
|
7933
|
bsw/jbe@1309
|
7934 // var node = wysihtml.dom.domNode(element).next({nodeTypes: [1,3], ignoreBlankTexts: true});
|
bsw/jbe@1309
|
7935 next: function(options) {
|
bsw/jbe@1309
|
7936 var nextNode = node.nextSibling,
|
bsw/jbe@1309
|
7937 types = (options && options.nodeTypes) ? options.nodeTypes : defaultNodeTypes;
|
bsw/jbe@1309
|
7938
|
bsw/jbe@1309
|
7939 if (!nextNode) {
|
bsw/jbe@1309
|
7940 return null;
|
bsw/jbe@1309
|
7941 }
|
bsw/jbe@1309
|
7942
|
bsw/jbe@1309
|
7943 if (
|
bsw/jbe@1309
|
7944 wysihtml.dom.domNode(nextNode).is.rangyBookmark() || // is Rangy temporary boomark element (bypass)
|
bsw/jbe@1309
|
7945 (!wysihtml.lang.array(types).contains(nextNode.nodeType)) || // nodeTypes check.
|
bsw/jbe@1309
|
7946 (options && options.ignoreBlankTexts && wysihtml.dom.domNode(nextNode).is.emptyTextNode(true)) // blank text nodes bypassed if set
|
bsw/jbe@1309
|
7947 ) {
|
bsw/jbe@1309
|
7948 return wysihtml.dom.domNode(nextNode).next(options);
|
bsw/jbe@1309
|
7949 }
|
bsw/jbe@1309
|
7950
|
bsw/jbe@1309
|
7951 return nextNode;
|
bsw/jbe@1309
|
7952 },
|
bsw/jbe@1309
|
7953
|
bsw/jbe@1309
|
7954 // Finds the common acnestor container of two nodes
|
bsw/jbe@1309
|
7955 // If container given stops search at the container
|
bsw/jbe@1309
|
7956 // If no common ancestor found returns null
|
bsw/jbe@1309
|
7957 // var node = wysihtml.dom.domNode(element).commonAncestor(node2, container);
|
bsw/jbe@1309
|
7958 commonAncestor: function(node2, container) {
|
bsw/jbe@1309
|
7959 var parents1 = parents(node, container),
|
bsw/jbe@1309
|
7960 parents2 = parents(node2, container);
|
bsw/jbe@1309
|
7961
|
bsw/jbe@1309
|
7962 // Ensure we have found a common ancestor, which will be the first one if anything
|
bsw/jbe@1309
|
7963 if (parents1[0] != parents2[0]) {
|
bsw/jbe@1309
|
7964 return null;
|
bsw/jbe@1309
|
7965 }
|
bsw/jbe@1309
|
7966
|
bsw/jbe@1309
|
7967 // Traverse up the hierarchy of parents until we reach where they're no longer
|
bsw/jbe@1309
|
7968 // the same. Then return previous which was the common ancestor.
|
bsw/jbe@1309
|
7969 for (var i = 0; i < parents1.length; i++) {
|
bsw/jbe@1309
|
7970 if (parents1[i] != parents2[i]) {
|
bsw/jbe@1309
|
7971 return parents1[i - 1];
|
bsw/jbe@1309
|
7972 }
|
bsw/jbe@1309
|
7973 }
|
bsw/jbe@1309
|
7974
|
bsw/jbe@1309
|
7975 return null;
|
bsw/jbe@1309
|
7976 },
|
bsw/jbe@1309
|
7977
|
bsw/jbe@1309
|
7978 // Traverses a node for last children and their chidren (including itself), and finds the last node that has no children.
|
bsw/jbe@1309
|
7979 // Array of classes for forced last-leaves (ex: uneditable-container) can be defined (options = {leafClasses: [...]})
|
bsw/jbe@1309
|
7980 // Useful for finding the actually visible element before cursor
|
bsw/jbe@1309
|
7981 lastLeafNode: function(options) {
|
bsw/jbe@1309
|
7982 var lastChild;
|
bsw/jbe@1309
|
7983
|
bsw/jbe@1309
|
7984 // Returns non-element nodes
|
bsw/jbe@1309
|
7985 if (node.nodeType !== 1) {
|
bsw/jbe@1309
|
7986 return node;
|
bsw/jbe@1309
|
7987 }
|
bsw/jbe@1309
|
7988
|
bsw/jbe@1309
|
7989 // Returns if element is leaf
|
bsw/jbe@1309
|
7990 lastChild = node.lastChild;
|
bsw/jbe@1309
|
7991 if (!lastChild) {
|
bsw/jbe@1309
|
7992 return node;
|
bsw/jbe@1309
|
7993 }
|
bsw/jbe@1309
|
7994
|
bsw/jbe@1309
|
7995 // Returns if element is of of options.leafClasses leaf
|
bsw/jbe@1309
|
7996 if (options && options.leafClasses) {
|
bsw/jbe@1309
|
7997 for (var i = options.leafClasses.length; i--;) {
|
bsw/jbe@1309
|
7998 if (wysihtml.dom.hasClass(node, options.leafClasses[i])) {
|
bsw/jbe@1309
|
7999 return node;
|
bsw/jbe@1309
|
8000 }
|
bsw/jbe@1309
|
8001 }
|
bsw/jbe@1309
|
8002 }
|
bsw/jbe@1309
|
8003
|
bsw/jbe@1309
|
8004 return wysihtml.dom.domNode(lastChild).lastLeafNode(options);
|
bsw/jbe@1309
|
8005 },
|
bsw/jbe@1309
|
8006
|
bsw/jbe@1309
|
8007 // Splits element at childnode and extracts the childNode out of the element context
|
bsw/jbe@1309
|
8008 // Example:
|
bsw/jbe@1309
|
8009 // var node = wysihtml.dom.domNode(node).escapeParent(parentNode);
|
bsw/jbe@1309
|
8010 escapeParent: function(element, newWrapper) {
|
bsw/jbe@1309
|
8011 var parent, split2, nodeWrap,
|
bsw/jbe@1309
|
8012 curNode = node;
|
bsw/jbe@1309
|
8013
|
bsw/jbe@1309
|
8014 // Stop if node is not a descendant of element
|
bsw/jbe@1309
|
8015 if (!wysihtml.dom.contains(element, node)) {
|
bsw/jbe@1309
|
8016 throw new Error("Child is not a descendant of node.");
|
bsw/jbe@1309
|
8017 }
|
bsw/jbe@1309
|
8018
|
bsw/jbe@1309
|
8019 // Climb up the node tree untill node is reached
|
bsw/jbe@1309
|
8020 do {
|
bsw/jbe@1309
|
8021 // Get current parent of node
|
bsw/jbe@1309
|
8022 parent = curNode.parentNode;
|
bsw/jbe@1309
|
8023
|
bsw/jbe@1309
|
8024 // Move after nodes to new clone wrapper
|
bsw/jbe@1309
|
8025 split2 = parent.cloneNode(false);
|
bsw/jbe@1309
|
8026 while (parent.lastChild && parent.lastChild !== curNode) {
|
bsw/jbe@1309
|
8027 split2.insertBefore(parent.lastChild, split2.firstChild);
|
bsw/jbe@1309
|
8028 }
|
bsw/jbe@1309
|
8029
|
bsw/jbe@1309
|
8030 // Move node up a level. If parent is not yet the container to escape, clone the parent around node, so inner nodes are escaped out too
|
bsw/jbe@1309
|
8031 if (parent !== element) {
|
bsw/jbe@1309
|
8032 nodeWrap = parent.cloneNode(false);
|
bsw/jbe@1309
|
8033 nodeWrap.appendChild(curNode);
|
bsw/jbe@1309
|
8034 curNode = nodeWrap;
|
bsw/jbe@1309
|
8035 }
|
bsw/jbe@1309
|
8036 parent.parentNode.insertBefore(curNode, parent.nextSibling);
|
bsw/jbe@1309
|
8037
|
bsw/jbe@1309
|
8038 // Add after nodes (unless empty)
|
bsw/jbe@1309
|
8039 if (split2.innerHTML !== '') {
|
bsw/jbe@1309
|
8040 // if contents are empty insert without wrap
|
bsw/jbe@1309
|
8041 if ((/^\s+$/).test(split2.innerHTML)) {
|
bsw/jbe@1309
|
8042 while (split2.lastChild) {
|
bsw/jbe@1309
|
8043 parent.parentNode.insertBefore(split2.lastChild, curNode.nextSibling);
|
bsw/jbe@1309
|
8044 }
|
bsw/jbe@1309
|
8045 } else {
|
bsw/jbe@1309
|
8046 parent.parentNode.insertBefore(split2, curNode.nextSibling);
|
bsw/jbe@1309
|
8047 }
|
bsw/jbe@1309
|
8048 }
|
bsw/jbe@1309
|
8049
|
bsw/jbe@1309
|
8050 // If the node left behind before the split (parent) is now empty then remove
|
bsw/jbe@1309
|
8051 if (parent.innerHTML === '') {
|
bsw/jbe@1309
|
8052 parent.parentNode.removeChild(parent);
|
bsw/jbe@1309
|
8053 } else if ((/^\s+$/).test(parent.innerHTML)) {
|
bsw/jbe@1309
|
8054 while (parent.firstChild) {
|
bsw/jbe@1309
|
8055 parent.parentNode.insertBefore(parent.firstChild, parent);
|
bsw/jbe@1309
|
8056 }
|
bsw/jbe@1309
|
8057 parent.parentNode.removeChild(parent);
|
bsw/jbe@1309
|
8058 }
|
bsw/jbe@1309
|
8059
|
bsw/jbe@1309
|
8060 } while (parent && parent !== element);
|
bsw/jbe@1309
|
8061
|
bsw/jbe@1309
|
8062 if (newWrapper && curNode) {
|
bsw/jbe@1309
|
8063 curNode.parentNode.insertBefore(newWrapper, curNode);
|
bsw/jbe@1309
|
8064 newWrapper.appendChild(curNode);
|
bsw/jbe@1309
|
8065 }
|
bsw/jbe@1309
|
8066 },
|
bsw/jbe@1309
|
8067
|
bsw/jbe@1309
|
8068 transferContentTo: function(targetNode, removeOldWrapper) {
|
bsw/jbe@1309
|
8069 if (node.nodeType === 1) {
|
bsw/jbe@1309
|
8070 if (wysihtml.dom.domNode(targetNode).is.voidElement() || targetNode.nodeType === 3) {
|
bsw/jbe@1309
|
8071 while (node.lastChild) {
|
bsw/jbe@1309
|
8072 targetNode.parentNode.insertBefore(node.lastChild, targetNode.nextSibling);
|
bsw/jbe@1309
|
8073 }
|
bsw/jbe@1309
|
8074 } else {
|
bsw/jbe@1309
|
8075 while (node.firstChild) {
|
bsw/jbe@1309
|
8076 targetNode.appendChild(node.firstChild);
|
bsw/jbe@1309
|
8077 }
|
bsw/jbe@1309
|
8078 }
|
bsw/jbe@1309
|
8079 if (removeOldWrapper) {
|
bsw/jbe@1309
|
8080 node.parentNode.removeChild(node);
|
bsw/jbe@1309
|
8081 }
|
bsw/jbe@1309
|
8082 } else if (node.nodeType === 3 || node.nodeType === 8){
|
bsw/jbe@1309
|
8083 if (wysihtml.dom.domNode(targetNode).is.voidElement()) {
|
bsw/jbe@1309
|
8084 targetNode.parentNode.insertBefore(node, targetNode.nextSibling);
|
bsw/jbe@1309
|
8085 } else {
|
bsw/jbe@1309
|
8086 targetNode.appendChild(node);
|
bsw/jbe@1309
|
8087 }
|
bsw/jbe@1309
|
8088 }
|
bsw/jbe@1309
|
8089 },
|
bsw/jbe@1309
|
8090
|
bsw/jbe@1309
|
8091 /*
|
bsw/jbe@1309
|
8092 Tests a node against properties, and returns true if matches.
|
bsw/jbe@1309
|
8093 Tests on principle that all properties defined must have at least one match.
|
bsw/jbe@1309
|
8094 styleValue parameter works in context of styleProperty and has no effect otherwise.
|
bsw/jbe@1309
|
8095 Returns true if element matches and false if it does not.
|
bsw/jbe@1309
|
8096
|
bsw/jbe@1309
|
8097 Properties for filtering element:
|
bsw/jbe@1309
|
8098 {
|
bsw/jbe@1309
|
8099 query: selector string,
|
bsw/jbe@1309
|
8100 nodeName: string (uppercase),
|
bsw/jbe@1309
|
8101 className: string,
|
bsw/jbe@1309
|
8102 classRegExp: regex,
|
bsw/jbe@1309
|
8103 styleProperty: string or [],
|
bsw/jbe@1309
|
8104 styleValue: string, [] or regex
|
bsw/jbe@1309
|
8105 }
|
bsw/jbe@1309
|
8106
|
bsw/jbe@1309
|
8107 Example:
|
bsw/jbe@1309
|
8108 var node = wysihtml.dom.domNode(element).test({})
|
bsw/jbe@1309
|
8109 */
|
bsw/jbe@1309
|
8110 test: function(properties) {
|
bsw/jbe@1309
|
8111 var prop;
|
bsw/jbe@1309
|
8112
|
bsw/jbe@1309
|
8113 // return false if properties object is not defined
|
bsw/jbe@1309
|
8114 if (!properties) {
|
bsw/jbe@1309
|
8115 return false;
|
bsw/jbe@1309
|
8116 }
|
bsw/jbe@1309
|
8117
|
bsw/jbe@1309
|
8118 // Only element nodes can be tested for these properties
|
bsw/jbe@1309
|
8119 if (node.nodeType !== 1) {
|
bsw/jbe@1309
|
8120 return false;
|
bsw/jbe@1309
|
8121 }
|
bsw/jbe@1309
|
8122
|
bsw/jbe@1309
|
8123 if (properties.query) {
|
bsw/jbe@1309
|
8124 if (!node.matches(properties.query)) {
|
bsw/jbe@1309
|
8125 return false;
|
bsw/jbe@1309
|
8126 }
|
bsw/jbe@1309
|
8127 }
|
bsw/jbe@1309
|
8128
|
bsw/jbe@1309
|
8129 if (properties.nodeName && node.nodeName.toLowerCase() !== properties.nodeName.toLowerCase()) {
|
bsw/jbe@1309
|
8130 return false;
|
bsw/jbe@1309
|
8131 }
|
bsw/jbe@1309
|
8132
|
bsw/jbe@1309
|
8133 if (properties.className && !node.classList.contains(properties.className)) {
|
bsw/jbe@1309
|
8134 return false;
|
bsw/jbe@1309
|
8135 }
|
bsw/jbe@1309
|
8136
|
bsw/jbe@1309
|
8137 // classRegExp check (useful for classname begins with logic)
|
bsw/jbe@1309
|
8138 if (properties.classRegExp) {
|
bsw/jbe@1309
|
8139 var matches = (node.className || "").match(properties.classRegExp) || [];
|
bsw/jbe@1309
|
8140 if (matches.length === 0) {
|
bsw/jbe@1309
|
8141 return false;
|
bsw/jbe@1309
|
8142 }
|
bsw/jbe@1309
|
8143 }
|
bsw/jbe@1309
|
8144
|
bsw/jbe@1309
|
8145 // styleProperty check
|
bsw/jbe@1309
|
8146 if (properties.styleProperty && properties.styleProperty.length > 0) {
|
bsw/jbe@1309
|
8147 var hasOneStyle = false,
|
bsw/jbe@1309
|
8148 styles = (Array.isArray(properties.styleProperty)) ? properties.styleProperty : [properties.styleProperty];
|
bsw/jbe@1309
|
8149 for (var j = 0, maxStyleP = styles.length; j < maxStyleP; j++) {
|
bsw/jbe@1309
|
8150 // Some old IE-s have different property name for cssFloat
|
bsw/jbe@1309
|
8151 prop = wysihtml.browser.fixStyleKey(styles[j]);
|
bsw/jbe@1309
|
8152 if (node.style[prop]) {
|
bsw/jbe@1309
|
8153 if (properties.styleValue) {
|
bsw/jbe@1309
|
8154 // Style value as additional parameter
|
bsw/jbe@1309
|
8155 if (properties.styleValue instanceof RegExp) {
|
bsw/jbe@1309
|
8156 // style value as Regexp
|
bsw/jbe@1309
|
8157 if (node.style[prop].trim().match(properties.styleValue).length > 0) {
|
bsw/jbe@1309
|
8158 hasOneStyle = true;
|
bsw/jbe@1309
|
8159 break;
|
bsw/jbe@1309
|
8160 }
|
bsw/jbe@1309
|
8161 } else if (Array.isArray(properties.styleValue)) {
|
bsw/jbe@1309
|
8162 // style value as array
|
bsw/jbe@1309
|
8163 if (properties.styleValue.indexOf(node.style[prop].trim())) {
|
bsw/jbe@1309
|
8164 hasOneStyle = true;
|
bsw/jbe@1309
|
8165 break;
|
bsw/jbe@1309
|
8166 }
|
bsw/jbe@1309
|
8167 } else {
|
bsw/jbe@1309
|
8168 // style value as string
|
bsw/jbe@1309
|
8169 if (properties.styleValue === node.style[prop].trim().replace(/, /g, ",")) {
|
bsw/jbe@1309
|
8170 hasOneStyle = true;
|
bsw/jbe@1309
|
8171 break;
|
bsw/jbe@1309
|
8172 }
|
bsw/jbe@1309
|
8173 }
|
bsw/jbe@1309
|
8174 } else {
|
bsw/jbe@1309
|
8175 hasOneStyle = true;
|
bsw/jbe@1309
|
8176 break;
|
bsw/jbe@1309
|
8177 }
|
bsw/jbe@1309
|
8178 }
|
bsw/jbe@1309
|
8179 if (!hasOneStyle) {
|
bsw/jbe@1309
|
8180 return false;
|
bsw/jbe@1309
|
8181 }
|
bsw/jbe@1309
|
8182 }
|
bsw/jbe@1309
|
8183 }
|
bsw/jbe@1309
|
8184
|
bsw/jbe@1309
|
8185 if (properties.attribute) {
|
bsw/jbe@1309
|
8186 var attr = wysihtml.dom.getAttributes(node),
|
bsw/jbe@1309
|
8187 attrList = [],
|
bsw/jbe@1309
|
8188 hasOneAttribute = false;
|
bsw/jbe@1309
|
8189
|
bsw/jbe@1309
|
8190 if (Array.isArray(properties.attribute)) {
|
bsw/jbe@1309
|
8191 attrList = properties.attribute;
|
bsw/jbe@1309
|
8192 } else {
|
bsw/jbe@1309
|
8193 attrList[properties.attribute] = properties.attributeValue;
|
bsw/jbe@1309
|
8194 }
|
bsw/jbe@1309
|
8195
|
bsw/jbe@1309
|
8196 for (var a in attrList) {
|
bsw/jbe@1309
|
8197 if (attrList.hasOwnProperty(a)) {
|
bsw/jbe@1309
|
8198 if (typeof attrList[a] === "undefined") {
|
bsw/jbe@1309
|
8199 if (typeof attr[a] !== "undefined") {
|
bsw/jbe@1309
|
8200 hasOneAttribute = true;
|
bsw/jbe@1309
|
8201 break;
|
bsw/jbe@1309
|
8202 }
|
bsw/jbe@1309
|
8203 } else if (attr[a] === attrList[a]) {
|
bsw/jbe@1309
|
8204 hasOneAttribute = true;
|
bsw/jbe@1309
|
8205 break;
|
bsw/jbe@1309
|
8206 }
|
bsw/jbe@1309
|
8207 }
|
bsw/jbe@1309
|
8208 }
|
bsw/jbe@1309
|
8209
|
bsw/jbe@1309
|
8210 if (!hasOneAttribute) {
|
bsw/jbe@1309
|
8211 return false;
|
bsw/jbe@1309
|
8212 }
|
bsw/jbe@1309
|
8213
|
bsw/jbe@1309
|
8214 }
|
bsw/jbe@1309
|
8215
|
bsw/jbe@1309
|
8216 return true;
|
bsw/jbe@1309
|
8217 }
|
bsw/jbe@1309
|
8218
|
bsw/jbe@1309
|
8219 };
|
bsw/jbe@1309
|
8220 };
|
bsw/jbe@1309
|
8221 })(wysihtml);
|
bsw/jbe@1309
|
8222
|
bsw/jbe@1309
|
8223 /**
|
bsw/jbe@1309
|
8224 * Returns the given html wrapped in a div element
|
bsw/jbe@1309
|
8225 *
|
bsw/jbe@1309
|
8226 * Fixing IE's inability to treat unknown elements (HTML5 section, article, ...) correctly
|
bsw/jbe@1309
|
8227 * when inserted via innerHTML
|
bsw/jbe@1309
|
8228 *
|
bsw/jbe@1309
|
8229 * @param {String} html The html which should be wrapped in a dom element
|
bsw/jbe@1309
|
8230 * @param {Obejct} [context] Document object of the context the html belongs to
|
bsw/jbe@1309
|
8231 *
|
bsw/jbe@1309
|
8232 * @example
|
bsw/jbe@1309
|
8233 * wysihtml.dom.getAsDom("<article>foo</article>");
|
bsw/jbe@1309
|
8234 */
|
bsw/jbe@1309
|
8235 wysihtml.dom.getAsDom = (function() {
|
bsw/jbe@1309
|
8236
|
bsw/jbe@1309
|
8237 var _innerHTMLShiv = function(html, context) {
|
bsw/jbe@1309
|
8238 var tempElement = context.createElement("div");
|
bsw/jbe@1309
|
8239 tempElement.style.display = "none";
|
bsw/jbe@1309
|
8240 context.body.appendChild(tempElement);
|
bsw/jbe@1309
|
8241 // IE throws an exception when trying to insert <frameset></frameset> via innerHTML
|
bsw/jbe@1309
|
8242 try { tempElement.innerHTML = html; } catch(e) {}
|
bsw/jbe@1309
|
8243 context.body.removeChild(tempElement);
|
bsw/jbe@1309
|
8244 return tempElement;
|
bsw/jbe@1309
|
8245 };
|
bsw/jbe@1309
|
8246
|
bsw/jbe@1309
|
8247 /**
|
bsw/jbe@1309
|
8248 * Make sure IE supports HTML5 tags, which is accomplished by simply creating one instance of each element
|
bsw/jbe@1309
|
8249 */
|
bsw/jbe@1309
|
8250 var _ensureHTML5Compatibility = function(context) {
|
bsw/jbe@1309
|
8251 if (context._wysihtml_supportsHTML5Tags) {
|
bsw/jbe@1309
|
8252 return;
|
bsw/jbe@1309
|
8253 }
|
bsw/jbe@1309
|
8254 for (var i=0, length=HTML5_ELEMENTS.length; i<length; i++) {
|
bsw/jbe@1309
|
8255 context.createElement(HTML5_ELEMENTS[i]);
|
bsw/jbe@1309
|
8256 }
|
bsw/jbe@1309
|
8257 context._wysihtml_supportsHTML5Tags = true;
|
bsw/jbe@1309
|
8258 };
|
bsw/jbe@1309
|
8259
|
bsw/jbe@1309
|
8260
|
bsw/jbe@1309
|
8261 /**
|
bsw/jbe@1309
|
8262 * List of html5 tags
|
bsw/jbe@1309
|
8263 * taken from http://simon.html5.org/html5-elements
|
bsw/jbe@1309
|
8264 */
|
bsw/jbe@1309
|
8265 var HTML5_ELEMENTS = [
|
bsw/jbe@1309
|
8266 "abbr", "article", "aside", "audio", "bdi", "canvas", "command", "datalist", "details", "figcaption",
|
bsw/jbe@1309
|
8267 "figure", "footer", "header", "hgroup", "keygen", "mark", "meter", "nav", "output", "progress",
|
bsw/jbe@1309
|
8268 "rp", "rt", "ruby", "svg", "section", "source", "summary", "time", "track", "video", "wbr"
|
bsw/jbe@1309
|
8269 ];
|
bsw/jbe@1309
|
8270
|
bsw/jbe@1309
|
8271 return function(html, context) {
|
bsw/jbe@1309
|
8272 context = context || document;
|
bsw/jbe@1309
|
8273 var tempElement;
|
bsw/jbe@1309
|
8274 if (typeof(html) === "object" && html.nodeType) {
|
bsw/jbe@1309
|
8275 tempElement = context.createElement("div");
|
bsw/jbe@1309
|
8276 tempElement.appendChild(html);
|
bsw/jbe@1309
|
8277 } else if (wysihtml.browser.supportsHTML5Tags(context)) {
|
bsw/jbe@1309
|
8278 tempElement = context.createElement("div");
|
bsw/jbe@1309
|
8279 tempElement.innerHTML = html;
|
bsw/jbe@1309
|
8280 } else {
|
bsw/jbe@1309
|
8281 _ensureHTML5Compatibility(context);
|
bsw/jbe@1309
|
8282 tempElement = _innerHTMLShiv(html, context);
|
bsw/jbe@1309
|
8283 }
|
bsw/jbe@1309
|
8284 return tempElement;
|
bsw/jbe@1309
|
8285 };
|
bsw/jbe@1309
|
8286 })();
|
bsw/jbe@1309
|
8287
|
bsw/jbe@1309
|
8288 /**
|
bsw/jbe@1309
|
8289 * Get a set of attribute from one element
|
bsw/jbe@1309
|
8290 *
|
bsw/jbe@1309
|
8291 * IE gives wrong results for hasAttribute/getAttribute, for example:
|
bsw/jbe@1309
|
8292 * var td = document.createElement("td");
|
bsw/jbe@1309
|
8293 * td.getAttribute("rowspan"); // => "1" in IE
|
bsw/jbe@1309
|
8294 *
|
bsw/jbe@1309
|
8295 * Therefore we have to check the element's outerHTML for the attribute
|
bsw/jbe@1309
|
8296 */
|
bsw/jbe@1309
|
8297
|
bsw/jbe@1309
|
8298 wysihtml.dom.getAttribute = function(node, attributeName) {
|
bsw/jbe@1309
|
8299 var HAS_GET_ATTRIBUTE_BUG = !wysihtml.browser.supportsGetAttributeCorrectly();
|
bsw/jbe@1309
|
8300 attributeName = attributeName.toLowerCase();
|
bsw/jbe@1309
|
8301 var nodeName = node.nodeName;
|
bsw/jbe@1309
|
8302 if (nodeName == "IMG" && attributeName == "src" && wysihtml.dom.isLoadedImage(node) === true) {
|
bsw/jbe@1309
|
8303 // Get 'src' attribute value via object property since this will always contain the
|
bsw/jbe@1309
|
8304 // full absolute url (http://...)
|
bsw/jbe@1309
|
8305 // this fixes a very annoying bug in firefox (ver 3.6 & 4) and IE 8 where images copied from the same host
|
bsw/jbe@1309
|
8306 // will have relative paths, which the sanitizer strips out (see attributeCheckMethods.url)
|
bsw/jbe@1309
|
8307 return node.src;
|
bsw/jbe@1309
|
8308 } else if (HAS_GET_ATTRIBUTE_BUG && "outerHTML" in node) {
|
bsw/jbe@1309
|
8309 // Don't trust getAttribute/hasAttribute in IE 6-8, instead check the element's outerHTML
|
bsw/jbe@1309
|
8310 var outerHTML = node.outerHTML.toLowerCase(),
|
bsw/jbe@1309
|
8311 // TODO: This might not work for attributes without value: <input disabled>
|
bsw/jbe@1309
|
8312 hasAttribute = outerHTML.indexOf(" " + attributeName + "=") != -1;
|
bsw/jbe@1309
|
8313
|
bsw/jbe@1309
|
8314 return hasAttribute ? node.getAttribute(attributeName) : null;
|
bsw/jbe@1309
|
8315 } else{
|
bsw/jbe@1309
|
8316 return node.getAttribute(attributeName);
|
bsw/jbe@1309
|
8317 }
|
bsw/jbe@1309
|
8318 };
|
bsw/jbe@1309
|
8319
|
bsw/jbe@1309
|
8320 /**
|
bsw/jbe@1309
|
8321 * Get all attributes of an element
|
bsw/jbe@1309
|
8322 *
|
bsw/jbe@1309
|
8323 * IE gives wrong results for hasAttribute/getAttribute, for example:
|
bsw/jbe@1309
|
8324 * var td = document.createElement("td");
|
bsw/jbe@1309
|
8325 * td.getAttribute("rowspan"); // => "1" in IE
|
bsw/jbe@1309
|
8326 *
|
bsw/jbe@1309
|
8327 * Therefore we have to check the element's outerHTML for the attribute
|
bsw/jbe@1309
|
8328 */
|
bsw/jbe@1309
|
8329
|
bsw/jbe@1309
|
8330 wysihtml.dom.getAttributes = function(node) {
|
bsw/jbe@1309
|
8331 var HAS_GET_ATTRIBUTE_BUG = !wysihtml.browser.supportsGetAttributeCorrectly(),
|
bsw/jbe@1309
|
8332 nodeName = node.nodeName,
|
bsw/jbe@1309
|
8333 attributes = [],
|
bsw/jbe@1309
|
8334 attr;
|
bsw/jbe@1309
|
8335
|
bsw/jbe@1309
|
8336 for (attr in node.attributes) {
|
bsw/jbe@1309
|
8337 if ((node.attributes.hasOwnProperty && node.attributes.hasOwnProperty(attr)) || (!node.attributes.hasOwnProperty && Object.prototype.hasOwnProperty.call(node.attributes, attr))) {
|
bsw/jbe@1309
|
8338 if (node.attributes[attr].specified) {
|
bsw/jbe@1309
|
8339 if (nodeName == "IMG" && node.attributes[attr].name.toLowerCase() == "src" && wysihtml.dom.isLoadedImage(node) === true) {
|
bsw/jbe@1309
|
8340 attributes['src'] = node.src;
|
bsw/jbe@1309
|
8341 } else if (wysihtml.lang.array(['rowspan', 'colspan']).contains(node.attributes[attr].name.toLowerCase()) && HAS_GET_ATTRIBUTE_BUG) {
|
bsw/jbe@1309
|
8342 if (node.attributes[attr].value !== 1) {
|
bsw/jbe@1309
|
8343 attributes[node.attributes[attr].name] = node.attributes[attr].value;
|
bsw/jbe@1309
|
8344 }
|
bsw/jbe@1309
|
8345 } else {
|
bsw/jbe@1309
|
8346 attributes[node.attributes[attr].name] = node.attributes[attr].value;
|
bsw/jbe@1309
|
8347 }
|
bsw/jbe@1309
|
8348 }
|
bsw/jbe@1309
|
8349 }
|
bsw/jbe@1309
|
8350 }
|
bsw/jbe@1309
|
8351 return attributes;
|
bsw/jbe@1309
|
8352 };
|
bsw/jbe@1309
|
8353
|
bsw/jbe@1309
|
8354 /**
|
bsw/jbe@1309
|
8355 * Walks the dom tree from the given node up until it finds a match
|
bsw/jbe@1309
|
8356 *
|
bsw/jbe@1309
|
8357 * @param {Element} node The from which to check the parent nodes
|
bsw/jbe@1309
|
8358 * @param {Object} matchingSet Object to match against, Properties for filtering element:
|
bsw/jbe@1309
|
8359 * {
|
bsw/jbe@1309
|
8360 * query: selector string,
|
bsw/jbe@1309
|
8361 * classRegExp: regex,
|
bsw/jbe@1309
|
8362 * styleProperty: string or [],
|
bsw/jbe@1309
|
8363 * styleValue: string, [] or regex
|
bsw/jbe@1309
|
8364 * }
|
bsw/jbe@1309
|
8365 * @param {Number} [levels] How many parents should the function check up from the current node (defaults to 50)
|
bsw/jbe@1309
|
8366 * @param {Element} Optional, defines the container that limits the search
|
bsw/jbe@1309
|
8367 *
|
bsw/jbe@1309
|
8368 * @return {null|Element} Returns the first element that matched the desiredNodeName(s)
|
bsw/jbe@1309
|
8369 */
|
bsw/jbe@1309
|
8370
|
bsw/jbe@1309
|
8371 wysihtml.dom.getParentElement = (function() {
|
bsw/jbe@1309
|
8372
|
bsw/jbe@1309
|
8373 return function(node, properties, levels, container) {
|
bsw/jbe@1309
|
8374 levels = levels || 50;
|
bsw/jbe@1309
|
8375 while (levels-- && node && node.nodeName !== "BODY" && (!container || node !== container)) {
|
bsw/jbe@1309
|
8376 if (wysihtml.dom.domNode(node).test(properties)) {
|
bsw/jbe@1309
|
8377 return node;
|
bsw/jbe@1309
|
8378 }
|
bsw/jbe@1309
|
8379 node = node.parentNode;
|
bsw/jbe@1309
|
8380 }
|
bsw/jbe@1309
|
8381 return null;
|
bsw/jbe@1309
|
8382 };
|
bsw/jbe@1309
|
8383
|
bsw/jbe@1309
|
8384 })();
|
bsw/jbe@1309
|
8385
|
bsw/jbe@1309
|
8386 /*
|
bsw/jbe@1309
|
8387 * Methods for fetching pasted html before it gets inserted into content
|
bsw/jbe@1309
|
8388 **/
|
bsw/jbe@1309
|
8389
|
bsw/jbe@1309
|
8390 /* Modern event.clipboardData driven approach.
|
bsw/jbe@1309
|
8391 * Advantage is that it does not have to loose selection or modify dom to catch the data.
|
bsw/jbe@1309
|
8392 * IE does not support though.
|
bsw/jbe@1309
|
8393 **/
|
bsw/jbe@1309
|
8394 wysihtml.dom.getPastedHtml = function(event) {
|
bsw/jbe@1309
|
8395 var html;
|
bsw/jbe@1309
|
8396 if (wysihtml.browser.supportsModernPaste() && event.clipboardData) {
|
bsw/jbe@1309
|
8397 if (wysihtml.lang.array(event.clipboardData.types).contains('text/html')) {
|
bsw/jbe@1309
|
8398 html = event.clipboardData.getData('text/html');
|
bsw/jbe@1309
|
8399 } else if (wysihtml.lang.array(event.clipboardData.types).contains('text/plain')) {
|
bsw/jbe@1309
|
8400 html = wysihtml.lang.string(event.clipboardData.getData('text/plain')).escapeHTML(true, true);
|
bsw/jbe@1309
|
8401 }
|
bsw/jbe@1309
|
8402 }
|
bsw/jbe@1309
|
8403 return html;
|
bsw/jbe@1309
|
8404 };
|
bsw/jbe@1309
|
8405
|
bsw/jbe@1309
|
8406 /* Older temprorary contenteditable as paste source catcher method for fallbacks */
|
bsw/jbe@1309
|
8407 wysihtml.dom.getPastedHtmlWithDiv = function (composer, f) {
|
bsw/jbe@1309
|
8408 var selBookmark = composer.selection.getBookmark(),
|
bsw/jbe@1309
|
8409 doc = composer.element.ownerDocument,
|
bsw/jbe@1309
|
8410 cleanerDiv = doc.createElement('DIV'),
|
bsw/jbe@1309
|
8411 scrollPos = composer.getScrollPos();
|
bsw/jbe@1309
|
8412
|
bsw/jbe@1309
|
8413 doc.body.appendChild(cleanerDiv);
|
bsw/jbe@1309
|
8414
|
bsw/jbe@1309
|
8415 cleanerDiv.style.width = "1px";
|
bsw/jbe@1309
|
8416 cleanerDiv.style.height = "1px";
|
bsw/jbe@1309
|
8417 cleanerDiv.style.overflow = "hidden";
|
bsw/jbe@1309
|
8418 cleanerDiv.style.position = "absolute";
|
bsw/jbe@1309
|
8419 cleanerDiv.style.top = scrollPos.y + "px";
|
bsw/jbe@1309
|
8420 cleanerDiv.style.left = scrollPos.x + "px";
|
bsw/jbe@1309
|
8421
|
bsw/jbe@1309
|
8422 cleanerDiv.setAttribute('contenteditable', 'true');
|
bsw/jbe@1309
|
8423 cleanerDiv.focus();
|
bsw/jbe@1309
|
8424
|
bsw/jbe@1309
|
8425 setTimeout(function () {
|
bsw/jbe@1309
|
8426 var html;
|
bsw/jbe@1309
|
8427
|
bsw/jbe@1309
|
8428 composer.selection.setBookmark(selBookmark);
|
bsw/jbe@1309
|
8429 html = cleanerDiv.innerHTML;
|
bsw/jbe@1309
|
8430 if (html && (/^<br\/?>$/i).test(html.trim())) {
|
bsw/jbe@1309
|
8431 html = false;
|
bsw/jbe@1309
|
8432 }
|
bsw/jbe@1309
|
8433 f(html);
|
bsw/jbe@1309
|
8434 cleanerDiv.parentNode.removeChild(cleanerDiv);
|
bsw/jbe@1309
|
8435 }, 0);
|
bsw/jbe@1309
|
8436 };
|
bsw/jbe@1309
|
8437
|
bsw/jbe@1309
|
8438 /**
|
bsw/jbe@1309
|
8439 * Get element's style for a specific css property
|
bsw/jbe@1309
|
8440 *
|
bsw/jbe@1309
|
8441 * @param {Element} element The element on which to retrieve the style
|
bsw/jbe@1309
|
8442 * @param {String} property The CSS property to retrieve ("float", "display", "text-align", ...)
|
bsw/jbe@1309
|
8443 *
|
bsw/jbe@1309
|
8444 * @example
|
bsw/jbe@1309
|
8445 * wysihtml.dom.getStyle("display").from(document.body);
|
bsw/jbe@1309
|
8446 * // => "block"
|
bsw/jbe@1309
|
8447 */
|
bsw/jbe@1309
|
8448 wysihtml.dom.getStyle = (function() {
|
bsw/jbe@1309
|
8449 var stylePropertyMapping = {
|
bsw/jbe@1309
|
8450 "float": ("styleFloat" in document.createElement("div").style) ? "styleFloat" : "cssFloat"
|
bsw/jbe@1309
|
8451 },
|
bsw/jbe@1309
|
8452 REG_EXP_CAMELIZE = /\-[a-z]/g;
|
bsw/jbe@1309
|
8453
|
bsw/jbe@1309
|
8454 function camelize(str) {
|
bsw/jbe@1309
|
8455 return str.replace(REG_EXP_CAMELIZE, function(match) {
|
bsw/jbe@1309
|
8456 return match.charAt(1).toUpperCase();
|
bsw/jbe@1309
|
8457 });
|
bsw/jbe@1309
|
8458 }
|
bsw/jbe@1309
|
8459
|
bsw/jbe@1309
|
8460 return function(property) {
|
bsw/jbe@1309
|
8461 return {
|
bsw/jbe@1309
|
8462 from: function(element) {
|
bsw/jbe@1309
|
8463 if (element.nodeType !== wysihtml.ELEMENT_NODE) {
|
bsw/jbe@1309
|
8464 return;
|
bsw/jbe@1309
|
8465 }
|
bsw/jbe@1309
|
8466
|
bsw/jbe@1309
|
8467 var doc = element.ownerDocument,
|
bsw/jbe@1309
|
8468 camelizedProperty = stylePropertyMapping[property] || camelize(property),
|
bsw/jbe@1309
|
8469 style = element.style,
|
bsw/jbe@1309
|
8470 currentStyle = element.currentStyle,
|
bsw/jbe@1309
|
8471 styleValue = style[camelizedProperty];
|
bsw/jbe@1309
|
8472 if (styleValue) {
|
bsw/jbe@1309
|
8473 return styleValue;
|
bsw/jbe@1309
|
8474 }
|
bsw/jbe@1309
|
8475
|
bsw/jbe@1309
|
8476 // currentStyle is no standard and only supported by Opera and IE but it has one important advantage over the standard-compliant
|
bsw/jbe@1309
|
8477 // window.getComputedStyle, since it returns css property values in their original unit:
|
bsw/jbe@1309
|
8478 // If you set an elements width to "50%", window.getComputedStyle will give you it's current width in px while currentStyle
|
bsw/jbe@1309
|
8479 // gives you the original "50%".
|
bsw/jbe@1309
|
8480 // Opera supports both, currentStyle and window.getComputedStyle, that's why checking for currentStyle should have higher prio
|
bsw/jbe@1309
|
8481 if (currentStyle) {
|
bsw/jbe@1309
|
8482 try {
|
bsw/jbe@1309
|
8483 return currentStyle[camelizedProperty];
|
bsw/jbe@1309
|
8484 } catch(e) {
|
bsw/jbe@1309
|
8485 //ie will occasionally fail for unknown reasons. swallowing exception
|
bsw/jbe@1309
|
8486 }
|
bsw/jbe@1309
|
8487 }
|
bsw/jbe@1309
|
8488
|
bsw/jbe@1309
|
8489 var win = doc.defaultView || doc.parentWindow,
|
bsw/jbe@1309
|
8490 needsOverflowReset = (property === "height" || property === "width") && element.nodeName === "TEXTAREA",
|
bsw/jbe@1309
|
8491 originalOverflow,
|
bsw/jbe@1309
|
8492 returnValue;
|
bsw/jbe@1309
|
8493
|
bsw/jbe@1309
|
8494 if (win.getComputedStyle) {
|
bsw/jbe@1309
|
8495 // Chrome and Safari both calculate a wrong width and height for textareas when they have scroll bars
|
bsw/jbe@1309
|
8496 // therfore we remove and restore the scrollbar and calculate the value in between
|
bsw/jbe@1309
|
8497 if (needsOverflowReset) {
|
bsw/jbe@1309
|
8498 originalOverflow = style.overflow;
|
bsw/jbe@1309
|
8499 style.overflow = "hidden";
|
bsw/jbe@1309
|
8500 }
|
bsw/jbe@1309
|
8501 returnValue = win.getComputedStyle(element, null).getPropertyValue(property);
|
bsw/jbe@1309
|
8502 if (needsOverflowReset) {
|
bsw/jbe@1309
|
8503 style.overflow = originalOverflow || "";
|
bsw/jbe@1309
|
8504 }
|
bsw/jbe@1309
|
8505 return returnValue;
|
bsw/jbe@1309
|
8506 }
|
bsw/jbe@1309
|
8507 }
|
bsw/jbe@1309
|
8508 };
|
bsw/jbe@1309
|
8509 };
|
bsw/jbe@1309
|
8510 })();
|
bsw/jbe@1309
|
8511
|
bsw/jbe@1309
|
8512 wysihtml.dom.getTextNodes = function(node, ingoreEmpty){
|
bsw/jbe@1309
|
8513 var all = [];
|
bsw/jbe@1309
|
8514 for (node=node.firstChild;node;node=node.nextSibling){
|
bsw/jbe@1309
|
8515 if (node.nodeType == 3) {
|
bsw/jbe@1309
|
8516 if (!ingoreEmpty || !(/^\s*$/).test(node.innerText || node.textContent)) {
|
bsw/jbe@1309
|
8517 all.push(node);
|
bsw/jbe@1309
|
8518 }
|
bsw/jbe@1309
|
8519 } else {
|
bsw/jbe@1309
|
8520 all = all.concat(wysihtml.dom.getTextNodes(node, ingoreEmpty));
|
bsw/jbe@1309
|
8521 }
|
bsw/jbe@1309
|
8522 }
|
bsw/jbe@1309
|
8523 return all;
|
bsw/jbe@1309
|
8524 };
|
bsw/jbe@1309
|
8525
|
bsw/jbe@1309
|
8526 /**
|
bsw/jbe@1309
|
8527 * High performant way to check whether an element with a specific class name is in the given document
|
bsw/jbe@1309
|
8528 * Optimized for being heavily executed
|
bsw/jbe@1309
|
8529 * Unleashes the power of live node lists
|
bsw/jbe@1309
|
8530 *
|
bsw/jbe@1309
|
8531 * @param {Object} doc The document object of the context where to check
|
bsw/jbe@1309
|
8532 * @param {String} tagName Upper cased tag name
|
bsw/jbe@1309
|
8533 * @example
|
bsw/jbe@1309
|
8534 * wysihtml.dom.hasElementWithClassName(document, "foobar");
|
bsw/jbe@1309
|
8535 */
|
bsw/jbe@1309
|
8536 (function(wysihtml) {
|
bsw/jbe@1309
|
8537 var LIVE_CACHE = {},
|
bsw/jbe@1309
|
8538 DOCUMENT_IDENTIFIER = 1;
|
bsw/jbe@1309
|
8539
|
bsw/jbe@1309
|
8540 function _getDocumentIdentifier(doc) {
|
bsw/jbe@1309
|
8541 return doc._wysihtml_identifier || (doc._wysihtml_identifier = DOCUMENT_IDENTIFIER++);
|
bsw/jbe@1309
|
8542 }
|
bsw/jbe@1309
|
8543
|
bsw/jbe@1309
|
8544 wysihtml.dom.hasElementWithClassName = function(doc, className) {
|
bsw/jbe@1309
|
8545 // getElementsByClassName is not supported by IE<9
|
bsw/jbe@1309
|
8546 // but is sometimes mocked via library code (which then doesn't return live node lists)
|
bsw/jbe@1309
|
8547 if (!wysihtml.browser.supportsNativeGetElementsByClassName()) {
|
bsw/jbe@1309
|
8548 return !!doc.querySelector("." + className);
|
bsw/jbe@1309
|
8549 }
|
bsw/jbe@1309
|
8550
|
bsw/jbe@1309
|
8551 var key = _getDocumentIdentifier(doc) + ":" + className,
|
bsw/jbe@1309
|
8552 cacheEntry = LIVE_CACHE[key];
|
bsw/jbe@1309
|
8553 if (!cacheEntry) {
|
bsw/jbe@1309
|
8554 cacheEntry = LIVE_CACHE[key] = doc.getElementsByClassName(className);
|
bsw/jbe@1309
|
8555 }
|
bsw/jbe@1309
|
8556
|
bsw/jbe@1309
|
8557 return cacheEntry.length > 0;
|
bsw/jbe@1309
|
8558 };
|
bsw/jbe@1309
|
8559 })(wysihtml);
|
bsw/jbe@1309
|
8560
|
bsw/jbe@1309
|
8561 /**
|
bsw/jbe@1309
|
8562 * High performant way to check whether an element with a specific tag name is in the given document
|
bsw/jbe@1309
|
8563 * Optimized for being heavily executed
|
bsw/jbe@1309
|
8564 * Unleashes the power of live node lists
|
bsw/jbe@1309
|
8565 *
|
bsw/jbe@1309
|
8566 * @param {Object} doc The document object of the context where to check
|
bsw/jbe@1309
|
8567 * @param {String} tagName Upper cased tag name
|
bsw/jbe@1309
|
8568 * @example
|
bsw/jbe@1309
|
8569 * wysihtml.dom.hasElementWithTagName(document, "IMG");
|
bsw/jbe@1309
|
8570 */
|
bsw/jbe@1309
|
8571 wysihtml.dom.hasElementWithTagName = (function() {
|
bsw/jbe@1309
|
8572 var LIVE_CACHE = {},
|
bsw/jbe@1309
|
8573 DOCUMENT_IDENTIFIER = 1;
|
bsw/jbe@1309
|
8574
|
bsw/jbe@1309
|
8575 function _getDocumentIdentifier(doc) {
|
bsw/jbe@1309
|
8576 return doc._wysihtml_identifier || (doc._wysihtml_identifier = DOCUMENT_IDENTIFIER++);
|
bsw/jbe@1309
|
8577 }
|
bsw/jbe@1309
|
8578
|
bsw/jbe@1309
|
8579 return function(doc, tagName) {
|
bsw/jbe@1309
|
8580 var key = _getDocumentIdentifier(doc) + ":" + tagName,
|
bsw/jbe@1309
|
8581 cacheEntry = LIVE_CACHE[key];
|
bsw/jbe@1309
|
8582 if (!cacheEntry) {
|
bsw/jbe@1309
|
8583 cacheEntry = LIVE_CACHE[key] = doc.getElementsByTagName(tagName);
|
bsw/jbe@1309
|
8584 }
|
bsw/jbe@1309
|
8585
|
bsw/jbe@1309
|
8586 return cacheEntry.length > 0;
|
bsw/jbe@1309
|
8587 };
|
bsw/jbe@1309
|
8588 })();
|
bsw/jbe@1309
|
8589
|
bsw/jbe@1309
|
8590 wysihtml.dom.insert = function(elementToInsert) {
|
bsw/jbe@1309
|
8591 return {
|
bsw/jbe@1309
|
8592 after: function(element) {
|
bsw/jbe@1309
|
8593 element.parentNode.insertBefore(elementToInsert, element.nextSibling);
|
bsw/jbe@1309
|
8594 },
|
bsw/jbe@1309
|
8595
|
bsw/jbe@1309
|
8596 before: function(element) {
|
bsw/jbe@1309
|
8597 element.parentNode.insertBefore(elementToInsert, element);
|
bsw/jbe@1309
|
8598 },
|
bsw/jbe@1309
|
8599
|
bsw/jbe@1309
|
8600 into: function(element) {
|
bsw/jbe@1309
|
8601 element.appendChild(elementToInsert);
|
bsw/jbe@1309
|
8602 }
|
bsw/jbe@1309
|
8603 };
|
bsw/jbe@1309
|
8604 };
|
bsw/jbe@1309
|
8605
|
bsw/jbe@1309
|
8606 wysihtml.dom.insertCSS = function(rules) {
|
bsw/jbe@1309
|
8607 rules = rules.join("\n");
|
bsw/jbe@1309
|
8608
|
bsw/jbe@1309
|
8609 return {
|
bsw/jbe@1309
|
8610 into: function(doc) {
|
bsw/jbe@1309
|
8611 var styleElement = doc.createElement("style");
|
bsw/jbe@1309
|
8612 styleElement.type = "text/css";
|
bsw/jbe@1309
|
8613
|
bsw/jbe@1309
|
8614 if (styleElement.styleSheet) {
|
bsw/jbe@1309
|
8615 styleElement.styleSheet.cssText = rules;
|
bsw/jbe@1309
|
8616 } else {
|
bsw/jbe@1309
|
8617 styleElement.appendChild(doc.createTextNode(rules));
|
bsw/jbe@1309
|
8618 }
|
bsw/jbe@1309
|
8619
|
bsw/jbe@1309
|
8620 var link = doc.querySelector("head link");
|
bsw/jbe@1309
|
8621 if (link) {
|
bsw/jbe@1309
|
8622 link.parentNode.insertBefore(styleElement, link);
|
bsw/jbe@1309
|
8623 return;
|
bsw/jbe@1309
|
8624 } else {
|
bsw/jbe@1309
|
8625 var head = doc.querySelector("head");
|
bsw/jbe@1309
|
8626 if (head) {
|
bsw/jbe@1309
|
8627 head.appendChild(styleElement);
|
bsw/jbe@1309
|
8628 }
|
bsw/jbe@1309
|
8629 }
|
bsw/jbe@1309
|
8630 }
|
bsw/jbe@1309
|
8631 };
|
bsw/jbe@1309
|
8632 };
|
bsw/jbe@1309
|
8633
|
bsw/jbe@1309
|
8634 /**
|
bsw/jbe@1309
|
8635 * Check whether the given node is a proper loaded image
|
bsw/jbe@1309
|
8636 * FIXME: Returns undefined when unknown (Chrome, Safari)
|
bsw/jbe@1309
|
8637 */
|
bsw/jbe@1309
|
8638
|
bsw/jbe@1309
|
8639 wysihtml.dom.isLoadedImage = function (node) {
|
bsw/jbe@1309
|
8640 try {
|
bsw/jbe@1309
|
8641 return node.complete && !node.mozMatchesSelector(":-moz-broken");
|
bsw/jbe@1309
|
8642 } catch(e) {
|
bsw/jbe@1309
|
8643 if (node.complete && node.readyState === "complete") {
|
bsw/jbe@1309
|
8644 return true;
|
bsw/jbe@1309
|
8645 }
|
bsw/jbe@1309
|
8646 }
|
bsw/jbe@1309
|
8647 };
|
bsw/jbe@1309
|
8648
|
bsw/jbe@1309
|
8649 // TODO: Refactor dom tree traversing here
|
bsw/jbe@1309
|
8650 (function(wysihtml) {
|
bsw/jbe@1309
|
8651 wysihtml.dom.lineBreaks = function(node) {
|
bsw/jbe@1309
|
8652
|
bsw/jbe@1309
|
8653 function _isLineBreak(n) {
|
bsw/jbe@1309
|
8654 return n.nodeName === "BR";
|
bsw/jbe@1309
|
8655 }
|
bsw/jbe@1309
|
8656
|
bsw/jbe@1309
|
8657 /**
|
bsw/jbe@1309
|
8658 * Checks whether the elment causes a visual line break
|
bsw/jbe@1309
|
8659 * (<br> or block elements)
|
bsw/jbe@1309
|
8660 */
|
bsw/jbe@1309
|
8661 function _isLineBreakOrBlockElement(element) {
|
bsw/jbe@1309
|
8662 if (_isLineBreak(element)) {
|
bsw/jbe@1309
|
8663 return true;
|
bsw/jbe@1309
|
8664 }
|
bsw/jbe@1309
|
8665
|
bsw/jbe@1309
|
8666 if (wysihtml.dom.getStyle("display").from(element) === "block") {
|
bsw/jbe@1309
|
8667 return true;
|
bsw/jbe@1309
|
8668 }
|
bsw/jbe@1309
|
8669
|
bsw/jbe@1309
|
8670 return false;
|
bsw/jbe@1309
|
8671 }
|
bsw/jbe@1309
|
8672
|
bsw/jbe@1309
|
8673 return {
|
bsw/jbe@1309
|
8674
|
bsw/jbe@1309
|
8675 /* wysihtml.dom.lineBreaks(element).add();
|
bsw/jbe@1309
|
8676 *
|
bsw/jbe@1309
|
8677 * Adds line breaks before and after the given node if the previous and next siblings
|
bsw/jbe@1309
|
8678 * aren't already causing a visual line break (block element or <br>)
|
bsw/jbe@1309
|
8679 */
|
bsw/jbe@1309
|
8680 add: function(options) {
|
bsw/jbe@1309
|
8681 var doc = node.ownerDocument,
|
bsw/jbe@1309
|
8682 nextSibling = wysihtml.dom.domNode(node).next({ignoreBlankTexts: true}),
|
bsw/jbe@1309
|
8683 previousSibling = wysihtml.dom.domNode(node).prev({ignoreBlankTexts: true});
|
bsw/jbe@1309
|
8684
|
bsw/jbe@1309
|
8685 if (nextSibling && !_isLineBreakOrBlockElement(nextSibling)) {
|
bsw/jbe@1309
|
8686 wysihtml.dom.insert(doc.createElement("br")).after(node);
|
bsw/jbe@1309
|
8687 }
|
bsw/jbe@1309
|
8688 if (previousSibling && !_isLineBreakOrBlockElement(previousSibling)) {
|
bsw/jbe@1309
|
8689 wysihtml.dom.insert(doc.createElement("br")).before(node);
|
bsw/jbe@1309
|
8690 }
|
bsw/jbe@1309
|
8691 },
|
bsw/jbe@1309
|
8692
|
bsw/jbe@1309
|
8693 /* wysihtml.dom.lineBreaks(element).remove();
|
bsw/jbe@1309
|
8694 *
|
bsw/jbe@1309
|
8695 * Removes line breaks before and after the given node
|
bsw/jbe@1309
|
8696 */
|
bsw/jbe@1309
|
8697 remove: function(options) {
|
bsw/jbe@1309
|
8698 var nextSibling = wysihtml.dom.domNode(node).next({ignoreBlankTexts: true}),
|
bsw/jbe@1309
|
8699 previousSibling = wysihtml.dom.domNode(node).prev({ignoreBlankTexts: true});
|
bsw/jbe@1309
|
8700
|
bsw/jbe@1309
|
8701 if (nextSibling && _isLineBreak(nextSibling)) {
|
bsw/jbe@1309
|
8702 nextSibling.parentNode.removeChild(nextSibling);
|
bsw/jbe@1309
|
8703 }
|
bsw/jbe@1309
|
8704 if (previousSibling && _isLineBreak(previousSibling)) {
|
bsw/jbe@1309
|
8705 previousSibling.parentNode.removeChild(previousSibling);
|
bsw/jbe@1309
|
8706 }
|
bsw/jbe@1309
|
8707 }
|
bsw/jbe@1309
|
8708 };
|
bsw/jbe@1309
|
8709 };
|
bsw/jbe@1309
|
8710 })(wysihtml);
|
bsw/jbe@1309
|
8711 /**
|
bsw/jbe@1309
|
8712 * Method to set dom events
|
bsw/jbe@1309
|
8713 *
|
bsw/jbe@1309
|
8714 * @example
|
bsw/jbe@1309
|
8715 * wysihtml.dom.observe(iframe.contentWindow.document.body, ["focus", "blur"], function() { ... });
|
bsw/jbe@1309
|
8716 */
|
bsw/jbe@1309
|
8717 wysihtml.dom.observe = function(element, eventNames, handler) {
|
bsw/jbe@1309
|
8718 eventNames = typeof(eventNames) === "string" ? [eventNames] : eventNames;
|
bsw/jbe@1309
|
8719
|
bsw/jbe@1309
|
8720 var handlerWrapper,
|
bsw/jbe@1309
|
8721 eventName,
|
bsw/jbe@1309
|
8722 i = 0,
|
bsw/jbe@1309
|
8723 length = eventNames.length;
|
bsw/jbe@1309
|
8724
|
bsw/jbe@1309
|
8725 for (; i<length; i++) {
|
bsw/jbe@1309
|
8726 eventName = eventNames[i];
|
bsw/jbe@1309
|
8727 if (element.addEventListener) {
|
bsw/jbe@1309
|
8728 element.addEventListener(eventName, handler, false);
|
bsw/jbe@1309
|
8729 } else {
|
bsw/jbe@1309
|
8730 handlerWrapper = function(event) {
|
bsw/jbe@1309
|
8731 if (!("target" in event)) {
|
bsw/jbe@1309
|
8732 event.target = event.srcElement;
|
bsw/jbe@1309
|
8733 }
|
bsw/jbe@1309
|
8734 event.preventDefault = event.preventDefault || function() {
|
bsw/jbe@1309
|
8735 this.returnValue = false;
|
bsw/jbe@1309
|
8736 };
|
bsw/jbe@1309
|
8737 event.stopPropagation = event.stopPropagation || function() {
|
bsw/jbe@1309
|
8738 this.cancelBubble = true;
|
bsw/jbe@1309
|
8739 };
|
bsw/jbe@1309
|
8740 handler.call(element, event);
|
bsw/jbe@1309
|
8741 };
|
bsw/jbe@1309
|
8742 element.attachEvent("on" + eventName, handlerWrapper);
|
bsw/jbe@1309
|
8743 }
|
bsw/jbe@1309
|
8744 }
|
bsw/jbe@1309
|
8745
|
bsw/jbe@1309
|
8746 return {
|
bsw/jbe@1309
|
8747 stop: function() {
|
bsw/jbe@1309
|
8748 var eventName,
|
bsw/jbe@1309
|
8749 i = 0,
|
bsw/jbe@1309
|
8750 length = eventNames.length;
|
bsw/jbe@1309
|
8751 for (; i<length; i++) {
|
bsw/jbe@1309
|
8752 eventName = eventNames[i];
|
bsw/jbe@1309
|
8753 if (element.removeEventListener) {
|
bsw/jbe@1309
|
8754 element.removeEventListener(eventName, handler, false);
|
bsw/jbe@1309
|
8755 } else {
|
bsw/jbe@1309
|
8756 element.detachEvent("on" + eventName, handlerWrapper);
|
bsw/jbe@1309
|
8757 }
|
bsw/jbe@1309
|
8758 }
|
bsw/jbe@1309
|
8759 }
|
bsw/jbe@1309
|
8760 };
|
bsw/jbe@1309
|
8761 };
|
bsw/jbe@1309
|
8762
|
bsw/jbe@1309
|
8763 /**
|
bsw/jbe@1309
|
8764 * HTML Sanitizer
|
bsw/jbe@1309
|
8765 * Rewrites the HTML based on given rules
|
bsw/jbe@1309
|
8766 *
|
bsw/jbe@1309
|
8767 * @param {Element|String} elementOrHtml HTML String to be sanitized OR element whose content should be sanitized
|
bsw/jbe@1309
|
8768 * @param {Object} [rules] List of rules for rewriting the HTML, if there's no rule for an element it will
|
bsw/jbe@1309
|
8769 * be converted to a "span". Each rule is a key/value pair where key is the tag to convert, and value the
|
bsw/jbe@1309
|
8770 * desired substitution.
|
bsw/jbe@1309
|
8771 * @param {Object} context Document object in which to parse the html, needed to sandbox the parsing
|
bsw/jbe@1309
|
8772 *
|
bsw/jbe@1309
|
8773 * @return {Element|String} Depends on the elementOrHtml parameter. When html then the sanitized html as string elsewise the element.
|
bsw/jbe@1309
|
8774 *
|
bsw/jbe@1309
|
8775 * @example
|
bsw/jbe@1309
|
8776 * var userHTML = '<div id="foo" onclick="alert(1);"><p><font color="red">foo</font><script>alert(1);</script></p></div>';
|
bsw/jbe@1309
|
8777 * wysihtml.dom.parse(userHTML, {
|
bsw/jbe@1309
|
8778 * tags {
|
bsw/jbe@1309
|
8779 * p: "div", // Rename p tags to div tags
|
bsw/jbe@1309
|
8780 * font: "span" // Rename font tags to span tags
|
bsw/jbe@1309
|
8781 * div: true, // Keep them, also possible (same result when passing: "div" or true)
|
bsw/jbe@1309
|
8782 * script: undefined // Remove script elements
|
bsw/jbe@1309
|
8783 * }
|
bsw/jbe@1309
|
8784 * });
|
bsw/jbe@1309
|
8785 * // => <div><div><span>foo bar</span></div></div>
|
bsw/jbe@1309
|
8786 *
|
bsw/jbe@1309
|
8787 * var userHTML = '<table><tbody><tr><td>I'm a table!</td></tr></tbody></table>';
|
bsw/jbe@1309
|
8788 * wysihtml.dom.parse(userHTML);
|
bsw/jbe@1309
|
8789 * // => '<span><span><span><span>I'm a table!</span></span></span></span>'
|
bsw/jbe@1309
|
8790 *
|
bsw/jbe@1309
|
8791 * var userHTML = '<div>foobar<br>foobar</div>';
|
bsw/jbe@1309
|
8792 * wysihtml.dom.parse(userHTML, {
|
bsw/jbe@1309
|
8793 * tags: {
|
bsw/jbe@1309
|
8794 * div: undefined,
|
bsw/jbe@1309
|
8795 * br: true
|
bsw/jbe@1309
|
8796 * }
|
bsw/jbe@1309
|
8797 * });
|
bsw/jbe@1309
|
8798 * // => ''
|
bsw/jbe@1309
|
8799 *
|
bsw/jbe@1309
|
8800 * var userHTML = '<div class="red">foo</div><div class="pink">bar</div>';
|
bsw/jbe@1309
|
8801 * wysihtml.dom.parse(userHTML, {
|
bsw/jbe@1309
|
8802 * classes: {
|
bsw/jbe@1309
|
8803 * red: 1,
|
bsw/jbe@1309
|
8804 * green: 1
|
bsw/jbe@1309
|
8805 * },
|
bsw/jbe@1309
|
8806 * tags: {
|
bsw/jbe@1309
|
8807 * div: {
|
bsw/jbe@1309
|
8808 * rename_tag: "p"
|
bsw/jbe@1309
|
8809 * }
|
bsw/jbe@1309
|
8810 * }
|
bsw/jbe@1309
|
8811 * });
|
bsw/jbe@1309
|
8812 * // => '<p class="red">foo</p><p>bar</p>'
|
bsw/jbe@1309
|
8813 */
|
bsw/jbe@1309
|
8814
|
bsw/jbe@1309
|
8815 wysihtml.dom.parse = function(elementOrHtml_current, config_current) {
|
bsw/jbe@1309
|
8816 /* TODO: Currently escaped module pattern as otherwise folloowing default swill be shared among multiple editors.
|
bsw/jbe@1309
|
8817 * Refactor whole code as this method while workind is kind of awkward too */
|
bsw/jbe@1309
|
8818
|
bsw/jbe@1309
|
8819 /**
|
bsw/jbe@1309
|
8820 * It's not possible to use a XMLParser/DOMParser as HTML5 is not always well-formed XML
|
bsw/jbe@1309
|
8821 * new DOMParser().parseFromString('<img src="foo.gif">') will cause a parseError since the
|
bsw/jbe@1309
|
8822 * node isn't closed
|
bsw/jbe@1309
|
8823 *
|
bsw/jbe@1309
|
8824 * Therefore we've to use the browser's ordinary HTML parser invoked by setting innerHTML.
|
bsw/jbe@1309
|
8825 */
|
bsw/jbe@1309
|
8826 var NODE_TYPE_MAPPING = {
|
bsw/jbe@1309
|
8827 "1": _handleElement,
|
bsw/jbe@1309
|
8828 "3": _handleText,
|
bsw/jbe@1309
|
8829 "8": _handleComment
|
bsw/jbe@1309
|
8830 },
|
bsw/jbe@1309
|
8831 // Rename unknown tags to this
|
bsw/jbe@1309
|
8832 DEFAULT_NODE_NAME = "span",
|
bsw/jbe@1309
|
8833 WHITE_SPACE_REG_EXP = /\s+/,
|
bsw/jbe@1309
|
8834 defaultRules = { tags: {}, classes: {} },
|
bsw/jbe@1309
|
8835 currentRules = {},
|
bsw/jbe@1309
|
8836 blockElements = ["ADDRESS" ,"BLOCKQUOTE" ,"CENTER" ,"DIR" ,"DIV" ,"DL" ,"FIELDSET" ,
|
bsw/jbe@1309
|
8837 "FORM", "H1" ,"H2" ,"H3" ,"H4" ,"H5" ,"H6" ,"ISINDEX" ,"MENU",
|
bsw/jbe@1309
|
8838 "NOFRAMES", "NOSCRIPT" ,"OL" ,"P" ,"PRE","TABLE", "UL"];
|
bsw/jbe@1309
|
8839
|
bsw/jbe@1309
|
8840 /**
|
bsw/jbe@1309
|
8841 * Iterates over all childs of the element, recreates them, appends them into a document fragment
|
bsw/jbe@1309
|
8842 * which later replaces the entire body content
|
bsw/jbe@1309
|
8843 */
|
bsw/jbe@1309
|
8844 function parse(elementOrHtml, config) {
|
bsw/jbe@1309
|
8845 wysihtml.lang.object(currentRules).merge(defaultRules).merge(config.rules).get();
|
bsw/jbe@1309
|
8846
|
bsw/jbe@1309
|
8847 var context = config.context || elementOrHtml.ownerDocument || document,
|
bsw/jbe@1309
|
8848 fragment = context.createDocumentFragment(),
|
bsw/jbe@1309
|
8849 isString = typeof(elementOrHtml) === "string",
|
bsw/jbe@1309
|
8850 clearInternals = false,
|
bsw/jbe@1309
|
8851 element,
|
bsw/jbe@1309
|
8852 newNode,
|
bsw/jbe@1309
|
8853 firstChild;
|
bsw/jbe@1309
|
8854
|
bsw/jbe@1309
|
8855 if (config.clearInternals === true) {
|
bsw/jbe@1309
|
8856 clearInternals = true;
|
bsw/jbe@1309
|
8857 }
|
bsw/jbe@1309
|
8858
|
bsw/jbe@1309
|
8859 if (isString) {
|
bsw/jbe@1309
|
8860 element = wysihtml.dom.getAsDom(elementOrHtml, context);
|
bsw/jbe@1309
|
8861 } else {
|
bsw/jbe@1309
|
8862 element = elementOrHtml;
|
bsw/jbe@1309
|
8863 }
|
bsw/jbe@1309
|
8864
|
bsw/jbe@1309
|
8865 if (currentRules.selectors) {
|
bsw/jbe@1309
|
8866 _applySelectorRules(element, currentRules.selectors);
|
bsw/jbe@1309
|
8867 }
|
bsw/jbe@1309
|
8868
|
bsw/jbe@1309
|
8869 while (element.firstChild) {
|
bsw/jbe@1309
|
8870 firstChild = element.firstChild;
|
bsw/jbe@1309
|
8871 newNode = _convert(firstChild, config.cleanUp, clearInternals, config.uneditableClass);
|
bsw/jbe@1309
|
8872 if (newNode) {
|
bsw/jbe@1309
|
8873 fragment.appendChild(newNode);
|
bsw/jbe@1309
|
8874 }
|
bsw/jbe@1309
|
8875 if (firstChild !== newNode) {
|
bsw/jbe@1309
|
8876 element.removeChild(firstChild);
|
bsw/jbe@1309
|
8877 }
|
bsw/jbe@1309
|
8878 }
|
bsw/jbe@1309
|
8879
|
bsw/jbe@1309
|
8880 if (config.unjoinNbsps) {
|
bsw/jbe@1309
|
8881 // replace joined non-breakable spaces with unjoined
|
bsw/jbe@1309
|
8882 var txtnodes = wysihtml.dom.getTextNodes(fragment);
|
bsw/jbe@1309
|
8883 for (var n = txtnodes.length; n--;) {
|
bsw/jbe@1309
|
8884 txtnodes[n].nodeValue = txtnodes[n].nodeValue.replace(/([\S\u00A0])\u00A0/gi, "$1 ");
|
bsw/jbe@1309
|
8885 }
|
bsw/jbe@1309
|
8886 }
|
bsw/jbe@1309
|
8887
|
bsw/jbe@1309
|
8888 // Clear element contents
|
bsw/jbe@1309
|
8889 element.innerHTML = "";
|
bsw/jbe@1309
|
8890
|
bsw/jbe@1309
|
8891 // Insert new DOM tree
|
bsw/jbe@1309
|
8892 element.appendChild(fragment);
|
bsw/jbe@1309
|
8893
|
bsw/jbe@1309
|
8894 return isString ? wysihtml.quirks.getCorrectInnerHTML(element) : element;
|
bsw/jbe@1309
|
8895 }
|
bsw/jbe@1309
|
8896
|
bsw/jbe@1309
|
8897 function _convert(oldNode, cleanUp, clearInternals, uneditableClass) {
|
bsw/jbe@1309
|
8898 var oldNodeType = oldNode.nodeType,
|
bsw/jbe@1309
|
8899 oldChilds = oldNode.childNodes,
|
bsw/jbe@1309
|
8900 oldChildsLength = oldChilds.length,
|
bsw/jbe@1309
|
8901 method = NODE_TYPE_MAPPING[oldNodeType],
|
bsw/jbe@1309
|
8902 i = 0,
|
bsw/jbe@1309
|
8903 fragment,
|
bsw/jbe@1309
|
8904 newNode,
|
bsw/jbe@1309
|
8905 newChild,
|
bsw/jbe@1309
|
8906 nodeDisplay;
|
bsw/jbe@1309
|
8907
|
bsw/jbe@1309
|
8908 // Passes directly elemets with uneditable class
|
bsw/jbe@1309
|
8909 if (uneditableClass && oldNodeType === 1 && wysihtml.dom.hasClass(oldNode, uneditableClass)) {
|
bsw/jbe@1309
|
8910 return oldNode;
|
bsw/jbe@1309
|
8911 }
|
bsw/jbe@1309
|
8912
|
bsw/jbe@1309
|
8913 newNode = method && method(oldNode, clearInternals);
|
bsw/jbe@1309
|
8914
|
bsw/jbe@1309
|
8915 // Remove or unwrap node in case of return value null or false
|
bsw/jbe@1309
|
8916 if (!newNode) {
|
bsw/jbe@1309
|
8917 if (newNode === false) {
|
bsw/jbe@1309
|
8918 // false defines that tag should be removed but contents should remain (unwrap)
|
bsw/jbe@1309
|
8919 fragment = oldNode.ownerDocument.createDocumentFragment();
|
bsw/jbe@1309
|
8920
|
bsw/jbe@1309
|
8921 for (i = oldChildsLength; i--;) {
|
bsw/jbe@1309
|
8922 if (oldChilds[i]) {
|
bsw/jbe@1309
|
8923 newChild = _convert(oldChilds[i], cleanUp, clearInternals, uneditableClass);
|
bsw/jbe@1309
|
8924 if (newChild) {
|
bsw/jbe@1309
|
8925 if (oldChilds[i] === newChild) {
|
bsw/jbe@1309
|
8926 i--;
|
bsw/jbe@1309
|
8927 }
|
bsw/jbe@1309
|
8928 fragment.insertBefore(newChild, fragment.firstChild);
|
bsw/jbe@1309
|
8929 }
|
bsw/jbe@1309
|
8930 }
|
bsw/jbe@1309
|
8931 }
|
bsw/jbe@1309
|
8932
|
bsw/jbe@1309
|
8933 nodeDisplay = wysihtml.dom.getStyle("display").from(oldNode);
|
bsw/jbe@1309
|
8934
|
bsw/jbe@1309
|
8935 if (nodeDisplay === '') {
|
bsw/jbe@1309
|
8936 // Handle display style when element not in dom
|
bsw/jbe@1309
|
8937 nodeDisplay = wysihtml.lang.array(blockElements).contains(oldNode.tagName) ? "block" : "";
|
bsw/jbe@1309
|
8938 }
|
bsw/jbe@1309
|
8939 if (wysihtml.lang.array(["block", "flex", "table"]).contains(nodeDisplay)) {
|
bsw/jbe@1309
|
8940 fragment.appendChild(oldNode.ownerDocument.createElement("br"));
|
bsw/jbe@1309
|
8941 }
|
bsw/jbe@1309
|
8942
|
bsw/jbe@1309
|
8943 // TODO: try to minimize surplus spaces
|
bsw/jbe@1309
|
8944 if (wysihtml.lang.array([
|
bsw/jbe@1309
|
8945 "div", "pre", "p",
|
bsw/jbe@1309
|
8946 "table", "td", "th",
|
bsw/jbe@1309
|
8947 "ul", "ol", "li",
|
bsw/jbe@1309
|
8948 "dd", "dl",
|
bsw/jbe@1309
|
8949 "footer", "header", "section",
|
bsw/jbe@1309
|
8950 "h1", "h2", "h3", "h4", "h5", "h6"
|
bsw/jbe@1309
|
8951 ]).contains(oldNode.nodeName.toLowerCase()) && oldNode.parentNode.lastChild !== oldNode) {
|
bsw/jbe@1309
|
8952 // add space at first when unwraping non-textflow elements
|
bsw/jbe@1309
|
8953 if (!oldNode.nextSibling || oldNode.nextSibling.nodeType !== 3 || !(/^\s/).test(oldNode.nextSibling.nodeValue)) {
|
bsw/jbe@1309
|
8954 fragment.appendChild(oldNode.ownerDocument.createTextNode(" "));
|
bsw/jbe@1309
|
8955 }
|
bsw/jbe@1309
|
8956 }
|
bsw/jbe@1309
|
8957
|
bsw/jbe@1309
|
8958 if (fragment.normalize) {
|
bsw/jbe@1309
|
8959 fragment.normalize();
|
bsw/jbe@1309
|
8960 }
|
bsw/jbe@1309
|
8961 return fragment;
|
bsw/jbe@1309
|
8962 } else {
|
bsw/jbe@1309
|
8963 // Remove
|
bsw/jbe@1309
|
8964 return null;
|
bsw/jbe@1309
|
8965 }
|
bsw/jbe@1309
|
8966 }
|
bsw/jbe@1309
|
8967
|
bsw/jbe@1309
|
8968 // Converts all childnodes
|
bsw/jbe@1309
|
8969 for (i=0; i<oldChildsLength; i++) {
|
bsw/jbe@1309
|
8970 if (oldChilds[i]) {
|
bsw/jbe@1309
|
8971 newChild = _convert(oldChilds[i], cleanUp, clearInternals, uneditableClass);
|
bsw/jbe@1309
|
8972 if (newChild) {
|
bsw/jbe@1309
|
8973 if (oldChilds[i] === newChild) {
|
bsw/jbe@1309
|
8974 i--;
|
bsw/jbe@1309
|
8975 }
|
bsw/jbe@1309
|
8976 newNode.appendChild(newChild);
|
bsw/jbe@1309
|
8977 }
|
bsw/jbe@1309
|
8978 }
|
bsw/jbe@1309
|
8979 }
|
bsw/jbe@1309
|
8980
|
bsw/jbe@1309
|
8981 // Cleanup senseless <span> elements
|
bsw/jbe@1309
|
8982 if (cleanUp &&
|
bsw/jbe@1309
|
8983 newNode.nodeName.toLowerCase() === DEFAULT_NODE_NAME &&
|
bsw/jbe@1309
|
8984 (!newNode.childNodes.length ||
|
bsw/jbe@1309
|
8985 ((/^\s*$/gi).test(newNode.innerHTML) && (clearInternals || (oldNode.className !== "_wysihtml-temp-placeholder" && oldNode.className !== "rangySelectionBoundary"))) ||
|
bsw/jbe@1309
|
8986 !newNode.attributes.length)
|
bsw/jbe@1309
|
8987 ) {
|
bsw/jbe@1309
|
8988 fragment = newNode.ownerDocument.createDocumentFragment();
|
bsw/jbe@1309
|
8989 while (newNode.firstChild) {
|
bsw/jbe@1309
|
8990 fragment.appendChild(newNode.firstChild);
|
bsw/jbe@1309
|
8991 }
|
bsw/jbe@1309
|
8992 if (fragment.normalize) {
|
bsw/jbe@1309
|
8993 fragment.normalize();
|
bsw/jbe@1309
|
8994 }
|
bsw/jbe@1309
|
8995 return fragment;
|
bsw/jbe@1309
|
8996 }
|
bsw/jbe@1309
|
8997
|
bsw/jbe@1309
|
8998 if (newNode.normalize) {
|
bsw/jbe@1309
|
8999 newNode.normalize();
|
bsw/jbe@1309
|
9000 }
|
bsw/jbe@1309
|
9001 return newNode;
|
bsw/jbe@1309
|
9002 }
|
bsw/jbe@1309
|
9003
|
bsw/jbe@1309
|
9004 function _applySelectorRules (element, selectorRules) {
|
bsw/jbe@1309
|
9005 var sel, method, els;
|
bsw/jbe@1309
|
9006
|
bsw/jbe@1309
|
9007 for (sel in selectorRules) {
|
bsw/jbe@1309
|
9008 if (selectorRules.hasOwnProperty(sel)) {
|
bsw/jbe@1309
|
9009 if (wysihtml.lang.object(selectorRules[sel]).isFunction()) {
|
bsw/jbe@1309
|
9010 method = selectorRules[sel];
|
bsw/jbe@1309
|
9011 } else if (typeof(selectorRules[sel]) === "string" && elementHandlingMethods[selectorRules[sel]]) {
|
bsw/jbe@1309
|
9012 method = elementHandlingMethods[selectorRules[sel]];
|
bsw/jbe@1309
|
9013 }
|
bsw/jbe@1309
|
9014 els = element.querySelectorAll(sel);
|
bsw/jbe@1309
|
9015 for (var i = els.length; i--;) {
|
bsw/jbe@1309
|
9016 method(els[i]);
|
bsw/jbe@1309
|
9017 }
|
bsw/jbe@1309
|
9018 }
|
bsw/jbe@1309
|
9019 }
|
bsw/jbe@1309
|
9020 }
|
bsw/jbe@1309
|
9021
|
bsw/jbe@1309
|
9022 function _handleElement(oldNode, clearInternals) {
|
bsw/jbe@1309
|
9023 var rule,
|
bsw/jbe@1309
|
9024 newNode,
|
bsw/jbe@1309
|
9025 tagRules = currentRules.tags,
|
bsw/jbe@1309
|
9026 nodeName = oldNode.nodeName.toLowerCase(),
|
bsw/jbe@1309
|
9027 scopeName = oldNode.scopeName,
|
bsw/jbe@1309
|
9028 renameTag;
|
bsw/jbe@1309
|
9029
|
bsw/jbe@1309
|
9030 /**
|
bsw/jbe@1309
|
9031 * We already parsed that element
|
bsw/jbe@1309
|
9032 * ignore it! (yes, this sometimes happens in IE8 when the html is invalid)
|
bsw/jbe@1309
|
9033 */
|
bsw/jbe@1309
|
9034 if (oldNode._wysihtml) {
|
bsw/jbe@1309
|
9035 return null;
|
bsw/jbe@1309
|
9036 }
|
bsw/jbe@1309
|
9037 oldNode._wysihtml = 1;
|
bsw/jbe@1309
|
9038
|
bsw/jbe@1309
|
9039 if (oldNode.className === "wysihtml-temp") {
|
bsw/jbe@1309
|
9040 return null;
|
bsw/jbe@1309
|
9041 }
|
bsw/jbe@1309
|
9042
|
bsw/jbe@1309
|
9043 /**
|
bsw/jbe@1309
|
9044 * IE is the only browser who doesn't include the namespace in the
|
bsw/jbe@1309
|
9045 * nodeName, that's why we have to prepend it by ourselves
|
bsw/jbe@1309
|
9046 * scopeName is a proprietary IE feature
|
bsw/jbe@1309
|
9047 * read more here http://msdn.microsoft.com/en-us/library/ms534388(v=vs.85).aspx
|
bsw/jbe@1309
|
9048 */
|
bsw/jbe@1309
|
9049 if (scopeName && scopeName != "HTML") {
|
bsw/jbe@1309
|
9050 nodeName = scopeName + ":" + nodeName;
|
bsw/jbe@1309
|
9051 }
|
bsw/jbe@1309
|
9052 /**
|
bsw/jbe@1309
|
9053 * Repair node
|
bsw/jbe@1309
|
9054 * IE is a bit bitchy when it comes to invalid nested markup which includes unclosed tags
|
bsw/jbe@1309
|
9055 * A <p> doesn't need to be closed according HTML4-5 spec, we simply replace it with a <div> to preserve its content and layout
|
bsw/jbe@1309
|
9056 */
|
bsw/jbe@1309
|
9057 if ("outerHTML" in oldNode) {
|
bsw/jbe@1309
|
9058 if (!wysihtml.browser.autoClosesUnclosedTags() &&
|
bsw/jbe@1309
|
9059 oldNode.nodeName === "P" &&
|
bsw/jbe@1309
|
9060 oldNode.outerHTML.slice(-4).toLowerCase() !== "</p>") {
|
bsw/jbe@1309
|
9061 nodeName = "div";
|
bsw/jbe@1309
|
9062 }
|
bsw/jbe@1309
|
9063 }
|
bsw/jbe@1309
|
9064
|
bsw/jbe@1309
|
9065 if (nodeName in tagRules) {
|
bsw/jbe@1309
|
9066 rule = tagRules[nodeName];
|
bsw/jbe@1309
|
9067 if (!rule || rule.remove) {
|
bsw/jbe@1309
|
9068 return null;
|
bsw/jbe@1309
|
9069 } else if (rule.unwrap) {
|
bsw/jbe@1309
|
9070 return false;
|
bsw/jbe@1309
|
9071 }
|
bsw/jbe@1309
|
9072 rule = typeof(rule) === "string" ? { rename_tag: rule } : rule;
|
bsw/jbe@1309
|
9073 } else if (oldNode.firstChild) {
|
bsw/jbe@1309
|
9074 rule = { rename_tag: DEFAULT_NODE_NAME };
|
bsw/jbe@1309
|
9075 } else {
|
bsw/jbe@1309
|
9076 // Remove empty unknown elements
|
bsw/jbe@1309
|
9077 return null;
|
bsw/jbe@1309
|
9078 }
|
bsw/jbe@1309
|
9079
|
bsw/jbe@1309
|
9080 // tests if type condition is met or node should be removed/unwrapped/renamed
|
bsw/jbe@1309
|
9081 if (rule.one_of_type && !_testTypes(oldNode, currentRules, rule.one_of_type, clearInternals)) {
|
bsw/jbe@1309
|
9082 if (rule.remove_action) {
|
bsw/jbe@1309
|
9083 if (rule.remove_action === "unwrap") {
|
bsw/jbe@1309
|
9084 return false;
|
bsw/jbe@1309
|
9085 } else if (rule.remove_action === "rename") {
|
bsw/jbe@1309
|
9086 renameTag = rule.remove_action_rename_to || DEFAULT_NODE_NAME;
|
bsw/jbe@1309
|
9087 } else {
|
bsw/jbe@1309
|
9088 return null;
|
bsw/jbe@1309
|
9089 }
|
bsw/jbe@1309
|
9090 } else {
|
bsw/jbe@1309
|
9091 return null;
|
bsw/jbe@1309
|
9092 }
|
bsw/jbe@1309
|
9093 }
|
bsw/jbe@1309
|
9094
|
bsw/jbe@1309
|
9095 newNode = oldNode.ownerDocument.createElement(renameTag || rule.rename_tag || nodeName);
|
bsw/jbe@1309
|
9096 _handleAttributes(oldNode, newNode, rule, clearInternals);
|
bsw/jbe@1309
|
9097 _handleStyles(oldNode, newNode, rule);
|
bsw/jbe@1309
|
9098
|
bsw/jbe@1309
|
9099 oldNode = null;
|
bsw/jbe@1309
|
9100
|
bsw/jbe@1309
|
9101 if (newNode.normalize) { newNode.normalize(); }
|
bsw/jbe@1309
|
9102 return newNode;
|
bsw/jbe@1309
|
9103 }
|
bsw/jbe@1309
|
9104
|
bsw/jbe@1309
|
9105 function _testTypes(oldNode, rules, types, clearInternals) {
|
bsw/jbe@1309
|
9106 var definition, type;
|
bsw/jbe@1309
|
9107
|
bsw/jbe@1309
|
9108 // do not interfere with placeholder span or pasting caret position is not maintained
|
bsw/jbe@1309
|
9109 if (oldNode.nodeName === "SPAN" && !clearInternals && (oldNode.className === "_wysihtml-temp-placeholder" || oldNode.className === "rangySelectionBoundary")) {
|
bsw/jbe@1309
|
9110 return true;
|
bsw/jbe@1309
|
9111 }
|
bsw/jbe@1309
|
9112
|
bsw/jbe@1309
|
9113 for (type in types) {
|
bsw/jbe@1309
|
9114 if (types.hasOwnProperty(type) && rules.type_definitions && rules.type_definitions[type]) {
|
bsw/jbe@1309
|
9115 definition = rules.type_definitions[type];
|
bsw/jbe@1309
|
9116 if (_testType(oldNode, definition)) {
|
bsw/jbe@1309
|
9117 return true;
|
bsw/jbe@1309
|
9118 }
|
bsw/jbe@1309
|
9119 }
|
bsw/jbe@1309
|
9120 }
|
bsw/jbe@1309
|
9121 return false;
|
bsw/jbe@1309
|
9122 }
|
bsw/jbe@1309
|
9123
|
bsw/jbe@1309
|
9124 function array_contains(a, obj) {
|
bsw/jbe@1309
|
9125 var i = a.length;
|
bsw/jbe@1309
|
9126 while (i--) {
|
bsw/jbe@1309
|
9127 if (a[i] === obj) {
|
bsw/jbe@1309
|
9128 return true;
|
bsw/jbe@1309
|
9129 }
|
bsw/jbe@1309
|
9130 }
|
bsw/jbe@1309
|
9131 return false;
|
bsw/jbe@1309
|
9132 }
|
bsw/jbe@1309
|
9133
|
bsw/jbe@1309
|
9134 function _testType(oldNode, definition) {
|
bsw/jbe@1309
|
9135
|
bsw/jbe@1309
|
9136 var nodeClasses = oldNode.getAttribute("class"),
|
bsw/jbe@1309
|
9137 nodeStyles = oldNode.getAttribute("style"),
|
bsw/jbe@1309
|
9138 classesLength, s, s_corrected, a, attr, currentClass, styleProp;
|
bsw/jbe@1309
|
9139
|
bsw/jbe@1309
|
9140 // test for methods
|
bsw/jbe@1309
|
9141 if (definition.methods) {
|
bsw/jbe@1309
|
9142 for (var m in definition.methods) {
|
bsw/jbe@1309
|
9143 if (definition.methods.hasOwnProperty(m) && typeCeckMethods[m]) {
|
bsw/jbe@1309
|
9144
|
bsw/jbe@1309
|
9145 if (typeCeckMethods[m](oldNode)) {
|
bsw/jbe@1309
|
9146 return true;
|
bsw/jbe@1309
|
9147 }
|
bsw/jbe@1309
|
9148 }
|
bsw/jbe@1309
|
9149 }
|
bsw/jbe@1309
|
9150 }
|
bsw/jbe@1309
|
9151
|
bsw/jbe@1309
|
9152 // test for classes, if one found return true
|
bsw/jbe@1309
|
9153 if (nodeClasses && definition.classes) {
|
bsw/jbe@1309
|
9154 nodeClasses = nodeClasses.replace(/^\s+/g, '').replace(/\s+$/g, '').split(WHITE_SPACE_REG_EXP);
|
bsw/jbe@1309
|
9155 classesLength = nodeClasses.length;
|
bsw/jbe@1309
|
9156 for (var i = 0; i < classesLength; i++) {
|
bsw/jbe@1309
|
9157 if (definition.classes[nodeClasses[i]]) {
|
bsw/jbe@1309
|
9158 return true;
|
bsw/jbe@1309
|
9159 }
|
bsw/jbe@1309
|
9160 }
|
bsw/jbe@1309
|
9161 }
|
bsw/jbe@1309
|
9162
|
bsw/jbe@1309
|
9163 // test for styles, if one found return true
|
bsw/jbe@1309
|
9164 if (nodeStyles && definition.styles) {
|
bsw/jbe@1309
|
9165
|
bsw/jbe@1309
|
9166 nodeStyles = nodeStyles.split(';');
|
bsw/jbe@1309
|
9167 for (s in definition.styles) {
|
bsw/jbe@1309
|
9168 if (definition.styles.hasOwnProperty(s)) {
|
bsw/jbe@1309
|
9169 for (var sp = nodeStyles.length; sp--;) {
|
bsw/jbe@1309
|
9170 styleProp = nodeStyles[sp].split(':');
|
bsw/jbe@1309
|
9171
|
bsw/jbe@1309
|
9172 if (styleProp[0].replace(/\s/g, '').toLowerCase() === s) {
|
bsw/jbe@1309
|
9173 if (definition.styles[s] === true || definition.styles[s] === 1 || wysihtml.lang.array(definition.styles[s]).contains(styleProp[1].replace(/\s/g, '').toLowerCase()) ) {
|
bsw/jbe@1309
|
9174 return true;
|
bsw/jbe@1309
|
9175 }
|
bsw/jbe@1309
|
9176 }
|
bsw/jbe@1309
|
9177 }
|
bsw/jbe@1309
|
9178 }
|
bsw/jbe@1309
|
9179 }
|
bsw/jbe@1309
|
9180 }
|
bsw/jbe@1309
|
9181
|
bsw/jbe@1309
|
9182 // test for attributes in general against regex match
|
bsw/jbe@1309
|
9183 if (definition.attrs) {
|
bsw/jbe@1309
|
9184 for (a in definition.attrs) {
|
bsw/jbe@1309
|
9185 if (definition.attrs.hasOwnProperty(a)) {
|
bsw/jbe@1309
|
9186 attr = wysihtml.dom.getAttribute(oldNode, a);
|
bsw/jbe@1309
|
9187 if (typeof(attr) === "string") {
|
bsw/jbe@1309
|
9188 if (attr.search(definition.attrs[a]) > -1) {
|
bsw/jbe@1309
|
9189 return true;
|
bsw/jbe@1309
|
9190 }
|
bsw/jbe@1309
|
9191 }
|
bsw/jbe@1309
|
9192 }
|
bsw/jbe@1309
|
9193 }
|
bsw/jbe@1309
|
9194 }
|
bsw/jbe@1309
|
9195 return false;
|
bsw/jbe@1309
|
9196 }
|
bsw/jbe@1309
|
9197
|
bsw/jbe@1309
|
9198 function _handleStyles(oldNode, newNode, rule) {
|
bsw/jbe@1309
|
9199 var s, v;
|
bsw/jbe@1309
|
9200 if(rule && rule.keep_styles) {
|
bsw/jbe@1309
|
9201 for (s in rule.keep_styles) {
|
bsw/jbe@1309
|
9202 if (rule.keep_styles.hasOwnProperty(s)) {
|
bsw/jbe@1309
|
9203 v = (s === "float") ? oldNode.style.styleFloat || oldNode.style.cssFloat : oldNode.style[s];
|
bsw/jbe@1309
|
9204 // value can be regex and if so should match or style skipped
|
bsw/jbe@1309
|
9205 if (rule.keep_styles[s] instanceof RegExp && !(rule.keep_styles[s].test(v))) {
|
bsw/jbe@1309
|
9206 continue;
|
bsw/jbe@1309
|
9207 }
|
bsw/jbe@1309
|
9208 if (s === "float") {
|
bsw/jbe@1309
|
9209 // IE compability
|
bsw/jbe@1309
|
9210 newNode.style[(oldNode.style.styleFloat) ? 'styleFloat': 'cssFloat'] = v;
|
bsw/jbe@1309
|
9211 } else if (oldNode.style[s]) {
|
bsw/jbe@1309
|
9212 newNode.style[s] = v;
|
bsw/jbe@1309
|
9213 }
|
bsw/jbe@1309
|
9214 }
|
bsw/jbe@1309
|
9215 }
|
bsw/jbe@1309
|
9216 }
|
bsw/jbe@1309
|
9217 };
|
bsw/jbe@1309
|
9218
|
bsw/jbe@1309
|
9219 function _getAttributesBeginningWith(beginning, attributes) {
|
bsw/jbe@1309
|
9220 var returnAttributes = [];
|
bsw/jbe@1309
|
9221 for (var attr in attributes) {
|
bsw/jbe@1309
|
9222 if (attributes.hasOwnProperty(attr) && attr.indexOf(beginning) === 0) {
|
bsw/jbe@1309
|
9223 returnAttributes.push(attr);
|
bsw/jbe@1309
|
9224 }
|
bsw/jbe@1309
|
9225 }
|
bsw/jbe@1309
|
9226 return returnAttributes;
|
bsw/jbe@1309
|
9227 }
|
bsw/jbe@1309
|
9228
|
bsw/jbe@1309
|
9229 function _checkAttribute(attributeName, attributeValue, methodName, nodeName) {
|
bsw/jbe@1309
|
9230 var method = wysihtml.lang.object(methodName).isFunction() ? methodName : attributeCheckMethods[methodName],
|
bsw/jbe@1309
|
9231 newAttributeValue;
|
bsw/jbe@1309
|
9232
|
bsw/jbe@1309
|
9233 if (method) {
|
bsw/jbe@1309
|
9234 newAttributeValue = method(attributeValue, nodeName);
|
bsw/jbe@1309
|
9235 if (typeof(newAttributeValue) === "string") {
|
bsw/jbe@1309
|
9236 return newAttributeValue;
|
bsw/jbe@1309
|
9237 }
|
bsw/jbe@1309
|
9238 }
|
bsw/jbe@1309
|
9239
|
bsw/jbe@1309
|
9240 return false;
|
bsw/jbe@1309
|
9241 }
|
bsw/jbe@1309
|
9242
|
bsw/jbe@1309
|
9243 function _checkAttributes(oldNode, local_attributes) {
|
bsw/jbe@1309
|
9244 var globalAttributes = wysihtml.lang.object(currentRules.attributes || {}).clone(), // global values for check/convert values of attributes
|
bsw/jbe@1309
|
9245 checkAttributes = wysihtml.lang.object(globalAttributes).merge( wysihtml.lang.object(local_attributes || {}).clone()).get(),
|
bsw/jbe@1309
|
9246 attributes = {},
|
bsw/jbe@1309
|
9247 oldAttributes = wysihtml.dom.getAttributes(oldNode),
|
bsw/jbe@1309
|
9248 attributeName, newValue, matchingAttributes;
|
bsw/jbe@1309
|
9249
|
bsw/jbe@1309
|
9250 for (attributeName in checkAttributes) {
|
bsw/jbe@1309
|
9251 if ((/\*$/).test(attributeName)) {
|
bsw/jbe@1309
|
9252
|
bsw/jbe@1309
|
9253 matchingAttributes = _getAttributesBeginningWith(attributeName.slice(0,-1), oldAttributes);
|
bsw/jbe@1309
|
9254 for (var i = 0, imax = matchingAttributes.length; i < imax; i++) {
|
bsw/jbe@1309
|
9255
|
bsw/jbe@1309
|
9256 newValue = _checkAttribute(matchingAttributes[i], oldAttributes[matchingAttributes[i]], checkAttributes[attributeName], oldNode.nodeName);
|
bsw/jbe@1309
|
9257 if (newValue !== false) {
|
bsw/jbe@1309
|
9258 attributes[matchingAttributes[i]] = newValue;
|
bsw/jbe@1309
|
9259 }
|
bsw/jbe@1309
|
9260 }
|
bsw/jbe@1309
|
9261 } else {
|
bsw/jbe@1309
|
9262 newValue = _checkAttribute(attributeName, oldAttributes[attributeName], checkAttributes[attributeName], oldNode.nodeName);
|
bsw/jbe@1309
|
9263 if (newValue !== false) {
|
bsw/jbe@1309
|
9264 attributes[attributeName] = newValue;
|
bsw/jbe@1309
|
9265 }
|
bsw/jbe@1309
|
9266 }
|
bsw/jbe@1309
|
9267 }
|
bsw/jbe@1309
|
9268
|
bsw/jbe@1309
|
9269 return attributes;
|
bsw/jbe@1309
|
9270 }
|
bsw/jbe@1309
|
9271
|
bsw/jbe@1309
|
9272 // TODO: refactor. Too long to read
|
bsw/jbe@1309
|
9273 function _handleAttributes(oldNode, newNode, rule, clearInternals) {
|
bsw/jbe@1309
|
9274 var attributes = {}, // fresh new set of attributes to set on newNode
|
bsw/jbe@1309
|
9275 setClass = rule.set_class, // classes to set
|
bsw/jbe@1309
|
9276 addClass = rule.add_class, // add classes based on existing attributes
|
bsw/jbe@1309
|
9277 addStyle = rule.add_style, // add styles based on existing attributes
|
bsw/jbe@1309
|
9278 setAttributes = rule.set_attributes, // attributes to set on the current node
|
bsw/jbe@1309
|
9279 allowedClasses = currentRules.classes,
|
bsw/jbe@1309
|
9280 i = 0,
|
bsw/jbe@1309
|
9281 classes = [],
|
bsw/jbe@1309
|
9282 styles = [],
|
bsw/jbe@1309
|
9283 newClasses = [],
|
bsw/jbe@1309
|
9284 oldClasses = [],
|
bsw/jbe@1309
|
9285 classesLength,
|
bsw/jbe@1309
|
9286 newClassesLength,
|
bsw/jbe@1309
|
9287 currentClass,
|
bsw/jbe@1309
|
9288 newClass,
|
bsw/jbe@1309
|
9289 attributeName,
|
bsw/jbe@1309
|
9290 method;
|
bsw/jbe@1309
|
9291
|
bsw/jbe@1309
|
9292 if (setAttributes) {
|
bsw/jbe@1309
|
9293 attributes = wysihtml.lang.object(setAttributes).clone();
|
bsw/jbe@1309
|
9294 }
|
bsw/jbe@1309
|
9295
|
bsw/jbe@1309
|
9296 // check/convert values of attributes
|
bsw/jbe@1309
|
9297 attributes = wysihtml.lang.object(attributes).merge(_checkAttributes(oldNode, rule.check_attributes)).get();
|
bsw/jbe@1309
|
9298
|
bsw/jbe@1309
|
9299 if (setClass) {
|
bsw/jbe@1309
|
9300 classes.push(setClass);
|
bsw/jbe@1309
|
9301 }
|
bsw/jbe@1309
|
9302
|
bsw/jbe@1309
|
9303 if (addClass) {
|
bsw/jbe@1309
|
9304 for (attributeName in addClass) {
|
bsw/jbe@1309
|
9305 method = addClassMethods[addClass[attributeName]];
|
bsw/jbe@1309
|
9306 if (!method) {
|
bsw/jbe@1309
|
9307 continue;
|
bsw/jbe@1309
|
9308 }
|
bsw/jbe@1309
|
9309 newClass = method(wysihtml.dom.getAttribute(oldNode, attributeName));
|
bsw/jbe@1309
|
9310 if (typeof(newClass) === "string") {
|
bsw/jbe@1309
|
9311 classes.push(newClass);
|
bsw/jbe@1309
|
9312 }
|
bsw/jbe@1309
|
9313 }
|
bsw/jbe@1309
|
9314 }
|
bsw/jbe@1309
|
9315
|
bsw/jbe@1309
|
9316 if (addStyle) {
|
bsw/jbe@1309
|
9317 for (attributeName in addStyle) {
|
bsw/jbe@1309
|
9318 method = addStyleMethods[addStyle[attributeName]];
|
bsw/jbe@1309
|
9319 if (!method) {
|
bsw/jbe@1309
|
9320 continue;
|
bsw/jbe@1309
|
9321 }
|
bsw/jbe@1309
|
9322
|
bsw/jbe@1309
|
9323 newStyle = method(wysihtml.dom.getAttribute(oldNode, attributeName));
|
bsw/jbe@1309
|
9324 if (typeof(newStyle) === "string") {
|
bsw/jbe@1309
|
9325 styles.push(newStyle);
|
bsw/jbe@1309
|
9326 }
|
bsw/jbe@1309
|
9327 }
|
bsw/jbe@1309
|
9328 }
|
bsw/jbe@1309
|
9329
|
bsw/jbe@1309
|
9330
|
bsw/jbe@1309
|
9331 if (typeof(allowedClasses) === "string" && allowedClasses === "any") {
|
bsw/jbe@1309
|
9332 if (oldNode.getAttribute("class")) {
|
bsw/jbe@1309
|
9333 if (currentRules.classes_blacklist) {
|
bsw/jbe@1309
|
9334 oldClasses = oldNode.getAttribute("class");
|
bsw/jbe@1309
|
9335 if (oldClasses) {
|
bsw/jbe@1309
|
9336 classes = classes.concat(oldClasses.split(WHITE_SPACE_REG_EXP));
|
bsw/jbe@1309
|
9337 }
|
bsw/jbe@1309
|
9338
|
bsw/jbe@1309
|
9339 classesLength = classes.length;
|
bsw/jbe@1309
|
9340 for (; i<classesLength; i++) {
|
bsw/jbe@1309
|
9341 currentClass = classes[i];
|
bsw/jbe@1309
|
9342 if (!currentRules.classes_blacklist[currentClass]) {
|
bsw/jbe@1309
|
9343 newClasses.push(currentClass);
|
bsw/jbe@1309
|
9344 }
|
bsw/jbe@1309
|
9345 }
|
bsw/jbe@1309
|
9346
|
bsw/jbe@1309
|
9347 if (newClasses.length) {
|
bsw/jbe@1309
|
9348 attributes["class"] = wysihtml.lang.array(newClasses).unique().join(" ");
|
bsw/jbe@1309
|
9349 }
|
bsw/jbe@1309
|
9350
|
bsw/jbe@1309
|
9351 } else {
|
bsw/jbe@1309
|
9352 attributes["class"] = oldNode.getAttribute("class");
|
bsw/jbe@1309
|
9353 }
|
bsw/jbe@1309
|
9354 } else {
|
bsw/jbe@1309
|
9355 if(classes && classes.length > 0) {
|
bsw/jbe@1309
|
9356 attributes["class"] = wysihtml.lang.array(classes).unique().join(" ");
|
bsw/jbe@1309
|
9357 }
|
bsw/jbe@1309
|
9358 }
|
bsw/jbe@1309
|
9359 } else {
|
bsw/jbe@1309
|
9360 // make sure that wysihtml temp class doesn't get stripped out
|
bsw/jbe@1309
|
9361 if (!clearInternals) {
|
bsw/jbe@1309
|
9362 allowedClasses["_wysihtml-temp-placeholder"] = 1;
|
bsw/jbe@1309
|
9363 allowedClasses["_rangySelectionBoundary"] = 1;
|
bsw/jbe@1309
|
9364 allowedClasses["wysiwyg-tmp-selected-cell"] = 1;
|
bsw/jbe@1309
|
9365 }
|
bsw/jbe@1309
|
9366
|
bsw/jbe@1309
|
9367 // add old classes last
|
bsw/jbe@1309
|
9368 oldClasses = oldNode.getAttribute("class");
|
bsw/jbe@1309
|
9369 if (oldClasses) {
|
bsw/jbe@1309
|
9370 classes = classes.concat(oldClasses.split(WHITE_SPACE_REG_EXP));
|
bsw/jbe@1309
|
9371 }
|
bsw/jbe@1309
|
9372 classesLength = classes.length;
|
bsw/jbe@1309
|
9373 for (; i<classesLength; i++) {
|
bsw/jbe@1309
|
9374 currentClass = classes[i];
|
bsw/jbe@1309
|
9375 if (allowedClasses[currentClass]) {
|
bsw/jbe@1309
|
9376 newClasses.push(currentClass);
|
bsw/jbe@1309
|
9377 }
|
bsw/jbe@1309
|
9378 }
|
bsw/jbe@1309
|
9379
|
bsw/jbe@1309
|
9380 if (newClasses.length) {
|
bsw/jbe@1309
|
9381 attributes["class"] = wysihtml.lang.array(newClasses).unique().join(" ");
|
bsw/jbe@1309
|
9382 }
|
bsw/jbe@1309
|
9383 }
|
bsw/jbe@1309
|
9384
|
bsw/jbe@1309
|
9385 // remove table selection class if present
|
bsw/jbe@1309
|
9386 if (attributes["class"] && clearInternals) {
|
bsw/jbe@1309
|
9387 attributes["class"] = attributes["class"].replace("wysiwyg-tmp-selected-cell", "");
|
bsw/jbe@1309
|
9388 if ((/^\s*$/g).test(attributes["class"])) {
|
bsw/jbe@1309
|
9389 delete attributes["class"];
|
bsw/jbe@1309
|
9390 }
|
bsw/jbe@1309
|
9391 }
|
bsw/jbe@1309
|
9392
|
bsw/jbe@1309
|
9393 if (styles.length) {
|
bsw/jbe@1309
|
9394 attributes["style"] = wysihtml.lang.array(styles).unique().join(" ");
|
bsw/jbe@1309
|
9395 }
|
bsw/jbe@1309
|
9396
|
bsw/jbe@1309
|
9397 // set attributes on newNode
|
bsw/jbe@1309
|
9398 for (attributeName in attributes) {
|
bsw/jbe@1309
|
9399 // Setting attributes can cause a js error in IE under certain circumstances
|
bsw/jbe@1309
|
9400 // eg. on a <img> under https when it's new attribute value is non-https
|
bsw/jbe@1309
|
9401 // TODO: Investigate this further and check for smarter handling
|
bsw/jbe@1309
|
9402 try {
|
bsw/jbe@1309
|
9403 newNode.setAttribute(attributeName, attributes[attributeName]);
|
bsw/jbe@1309
|
9404 } catch(e) {}
|
bsw/jbe@1309
|
9405 }
|
bsw/jbe@1309
|
9406
|
bsw/jbe@1309
|
9407 // IE8 sometimes loses the width/height attributes when those are set before the "src"
|
bsw/jbe@1309
|
9408 // so we make sure to set them again
|
bsw/jbe@1309
|
9409 if (attributes.src) {
|
bsw/jbe@1309
|
9410 if (typeof(attributes.width) !== "undefined") {
|
bsw/jbe@1309
|
9411 newNode.setAttribute("width", attributes.width);
|
bsw/jbe@1309
|
9412 }
|
bsw/jbe@1309
|
9413 if (typeof(attributes.height) !== "undefined") {
|
bsw/jbe@1309
|
9414 newNode.setAttribute("height", attributes.height);
|
bsw/jbe@1309
|
9415 }
|
bsw/jbe@1309
|
9416 }
|
bsw/jbe@1309
|
9417 }
|
bsw/jbe@1309
|
9418
|
bsw/jbe@1309
|
9419 function _handleText(oldNode) {
|
bsw/jbe@1309
|
9420 var nextSibling = oldNode.nextSibling;
|
bsw/jbe@1309
|
9421 if (nextSibling && nextSibling.nodeType === wysihtml.TEXT_NODE) {
|
bsw/jbe@1309
|
9422 // Concatenate text nodes
|
bsw/jbe@1309
|
9423 nextSibling.data = oldNode.data.replace(wysihtml.INVISIBLE_SPACE_REG_EXP, "") + nextSibling.data.replace(wysihtml.INVISIBLE_SPACE_REG_EXP, "");
|
bsw/jbe@1309
|
9424 } else {
|
bsw/jbe@1309
|
9425 // \uFEFF = wysihtml.INVISIBLE_SPACE (used as a hack in certain rich text editing situations)
|
bsw/jbe@1309
|
9426 var data = oldNode.data.replace(wysihtml.INVISIBLE_SPACE_REG_EXP, "");
|
bsw/jbe@1309
|
9427 return oldNode.ownerDocument.createTextNode(data);
|
bsw/jbe@1309
|
9428 }
|
bsw/jbe@1309
|
9429 }
|
bsw/jbe@1309
|
9430
|
bsw/jbe@1309
|
9431 function _handleComment(oldNode) {
|
bsw/jbe@1309
|
9432 if (currentRules.comments) {
|
bsw/jbe@1309
|
9433 return oldNode.ownerDocument.createComment(oldNode.nodeValue);
|
bsw/jbe@1309
|
9434 }
|
bsw/jbe@1309
|
9435 }
|
bsw/jbe@1309
|
9436
|
bsw/jbe@1309
|
9437 // ------------ attribute checks ------------ \\
|
bsw/jbe@1309
|
9438 var attributeCheckMethods = {
|
bsw/jbe@1309
|
9439 url: (function() {
|
bsw/jbe@1309
|
9440 var REG_EXP = /^https?:\/\//i;
|
bsw/jbe@1309
|
9441 return function(attributeValue) {
|
bsw/jbe@1309
|
9442 if (!attributeValue || !attributeValue.match(REG_EXP)) {
|
bsw/jbe@1309
|
9443 return null;
|
bsw/jbe@1309
|
9444 }
|
bsw/jbe@1309
|
9445 return attributeValue.replace(REG_EXP, function(match) {
|
bsw/jbe@1309
|
9446 return match.toLowerCase();
|
bsw/jbe@1309
|
9447 });
|
bsw/jbe@1309
|
9448 };
|
bsw/jbe@1309
|
9449 })(),
|
bsw/jbe@1309
|
9450
|
bsw/jbe@1309
|
9451 src: (function() {
|
bsw/jbe@1309
|
9452 var REG_EXP = /^(\/|https?:\/\/)/i;
|
bsw/jbe@1309
|
9453 return function(attributeValue) {
|
bsw/jbe@1309
|
9454 if (!attributeValue || !attributeValue.match(REG_EXP)) {
|
bsw/jbe@1309
|
9455 return null;
|
bsw/jbe@1309
|
9456 }
|
bsw/jbe@1309
|
9457 return attributeValue.replace(REG_EXP, function(match) {
|
bsw/jbe@1309
|
9458 return match.toLowerCase();
|
bsw/jbe@1309
|
9459 });
|
bsw/jbe@1309
|
9460 };
|
bsw/jbe@1309
|
9461 })(),
|
bsw/jbe@1309
|
9462
|
bsw/jbe@1309
|
9463 href: (function() {
|
bsw/jbe@1309
|
9464 var REG_EXP = /^(#|\/|https?:\/\/|mailto:|tel:)/i;
|
bsw/jbe@1309
|
9465 return function(attributeValue) {
|
bsw/jbe@1309
|
9466 if (!attributeValue || !attributeValue.match(REG_EXP)) {
|
bsw/jbe@1309
|
9467 return null;
|
bsw/jbe@1309
|
9468 }
|
bsw/jbe@1309
|
9469 return attributeValue.replace(REG_EXP, function(match) {
|
bsw/jbe@1309
|
9470 return match.toLowerCase();
|
bsw/jbe@1309
|
9471 });
|
bsw/jbe@1309
|
9472 };
|
bsw/jbe@1309
|
9473 })(),
|
bsw/jbe@1309
|
9474
|
bsw/jbe@1309
|
9475 alt: (function() {
|
bsw/jbe@1309
|
9476 var REG_EXP = /[^ a-z0-9_\-]/gi;
|
bsw/jbe@1309
|
9477 return function(attributeValue, nodeName) {
|
bsw/jbe@1309
|
9478 if (!attributeValue) {
|
bsw/jbe@1309
|
9479 if (nodeName === "IMG") {
|
bsw/jbe@1309
|
9480 return "";
|
bsw/jbe@1309
|
9481 } else {
|
bsw/jbe@1309
|
9482 return null;
|
bsw/jbe@1309
|
9483 }
|
bsw/jbe@1309
|
9484 }
|
bsw/jbe@1309
|
9485 return attributeValue.replace(REG_EXP, "");
|
bsw/jbe@1309
|
9486 };
|
bsw/jbe@1309
|
9487 })(),
|
bsw/jbe@1309
|
9488
|
bsw/jbe@1309
|
9489 // Integers. Does not work with floating point numbers and units
|
bsw/jbe@1309
|
9490 numbers: (function() {
|
bsw/jbe@1309
|
9491 var REG_EXP = /\D/g;
|
bsw/jbe@1309
|
9492 return function(attributeValue) {
|
bsw/jbe@1309
|
9493 attributeValue = (attributeValue || "").replace(REG_EXP, "");
|
bsw/jbe@1309
|
9494 return attributeValue || null;
|
bsw/jbe@1309
|
9495 };
|
bsw/jbe@1309
|
9496 })(),
|
bsw/jbe@1309
|
9497
|
bsw/jbe@1309
|
9498 // Useful for with/height attributes where floating points and percentages are allowed
|
bsw/jbe@1309
|
9499 dimension: (function() {
|
bsw/jbe@1309
|
9500 var REG_EXP = /\D*(\d+)(\.\d+)?\s?(%)?\D*/;
|
bsw/jbe@1309
|
9501 return function(attributeValue) {
|
bsw/jbe@1309
|
9502 attributeValue = (attributeValue || "").replace(REG_EXP, "$1$2$3");
|
bsw/jbe@1309
|
9503 return attributeValue || null;
|
bsw/jbe@1309
|
9504 };
|
bsw/jbe@1309
|
9505 })(),
|
bsw/jbe@1309
|
9506
|
bsw/jbe@1309
|
9507 any: (function() {
|
bsw/jbe@1309
|
9508 return function(attributeValue) {
|
bsw/jbe@1309
|
9509 if (!attributeValue) {
|
bsw/jbe@1309
|
9510 return null;
|
bsw/jbe@1309
|
9511 }
|
bsw/jbe@1309
|
9512 return attributeValue;
|
bsw/jbe@1309
|
9513 };
|
bsw/jbe@1309
|
9514 })()
|
bsw/jbe@1309
|
9515 };
|
bsw/jbe@1309
|
9516
|
bsw/jbe@1309
|
9517 // ------------ style converter (converts an html attribute to a style) ------------ \\
|
bsw/jbe@1309
|
9518 var addStyleMethods = {
|
bsw/jbe@1309
|
9519 align_text: (function() {
|
bsw/jbe@1309
|
9520 var mapping = {
|
bsw/jbe@1309
|
9521 left: "text-align: left;",
|
bsw/jbe@1309
|
9522 right: "text-align: right;",
|
bsw/jbe@1309
|
9523 center: "text-align: center;"
|
bsw/jbe@1309
|
9524 };
|
bsw/jbe@1309
|
9525 return function(attributeValue) {
|
bsw/jbe@1309
|
9526 return mapping[String(attributeValue).toLowerCase()];
|
bsw/jbe@1309
|
9527 };
|
bsw/jbe@1309
|
9528 })(),
|
bsw/jbe@1309
|
9529 };
|
bsw/jbe@1309
|
9530
|
bsw/jbe@1309
|
9531 // ------------ class converter (converts an html attribute to a class name) ------------ \\
|
bsw/jbe@1309
|
9532 var addClassMethods = {
|
bsw/jbe@1309
|
9533 align_img: (function() {
|
bsw/jbe@1309
|
9534 var mapping = {
|
bsw/jbe@1309
|
9535 left: "wysiwyg-float-left",
|
bsw/jbe@1309
|
9536 right: "wysiwyg-float-right"
|
bsw/jbe@1309
|
9537 };
|
bsw/jbe@1309
|
9538 return function(attributeValue) {
|
bsw/jbe@1309
|
9539 return mapping[String(attributeValue).toLowerCase()];
|
bsw/jbe@1309
|
9540 };
|
bsw/jbe@1309
|
9541 })(),
|
bsw/jbe@1309
|
9542
|
bsw/jbe@1309
|
9543 align_text: (function() {
|
bsw/jbe@1309
|
9544 var mapping = {
|
bsw/jbe@1309
|
9545 left: "wysiwyg-text-align-left",
|
bsw/jbe@1309
|
9546 right: "wysiwyg-text-align-right",
|
bsw/jbe@1309
|
9547 center: "wysiwyg-text-align-center",
|
bsw/jbe@1309
|
9548 justify: "wysiwyg-text-align-justify"
|
bsw/jbe@1309
|
9549 };
|
bsw/jbe@1309
|
9550 return function(attributeValue) {
|
bsw/jbe@1309
|
9551 return mapping[String(attributeValue).toLowerCase()];
|
bsw/jbe@1309
|
9552 };
|
bsw/jbe@1309
|
9553 })(),
|
bsw/jbe@1309
|
9554
|
bsw/jbe@1309
|
9555 clear_br: (function() {
|
bsw/jbe@1309
|
9556 var mapping = {
|
bsw/jbe@1309
|
9557 left: "wysiwyg-clear-left",
|
bsw/jbe@1309
|
9558 right: "wysiwyg-clear-right",
|
bsw/jbe@1309
|
9559 both: "wysiwyg-clear-both",
|
bsw/jbe@1309
|
9560 all: "wysiwyg-clear-both"
|
bsw/jbe@1309
|
9561 };
|
bsw/jbe@1309
|
9562 return function(attributeValue) {
|
bsw/jbe@1309
|
9563 return mapping[String(attributeValue).toLowerCase()];
|
bsw/jbe@1309
|
9564 };
|
bsw/jbe@1309
|
9565 })(),
|
bsw/jbe@1309
|
9566
|
bsw/jbe@1309
|
9567 size_font: (function() {
|
bsw/jbe@1309
|
9568 var mapping = {
|
bsw/jbe@1309
|
9569 "1": "wysiwyg-font-size-xx-small",
|
bsw/jbe@1309
|
9570 "2": "wysiwyg-font-size-small",
|
bsw/jbe@1309
|
9571 "3": "wysiwyg-font-size-medium",
|
bsw/jbe@1309
|
9572 "4": "wysiwyg-font-size-large",
|
bsw/jbe@1309
|
9573 "5": "wysiwyg-font-size-x-large",
|
bsw/jbe@1309
|
9574 "6": "wysiwyg-font-size-xx-large",
|
bsw/jbe@1309
|
9575 "7": "wysiwyg-font-size-xx-large",
|
bsw/jbe@1309
|
9576 "-": "wysiwyg-font-size-smaller",
|
bsw/jbe@1309
|
9577 "+": "wysiwyg-font-size-larger"
|
bsw/jbe@1309
|
9578 };
|
bsw/jbe@1309
|
9579 return function(attributeValue) {
|
bsw/jbe@1309
|
9580 return mapping[String(attributeValue).charAt(0)];
|
bsw/jbe@1309
|
9581 };
|
bsw/jbe@1309
|
9582 })()
|
bsw/jbe@1309
|
9583 };
|
bsw/jbe@1309
|
9584
|
bsw/jbe@1309
|
9585 // checks if element is possibly visible
|
bsw/jbe@1309
|
9586 var typeCeckMethods = {
|
bsw/jbe@1309
|
9587 has_visible_contet: (function() {
|
bsw/jbe@1309
|
9588 var txt,
|
bsw/jbe@1309
|
9589 isVisible = false,
|
bsw/jbe@1309
|
9590 visibleElements = ['img', 'video', 'picture', 'br', 'script', 'noscript',
|
bsw/jbe@1309
|
9591 'style', 'table', 'iframe', 'object', 'embed', 'audio',
|
bsw/jbe@1309
|
9592 'svg', 'input', 'button', 'select','textarea', 'canvas'];
|
bsw/jbe@1309
|
9593
|
bsw/jbe@1309
|
9594 return function(el) {
|
bsw/jbe@1309
|
9595
|
bsw/jbe@1309
|
9596 // has visible innertext. so is visible
|
bsw/jbe@1309
|
9597 txt = (el.innerText || el.textContent).replace(/\s/g, '');
|
bsw/jbe@1309
|
9598 if (txt && txt.length > 0) {
|
bsw/jbe@1309
|
9599 return true;
|
bsw/jbe@1309
|
9600 }
|
bsw/jbe@1309
|
9601
|
bsw/jbe@1309
|
9602 // matches list of visible dimensioned elements
|
bsw/jbe@1309
|
9603 for (var i = visibleElements.length; i--;) {
|
bsw/jbe@1309
|
9604 if (el.querySelector(visibleElements[i])) {
|
bsw/jbe@1309
|
9605 return true;
|
bsw/jbe@1309
|
9606 }
|
bsw/jbe@1309
|
9607 }
|
bsw/jbe@1309
|
9608
|
bsw/jbe@1309
|
9609 // try to measure dimesions in last resort. (can find only of elements in dom)
|
bsw/jbe@1309
|
9610 if (el.offsetWidth && el.offsetWidth > 0 && el.offsetHeight && el.offsetHeight > 0) {
|
bsw/jbe@1309
|
9611 return true;
|
bsw/jbe@1309
|
9612 }
|
bsw/jbe@1309
|
9613
|
bsw/jbe@1309
|
9614 return false;
|
bsw/jbe@1309
|
9615 };
|
bsw/jbe@1309
|
9616 })()
|
bsw/jbe@1309
|
9617 };
|
bsw/jbe@1309
|
9618
|
bsw/jbe@1309
|
9619 var elementHandlingMethods = {
|
bsw/jbe@1309
|
9620 unwrap: function (element) {
|
bsw/jbe@1309
|
9621 wysihtml.dom.unwrap(element);
|
bsw/jbe@1309
|
9622 },
|
bsw/jbe@1309
|
9623
|
bsw/jbe@1309
|
9624 remove: function (element) {
|
bsw/jbe@1309
|
9625 element.parentNode.removeChild(element);
|
bsw/jbe@1309
|
9626 }
|
bsw/jbe@1309
|
9627 };
|
bsw/jbe@1309
|
9628
|
bsw/jbe@1309
|
9629 return parse(elementOrHtml_current, config_current);
|
bsw/jbe@1309
|
9630 };
|
bsw/jbe@1309
|
9631
|
bsw/jbe@1309
|
9632 // does a selector query on element or array of elements
|
bsw/jbe@1309
|
9633 wysihtml.dom.query = function(elements, query) {
|
bsw/jbe@1309
|
9634 var ret = [],
|
bsw/jbe@1309
|
9635 q;
|
bsw/jbe@1309
|
9636
|
bsw/jbe@1309
|
9637 if (elements.nodeType) {
|
bsw/jbe@1309
|
9638 elements = [elements];
|
bsw/jbe@1309
|
9639 }
|
bsw/jbe@1309
|
9640
|
bsw/jbe@1309
|
9641 for (var e = 0, len = elements.length; e < len; e++) {
|
bsw/jbe@1309
|
9642 q = elements[e].querySelectorAll(query);
|
bsw/jbe@1309
|
9643 if (q) {
|
bsw/jbe@1309
|
9644 for(var i = q.length; i--; ret.unshift(q[i]));
|
bsw/jbe@1309
|
9645 }
|
bsw/jbe@1309
|
9646 }
|
bsw/jbe@1309
|
9647 return ret;
|
bsw/jbe@1309
|
9648 };
|
bsw/jbe@1309
|
9649
|
bsw/jbe@1309
|
9650 /**
|
bsw/jbe@1309
|
9651 * Checks for empty text node childs and removes them
|
bsw/jbe@1309
|
9652 *
|
bsw/jbe@1309
|
9653 * @param {Element} node The element in which to cleanup
|
bsw/jbe@1309
|
9654 * @example
|
bsw/jbe@1309
|
9655 * wysihtml.dom.removeEmptyTextNodes(element);
|
bsw/jbe@1309
|
9656 */
|
bsw/jbe@1309
|
9657 wysihtml.dom.removeEmptyTextNodes = function(node) {
|
bsw/jbe@1309
|
9658 var childNode,
|
bsw/jbe@1309
|
9659 childNodes = wysihtml.lang.array(node.childNodes).get(),
|
bsw/jbe@1309
|
9660 childNodesLength = childNodes.length,
|
bsw/jbe@1309
|
9661 i = 0;
|
bsw/jbe@1309
|
9662
|
bsw/jbe@1309
|
9663 for (; i<childNodesLength; i++) {
|
bsw/jbe@1309
|
9664 childNode = childNodes[i];
|
bsw/jbe@1309
|
9665 if (childNode.nodeType === wysihtml.TEXT_NODE && (/^[\n\r]*$/).test(childNode.data)) {
|
bsw/jbe@1309
|
9666 childNode.parentNode.removeChild(childNode);
|
bsw/jbe@1309
|
9667 }
|
bsw/jbe@1309
|
9668 }
|
bsw/jbe@1309
|
9669 };
|
bsw/jbe@1309
|
9670
|
bsw/jbe@1309
|
9671 wysihtml.dom.removeInvisibleSpaces = function(node) {
|
bsw/jbe@1309
|
9672 var textNodes = wysihtml.dom.getTextNodes(node);
|
bsw/jbe@1309
|
9673 for (var n = textNodes.length; n--;) {
|
bsw/jbe@1309
|
9674 textNodes[n].nodeValue = textNodes[n].nodeValue.replace(wysihtml.INVISIBLE_SPACE_REG_EXP, "");
|
bsw/jbe@1309
|
9675 }
|
bsw/jbe@1309
|
9676 };
|
bsw/jbe@1309
|
9677
|
bsw/jbe@1309
|
9678 /**
|
bsw/jbe@1309
|
9679 * Renames an element (eg. a <div> to a <p>) and keeps its childs
|
bsw/jbe@1309
|
9680 *
|
bsw/jbe@1309
|
9681 * @param {Element} element The list element which should be renamed
|
bsw/jbe@1309
|
9682 * @param {Element} newNodeName The desired tag name
|
bsw/jbe@1309
|
9683 *
|
bsw/jbe@1309
|
9684 * @example
|
bsw/jbe@1309
|
9685 * <!-- Assume the following dom: -->
|
bsw/jbe@1309
|
9686 * <ul id="list">
|
bsw/jbe@1309
|
9687 * <li>eminem</li>
|
bsw/jbe@1309
|
9688 * <li>dr. dre</li>
|
bsw/jbe@1309
|
9689 * <li>50 Cent</li>
|
bsw/jbe@1309
|
9690 * </ul>
|
bsw/jbe@1309
|
9691 *
|
bsw/jbe@1309
|
9692 * <script>
|
bsw/jbe@1309
|
9693 * wysihtml.dom.renameElement(document.getElementById("list"), "ol");
|
bsw/jbe@1309
|
9694 * </script>
|
bsw/jbe@1309
|
9695 *
|
bsw/jbe@1309
|
9696 * <!-- Will result in: -->
|
bsw/jbe@1309
|
9697 * <ol>
|
bsw/jbe@1309
|
9698 * <li>eminem</li>
|
bsw/jbe@1309
|
9699 * <li>dr. dre</li>
|
bsw/jbe@1309
|
9700 * <li>50 Cent</li>
|
bsw/jbe@1309
|
9701 * </ol>
|
bsw/jbe@1309
|
9702 */
|
bsw/jbe@1309
|
9703 wysihtml.dom.renameElement = function(element, newNodeName) {
|
bsw/jbe@1309
|
9704 var newElement = element.ownerDocument.createElement(newNodeName),
|
bsw/jbe@1309
|
9705 firstChild;
|
bsw/jbe@1309
|
9706 while (firstChild = element.firstChild) {
|
bsw/jbe@1309
|
9707 newElement.appendChild(firstChild);
|
bsw/jbe@1309
|
9708 }
|
bsw/jbe@1309
|
9709 wysihtml.dom.copyAttributes(["align", "className"]).from(element).to(newElement);
|
bsw/jbe@1309
|
9710
|
bsw/jbe@1309
|
9711 if (element.parentNode) {
|
bsw/jbe@1309
|
9712 element.parentNode.replaceChild(newElement, element);
|
bsw/jbe@1309
|
9713 }
|
bsw/jbe@1309
|
9714
|
bsw/jbe@1309
|
9715 return newElement;
|
bsw/jbe@1309
|
9716 };
|
bsw/jbe@1309
|
9717
|
bsw/jbe@1309
|
9718 /**
|
bsw/jbe@1309
|
9719 * Takes an element, removes it and replaces it with it's childs
|
bsw/jbe@1309
|
9720 *
|
bsw/jbe@1309
|
9721 * @param {Object} node The node which to replace with it's child nodes
|
bsw/jbe@1309
|
9722 * @example
|
bsw/jbe@1309
|
9723 * <div id="foo">
|
bsw/jbe@1309
|
9724 * <span>hello</span>
|
bsw/jbe@1309
|
9725 * </div>
|
bsw/jbe@1309
|
9726 * <script>
|
bsw/jbe@1309
|
9727 * // Remove #foo and replace with it's children
|
bsw/jbe@1309
|
9728 * wysihtml.dom.replaceWithChildNodes(document.getElementById("foo"));
|
bsw/jbe@1309
|
9729 * </script>
|
bsw/jbe@1309
|
9730 */
|
bsw/jbe@1309
|
9731 wysihtml.dom.replaceWithChildNodes = function(node) {
|
bsw/jbe@1309
|
9732 if (!node.parentNode) {
|
bsw/jbe@1309
|
9733 return;
|
bsw/jbe@1309
|
9734 }
|
bsw/jbe@1309
|
9735
|
bsw/jbe@1309
|
9736 while (node.firstChild) {
|
bsw/jbe@1309
|
9737 node.parentNode.insertBefore(node.firstChild, node);
|
bsw/jbe@1309
|
9738 }
|
bsw/jbe@1309
|
9739 node.parentNode.removeChild(node);
|
bsw/jbe@1309
|
9740 };
|
bsw/jbe@1309
|
9741
|
bsw/jbe@1309
|
9742 /**
|
bsw/jbe@1309
|
9743 * Unwraps an unordered/ordered list
|
bsw/jbe@1309
|
9744 *
|
bsw/jbe@1309
|
9745 * @param {Element} element The list element which should be unwrapped
|
bsw/jbe@1309
|
9746 *
|
bsw/jbe@1309
|
9747 * @example
|
bsw/jbe@1309
|
9748 * <!-- Assume the following dom: -->
|
bsw/jbe@1309
|
9749 * <ul id="list">
|
bsw/jbe@1309
|
9750 * <li>eminem</li>
|
bsw/jbe@1309
|
9751 * <li>dr. dre</li>
|
bsw/jbe@1309
|
9752 * <li>50 Cent</li>
|
bsw/jbe@1309
|
9753 * </ul>
|
bsw/jbe@1309
|
9754 *
|
bsw/jbe@1309
|
9755 * <script>
|
bsw/jbe@1309
|
9756 * wysihtml.dom.resolveList(document.getElementById("list"));
|
bsw/jbe@1309
|
9757 * </script>
|
bsw/jbe@1309
|
9758 *
|
bsw/jbe@1309
|
9759 * <!-- Will result in: -->
|
bsw/jbe@1309
|
9760 * eminem<br>
|
bsw/jbe@1309
|
9761 * dr. dre<br>
|
bsw/jbe@1309
|
9762 * 50 Cent<br>
|
bsw/jbe@1309
|
9763 */
|
bsw/jbe@1309
|
9764 (function(dom) {
|
bsw/jbe@1309
|
9765 function _isBlockElement(node) {
|
bsw/jbe@1309
|
9766 return dom.getStyle("display").from(node) === "block";
|
bsw/jbe@1309
|
9767 }
|
bsw/jbe@1309
|
9768
|
bsw/jbe@1309
|
9769 function _isLineBreak(node) {
|
bsw/jbe@1309
|
9770 return node.nodeName === "BR";
|
bsw/jbe@1309
|
9771 }
|
bsw/jbe@1309
|
9772
|
bsw/jbe@1309
|
9773 function _appendLineBreak(element) {
|
bsw/jbe@1309
|
9774 var lineBreak = element.ownerDocument.createElement("br");
|
bsw/jbe@1309
|
9775 element.appendChild(lineBreak);
|
bsw/jbe@1309
|
9776 }
|
bsw/jbe@1309
|
9777
|
bsw/jbe@1309
|
9778 function resolveList(list, useLineBreaks) {
|
bsw/jbe@1309
|
9779 if (!list.nodeName.match(/^(MENU|UL|OL)$/)) {
|
bsw/jbe@1309
|
9780 return;
|
bsw/jbe@1309
|
9781 }
|
bsw/jbe@1309
|
9782
|
bsw/jbe@1309
|
9783 var doc = list.ownerDocument,
|
bsw/jbe@1309
|
9784 fragment = doc.createDocumentFragment(),
|
bsw/jbe@1309
|
9785 previousSibling = wysihtml.dom.domNode(list).prev({ignoreBlankTexts: true}),
|
bsw/jbe@1309
|
9786 nextSibling = wysihtml.dom.domNode(list).next({ignoreBlankTexts: true}),
|
bsw/jbe@1309
|
9787 firstChild,
|
bsw/jbe@1309
|
9788 lastChild,
|
bsw/jbe@1309
|
9789 isLastChild,
|
bsw/jbe@1309
|
9790 shouldAppendLineBreak,
|
bsw/jbe@1309
|
9791 paragraph,
|
bsw/jbe@1309
|
9792 listItem,
|
bsw/jbe@1309
|
9793 lastListItem = list.lastElementChild || list.lastChild,
|
bsw/jbe@1309
|
9794 isLastItem;
|
bsw/jbe@1309
|
9795
|
bsw/jbe@1309
|
9796 if (useLineBreaks) {
|
bsw/jbe@1309
|
9797 // Insert line break if list is after a non-block element
|
bsw/jbe@1309
|
9798 if (previousSibling && !_isBlockElement(previousSibling) && !_isLineBreak(previousSibling)) {
|
bsw/jbe@1309
|
9799 _appendLineBreak(fragment);
|
bsw/jbe@1309
|
9800 }
|
bsw/jbe@1309
|
9801
|
bsw/jbe@1309
|
9802 while (listItem = (list.firstElementChild || list.firstChild)) {
|
bsw/jbe@1309
|
9803 lastChild = listItem.lastChild;
|
bsw/jbe@1309
|
9804 isLastItem = listItem === lastListItem;
|
bsw/jbe@1309
|
9805 while (firstChild = listItem.firstChild) {
|
bsw/jbe@1309
|
9806 isLastChild = firstChild === lastChild;
|
bsw/jbe@1309
|
9807 // This needs to be done before appending it to the fragment, as it otherwise will lose style information
|
bsw/jbe@1309
|
9808 shouldAppendLineBreak = (!isLastItem || (nextSibling && !_isBlockElement(nextSibling))) && isLastChild && !_isBlockElement(firstChild) && !_isLineBreak(firstChild);
|
bsw/jbe@1309
|
9809 fragment.appendChild(firstChild);
|
bsw/jbe@1309
|
9810 if (shouldAppendLineBreak) {
|
bsw/jbe@1309
|
9811 _appendLineBreak(fragment);
|
bsw/jbe@1309
|
9812 }
|
bsw/jbe@1309
|
9813 }
|
bsw/jbe@1309
|
9814
|
bsw/jbe@1309
|
9815 listItem.parentNode.removeChild(listItem);
|
bsw/jbe@1309
|
9816 }
|
bsw/jbe@1309
|
9817 } else {
|
bsw/jbe@1309
|
9818 while (listItem = (list.firstElementChild || list.firstChild)) {
|
bsw/jbe@1309
|
9819 if (listItem.querySelector && listItem.querySelector("div, p, ul, ol, menu, blockquote, h1, h2, h3, h4, h5, h6")) {
|
bsw/jbe@1309
|
9820 while (firstChild = listItem.firstChild) {
|
bsw/jbe@1309
|
9821 fragment.appendChild(firstChild);
|
bsw/jbe@1309
|
9822 }
|
bsw/jbe@1309
|
9823 } else {
|
bsw/jbe@1309
|
9824 paragraph = doc.createElement("p");
|
bsw/jbe@1309
|
9825 while (firstChild = listItem.firstChild) {
|
bsw/jbe@1309
|
9826 paragraph.appendChild(firstChild);
|
bsw/jbe@1309
|
9827 }
|
bsw/jbe@1309
|
9828 fragment.appendChild(paragraph);
|
bsw/jbe@1309
|
9829 }
|
bsw/jbe@1309
|
9830 listItem.parentNode.removeChild(listItem);
|
bsw/jbe@1309
|
9831 }
|
bsw/jbe@1309
|
9832 }
|
bsw/jbe@1309
|
9833
|
bsw/jbe@1309
|
9834 list.parentNode.replaceChild(fragment, list);
|
bsw/jbe@1309
|
9835 }
|
bsw/jbe@1309
|
9836
|
bsw/jbe@1309
|
9837 dom.resolveList = resolveList;
|
bsw/jbe@1309
|
9838 })(wysihtml.dom);
|
bsw/jbe@1309
|
9839
|
bsw/jbe@1309
|
9840 /**
|
bsw/jbe@1309
|
9841 * Sandbox for executing javascript, parsing css styles and doing dom operations in a secure way
|
bsw/jbe@1309
|
9842 *
|
bsw/jbe@1309
|
9843 * Browser Compatibility:
|
bsw/jbe@1309
|
9844 * - Secure in MSIE 6+, but only when the user hasn't made changes to his security level "restricted"
|
bsw/jbe@1309
|
9845 * - Partially secure in other browsers (Firefox, Opera, Safari, Chrome, ...)
|
bsw/jbe@1309
|
9846 *
|
bsw/jbe@1309
|
9847 * Please note that this class can't benefit from the HTML5 sandbox attribute for the following reasons:
|
bsw/jbe@1309
|
9848 * - sandboxing doesn't work correctly with inlined content (src="javascript:'<html>...</html>'")
|
bsw/jbe@1309
|
9849 * - sandboxing of physical documents causes that the dom isn't accessible anymore from the outside (iframe.contentWindow, ...)
|
bsw/jbe@1309
|
9850 * - setting the "allow-same-origin" flag would fix that, but then still javascript and dom events refuse to fire
|
bsw/jbe@1309
|
9851 * - therefore the "allow-scripts" flag is needed, which then would deactivate any security, as the js executed inside the iframe
|
bsw/jbe@1309
|
9852 * can do anything as if the sandbox attribute wasn't set
|
bsw/jbe@1309
|
9853 *
|
bsw/jbe@1309
|
9854 * @param {Function} [readyCallback] Method that gets invoked when the sandbox is ready
|
bsw/jbe@1309
|
9855 * @param {Object} [config] Optional parameters
|
bsw/jbe@1309
|
9856 *
|
bsw/jbe@1309
|
9857 * @example
|
bsw/jbe@1309
|
9858 * new wysihtml.dom.Sandbox(function(sandbox) {
|
bsw/jbe@1309
|
9859 * sandbox.getWindow().document.body.innerHTML = '<img src=foo.gif onerror="alert(document.cookie)">';
|
bsw/jbe@1309
|
9860 * });
|
bsw/jbe@1309
|
9861 */
|
bsw/jbe@1309
|
9862 (function(wysihtml) {
|
bsw/jbe@1309
|
9863 var /**
|
bsw/jbe@1309
|
9864 * Default configuration
|
bsw/jbe@1309
|
9865 */
|
bsw/jbe@1309
|
9866 doc = document,
|
bsw/jbe@1309
|
9867 /**
|
bsw/jbe@1309
|
9868 * Properties to unset/protect on the window object
|
bsw/jbe@1309
|
9869 */
|
bsw/jbe@1309
|
9870 windowProperties = [
|
bsw/jbe@1309
|
9871 "parent", "top", "opener", "frameElement", "frames",
|
bsw/jbe@1309
|
9872 "localStorage", "globalStorage", "sessionStorage", "indexedDB"
|
bsw/jbe@1309
|
9873 ],
|
bsw/jbe@1309
|
9874 /**
|
bsw/jbe@1309
|
9875 * Properties on the window object which are set to an empty function
|
bsw/jbe@1309
|
9876 */
|
bsw/jbe@1309
|
9877 windowProperties2 = [
|
bsw/jbe@1309
|
9878 "open", "close", "openDialog", "showModalDialog",
|
bsw/jbe@1309
|
9879 "alert", "confirm", "prompt",
|
bsw/jbe@1309
|
9880 "openDatabase", "postMessage",
|
bsw/jbe@1309
|
9881 "XMLHttpRequest", "XDomainRequest"
|
bsw/jbe@1309
|
9882 ],
|
bsw/jbe@1309
|
9883 /**
|
bsw/jbe@1309
|
9884 * Properties to unset/protect on the document object
|
bsw/jbe@1309
|
9885 */
|
bsw/jbe@1309
|
9886 documentProperties = [
|
bsw/jbe@1309
|
9887 "referrer",
|
bsw/jbe@1309
|
9888 "write", "open", "close"
|
bsw/jbe@1309
|
9889 ];
|
bsw/jbe@1309
|
9890
|
bsw/jbe@1309
|
9891 wysihtml.dom.Sandbox = Base.extend(
|
bsw/jbe@1309
|
9892 /** @scope wysihtml.dom.Sandbox.prototype */ {
|
bsw/jbe@1309
|
9893
|
bsw/jbe@1309
|
9894 constructor: function(readyCallback, config) {
|
bsw/jbe@1309
|
9895 this.callback = readyCallback || wysihtml.EMPTY_FUNCTION;
|
bsw/jbe@1309
|
9896 this.config = wysihtml.lang.object({}).merge(config).get();
|
bsw/jbe@1309
|
9897 if (!this.config.className) {
|
bsw/jbe@1309
|
9898 this.config.className = "wysihtml-sandbox";
|
bsw/jbe@1309
|
9899 }
|
bsw/jbe@1309
|
9900 this.editableArea = this._createIframe();
|
bsw/jbe@1309
|
9901 },
|
bsw/jbe@1309
|
9902
|
bsw/jbe@1309
|
9903 insertInto: function(element) {
|
bsw/jbe@1309
|
9904 if (typeof(element) === "string") {
|
bsw/jbe@1309
|
9905 element = doc.getElementById(element);
|
bsw/jbe@1309
|
9906 }
|
bsw/jbe@1309
|
9907
|
bsw/jbe@1309
|
9908 element.appendChild(this.editableArea);
|
bsw/jbe@1309
|
9909 },
|
bsw/jbe@1309
|
9910
|
bsw/jbe@1309
|
9911 getIframe: function() {
|
bsw/jbe@1309
|
9912 return this.editableArea;
|
bsw/jbe@1309
|
9913 },
|
bsw/jbe@1309
|
9914
|
bsw/jbe@1309
|
9915 getWindow: function() {
|
bsw/jbe@1309
|
9916 this._readyError();
|
bsw/jbe@1309
|
9917 },
|
bsw/jbe@1309
|
9918
|
bsw/jbe@1309
|
9919 getDocument: function() {
|
bsw/jbe@1309
|
9920 this._readyError();
|
bsw/jbe@1309
|
9921 },
|
bsw/jbe@1309
|
9922
|
bsw/jbe@1309
|
9923 destroy: function() {
|
bsw/jbe@1309
|
9924 var iframe = this.getIframe();
|
bsw/jbe@1309
|
9925 iframe.parentNode.removeChild(iframe);
|
bsw/jbe@1309
|
9926 },
|
bsw/jbe@1309
|
9927
|
bsw/jbe@1309
|
9928 _readyError: function() {
|
bsw/jbe@1309
|
9929 throw new Error("wysihtml.Sandbox: Sandbox iframe isn't loaded yet");
|
bsw/jbe@1309
|
9930 },
|
bsw/jbe@1309
|
9931
|
bsw/jbe@1309
|
9932 /**
|
bsw/jbe@1309
|
9933 * Creates the sandbox iframe
|
bsw/jbe@1309
|
9934 *
|
bsw/jbe@1309
|
9935 * Some important notes:
|
bsw/jbe@1309
|
9936 * - We can't use HTML5 sandbox for now:
|
bsw/jbe@1309
|
9937 * setting it causes that the iframe's dom can't be accessed from the outside
|
bsw/jbe@1309
|
9938 * Therefore we need to set the "allow-same-origin" flag which enables accessing the iframe's dom
|
bsw/jbe@1309
|
9939 * But then there's another problem, DOM events (focus, blur, change, keypress, ...) aren't fired.
|
bsw/jbe@1309
|
9940 * In order to make this happen we need to set the "allow-scripts" flag.
|
bsw/jbe@1309
|
9941 * A combination of allow-scripts and allow-same-origin is almost the same as setting no sandbox attribute at all.
|
bsw/jbe@1309
|
9942 * - Chrome & Safari, doesn't seem to support sandboxing correctly when the iframe's html is inlined (no physical document)
|
bsw/jbe@1309
|
9943 * - IE needs to have the security="restricted" attribute set before the iframe is
|
bsw/jbe@1309
|
9944 * inserted into the dom tree
|
bsw/jbe@1309
|
9945 * - Believe it or not but in IE "security" in document.createElement("iframe") is false, even
|
bsw/jbe@1309
|
9946 * though it supports it
|
bsw/jbe@1309
|
9947 * - When an iframe has security="restricted", in IE eval() & execScript() don't work anymore
|
bsw/jbe@1309
|
9948 * - IE doesn't fire the onload event when the content is inlined in the src attribute, therefore we rely
|
bsw/jbe@1309
|
9949 * on the onreadystatechange event
|
bsw/jbe@1309
|
9950 */
|
bsw/jbe@1309
|
9951 _createIframe: function() {
|
bsw/jbe@1309
|
9952 var that = this,
|
bsw/jbe@1309
|
9953 iframe = doc.createElement("iframe");
|
bsw/jbe@1309
|
9954 iframe.className = this.config.className;
|
bsw/jbe@1309
|
9955 wysihtml.dom.setAttributes({
|
bsw/jbe@1309
|
9956 "security": "restricted",
|
bsw/jbe@1309
|
9957 "allowtransparency": "true",
|
bsw/jbe@1309
|
9958 "frameborder": 0,
|
bsw/jbe@1309
|
9959 "width": 0,
|
bsw/jbe@1309
|
9960 "height": 0,
|
bsw/jbe@1309
|
9961 "marginwidth": 0,
|
bsw/jbe@1309
|
9962 "marginheight": 0
|
bsw/jbe@1309
|
9963 }).on(iframe);
|
bsw/jbe@1309
|
9964
|
bsw/jbe@1309
|
9965 // Setting the src like this prevents ssl warnings in IE6
|
bsw/jbe@1309
|
9966 if (wysihtml.browser.throwsMixedContentWarningWhenIframeSrcIsEmpty()) {
|
bsw/jbe@1309
|
9967 iframe.src = "javascript:'<html></html>'";
|
bsw/jbe@1309
|
9968 }
|
bsw/jbe@1309
|
9969
|
bsw/jbe@1309
|
9970 iframe.onload = function() {
|
bsw/jbe@1309
|
9971 iframe.onreadystatechange = iframe.onload = null;
|
bsw/jbe@1309
|
9972 that._onLoadIframe(iframe);
|
bsw/jbe@1309
|
9973 };
|
bsw/jbe@1309
|
9974
|
bsw/jbe@1309
|
9975 iframe.onreadystatechange = function() {
|
bsw/jbe@1309
|
9976 if (/loaded|complete/.test(iframe.readyState)) {
|
bsw/jbe@1309
|
9977 iframe.onreadystatechange = iframe.onload = null;
|
bsw/jbe@1309
|
9978 that._onLoadIframe(iframe);
|
bsw/jbe@1309
|
9979 }
|
bsw/jbe@1309
|
9980 };
|
bsw/jbe@1309
|
9981
|
bsw/jbe@1309
|
9982 return iframe;
|
bsw/jbe@1309
|
9983 },
|
bsw/jbe@1309
|
9984
|
bsw/jbe@1309
|
9985 /**
|
bsw/jbe@1309
|
9986 * Callback for when the iframe has finished loading
|
bsw/jbe@1309
|
9987 */
|
bsw/jbe@1309
|
9988 _onLoadIframe: function(iframe) {
|
bsw/jbe@1309
|
9989 // don't resume when the iframe got unloaded (eg. by removing it from the dom)
|
bsw/jbe@1309
|
9990 if (!wysihtml.dom.contains(doc.documentElement, iframe)) {
|
bsw/jbe@1309
|
9991 return;
|
bsw/jbe@1309
|
9992 }
|
bsw/jbe@1309
|
9993
|
bsw/jbe@1309
|
9994 var that = this,
|
bsw/jbe@1309
|
9995 iframeWindow = iframe.contentWindow,
|
bsw/jbe@1309
|
9996 iframeDocument = iframe.contentWindow.document,
|
bsw/jbe@1309
|
9997 charset = doc.characterSet || doc.charset || "utf-8",
|
bsw/jbe@1309
|
9998 sandboxHtml = this._getHtml({
|
bsw/jbe@1309
|
9999 charset: charset,
|
bsw/jbe@1309
|
10000 stylesheets: this.config.stylesheets
|
bsw/jbe@1309
|
10001 });
|
bsw/jbe@1309
|
10002
|
bsw/jbe@1309
|
10003 // Create the basic dom tree including proper DOCTYPE and charset
|
bsw/jbe@1309
|
10004 iframeDocument.open("text/html", "replace");
|
bsw/jbe@1309
|
10005 iframeDocument.write(sandboxHtml);
|
bsw/jbe@1309
|
10006 iframeDocument.close();
|
bsw/jbe@1309
|
10007
|
bsw/jbe@1309
|
10008 this.getWindow = function() { return iframe.contentWindow; };
|
bsw/jbe@1309
|
10009 this.getDocument = function() { return iframe.contentWindow.document; };
|
bsw/jbe@1309
|
10010
|
bsw/jbe@1309
|
10011 // Catch js errors and pass them to the parent's onerror event
|
bsw/jbe@1309
|
10012 // addEventListener("error") doesn't work properly in some browsers
|
bsw/jbe@1309
|
10013 // TODO: apparently this doesn't work in IE9!
|
bsw/jbe@1309
|
10014 iframeWindow.onerror = function(errorMessage, fileName, lineNumber) {
|
bsw/jbe@1309
|
10015 throw new Error("wysihtml.Sandbox: " + errorMessage, fileName, lineNumber);
|
bsw/jbe@1309
|
10016 };
|
bsw/jbe@1309
|
10017
|
bsw/jbe@1309
|
10018 if (!wysihtml.browser.supportsSandboxedIframes()) {
|
bsw/jbe@1309
|
10019 // Unset a bunch of sensitive variables
|
bsw/jbe@1309
|
10020 // Please note: This isn't hack safe!
|
bsw/jbe@1309
|
10021 // It more or less just takes care of basic attacks and prevents accidental theft of sensitive information
|
bsw/jbe@1309
|
10022 // IE is secure though, which is the most important thing, since IE is the only browser, who
|
bsw/jbe@1309
|
10023 // takes over scripts & styles into contentEditable elements when copied from external websites
|
bsw/jbe@1309
|
10024 // or applications (Microsoft Word, ...)
|
bsw/jbe@1309
|
10025 var i, length;
|
bsw/jbe@1309
|
10026 for (i=0, length=windowProperties.length; i<length; i++) {
|
bsw/jbe@1309
|
10027 this._unset(iframeWindow, windowProperties[i]);
|
bsw/jbe@1309
|
10028 }
|
bsw/jbe@1309
|
10029 for (i=0, length=windowProperties2.length; i<length; i++) {
|
bsw/jbe@1309
|
10030 this._unset(iframeWindow, windowProperties2[i], wysihtml.EMPTY_FUNCTION);
|
bsw/jbe@1309
|
10031 }
|
bsw/jbe@1309
|
10032 for (i=0, length=documentProperties.length; i<length; i++) {
|
bsw/jbe@1309
|
10033 this._unset(iframeDocument, documentProperties[i]);
|
bsw/jbe@1309
|
10034 }
|
bsw/jbe@1309
|
10035 // This doesn't work in Safari 5
|
bsw/jbe@1309
|
10036 // See http://stackoverflow.com/questions/992461/is-it-possible-to-override-document-cookie-in-webkit
|
bsw/jbe@1309
|
10037 this._unset(iframeDocument, "cookie", "", true);
|
bsw/jbe@1309
|
10038 }
|
bsw/jbe@1309
|
10039
|
bsw/jbe@1309
|
10040 if (wysihtml.polyfills) {
|
bsw/jbe@1309
|
10041 wysihtml.polyfills(iframeWindow, iframeDocument).apply();
|
bsw/jbe@1309
|
10042 }
|
bsw/jbe@1309
|
10043
|
bsw/jbe@1309
|
10044 this.loaded = true;
|
bsw/jbe@1309
|
10045
|
bsw/jbe@1309
|
10046 // Trigger the callback
|
bsw/jbe@1309
|
10047 setTimeout(function() { that.callback(that); }, 0);
|
bsw/jbe@1309
|
10048 },
|
bsw/jbe@1309
|
10049
|
bsw/jbe@1309
|
10050 _getHtml: function(templateVars) {
|
bsw/jbe@1309
|
10051 var stylesheets = templateVars.stylesheets,
|
bsw/jbe@1309
|
10052 html = "",
|
bsw/jbe@1309
|
10053 i = 0,
|
bsw/jbe@1309
|
10054 length;
|
bsw/jbe@1309
|
10055 stylesheets = typeof(stylesheets) === "string" ? [stylesheets] : stylesheets;
|
bsw/jbe@1309
|
10056 if (stylesheets) {
|
bsw/jbe@1309
|
10057 length = stylesheets.length;
|
bsw/jbe@1309
|
10058 for (; i<length; i++) {
|
bsw/jbe@1309
|
10059 html += '<link rel="stylesheet" href="' + stylesheets[i] + '">';
|
bsw/jbe@1309
|
10060 }
|
bsw/jbe@1309
|
10061 }
|
bsw/jbe@1309
|
10062 templateVars.stylesheets = html;
|
bsw/jbe@1309
|
10063
|
bsw/jbe@1309
|
10064 return wysihtml.lang.string(
|
bsw/jbe@1309
|
10065 '<!DOCTYPE html><html><head>'
|
bsw/jbe@1309
|
10066 + '<meta charset="#{charset}">#{stylesheets}</head>'
|
bsw/jbe@1309
|
10067 + '<body></body></html>'
|
bsw/jbe@1309
|
10068 ).interpolate(templateVars);
|
bsw/jbe@1309
|
10069 },
|
bsw/jbe@1309
|
10070
|
bsw/jbe@1309
|
10071 /**
|
bsw/jbe@1309
|
10072 * Method to unset/override existing variables
|
bsw/jbe@1309
|
10073 * @example
|
bsw/jbe@1309
|
10074 * // Make cookie unreadable and unwritable
|
bsw/jbe@1309
|
10075 * this._unset(document, "cookie", "", true);
|
bsw/jbe@1309
|
10076 */
|
bsw/jbe@1309
|
10077 _unset: function(object, property, value, setter) {
|
bsw/jbe@1309
|
10078 try { object[property] = value; } catch(e) {}
|
bsw/jbe@1309
|
10079
|
bsw/jbe@1309
|
10080 try { object.__defineGetter__(property, function() { return value; }); } catch(e) {}
|
bsw/jbe@1309
|
10081 if (setter) {
|
bsw/jbe@1309
|
10082 try { object.__defineSetter__(property, function() {}); } catch(e) {}
|
bsw/jbe@1309
|
10083 }
|
bsw/jbe@1309
|
10084
|
bsw/jbe@1309
|
10085 if (!wysihtml.browser.crashesWhenDefineProperty(property)) {
|
bsw/jbe@1309
|
10086 try {
|
bsw/jbe@1309
|
10087 var config = {
|
bsw/jbe@1309
|
10088 get: function() { return value; }
|
bsw/jbe@1309
|
10089 };
|
bsw/jbe@1309
|
10090 if (setter) {
|
bsw/jbe@1309
|
10091 config.set = function() {};
|
bsw/jbe@1309
|
10092 }
|
bsw/jbe@1309
|
10093 Object.defineProperty(object, property, config);
|
bsw/jbe@1309
|
10094 } catch(e) {}
|
bsw/jbe@1309
|
10095 }
|
bsw/jbe@1309
|
10096 }
|
bsw/jbe@1309
|
10097 });
|
bsw/jbe@1309
|
10098 })(wysihtml);
|
bsw/jbe@1309
|
10099
|
bsw/jbe@1309
|
10100 (function() {
|
bsw/jbe@1309
|
10101 var mapping = {
|
bsw/jbe@1309
|
10102 "className": "class"
|
bsw/jbe@1309
|
10103 };
|
bsw/jbe@1309
|
10104 wysihtml.dom.setAttributes = function(attributes) {
|
bsw/jbe@1309
|
10105 return {
|
bsw/jbe@1309
|
10106 on: function(element) {
|
bsw/jbe@1309
|
10107 for (var i in attributes) {
|
bsw/jbe@1309
|
10108 element.setAttribute(mapping[i] || i, attributes[i]);
|
bsw/jbe@1309
|
10109 }
|
bsw/jbe@1309
|
10110 }
|
bsw/jbe@1309
|
10111 };
|
bsw/jbe@1309
|
10112 };
|
bsw/jbe@1309
|
10113 })();
|
bsw/jbe@1309
|
10114
|
bsw/jbe@1309
|
10115 wysihtml.dom.setStyles = function(styles) {
|
bsw/jbe@1309
|
10116 return {
|
bsw/jbe@1309
|
10117 on: function(element) {
|
bsw/jbe@1309
|
10118 var style = element.style;
|
bsw/jbe@1309
|
10119 if (typeof(styles) === "string") {
|
bsw/jbe@1309
|
10120 style.cssText += ";" + styles;
|
bsw/jbe@1309
|
10121 return;
|
bsw/jbe@1309
|
10122 }
|
bsw/jbe@1309
|
10123 for (var i in styles) {
|
bsw/jbe@1309
|
10124 if (i === "float") {
|
bsw/jbe@1309
|
10125 style.cssFloat = styles[i];
|
bsw/jbe@1309
|
10126 style.styleFloat = styles[i];
|
bsw/jbe@1309
|
10127 } else {
|
bsw/jbe@1309
|
10128 style[i] = styles[i];
|
bsw/jbe@1309
|
10129 }
|
bsw/jbe@1309
|
10130 }
|
bsw/jbe@1309
|
10131 }
|
bsw/jbe@1309
|
10132 };
|
bsw/jbe@1309
|
10133 };
|
bsw/jbe@1309
|
10134
|
bsw/jbe@1309
|
10135 /**
|
bsw/jbe@1309
|
10136 * Simulate HTML5 placeholder attribute
|
bsw/jbe@1309
|
10137 *
|
bsw/jbe@1309
|
10138 * Needed since
|
bsw/jbe@1309
|
10139 * - div[contentEditable] elements don't support it
|
bsw/jbe@1309
|
10140 * - older browsers (such as IE8 and Firefox 3.6) don't support it at all
|
bsw/jbe@1309
|
10141 *
|
bsw/jbe@1309
|
10142 * @param {Object} parent Instance of main wysihtml.Editor class
|
bsw/jbe@1309
|
10143 * @param {Element} view Instance of wysihtml.views.* class
|
bsw/jbe@1309
|
10144 * @param {String} placeholderText
|
bsw/jbe@1309
|
10145 *
|
bsw/jbe@1309
|
10146 * @example
|
bsw/jbe@1309
|
10147 * wysihtml.dom.simulatePlaceholder(this, composer, "Foobar");
|
bsw/jbe@1309
|
10148 */
|
bsw/jbe@1309
|
10149 (function(dom) {
|
bsw/jbe@1309
|
10150 dom.simulatePlaceholder = function(editor, view, placeholderText, placeholderClassName) {
|
bsw/jbe@1309
|
10151 var CLASS_NAME = placeholderClassName || "wysihtml-placeholder",
|
bsw/jbe@1309
|
10152 unset = function() {
|
bsw/jbe@1309
|
10153 var composerIsVisible = view.element.offsetWidth > 0 && view.element.offsetHeight > 0;
|
bsw/jbe@1309
|
10154 if (view.hasPlaceholderSet()) {
|
bsw/jbe@1309
|
10155 view.clear();
|
bsw/jbe@1309
|
10156 view.element.focus();
|
bsw/jbe@1309
|
10157 if (composerIsVisible ) {
|
bsw/jbe@1309
|
10158 setTimeout(function() {
|
bsw/jbe@1309
|
10159 var sel = view.selection.getSelection();
|
bsw/jbe@1309
|
10160 if (!sel.focusNode || !sel.anchorNode) {
|
bsw/jbe@1309
|
10161 view.selection.selectNode(view.element.firstChild || view.element);
|
bsw/jbe@1309
|
10162 }
|
bsw/jbe@1309
|
10163 }, 0);
|
bsw/jbe@1309
|
10164 }
|
bsw/jbe@1309
|
10165 }
|
bsw/jbe@1309
|
10166 view.placeholderSet = false;
|
bsw/jbe@1309
|
10167 dom.removeClass(view.element, CLASS_NAME);
|
bsw/jbe@1309
|
10168 },
|
bsw/jbe@1309
|
10169 set = function() {
|
bsw/jbe@1309
|
10170 if (view.isEmpty() && !view.placeholderSet) {
|
bsw/jbe@1309
|
10171 view.placeholderSet = true;
|
bsw/jbe@1309
|
10172 view.setValue(placeholderText, false);
|
bsw/jbe@1309
|
10173 dom.addClass(view.element, CLASS_NAME);
|
bsw/jbe@1309
|
10174 }
|
bsw/jbe@1309
|
10175 };
|
bsw/jbe@1309
|
10176
|
bsw/jbe@1309
|
10177 editor
|
bsw/jbe@1309
|
10178 .on("set_placeholder", set)
|
bsw/jbe@1309
|
10179 .on("unset_placeholder", unset)
|
bsw/jbe@1309
|
10180 .on("focus:composer", unset)
|
bsw/jbe@1309
|
10181 .on("paste:composer", unset)
|
bsw/jbe@1309
|
10182 .on("blur:composer", set);
|
bsw/jbe@1309
|
10183
|
bsw/jbe@1309
|
10184 set();
|
bsw/jbe@1309
|
10185 };
|
bsw/jbe@1309
|
10186 })(wysihtml.dom);
|
bsw/jbe@1309
|
10187
|
bsw/jbe@1309
|
10188 (function(dom) {
|
bsw/jbe@1309
|
10189 var documentElement = document.documentElement;
|
bsw/jbe@1309
|
10190 if ("textContent" in documentElement) {
|
bsw/jbe@1309
|
10191 dom.setTextContent = function(element, text) {
|
bsw/jbe@1309
|
10192 element.textContent = text;
|
bsw/jbe@1309
|
10193 };
|
bsw/jbe@1309
|
10194
|
bsw/jbe@1309
|
10195 dom.getTextContent = function(element) {
|
bsw/jbe@1309
|
10196 return element.textContent;
|
bsw/jbe@1309
|
10197 };
|
bsw/jbe@1309
|
10198 } else if ("innerText" in documentElement) {
|
bsw/jbe@1309
|
10199 dom.setTextContent = function(element, text) {
|
bsw/jbe@1309
|
10200 element.innerText = text;
|
bsw/jbe@1309
|
10201 };
|
bsw/jbe@1309
|
10202
|
bsw/jbe@1309
|
10203 dom.getTextContent = function(element) {
|
bsw/jbe@1309
|
10204 return element.innerText;
|
bsw/jbe@1309
|
10205 };
|
bsw/jbe@1309
|
10206 } else {
|
bsw/jbe@1309
|
10207 dom.setTextContent = function(element, text) {
|
bsw/jbe@1309
|
10208 element.nodeValue = text;
|
bsw/jbe@1309
|
10209 };
|
bsw/jbe@1309
|
10210
|
bsw/jbe@1309
|
10211 dom.getTextContent = function(element) {
|
bsw/jbe@1309
|
10212 return element.nodeValue;
|
bsw/jbe@1309
|
10213 };
|
bsw/jbe@1309
|
10214 }
|
bsw/jbe@1309
|
10215 })(wysihtml.dom);
|
bsw/jbe@1309
|
10216
|
bsw/jbe@1309
|
10217 /* Unwraps element and returns list of childNodes that the node contained.
|
bsw/jbe@1309
|
10218 *
|
bsw/jbe@1309
|
10219 * Example:
|
bsw/jbe@1309
|
10220 * var childnodes = wysihtml.dom.unwrap(document.querySelector('.unwrap-me'));
|
bsw/jbe@1309
|
10221 */
|
bsw/jbe@1309
|
10222
|
bsw/jbe@1309
|
10223 wysihtml.dom.unwrap = function(node) {
|
bsw/jbe@1309
|
10224 var children = [];
|
bsw/jbe@1309
|
10225 if (node.parentNode) {
|
bsw/jbe@1309
|
10226 while (node.lastChild) {
|
bsw/jbe@1309
|
10227 children.unshift(node.lastChild);
|
bsw/jbe@1309
|
10228 wysihtml.dom.insert(node.lastChild).after(node);
|
bsw/jbe@1309
|
10229 }
|
bsw/jbe@1309
|
10230 node.parentNode.removeChild(node);
|
bsw/jbe@1309
|
10231 }
|
bsw/jbe@1309
|
10232 return children;
|
bsw/jbe@1309
|
10233 };
|
bsw/jbe@1309
|
10234
|
bsw/jbe@1309
|
10235 /**
|
bsw/jbe@1309
|
10236 * Fix most common html formatting misbehaviors of browsers implementation when inserting
|
bsw/jbe@1309
|
10237 * content via copy & paste contentEditable
|
bsw/jbe@1309
|
10238 *
|
bsw/jbe@1309
|
10239 * @author Christopher Blum
|
bsw/jbe@1309
|
10240 */
|
bsw/jbe@1309
|
10241 wysihtml.quirks.cleanPastedHTML = (function() {
|
bsw/jbe@1309
|
10242
|
bsw/jbe@1309
|
10243 var styleToRegex = function (styleStr) {
|
bsw/jbe@1309
|
10244 var trimmedStr = wysihtml.lang.string(styleStr).trim(),
|
bsw/jbe@1309
|
10245 escapedStr = trimmedStr.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
|
bsw/jbe@1309
|
10246
|
bsw/jbe@1309
|
10247 return new RegExp("^((?!^" + escapedStr + "$).)*$", "i");
|
bsw/jbe@1309
|
10248 };
|
bsw/jbe@1309
|
10249
|
bsw/jbe@1309
|
10250 var extendRulesWithStyleExceptions = function (rules, exceptStyles) {
|
bsw/jbe@1309
|
10251 var newRules = wysihtml.lang.object(rules).clone(true),
|
bsw/jbe@1309
|
10252 tag, style;
|
bsw/jbe@1309
|
10253
|
bsw/jbe@1309
|
10254 for (tag in newRules.tags) {
|
bsw/jbe@1309
|
10255
|
bsw/jbe@1309
|
10256 if (newRules.tags.hasOwnProperty(tag)) {
|
bsw/jbe@1309
|
10257 if (newRules.tags[tag].keep_styles) {
|
bsw/jbe@1309
|
10258 for (style in newRules.tags[tag].keep_styles) {
|
bsw/jbe@1309
|
10259 if (newRules.tags[tag].keep_styles.hasOwnProperty(style)) {
|
bsw/jbe@1309
|
10260 if (exceptStyles[style]) {
|
bsw/jbe@1309
|
10261 newRules.tags[tag].keep_styles[style] = styleToRegex(exceptStyles[style]);
|
bsw/jbe@1309
|
10262 }
|
bsw/jbe@1309
|
10263 }
|
bsw/jbe@1309
|
10264 }
|
bsw/jbe@1309
|
10265 }
|
bsw/jbe@1309
|
10266 }
|
bsw/jbe@1309
|
10267 }
|
bsw/jbe@1309
|
10268
|
bsw/jbe@1309
|
10269 return newRules;
|
bsw/jbe@1309
|
10270 };
|
bsw/jbe@1309
|
10271
|
bsw/jbe@1309
|
10272 var pickRuleset = function(ruleset, html) {
|
bsw/jbe@1309
|
10273 var pickedSet, defaultSet;
|
bsw/jbe@1309
|
10274
|
bsw/jbe@1309
|
10275 if (!ruleset) {
|
bsw/jbe@1309
|
10276 return null;
|
bsw/jbe@1309
|
10277 }
|
bsw/jbe@1309
|
10278
|
bsw/jbe@1309
|
10279 for (var i = 0, max = ruleset.length; i < max; i++) {
|
bsw/jbe@1309
|
10280 if (!ruleset[i].condition) {
|
bsw/jbe@1309
|
10281 defaultSet = ruleset[i].set;
|
bsw/jbe@1309
|
10282 }
|
bsw/jbe@1309
|
10283 if (ruleset[i].condition && ruleset[i].condition.test(html)) {
|
bsw/jbe@1309
|
10284 return ruleset[i].set;
|
bsw/jbe@1309
|
10285 }
|
bsw/jbe@1309
|
10286 }
|
bsw/jbe@1309
|
10287
|
bsw/jbe@1309
|
10288 return defaultSet;
|
bsw/jbe@1309
|
10289 };
|
bsw/jbe@1309
|
10290
|
bsw/jbe@1309
|
10291 return function(html, options) {
|
bsw/jbe@1309
|
10292 var exceptStyles = {
|
bsw/jbe@1309
|
10293 'color': wysihtml.dom.getStyle("color").from(options.referenceNode),
|
bsw/jbe@1309
|
10294 'fontSize': wysihtml.dom.getStyle("font-size").from(options.referenceNode)
|
bsw/jbe@1309
|
10295 },
|
bsw/jbe@1309
|
10296 rules = extendRulesWithStyleExceptions(pickRuleset(options.rules, html) || {}, exceptStyles),
|
bsw/jbe@1309
|
10297 newHtml;
|
bsw/jbe@1309
|
10298
|
bsw/jbe@1309
|
10299 newHtml = wysihtml.dom.parse(html, {
|
bsw/jbe@1309
|
10300 "rules": rules,
|
bsw/jbe@1309
|
10301 "cleanUp": true, // <span> elements, empty or without attributes, should be removed/replaced with their content
|
bsw/jbe@1309
|
10302 "context": options.referenceNode.ownerDocument,
|
bsw/jbe@1309
|
10303 "uneditableClass": options.uneditableClass,
|
bsw/jbe@1309
|
10304 "clearInternals" : true, // don't paste temprorary selection and other markings
|
bsw/jbe@1309
|
10305 "unjoinNbsps" : true
|
bsw/jbe@1309
|
10306 });
|
bsw/jbe@1309
|
10307
|
bsw/jbe@1309
|
10308 return newHtml;
|
bsw/jbe@1309
|
10309 };
|
bsw/jbe@1309
|
10310
|
bsw/jbe@1309
|
10311 })();
|
bsw/jbe@1309
|
10312
|
bsw/jbe@1309
|
10313 /**
|
bsw/jbe@1309
|
10314 * IE and Opera leave an empty paragraph in the contentEditable element after clearing it
|
bsw/jbe@1309
|
10315 *
|
bsw/jbe@1309
|
10316 * @param {Object} contentEditableElement The contentEditable element to observe for clearing events
|
bsw/jbe@1309
|
10317 * @exaple
|
bsw/jbe@1309
|
10318 * wysihtml.quirks.ensureProperClearing(myContentEditableElement);
|
bsw/jbe@1309
|
10319 */
|
bsw/jbe@1309
|
10320 wysihtml.quirks.ensureProperClearing = (function() {
|
bsw/jbe@1309
|
10321 var clearIfNecessary = function() {
|
bsw/jbe@1309
|
10322 var element = this;
|
bsw/jbe@1309
|
10323 setTimeout(function() {
|
bsw/jbe@1309
|
10324 var innerHTML = element.innerHTML.toLowerCase();
|
bsw/jbe@1309
|
10325 if (innerHTML == "<p> </p>" ||
|
bsw/jbe@1309
|
10326 innerHTML == "<p> </p><p> </p>") {
|
bsw/jbe@1309
|
10327 element.innerHTML = "";
|
bsw/jbe@1309
|
10328 }
|
bsw/jbe@1309
|
10329 }, 0);
|
bsw/jbe@1309
|
10330 };
|
bsw/jbe@1309
|
10331
|
bsw/jbe@1309
|
10332 return function(composer) {
|
bsw/jbe@1309
|
10333 wysihtml.dom.observe(composer.element, ["cut", "keydown"], clearIfNecessary);
|
bsw/jbe@1309
|
10334 };
|
bsw/jbe@1309
|
10335 })();
|
bsw/jbe@1309
|
10336
|
bsw/jbe@1309
|
10337 // See https://bugzilla.mozilla.org/show_bug.cgi?id=664398
|
bsw/jbe@1309
|
10338 //
|
bsw/jbe@1309
|
10339 // In Firefox this:
|
bsw/jbe@1309
|
10340 // var d = document.createElement("div");
|
bsw/jbe@1309
|
10341 // d.innerHTML ='<a href="~"></a>';
|
bsw/jbe@1309
|
10342 // d.innerHTML;
|
bsw/jbe@1309
|
10343 // will result in:
|
bsw/jbe@1309
|
10344 // <a href="%7E"></a>
|
bsw/jbe@1309
|
10345 // which is wrong
|
bsw/jbe@1309
|
10346 (function(wysihtml) {
|
bsw/jbe@1309
|
10347 var TILDE_ESCAPED = "%7E";
|
bsw/jbe@1309
|
10348 wysihtml.quirks.getCorrectInnerHTML = function(element) {
|
bsw/jbe@1309
|
10349 var innerHTML = element.innerHTML;
|
bsw/jbe@1309
|
10350 if (innerHTML.indexOf(TILDE_ESCAPED) === -1) {
|
bsw/jbe@1309
|
10351 return innerHTML;
|
bsw/jbe@1309
|
10352 }
|
bsw/jbe@1309
|
10353
|
bsw/jbe@1309
|
10354 var elementsWithTilde = element.querySelectorAll("[href*='~'], [src*='~']"),
|
bsw/jbe@1309
|
10355 url,
|
bsw/jbe@1309
|
10356 urlToSearch,
|
bsw/jbe@1309
|
10357 length,
|
bsw/jbe@1309
|
10358 i;
|
bsw/jbe@1309
|
10359 for (i=0, length=elementsWithTilde.length; i<length; i++) {
|
bsw/jbe@1309
|
10360 url = elementsWithTilde[i].href || elementsWithTilde[i].src;
|
bsw/jbe@1309
|
10361 urlToSearch = wysihtml.lang.string(url).replace("~").by(TILDE_ESCAPED);
|
bsw/jbe@1309
|
10362 innerHTML = wysihtml.lang.string(innerHTML).replace(urlToSearch).by(url);
|
bsw/jbe@1309
|
10363 }
|
bsw/jbe@1309
|
10364 return innerHTML;
|
bsw/jbe@1309
|
10365 };
|
bsw/jbe@1309
|
10366 })(wysihtml);
|
bsw/jbe@1309
|
10367
|
bsw/jbe@1309
|
10368 /**
|
bsw/jbe@1309
|
10369 * Force rerendering of a given element
|
bsw/jbe@1309
|
10370 * Needed to fix display misbehaviors of IE
|
bsw/jbe@1309
|
10371 *
|
bsw/jbe@1309
|
10372 * @param {Element} element The element object which needs to be rerendered
|
bsw/jbe@1309
|
10373 * @example
|
bsw/jbe@1309
|
10374 * wysihtml.quirks.redraw(document.body);
|
bsw/jbe@1309
|
10375 */
|
bsw/jbe@1309
|
10376 (function(wysihtml) {
|
bsw/jbe@1309
|
10377 var CLASS_NAME = "wysihtml-quirks-redraw";
|
bsw/jbe@1309
|
10378
|
bsw/jbe@1309
|
10379 wysihtml.quirks.redraw = function(element) {
|
bsw/jbe@1309
|
10380 wysihtml.dom.addClass(element, CLASS_NAME);
|
bsw/jbe@1309
|
10381 wysihtml.dom.removeClass(element, CLASS_NAME);
|
bsw/jbe@1309
|
10382
|
bsw/jbe@1309
|
10383 // Following hack is needed for firefox to make sure that image resize handles are properly removed
|
bsw/jbe@1309
|
10384 try {
|
bsw/jbe@1309
|
10385 var doc = element.ownerDocument;
|
bsw/jbe@1309
|
10386 doc.execCommand("italic", false, null);
|
bsw/jbe@1309
|
10387 doc.execCommand("italic", false, null);
|
bsw/jbe@1309
|
10388 } catch(e) {}
|
bsw/jbe@1309
|
10389 };
|
bsw/jbe@1309
|
10390 })(wysihtml);
|
bsw/jbe@1309
|
10391
|
bsw/jbe@1309
|
10392 (function(wysihtml) {
|
bsw/jbe@1309
|
10393
|
bsw/jbe@1309
|
10394 // List of supported color format parsing methods
|
bsw/jbe@1309
|
10395 // If radix is not defined 10 is expected as default
|
bsw/jbe@1309
|
10396 var colorParseMethods = {
|
bsw/jbe@1309
|
10397 rgba : {
|
bsw/jbe@1309
|
10398 regex: /^rgba\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*([\d\.]+)\s*\)/i,
|
bsw/jbe@1309
|
10399 name: "rgba"
|
bsw/jbe@1309
|
10400 },
|
bsw/jbe@1309
|
10401 rgb : {
|
bsw/jbe@1309
|
10402 regex: /^rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)/i,
|
bsw/jbe@1309
|
10403 name: "rgb"
|
bsw/jbe@1309
|
10404 },
|
bsw/jbe@1309
|
10405 hex6 : {
|
bsw/jbe@1309
|
10406 regex: /^#([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])/i,
|
bsw/jbe@1309
|
10407 name: "hex",
|
bsw/jbe@1309
|
10408 radix: 16
|
bsw/jbe@1309
|
10409 },
|
bsw/jbe@1309
|
10410 hex3 : {
|
bsw/jbe@1309
|
10411 regex: /^#([0-9a-f])([0-9a-f])([0-9a-f])/i,
|
bsw/jbe@1309
|
10412 name: "hex",
|
bsw/jbe@1309
|
10413 radix: 16
|
bsw/jbe@1309
|
10414 }
|
bsw/jbe@1309
|
10415 },
|
bsw/jbe@1309
|
10416 // Takes a style key name as an argument and makes a regex that can be used to the match key:value pair from style string
|
bsw/jbe@1309
|
10417 makeParamRegExp = function (p) {
|
bsw/jbe@1309
|
10418 return new RegExp("(^|\\s|;)" + p + "\\s*:\\s*[^;$]+", "gi");
|
bsw/jbe@1309
|
10419 };
|
bsw/jbe@1309
|
10420
|
bsw/jbe@1309
|
10421 // Takes color string value ("#abc", "rgb(1,2,3)", ...) as an argument and returns suitable parsing method for it
|
bsw/jbe@1309
|
10422 function getColorParseMethod (colorStr) {
|
bsw/jbe@1309
|
10423 var prop, colorTypeConf;
|
bsw/jbe@1309
|
10424
|
bsw/jbe@1309
|
10425 for (prop in colorParseMethods) {
|
bsw/jbe@1309
|
10426 if (!colorParseMethods.hasOwnProperty(prop)) { continue; }
|
bsw/jbe@1309
|
10427
|
bsw/jbe@1309
|
10428 colorTypeConf = colorParseMethods[prop];
|
bsw/jbe@1309
|
10429
|
bsw/jbe@1309
|
10430 if (colorTypeConf.regex.test(colorStr)) {
|
bsw/jbe@1309
|
10431 return colorTypeConf;
|
bsw/jbe@1309
|
10432 }
|
bsw/jbe@1309
|
10433 }
|
bsw/jbe@1309
|
10434 }
|
bsw/jbe@1309
|
10435
|
bsw/jbe@1309
|
10436 // Takes color string value ("#abc", "rgb(1,2,3)", ...) as an argument and returns the type of that color format "hex", "rgb", "rgba".
|
bsw/jbe@1309
|
10437 function getColorFormat (colorStr) {
|
bsw/jbe@1309
|
10438 var type = getColorParseMethod(colorStr);
|
bsw/jbe@1309
|
10439
|
bsw/jbe@1309
|
10440 return type ? type.name : undefined;
|
bsw/jbe@1309
|
10441 }
|
bsw/jbe@1309
|
10442
|
bsw/jbe@1309
|
10443 // Public API functions for styleParser
|
bsw/jbe@1309
|
10444 wysihtml.quirks.styleParser = {
|
bsw/jbe@1309
|
10445
|
bsw/jbe@1309
|
10446 // Takes color string value as an argument and returns suitable parsing method for it
|
bsw/jbe@1309
|
10447 getColorParseMethod : getColorParseMethod,
|
bsw/jbe@1309
|
10448
|
bsw/jbe@1309
|
10449 // Takes color string value as an argument and returns the type of that color format "hex", "rgb", "rgba".
|
bsw/jbe@1309
|
10450 getColorFormat : getColorFormat,
|
bsw/jbe@1309
|
10451
|
bsw/jbe@1309
|
10452 /* Parses a color string to and array of [red, green, blue, alpha].
|
bsw/jbe@1309
|
10453 * paramName: optional argument to parse color value directly from style string parameter
|
bsw/jbe@1309
|
10454 *
|
bsw/jbe@1309
|
10455 * Examples:
|
bsw/jbe@1309
|
10456 * var colorArray = wysihtml.quirks.styleParser.parseColor("#ABC"); // [170, 187, 204, 1]
|
bsw/jbe@1309
|
10457 * var colorArray = wysihtml.quirks.styleParser.parseColor("#AABBCC"); // [170, 187, 204, 1]
|
bsw/jbe@1309
|
10458 * var colorArray = wysihtml.quirks.styleParser.parseColor("rgb(1,2,3)"); // [1, 2, 3, 1]
|
bsw/jbe@1309
|
10459 * var colorArray = wysihtml.quirks.styleParser.parseColor("rgba(1,2,3,0.5)"); // [1, 2, 3, 0.5]
|
bsw/jbe@1309
|
10460 *
|
bsw/jbe@1309
|
10461 * var colorArray = wysihtml.quirks.styleParser.parseColor("background-color: #ABC; color: #000;", "background-color"); // [170, 187, 204, 1]
|
bsw/jbe@1309
|
10462 * var colorArray = wysihtml.quirks.styleParser.parseColor("background-color: #ABC; color: #000;", "color"); // [0, 0, 0, 1]
|
bsw/jbe@1309
|
10463 */
|
bsw/jbe@1309
|
10464 parseColor : function (stylesStr, paramName) {
|
bsw/jbe@1309
|
10465 var paramsRegex, params, colorType, colorMatch, radix,
|
bsw/jbe@1309
|
10466 colorStr = stylesStr;
|
bsw/jbe@1309
|
10467
|
bsw/jbe@1309
|
10468 if (paramName) {
|
bsw/jbe@1309
|
10469 paramsRegex = makeParamRegExp(paramName);
|
bsw/jbe@1309
|
10470
|
bsw/jbe@1309
|
10471 if (!(params = stylesStr.match(paramsRegex))) { return false; }
|
bsw/jbe@1309
|
10472
|
bsw/jbe@1309
|
10473 params = params.pop().split(":")[1];
|
bsw/jbe@1309
|
10474 colorStr = wysihtml.lang.string(params).trim();
|
bsw/jbe@1309
|
10475 }
|
bsw/jbe@1309
|
10476
|
bsw/jbe@1309
|
10477 if (!(colorType = getColorParseMethod(colorStr))) { return false; }
|
bsw/jbe@1309
|
10478 if (!(colorMatch = colorStr.match(colorType.regex))) { return false; }
|
bsw/jbe@1309
|
10479
|
bsw/jbe@1309
|
10480 radix = colorType.radix || 10;
|
bsw/jbe@1309
|
10481
|
bsw/jbe@1309
|
10482 if (colorType === colorParseMethods.hex3) {
|
bsw/jbe@1309
|
10483 colorMatch.shift();
|
bsw/jbe@1309
|
10484 colorMatch.push(1);
|
bsw/jbe@1309
|
10485 return wysihtml.lang.array(colorMatch).map(function(d, idx) {
|
bsw/jbe@1309
|
10486 return (idx < 3) ? (parseInt(d, radix) * radix) + parseInt(d, radix): parseFloat(d);
|
bsw/jbe@1309
|
10487 });
|
bsw/jbe@1309
|
10488 }
|
bsw/jbe@1309
|
10489
|
bsw/jbe@1309
|
10490 colorMatch.shift();
|
bsw/jbe@1309
|
10491
|
bsw/jbe@1309
|
10492 if (!colorMatch[3]) {
|
bsw/jbe@1309
|
10493 colorMatch.push(1);
|
bsw/jbe@1309
|
10494 }
|
bsw/jbe@1309
|
10495
|
bsw/jbe@1309
|
10496 return wysihtml.lang.array(colorMatch).map(function(d, idx) {
|
bsw/jbe@1309
|
10497 return (idx < 3) ? parseInt(d, radix): parseFloat(d);
|
bsw/jbe@1309
|
10498 });
|
bsw/jbe@1309
|
10499 },
|
bsw/jbe@1309
|
10500
|
bsw/jbe@1309
|
10501 /* Takes rgba color array [r,g,b,a] as a value and formats it to color string with given format type
|
bsw/jbe@1309
|
10502 * If no format is given, rgba/rgb is returned based on alpha value
|
bsw/jbe@1309
|
10503 *
|
bsw/jbe@1309
|
10504 * Example:
|
bsw/jbe@1309
|
10505 * var colorStr = wysihtml.quirks.styleParser.unparseColor([170, 187, 204, 1], "hash"); // "#AABBCC"
|
bsw/jbe@1309
|
10506 * var colorStr = wysihtml.quirks.styleParser.unparseColor([170, 187, 204, 1], "hex"); // "AABBCC"
|
bsw/jbe@1309
|
10507 * var colorStr = wysihtml.quirks.styleParser.unparseColor([170, 187, 204, 1], "csv"); // "170, 187, 204, 1"
|
bsw/jbe@1309
|
10508 * var colorStr = wysihtml.quirks.styleParser.unparseColor([170, 187, 204, 1], "rgba"); // "rgba(170,187,204,1)"
|
bsw/jbe@1309
|
10509 * var colorStr = wysihtml.quirks.styleParser.unparseColor([170, 187, 204, 1], "rgb"); // "rgb(170,187,204)"
|
bsw/jbe@1309
|
10510 *
|
bsw/jbe@1309
|
10511 * var colorStr = wysihtml.quirks.styleParser.unparseColor([170, 187, 204, 0.5]); // "rgba(170,187,204,0.5)"
|
bsw/jbe@1309
|
10512 * var colorStr = wysihtml.quirks.styleParser.unparseColor([170, 187, 204, 1]); // "rgb(170,187,204)"
|
bsw/jbe@1309
|
10513 */
|
bsw/jbe@1309
|
10514 unparseColor: function(val, colorFormat) {
|
bsw/jbe@1309
|
10515 var hexRadix = 16;
|
bsw/jbe@1309
|
10516
|
bsw/jbe@1309
|
10517 if (colorFormat === "hex") {
|
bsw/jbe@1309
|
10518 return (val[0].toString(hexRadix) + val[1].toString(hexRadix) + val[2].toString(hexRadix)).toUpperCase();
|
bsw/jbe@1309
|
10519 } else if (colorFormat === "hash") {
|
bsw/jbe@1309
|
10520 return "#" + (val[0].toString(hexRadix) + val[1].toString(hexRadix) + val[2].toString(hexRadix)).toUpperCase();
|
bsw/jbe@1309
|
10521 } else if (colorFormat === "rgb") {
|
bsw/jbe@1309
|
10522 return "rgb(" + val[0] + "," + val[1] + "," + val[2] + ")";
|
bsw/jbe@1309
|
10523 } else if (colorFormat === "rgba") {
|
bsw/jbe@1309
|
10524 return "rgba(" + val[0] + "," + val[1] + "," + val[2] + "," + val[3] + ")";
|
bsw/jbe@1309
|
10525 } else if (colorFormat === "csv") {
|
bsw/jbe@1309
|
10526 return val[0] + "," + val[1] + "," + val[2] + "," + val[3];
|
bsw/jbe@1309
|
10527 }
|
bsw/jbe@1309
|
10528
|
bsw/jbe@1309
|
10529 if (val[3] && val[3] !== 1) {
|
bsw/jbe@1309
|
10530 return "rgba(" + val[0] + "," + val[1] + "," + val[2] + "," + val[3] + ")";
|
bsw/jbe@1309
|
10531 } else {
|
bsw/jbe@1309
|
10532 return "rgb(" + val[0] + "," + val[1] + "," + val[2] + ")";
|
bsw/jbe@1309
|
10533 }
|
bsw/jbe@1309
|
10534 },
|
bsw/jbe@1309
|
10535
|
bsw/jbe@1309
|
10536 // Parses font size value from style string
|
bsw/jbe@1309
|
10537 parseFontSize: function(stylesStr) {
|
bsw/jbe@1309
|
10538 var params = stylesStr.match(makeParamRegExp("font-size"));
|
bsw/jbe@1309
|
10539 if (params) {
|
bsw/jbe@1309
|
10540 return wysihtml.lang.string(params[params.length - 1].split(":")[1]).trim();
|
bsw/jbe@1309
|
10541 }
|
bsw/jbe@1309
|
10542 return false;
|
bsw/jbe@1309
|
10543 }
|
bsw/jbe@1309
|
10544 };
|
bsw/jbe@1309
|
10545
|
bsw/jbe@1309
|
10546 })(wysihtml);
|
bsw/jbe@1309
|
10547
|
bsw/jbe@1309
|
10548 /**
|
bsw/jbe@1309
|
10549 * Selection API
|
bsw/jbe@1309
|
10550 *
|
bsw/jbe@1309
|
10551 * @example
|
bsw/jbe@1309
|
10552 * var selection = new wysihtml.Selection(editor);
|
bsw/jbe@1309
|
10553 */
|
bsw/jbe@1309
|
10554 (function(wysihtml) {
|
bsw/jbe@1309
|
10555 var dom = wysihtml.dom;
|
bsw/jbe@1309
|
10556
|
bsw/jbe@1309
|
10557 function _getCumulativeOffsetTop(element) {
|
bsw/jbe@1309
|
10558 var top = 0;
|
bsw/jbe@1309
|
10559 if (element.parentNode) {
|
bsw/jbe@1309
|
10560 do {
|
bsw/jbe@1309
|
10561 top += element.offsetTop || 0;
|
bsw/jbe@1309
|
10562 element = element.offsetParent;
|
bsw/jbe@1309
|
10563 } while (element);
|
bsw/jbe@1309
|
10564 }
|
bsw/jbe@1309
|
10565 return top;
|
bsw/jbe@1309
|
10566 }
|
bsw/jbe@1309
|
10567
|
bsw/jbe@1309
|
10568 // Provides the depth of ``descendant`` relative to ``ancestor``
|
bsw/jbe@1309
|
10569 function getDepth(ancestor, descendant) {
|
bsw/jbe@1309
|
10570 var ret = 0;
|
bsw/jbe@1309
|
10571 while (descendant !== ancestor) {
|
bsw/jbe@1309
|
10572 ret++;
|
bsw/jbe@1309
|
10573 descendant = descendant.parentNode;
|
bsw/jbe@1309
|
10574 if (!descendant)
|
bsw/jbe@1309
|
10575 throw new Error("not a descendant of ancestor!");
|
bsw/jbe@1309
|
10576 }
|
bsw/jbe@1309
|
10577 return ret;
|
bsw/jbe@1309
|
10578 }
|
bsw/jbe@1309
|
10579
|
bsw/jbe@1309
|
10580 function getRangeNode(node, offset) {
|
bsw/jbe@1309
|
10581 if (node.nodeType === 3) {
|
bsw/jbe@1309
|
10582 return node;
|
bsw/jbe@1309
|
10583 } else {
|
bsw/jbe@1309
|
10584 return node.childNodes[offset] || node;
|
bsw/jbe@1309
|
10585 }
|
bsw/jbe@1309
|
10586 }
|
bsw/jbe@1309
|
10587
|
bsw/jbe@1309
|
10588 function getWebkitSelectionFixNode(container) {
|
bsw/jbe@1309
|
10589 var blankNode = document.createElement('span');
|
bsw/jbe@1309
|
10590
|
bsw/jbe@1309
|
10591 var placeholderRemover = function(event) {
|
bsw/jbe@1309
|
10592 // Self-destructs the caret and keeps the text inserted into it by user
|
bsw/jbe@1309
|
10593 var lastChild;
|
bsw/jbe@1309
|
10594
|
bsw/jbe@1309
|
10595 container.removeEventListener('mouseup', placeholderRemover);
|
bsw/jbe@1309
|
10596 container.removeEventListener('keydown', placeholderRemover);
|
bsw/jbe@1309
|
10597 container.removeEventListener('touchstart', placeholderRemover);
|
bsw/jbe@1309
|
10598 container.removeEventListener('focus', placeholderRemover);
|
bsw/jbe@1309
|
10599 container.removeEventListener('blur', placeholderRemover);
|
bsw/jbe@1309
|
10600 container.removeEventListener('paste', delayedPlaceholderRemover);
|
bsw/jbe@1309
|
10601 container.removeEventListener('drop', delayedPlaceholderRemover);
|
bsw/jbe@1309
|
10602 container.removeEventListener('beforepaste', delayedPlaceholderRemover);
|
bsw/jbe@1309
|
10603
|
bsw/jbe@1309
|
10604 if (blankNode && blankNode.parentNode) {
|
bsw/jbe@1309
|
10605 blankNode.parentNode.removeChild(blankNode);
|
bsw/jbe@1309
|
10606 }
|
bsw/jbe@1309
|
10607 },
|
bsw/jbe@1309
|
10608 delayedPlaceholderRemover = function (event) {
|
bsw/jbe@1309
|
10609 if (blankNode && blankNode.parentNode) {
|
bsw/jbe@1309
|
10610 setTimeout(placeholderRemover, 0);
|
bsw/jbe@1309
|
10611 }
|
bsw/jbe@1309
|
10612 };
|
bsw/jbe@1309
|
10613
|
bsw/jbe@1309
|
10614 blankNode.appendChild(container.ownerDocument.createTextNode(wysihtml.INVISIBLE_SPACE));
|
bsw/jbe@1309
|
10615 blankNode.className = '_wysihtml-temp-caret-fix';
|
bsw/jbe@1309
|
10616 blankNode.style.display = 'block';
|
bsw/jbe@1309
|
10617 blankNode.style.minWidth = '1px';
|
bsw/jbe@1309
|
10618 blankNode.style.height = '0px';
|
bsw/jbe@1309
|
10619
|
bsw/jbe@1309
|
10620 container.addEventListener('mouseup', placeholderRemover);
|
bsw/jbe@1309
|
10621 container.addEventListener('keydown', placeholderRemover);
|
bsw/jbe@1309
|
10622 container.addEventListener('touchstart', placeholderRemover);
|
bsw/jbe@1309
|
10623 container.addEventListener('focus', placeholderRemover);
|
bsw/jbe@1309
|
10624 container.addEventListener('blur', placeholderRemover);
|
bsw/jbe@1309
|
10625 container.addEventListener('paste', delayedPlaceholderRemover);
|
bsw/jbe@1309
|
10626 container.addEventListener('drop', delayedPlaceholderRemover);
|
bsw/jbe@1309
|
10627 container.addEventListener('beforepaste', delayedPlaceholderRemover);
|
bsw/jbe@1309
|
10628
|
bsw/jbe@1309
|
10629 return blankNode;
|
bsw/jbe@1309
|
10630 }
|
bsw/jbe@1309
|
10631
|
bsw/jbe@1309
|
10632 // Should fix the obtained ranges that cannot surrond contents normally to apply changes upon
|
bsw/jbe@1309
|
10633 // Being considerate to firefox that sets range start start out of span and end inside on doubleclick initiated selection
|
bsw/jbe@1309
|
10634 function expandRangeToSurround(range) {
|
bsw/jbe@1309
|
10635 if (range.canSurroundContents()) return;
|
bsw/jbe@1309
|
10636
|
bsw/jbe@1309
|
10637 var common = range.commonAncestorContainer,
|
bsw/jbe@1309
|
10638 start_depth = getDepth(common, range.startContainer),
|
bsw/jbe@1309
|
10639 end_depth = getDepth(common, range.endContainer);
|
bsw/jbe@1309
|
10640
|
bsw/jbe@1309
|
10641 while(!range.canSurroundContents()) {
|
bsw/jbe@1309
|
10642 // In the following branches, we cannot just decrement the depth variables because the setStartBefore/setEndAfter may move the start or end of the range more than one level relative to ``common``. So we need to recompute the depth.
|
bsw/jbe@1309
|
10643 if (start_depth > end_depth) {
|
bsw/jbe@1309
|
10644 range.setStartBefore(range.startContainer);
|
bsw/jbe@1309
|
10645 start_depth = getDepth(common, range.startContainer);
|
bsw/jbe@1309
|
10646 }
|
bsw/jbe@1309
|
10647 else {
|
bsw/jbe@1309
|
10648 range.setEndAfter(range.endContainer);
|
bsw/jbe@1309
|
10649 end_depth = getDepth(common, range.endContainer);
|
bsw/jbe@1309
|
10650 }
|
bsw/jbe@1309
|
10651 }
|
bsw/jbe@1309
|
10652 }
|
bsw/jbe@1309
|
10653
|
bsw/jbe@1309
|
10654 wysihtml.Selection = Base.extend(
|
bsw/jbe@1309
|
10655 /** @scope wysihtml.Selection.prototype */ {
|
bsw/jbe@1309
|
10656 constructor: function(editor, contain, unselectableClass) {
|
bsw/jbe@1309
|
10657 // Make sure that our external range library is initialized
|
bsw/jbe@1309
|
10658 rangy.init();
|
bsw/jbe@1309
|
10659
|
bsw/jbe@1309
|
10660 this.editor = editor;
|
bsw/jbe@1309
|
10661 this.composer = editor.composer;
|
bsw/jbe@1309
|
10662 this.doc = this.composer.doc;
|
bsw/jbe@1309
|
10663 this.win = this.composer.win;
|
bsw/jbe@1309
|
10664 this.contain = contain;
|
bsw/jbe@1309
|
10665 this.unselectableClass = unselectableClass || false;
|
bsw/jbe@1309
|
10666 },
|
bsw/jbe@1309
|
10667
|
bsw/jbe@1309
|
10668 /**
|
bsw/jbe@1309
|
10669 * Get the current selection as a bookmark to be able to later restore it
|
bsw/jbe@1309
|
10670 *
|
bsw/jbe@1309
|
10671 * @return {Object} An object that represents the current selection
|
bsw/jbe@1309
|
10672 */
|
bsw/jbe@1309
|
10673 getBookmark: function() {
|
bsw/jbe@1309
|
10674 var range = this.getRange();
|
bsw/jbe@1309
|
10675 return range && range.cloneRange();
|
bsw/jbe@1309
|
10676 },
|
bsw/jbe@1309
|
10677
|
bsw/jbe@1309
|
10678 /**
|
bsw/jbe@1309
|
10679 * Restore a selection retrieved via wysihtml.Selection.prototype.getBookmark
|
bsw/jbe@1309
|
10680 *
|
bsw/jbe@1309
|
10681 * @param {Object} bookmark An object that represents the current selection
|
bsw/jbe@1309
|
10682 */
|
bsw/jbe@1309
|
10683 setBookmark: function(bookmark) {
|
bsw/jbe@1309
|
10684 if (!bookmark) {
|
bsw/jbe@1309
|
10685 return;
|
bsw/jbe@1309
|
10686 }
|
bsw/jbe@1309
|
10687
|
bsw/jbe@1309
|
10688 this.setSelection(bookmark);
|
bsw/jbe@1309
|
10689 },
|
bsw/jbe@1309
|
10690
|
bsw/jbe@1309
|
10691 /**
|
bsw/jbe@1309
|
10692 * Set the caret in front of the given node
|
bsw/jbe@1309
|
10693 *
|
bsw/jbe@1309
|
10694 * @param {Object} node The element or text node where to position the caret in front of
|
bsw/jbe@1309
|
10695 * @example
|
bsw/jbe@1309
|
10696 * selection.setBefore(myElement);
|
bsw/jbe@1309
|
10697 */
|
bsw/jbe@1309
|
10698 setBefore: function(node) {
|
bsw/jbe@1309
|
10699 var range = rangy.createRange(this.doc);
|
bsw/jbe@1309
|
10700 range.setStartBefore(node);
|
bsw/jbe@1309
|
10701 range.setEndBefore(node);
|
bsw/jbe@1309
|
10702 return this.setSelection(range);
|
bsw/jbe@1309
|
10703 },
|
bsw/jbe@1309
|
10704
|
bsw/jbe@1309
|
10705 // Constructs a self removing whitespace (ain absolute positioned span) for placing selection caret when normal methods fail.
|
bsw/jbe@1309
|
10706 // Webkit has an issue with placing caret into places where there are no textnodes near by.
|
bsw/jbe@1309
|
10707 createTemporaryCaretSpaceAfter: function (node) {
|
bsw/jbe@1309
|
10708 var caretPlaceholder = this.doc.createElement('span'),
|
bsw/jbe@1309
|
10709 caretPlaceholderText = this.doc.createTextNode(wysihtml.INVISIBLE_SPACE),
|
bsw/jbe@1309
|
10710 placeholderRemover = (function(event) {
|
bsw/jbe@1309
|
10711 // Self-destructs the caret and keeps the text inserted into it by user
|
bsw/jbe@1309
|
10712 var lastChild;
|
bsw/jbe@1309
|
10713
|
bsw/jbe@1309
|
10714 this.contain.removeEventListener('mouseup', placeholderRemover);
|
bsw/jbe@1309
|
10715 this.contain.removeEventListener('keydown', keyDownHandler);
|
bsw/jbe@1309
|
10716 this.contain.removeEventListener('touchstart', placeholderRemover);
|
bsw/jbe@1309
|
10717 this.contain.removeEventListener('focus', placeholderRemover);
|
bsw/jbe@1309
|
10718 this.contain.removeEventListener('blur', placeholderRemover);
|
bsw/jbe@1309
|
10719 this.contain.removeEventListener('paste', delayedPlaceholderRemover);
|
bsw/jbe@1309
|
10720 this.contain.removeEventListener('drop', delayedPlaceholderRemover);
|
bsw/jbe@1309
|
10721 this.contain.removeEventListener('beforepaste', delayedPlaceholderRemover);
|
bsw/jbe@1309
|
10722
|
bsw/jbe@1309
|
10723 // If user inserted sth it is in the placeholder and sgould be unwrapped and stripped of invisible whitespace hack
|
bsw/jbe@1309
|
10724 // Otherwise the wrapper can just be removed
|
bsw/jbe@1309
|
10725 if (caretPlaceholder && caretPlaceholder.parentNode) {
|
bsw/jbe@1309
|
10726 caretPlaceholder.innerHTML = caretPlaceholder.innerHTML.replace(wysihtml.INVISIBLE_SPACE_REG_EXP, "");
|
bsw/jbe@1309
|
10727 if ((/[^\s]+/).test(caretPlaceholder.innerHTML)) {
|
bsw/jbe@1309
|
10728 lastChild = caretPlaceholder.lastChild;
|
bsw/jbe@1309
|
10729 wysihtml.dom.unwrap(caretPlaceholder);
|
bsw/jbe@1309
|
10730 this.setAfter(lastChild);
|
bsw/jbe@1309
|
10731 } else {
|
bsw/jbe@1309
|
10732 caretPlaceholder.parentNode.removeChild(caretPlaceholder);
|
bsw/jbe@1309
|
10733 }
|
bsw/jbe@1309
|
10734
|
bsw/jbe@1309
|
10735 }
|
bsw/jbe@1309
|
10736 }).bind(this),
|
bsw/jbe@1309
|
10737 delayedPlaceholderRemover = function (event) {
|
bsw/jbe@1309
|
10738 if (caretPlaceholder && caretPlaceholder.parentNode) {
|
bsw/jbe@1309
|
10739 setTimeout(placeholderRemover, 0);
|
bsw/jbe@1309
|
10740 }
|
bsw/jbe@1309
|
10741 },
|
bsw/jbe@1309
|
10742 keyDownHandler = function(event) {
|
bsw/jbe@1309
|
10743 if (event.which !== 8 && event.which !== 91 && event.which !== 17 && (event.which !== 86 || (!event.ctrlKey && !event.metaKey))) {
|
bsw/jbe@1309
|
10744 placeholderRemover();
|
bsw/jbe@1309
|
10745 }
|
bsw/jbe@1309
|
10746 };
|
bsw/jbe@1309
|
10747
|
bsw/jbe@1309
|
10748 caretPlaceholder.className = '_wysihtml-temp-caret-fix';
|
bsw/jbe@1309
|
10749 caretPlaceholder.style.position = 'absolute';
|
bsw/jbe@1309
|
10750 caretPlaceholder.style.display = 'block';
|
bsw/jbe@1309
|
10751 caretPlaceholder.style.minWidth = '1px';
|
bsw/jbe@1309
|
10752 caretPlaceholder.style.zIndex = '99999';
|
bsw/jbe@1309
|
10753 caretPlaceholder.appendChild(caretPlaceholderText);
|
bsw/jbe@1309
|
10754
|
bsw/jbe@1309
|
10755 node.parentNode.insertBefore(caretPlaceholder, node.nextSibling);
|
bsw/jbe@1309
|
10756 this.setBefore(caretPlaceholderText);
|
bsw/jbe@1309
|
10757
|
bsw/jbe@1309
|
10758 // Remove the caret fix on any of the following events (some are delayed as content change happens after event)
|
bsw/jbe@1309
|
10759 this.contain.addEventListener('mouseup', placeholderRemover);
|
bsw/jbe@1309
|
10760 this.contain.addEventListener('keydown', keyDownHandler);
|
bsw/jbe@1309
|
10761 this.contain.addEventListener('touchstart', placeholderRemover);
|
bsw/jbe@1309
|
10762 this.contain.addEventListener('focus', placeholderRemover);
|
bsw/jbe@1309
|
10763 this.contain.addEventListener('blur', placeholderRemover);
|
bsw/jbe@1309
|
10764 this.contain.addEventListener('paste', delayedPlaceholderRemover);
|
bsw/jbe@1309
|
10765 this.contain.addEventListener('drop', delayedPlaceholderRemover);
|
bsw/jbe@1309
|
10766 this.contain.addEventListener('beforepaste', delayedPlaceholderRemover);
|
bsw/jbe@1309
|
10767
|
bsw/jbe@1309
|
10768 return caretPlaceholder;
|
bsw/jbe@1309
|
10769 },
|
bsw/jbe@1309
|
10770
|
bsw/jbe@1309
|
10771 /**
|
bsw/jbe@1309
|
10772 * Set the caret after the given node
|
bsw/jbe@1309
|
10773 *
|
bsw/jbe@1309
|
10774 * @param {Object} node The element or text node where to position the caret in front of
|
bsw/jbe@1309
|
10775 * @example
|
bsw/jbe@1309
|
10776 * selection.setBefore(myElement);
|
bsw/jbe@1309
|
10777 * callback is an optional parameter accepting a function to execute when selection ahs been set
|
bsw/jbe@1309
|
10778 */
|
bsw/jbe@1309
|
10779 setAfter: function(node, notVisual, callback) {
|
bsw/jbe@1309
|
10780 var win = this.win,
|
bsw/jbe@1309
|
10781 range = rangy.createRange(this.doc),
|
bsw/jbe@1309
|
10782 fixWebkitSelection = function() {
|
bsw/jbe@1309
|
10783 // Webkit fails to add selection if there are no textnodes in that region
|
bsw/jbe@1309
|
10784 // (like an uneditable container at the end of content).
|
bsw/jbe@1309
|
10785 var parent = node.parentNode,
|
bsw/jbe@1309
|
10786 lastSibling = parent ? parent.childNodes[parent.childNodes.length - 1] : null;
|
bsw/jbe@1309
|
10787
|
bsw/jbe@1309
|
10788 if (!sel || (lastSibling === node && node.nodeType === 1 && win.getComputedStyle(node).display === "block")) {
|
bsw/jbe@1309
|
10789 if (notVisual) {
|
bsw/jbe@1309
|
10790 // If setAfter is used as internal between actions, self-removing caretPlaceholder has simpler implementation
|
bsw/jbe@1309
|
10791 // and remove itself in call stack end instead on user interaction
|
bsw/jbe@1309
|
10792 var caretPlaceholder = this.doc.createTextNode(wysihtml.INVISIBLE_SPACE);
|
bsw/jbe@1309
|
10793 node.parentNode.insertBefore(caretPlaceholder, node.nextSibling);
|
bsw/jbe@1309
|
10794 this.selectNode(caretPlaceholder);
|
bsw/jbe@1309
|
10795 setTimeout(function() {
|
bsw/jbe@1309
|
10796 if (caretPlaceholder && caretPlaceholder.parentNode) {
|
bsw/jbe@1309
|
10797 caretPlaceholder.parentNode.removeChild(caretPlaceholder);
|
bsw/jbe@1309
|
10798 }
|
bsw/jbe@1309
|
10799 }, 0);
|
bsw/jbe@1309
|
10800 } else {
|
bsw/jbe@1309
|
10801 this.createTemporaryCaretSpaceAfter(node);
|
bsw/jbe@1309
|
10802 }
|
bsw/jbe@1309
|
10803 }
|
bsw/jbe@1309
|
10804 }.bind(this),
|
bsw/jbe@1309
|
10805 sel;
|
bsw/jbe@1309
|
10806
|
bsw/jbe@1309
|
10807 range.setStartAfter(node);
|
bsw/jbe@1309
|
10808 range.setEndAfter(node);
|
bsw/jbe@1309
|
10809
|
bsw/jbe@1309
|
10810 // In IE contenteditable must be focused before we can set selection
|
bsw/jbe@1309
|
10811 // thus setting the focus if activeElement is not this composer
|
bsw/jbe@1309
|
10812 if (!document.activeElement || document.activeElement !== this.composer.element) {
|
bsw/jbe@1309
|
10813 var scrollPos = this.composer.getScrollPos();
|
bsw/jbe@1309
|
10814 this.composer.element.focus();
|
bsw/jbe@1309
|
10815 this.composer.setScrollPos(scrollPos);
|
bsw/jbe@1309
|
10816 setTimeout(function() {
|
bsw/jbe@1309
|
10817 sel = this.setSelection(range);
|
bsw/jbe@1309
|
10818 fixWebkitSelection();
|
bsw/jbe@1309
|
10819 if (callback) {
|
bsw/jbe@1309
|
10820 callback(sel);
|
bsw/jbe@1309
|
10821 }
|
bsw/jbe@1309
|
10822 }.bind(this), 0);
|
bsw/jbe@1309
|
10823 } else {
|
bsw/jbe@1309
|
10824 sel = this.setSelection(range);
|
bsw/jbe@1309
|
10825 fixWebkitSelection();
|
bsw/jbe@1309
|
10826 if (callback) {
|
bsw/jbe@1309
|
10827 callback(sel);
|
bsw/jbe@1309
|
10828 }
|
bsw/jbe@1309
|
10829 }
|
bsw/jbe@1309
|
10830 },
|
bsw/jbe@1309
|
10831
|
bsw/jbe@1309
|
10832 /**
|
bsw/jbe@1309
|
10833 * Ability to select/mark nodes
|
bsw/jbe@1309
|
10834 *
|
bsw/jbe@1309
|
10835 * @param {Element} node The node/element to select
|
bsw/jbe@1309
|
10836 * @example
|
bsw/jbe@1309
|
10837 * selection.selectNode(document.getElementById("my-image"));
|
bsw/jbe@1309
|
10838 */
|
bsw/jbe@1309
|
10839 selectNode: function(node, avoidInvisibleSpace) {
|
bsw/jbe@1309
|
10840 var range = rangy.createRange(this.doc),
|
bsw/jbe@1309
|
10841 isElement = node.nodeType === wysihtml.ELEMENT_NODE,
|
bsw/jbe@1309
|
10842 canHaveHTML = "canHaveHTML" in node ? node.canHaveHTML : (node.nodeName !== "IMG"),
|
bsw/jbe@1309
|
10843 content = isElement ? node.innerHTML : node.data,
|
bsw/jbe@1309
|
10844 isEmpty = (content === "" || content === wysihtml.INVISIBLE_SPACE),
|
bsw/jbe@1309
|
10845 displayStyle = dom.getStyle("display").from(node),
|
bsw/jbe@1309
|
10846 isBlockElement = (displayStyle === "block" || displayStyle === "list-item");
|
bsw/jbe@1309
|
10847
|
bsw/jbe@1309
|
10848 if (isEmpty && isElement && canHaveHTML && !avoidInvisibleSpace) {
|
bsw/jbe@1309
|
10849 // Make sure that caret is visible in node by inserting a zero width no breaking space
|
bsw/jbe@1309
|
10850 try { node.innerHTML = wysihtml.INVISIBLE_SPACE; } catch(e) {}
|
bsw/jbe@1309
|
10851 }
|
bsw/jbe@1309
|
10852 if (canHaveHTML) {
|
bsw/jbe@1309
|
10853 range.selectNodeContents(node);
|
bsw/jbe@1309
|
10854 } else {
|
bsw/jbe@1309
|
10855 range.selectNode(node);
|
bsw/jbe@1309
|
10856 }
|
bsw/jbe@1309
|
10857
|
bsw/jbe@1309
|
10858 if (canHaveHTML && isEmpty && isElement) {
|
bsw/jbe@1309
|
10859 range.collapse(isBlockElement);
|
bsw/jbe@1309
|
10860 } else if (canHaveHTML && isEmpty) {
|
bsw/jbe@1309
|
10861 range.setStartAfter(node);
|
bsw/jbe@1309
|
10862 range.setEndAfter(node);
|
bsw/jbe@1309
|
10863 }
|
bsw/jbe@1309
|
10864
|
bsw/jbe@1309
|
10865 this.setSelection(range);
|
bsw/jbe@1309
|
10866 },
|
bsw/jbe@1309
|
10867
|
bsw/jbe@1309
|
10868 /**
|
bsw/jbe@1309
|
10869 * Get the node which contains the selection
|
bsw/jbe@1309
|
10870 *
|
bsw/jbe@1309
|
10871 * @param {Boolean} [controlRange] (only IE) Whether it should return the selected ControlRange element when the selection type is a "ControlRange"
|
bsw/jbe@1309
|
10872 * @return {Object} The node that contains the caret
|
bsw/jbe@1309
|
10873 * @example
|
bsw/jbe@1309
|
10874 * var nodeThatContainsCaret = selection.getSelectedNode();
|
bsw/jbe@1309
|
10875 */
|
bsw/jbe@1309
|
10876 getSelectedNode: function(controlRange) {
|
bsw/jbe@1309
|
10877 var selection,
|
bsw/jbe@1309
|
10878 range;
|
bsw/jbe@1309
|
10879
|
bsw/jbe@1309
|
10880 if (controlRange && this.doc.selection && this.doc.selection.type === "Control") {
|
bsw/jbe@1309
|
10881 range = this.doc.selection.createRange();
|
bsw/jbe@1309
|
10882 if (range && range.length) {
|
bsw/jbe@1309
|
10883 return range.item(0);
|
bsw/jbe@1309
|
10884 }
|
bsw/jbe@1309
|
10885 }
|
bsw/jbe@1309
|
10886
|
bsw/jbe@1309
|
10887 selection = this.getSelection(this.doc);
|
bsw/jbe@1309
|
10888 if (selection.focusNode === selection.anchorNode) {
|
bsw/jbe@1309
|
10889 return selection.focusNode;
|
bsw/jbe@1309
|
10890 } else {
|
bsw/jbe@1309
|
10891 range = this.getRange(this.doc);
|
bsw/jbe@1309
|
10892 return range ? range.commonAncestorContainer : this.doc.body;
|
bsw/jbe@1309
|
10893 }
|
bsw/jbe@1309
|
10894 },
|
bsw/jbe@1309
|
10895
|
bsw/jbe@1309
|
10896 fixSelBorders: function() {
|
bsw/jbe@1309
|
10897 var range = this.getRange();
|
bsw/jbe@1309
|
10898 expandRangeToSurround(range);
|
bsw/jbe@1309
|
10899 this.setSelection(range);
|
bsw/jbe@1309
|
10900 },
|
bsw/jbe@1309
|
10901
|
bsw/jbe@1309
|
10902 getSelectedOwnNodes: function(controlRange) {
|
bsw/jbe@1309
|
10903 var selection,
|
bsw/jbe@1309
|
10904 ranges = this.getOwnRanges(),
|
bsw/jbe@1309
|
10905 ownNodes = [];
|
bsw/jbe@1309
|
10906
|
bsw/jbe@1309
|
10907 for (var i = 0, maxi = ranges.length; i < maxi; i++) {
|
bsw/jbe@1309
|
10908 ownNodes.push(ranges[i].commonAncestorContainer || this.doc.body);
|
bsw/jbe@1309
|
10909 }
|
bsw/jbe@1309
|
10910 return ownNodes;
|
bsw/jbe@1309
|
10911 },
|
bsw/jbe@1309
|
10912
|
bsw/jbe@1309
|
10913 findNodesInSelection: function(nodeTypes) {
|
bsw/jbe@1309
|
10914 var ranges = this.getOwnRanges(),
|
bsw/jbe@1309
|
10915 nodes = [], curNodes;
|
bsw/jbe@1309
|
10916 for (var i = 0, maxi = ranges.length; i < maxi; i++) {
|
bsw/jbe@1309
|
10917 curNodes = ranges[i].getNodes([1], function(node) {
|
bsw/jbe@1309
|
10918 return wysihtml.lang.array(nodeTypes).contains(node.nodeName);
|
bsw/jbe@1309
|
10919 });
|
bsw/jbe@1309
|
10920 nodes = nodes.concat(curNodes);
|
bsw/jbe@1309
|
10921 }
|
bsw/jbe@1309
|
10922 return nodes;
|
bsw/jbe@1309
|
10923 },
|
bsw/jbe@1309
|
10924
|
bsw/jbe@1309
|
10925 filterElements: function(filter) {
|
bsw/jbe@1309
|
10926 var ranges = this.getOwnRanges(),
|
bsw/jbe@1309
|
10927 nodes = [], curNodes;
|
bsw/jbe@1309
|
10928
|
bsw/jbe@1309
|
10929 for (var i = 0, maxi = ranges.length; i < maxi; i++) {
|
bsw/jbe@1309
|
10930 curNodes = ranges[i].getNodes([1], function(element){
|
bsw/jbe@1309
|
10931 return filter(element, ranges[i]);
|
bsw/jbe@1309
|
10932 });
|
bsw/jbe@1309
|
10933 nodes = nodes.concat(curNodes);
|
bsw/jbe@1309
|
10934 }
|
bsw/jbe@1309
|
10935 return nodes;
|
bsw/jbe@1309
|
10936 },
|
bsw/jbe@1309
|
10937
|
bsw/jbe@1309
|
10938 containsUneditable: function() {
|
bsw/jbe@1309
|
10939 var uneditables = this.getOwnUneditables(),
|
bsw/jbe@1309
|
10940 selection = this.getSelection();
|
bsw/jbe@1309
|
10941
|
bsw/jbe@1309
|
10942 for (var i = 0, maxi = uneditables.length; i < maxi; i++) {
|
bsw/jbe@1309
|
10943 if (selection.containsNode(uneditables[i])) {
|
bsw/jbe@1309
|
10944 return true;
|
bsw/jbe@1309
|
10945 }
|
bsw/jbe@1309
|
10946 }
|
bsw/jbe@1309
|
10947
|
bsw/jbe@1309
|
10948 return false;
|
bsw/jbe@1309
|
10949 },
|
bsw/jbe@1309
|
10950
|
bsw/jbe@1309
|
10951 // Deletes selection contents making sure uneditables/unselectables are not partially deleted
|
bsw/jbe@1309
|
10952 // Triggers wysihtml:uneditable:delete custom event on all deleted uneditables if customevents suppoorted
|
bsw/jbe@1309
|
10953 deleteContents: function() {
|
bsw/jbe@1309
|
10954 var range = this.getRange();
|
bsw/jbe@1309
|
10955 this.deleteRangeContents(range);
|
bsw/jbe@1309
|
10956 this.setSelection(range);
|
bsw/jbe@1309
|
10957 },
|
bsw/jbe@1309
|
10958
|
bsw/jbe@1309
|
10959 // Makes sure all uneditable sare notified before deleting contents
|
bsw/jbe@1309
|
10960 deleteRangeContents: function (range) {
|
bsw/jbe@1309
|
10961 var startParent, endParent, uneditables, ev;
|
bsw/jbe@1309
|
10962
|
bsw/jbe@1309
|
10963 if (this.unselectableClass) {
|
bsw/jbe@1309
|
10964 if ((startParent = wysihtml.dom.getParentElement(range.startContainer, { query: "." + this.unselectableClass }, false, this.contain))) {
|
bsw/jbe@1309
|
10965 range.setStartBefore(startParent);
|
bsw/jbe@1309
|
10966 }
|
bsw/jbe@1309
|
10967 if ((endParent = wysihtml.dom.getParentElement(range.endContainer, { query: "." + this.unselectableClass }, false, this.contain))) {
|
bsw/jbe@1309
|
10968 range.setEndAfter(endParent);
|
bsw/jbe@1309
|
10969 }
|
bsw/jbe@1309
|
10970
|
bsw/jbe@1309
|
10971 // If customevents present notify uneditable elements of being deleted
|
bsw/jbe@1309
|
10972 uneditables = range.getNodes([1], (function (node) {
|
bsw/jbe@1309
|
10973 return wysihtml.dom.hasClass(node, this.unselectableClass);
|
bsw/jbe@1309
|
10974 }).bind(this));
|
bsw/jbe@1309
|
10975 for (var i = uneditables.length; i--;) {
|
bsw/jbe@1309
|
10976 try {
|
bsw/jbe@1309
|
10977 ev = new CustomEvent("wysihtml:uneditable:delete");
|
bsw/jbe@1309
|
10978 uneditables[i].dispatchEvent(ev);
|
bsw/jbe@1309
|
10979 } catch (err) {}
|
bsw/jbe@1309
|
10980 }
|
bsw/jbe@1309
|
10981 }
|
bsw/jbe@1309
|
10982 range.deleteContents();
|
bsw/jbe@1309
|
10983 },
|
bsw/jbe@1309
|
10984
|
bsw/jbe@1309
|
10985 getCaretNode: function () {
|
bsw/jbe@1309
|
10986 var selection = this.getSelection();
|
bsw/jbe@1309
|
10987 return (selection && selection.anchorNode) ? getRangeNode(selection.anchorNode, selection.anchorOffset) : null;
|
bsw/jbe@1309
|
10988 },
|
bsw/jbe@1309
|
10989
|
bsw/jbe@1309
|
10990 getPreviousNode: function(node, ignoreEmpty) {
|
bsw/jbe@1309
|
10991 var displayStyle;
|
bsw/jbe@1309
|
10992 if (!node) {
|
bsw/jbe@1309
|
10993 var selection = this.getSelection();
|
bsw/jbe@1309
|
10994 node = (selection && selection.anchorNode) ? getRangeNode(selection.anchorNode, selection.anchorOffset) : null;
|
bsw/jbe@1309
|
10995 }
|
bsw/jbe@1309
|
10996
|
bsw/jbe@1309
|
10997 if (node === this.contain) {
|
bsw/jbe@1309
|
10998 return false;
|
bsw/jbe@1309
|
10999 }
|
bsw/jbe@1309
|
11000
|
bsw/jbe@1309
|
11001 var ret = node.previousSibling,
|
bsw/jbe@1309
|
11002 parent;
|
bsw/jbe@1309
|
11003
|
bsw/jbe@1309
|
11004 if (ret === this.contain) {
|
bsw/jbe@1309
|
11005 return false;
|
bsw/jbe@1309
|
11006 }
|
bsw/jbe@1309
|
11007
|
bsw/jbe@1309
|
11008 if (ret && ret.nodeType !== 3 && ret.nodeType !== 1) {
|
bsw/jbe@1309
|
11009 // do not count comments and other node types
|
bsw/jbe@1309
|
11010 ret = this.getPreviousNode(ret, ignoreEmpty);
|
bsw/jbe@1309
|
11011 } else if (ret && ret.nodeType === 3 && (/^\s*$/).test(ret.textContent)) {
|
bsw/jbe@1309
|
11012 // do not count empty textnodes as previous nodes
|
bsw/jbe@1309
|
11013 ret = this.getPreviousNode(ret, ignoreEmpty);
|
bsw/jbe@1309
|
11014 } else if (ignoreEmpty && ret && ret.nodeType === 1) {
|
bsw/jbe@1309
|
11015 // Do not count empty nodes if param set.
|
bsw/jbe@1309
|
11016 // Contenteditable tends to bypass and delete these silently when deleting with caret when element is inline-like
|
bsw/jbe@1309
|
11017 displayStyle = wysihtml.dom.getStyle("display").from(ret);
|
bsw/jbe@1309
|
11018 if (
|
bsw/jbe@1309
|
11019 !wysihtml.lang.array(["BR", "HR", "IMG"]).contains(ret.nodeName) &&
|
bsw/jbe@1309
|
11020 !wysihtml.lang.array(["block", "inline-block", "flex", "list-item", "table"]).contains(displayStyle) &&
|
bsw/jbe@1309
|
11021 (/^[\s]*$/).test(ret.innerHTML)
|
bsw/jbe@1309
|
11022 ) {
|
bsw/jbe@1309
|
11023 ret = this.getPreviousNode(ret, ignoreEmpty);
|
bsw/jbe@1309
|
11024 }
|
bsw/jbe@1309
|
11025 } else if (!ret && node !== this.contain) {
|
bsw/jbe@1309
|
11026 parent = node.parentNode;
|
bsw/jbe@1309
|
11027 if (parent !== this.contain) {
|
bsw/jbe@1309
|
11028 ret = this.getPreviousNode(parent, ignoreEmpty);
|
bsw/jbe@1309
|
11029 }
|
bsw/jbe@1309
|
11030 }
|
bsw/jbe@1309
|
11031
|
bsw/jbe@1309
|
11032 return (ret !== this.contain) ? ret : false;
|
bsw/jbe@1309
|
11033 },
|
bsw/jbe@1309
|
11034
|
bsw/jbe@1309
|
11035 // Gather info about caret location (caret node, previous and next node)
|
bsw/jbe@1309
|
11036 getNodesNearCaret: function() {
|
bsw/jbe@1309
|
11037 if (!this.isCollapsed()) {
|
bsw/jbe@1309
|
11038 throw "Selection must be caret when using selection.getNodesNearCaret()";
|
bsw/jbe@1309
|
11039 }
|
bsw/jbe@1309
|
11040
|
bsw/jbe@1309
|
11041 var r = this.getOwnRanges(),
|
bsw/jbe@1309
|
11042 caretNode, prevNode, nextNode, offset;
|
bsw/jbe@1309
|
11043
|
bsw/jbe@1309
|
11044 if (r && r.length > 0) {
|
bsw/jbe@1309
|
11045 if (r[0].startContainer.nodeType === 1) {
|
bsw/jbe@1309
|
11046 caretNode = r[0].startContainer.childNodes[r[0].startOffset - 1];
|
bsw/jbe@1309
|
11047 if (!caretNode && r[0].startOffset === 0) {
|
bsw/jbe@1309
|
11048 // Is first position before all nodes
|
bsw/jbe@1309
|
11049 nextNode = r[0].startContainer.childNodes[0];
|
bsw/jbe@1309
|
11050 } else if (caretNode) {
|
bsw/jbe@1309
|
11051 prevNode = caretNode.previousSibling;
|
bsw/jbe@1309
|
11052 nextNode = caretNode.nextSibling;
|
bsw/jbe@1309
|
11053 }
|
bsw/jbe@1309
|
11054 } else {
|
bsw/jbe@1309
|
11055 if (r[0].startOffset === 0 && r[0].startContainer.previousSibling) {
|
bsw/jbe@1309
|
11056 caretNode = r[0].startContainer.previousSibling;
|
bsw/jbe@1309
|
11057 if (caretNode.nodeType === 3) {
|
bsw/jbe@1309
|
11058 offset = caretNode.data.length;
|
bsw/jbe@1309
|
11059 }
|
bsw/jbe@1309
|
11060 } else {
|
bsw/jbe@1309
|
11061 caretNode = r[0].startContainer;
|
bsw/jbe@1309
|
11062 offset = r[0].startOffset;
|
bsw/jbe@1309
|
11063 }
|
bsw/jbe@1309
|
11064 prevNode = caretNode.previousSibling;
|
bsw/jbe@1309
|
11065 nextNode = caretNode.nextSibling;
|
bsw/jbe@1309
|
11066 }
|
bsw/jbe@1309
|
11067
|
bsw/jbe@1309
|
11068 return {
|
bsw/jbe@1309
|
11069 "caretNode": caretNode,
|
bsw/jbe@1309
|
11070 "prevNode": prevNode,
|
bsw/jbe@1309
|
11071 "nextNode": nextNode,
|
bsw/jbe@1309
|
11072 "textOffset": offset
|
bsw/jbe@1309
|
11073 };
|
bsw/jbe@1309
|
11074 }
|
bsw/jbe@1309
|
11075
|
bsw/jbe@1309
|
11076 return null;
|
bsw/jbe@1309
|
11077 },
|
bsw/jbe@1309
|
11078
|
bsw/jbe@1309
|
11079 getSelectionParentsByTag: function(tagName) {
|
bsw/jbe@1309
|
11080 var nodes = this.getSelectedOwnNodes(),
|
bsw/jbe@1309
|
11081 curEl, parents = [];
|
bsw/jbe@1309
|
11082
|
bsw/jbe@1309
|
11083 for (var i = 0, maxi = nodes.length; i < maxi; i++) {
|
bsw/jbe@1309
|
11084 curEl = (nodes[i].nodeName && nodes[i].nodeName === 'LI') ? nodes[i] : wysihtml.dom.getParentElement(nodes[i], { query: 'li'}, false, this.contain);
|
bsw/jbe@1309
|
11085 if (curEl) {
|
bsw/jbe@1309
|
11086 parents.push(curEl);
|
bsw/jbe@1309
|
11087 }
|
bsw/jbe@1309
|
11088 }
|
bsw/jbe@1309
|
11089 return (parents.length) ? parents : null;
|
bsw/jbe@1309
|
11090 },
|
bsw/jbe@1309
|
11091
|
bsw/jbe@1309
|
11092 getRangeToNodeEnd: function() {
|
bsw/jbe@1309
|
11093 if (this.isCollapsed()) {
|
bsw/jbe@1309
|
11094 var range = this.getRange(),
|
bsw/jbe@1309
|
11095 sNode, pos, lastR;
|
bsw/jbe@1309
|
11096 if (range) {
|
bsw/jbe@1309
|
11097 sNode = range.startContainer;
|
bsw/jbe@1309
|
11098 pos = range.startOffset;
|
bsw/jbe@1309
|
11099 lastR = rangy.createRange(this.doc);
|
bsw/jbe@1309
|
11100
|
bsw/jbe@1309
|
11101 lastR.selectNodeContents(sNode);
|
bsw/jbe@1309
|
11102 lastR.setStart(sNode, pos);
|
bsw/jbe@1309
|
11103 return lastR;
|
bsw/jbe@1309
|
11104 }
|
bsw/jbe@1309
|
11105 }
|
bsw/jbe@1309
|
11106 },
|
bsw/jbe@1309
|
11107
|
bsw/jbe@1309
|
11108 getRangeToNodeBeginning: function() {
|
bsw/jbe@1309
|
11109 if (this.isCollapsed()) {
|
bsw/jbe@1309
|
11110 var range = this.getRange(),
|
bsw/jbe@1309
|
11111 sNode = range.startContainer,
|
bsw/jbe@1309
|
11112 pos = range.startOffset,
|
bsw/jbe@1309
|
11113 lastR = rangy.createRange(this.doc);
|
bsw/jbe@1309
|
11114
|
bsw/jbe@1309
|
11115 lastR.selectNodeContents(sNode);
|
bsw/jbe@1309
|
11116 lastR.setEnd(sNode, pos);
|
bsw/jbe@1309
|
11117 return lastR;
|
bsw/jbe@1309
|
11118 }
|
bsw/jbe@1309
|
11119 },
|
bsw/jbe@1309
|
11120
|
bsw/jbe@1309
|
11121 // This function returns if caret is last in a node (no textual visible content follows)
|
bsw/jbe@1309
|
11122 caretIsInTheEndOfNode: function(ignoreIfSpaceIsBeforeCaret) {
|
bsw/jbe@1309
|
11123 var r = rangy.createRange(this.doc),
|
bsw/jbe@1309
|
11124 s = this.getSelection(),
|
bsw/jbe@1309
|
11125 rangeToNodeEnd = this.getRangeToNodeEnd(),
|
bsw/jbe@1309
|
11126 endc, endtxt, beginc, begintxt;
|
bsw/jbe@1309
|
11127
|
bsw/jbe@1309
|
11128 if (rangeToNodeEnd) {
|
bsw/jbe@1309
|
11129 endc = rangeToNodeEnd.cloneContents();
|
bsw/jbe@1309
|
11130 endtxt = endc.textContent;
|
bsw/jbe@1309
|
11131
|
bsw/jbe@1309
|
11132 if ((/^\s*$/).test(endtxt)) {
|
bsw/jbe@1309
|
11133 if (ignoreIfSpaceIsBeforeCaret) {
|
bsw/jbe@1309
|
11134 beginc = this.getRangeToNodeBeginning().cloneContents();
|
bsw/jbe@1309
|
11135 begintxt = beginc.textContent;
|
bsw/jbe@1309
|
11136 return !(/[\u00A0 ][\s\uFEFF]*$/).test(begintxt);
|
bsw/jbe@1309
|
11137 } else {
|
bsw/jbe@1309
|
11138 return true;
|
bsw/jbe@1309
|
11139 }
|
bsw/jbe@1309
|
11140 } else {
|
bsw/jbe@1309
|
11141 return false;
|
bsw/jbe@1309
|
11142 }
|
bsw/jbe@1309
|
11143 } else {
|
bsw/jbe@1309
|
11144 return false;
|
bsw/jbe@1309
|
11145 }
|
bsw/jbe@1309
|
11146 },
|
bsw/jbe@1309
|
11147
|
bsw/jbe@1309
|
11148 caretIsFirstInSelection: function(includeLineBreaks) {
|
bsw/jbe@1309
|
11149 var r = rangy.createRange(this.doc),
|
bsw/jbe@1309
|
11150 s = this.getSelection(),
|
bsw/jbe@1309
|
11151 range = this.getRange(),
|
bsw/jbe@1309
|
11152 startNode = getRangeNode(range.startContainer, range.startOffset);
|
bsw/jbe@1309
|
11153
|
bsw/jbe@1309
|
11154 if (startNode) {
|
bsw/jbe@1309
|
11155 if (startNode.nodeType === wysihtml.TEXT_NODE) {
|
bsw/jbe@1309
|
11156 if (!startNode.parentNode) {
|
bsw/jbe@1309
|
11157 return false;
|
bsw/jbe@1309
|
11158 }
|
bsw/jbe@1309
|
11159 if (!this.isCollapsed() || (startNode.parentNode.firstChild !== startNode && !wysihtml.dom.domNode(startNode.previousSibling).is.block())) {
|
bsw/jbe@1309
|
11160 return false;
|
bsw/jbe@1309
|
11161 }
|
bsw/jbe@1309
|
11162 var ws = this.win.getComputedStyle(startNode.parentNode).whiteSpace;
|
bsw/jbe@1309
|
11163 return (ws === "pre" || ws === "pre-wrap") ? range.startOffset === 0 : (/^\s*$/).test(startNode.data.substr(0,range.startOffset));
|
bsw/jbe@1309
|
11164 } else if (includeLineBreaks && wysihtml.dom.domNode(startNode).is.lineBreak()) {
|
bsw/jbe@1309
|
11165 return true;
|
bsw/jbe@1309
|
11166 } else {
|
bsw/jbe@1309
|
11167 r.selectNodeContents(this.getRange().commonAncestorContainer);
|
bsw/jbe@1309
|
11168 r.collapse(true);
|
bsw/jbe@1309
|
11169 return (this.isCollapsed() && (r.startContainer === s.anchorNode || r.endContainer === s.anchorNode) && r.startOffset === s.anchorOffset);
|
bsw/jbe@1309
|
11170 }
|
bsw/jbe@1309
|
11171 }
|
bsw/jbe@1309
|
11172 },
|
bsw/jbe@1309
|
11173
|
bsw/jbe@1309
|
11174 caretIsInTheBeginnig: function(ofNode) {
|
bsw/jbe@1309
|
11175 var selection = this.getSelection(),
|
bsw/jbe@1309
|
11176 node = selection.anchorNode,
|
bsw/jbe@1309
|
11177 offset = selection.anchorOffset;
|
bsw/jbe@1309
|
11178 if (ofNode && node) {
|
bsw/jbe@1309
|
11179 return (offset === 0 && (node.nodeName && node.nodeName === ofNode.toUpperCase() || wysihtml.dom.getParentElement(node.parentNode, { query: ofNode }, 1)));
|
bsw/jbe@1309
|
11180 } else if (node) {
|
bsw/jbe@1309
|
11181 return (offset === 0 && !this.getPreviousNode(node, true));
|
bsw/jbe@1309
|
11182 }
|
bsw/jbe@1309
|
11183 },
|
bsw/jbe@1309
|
11184
|
bsw/jbe@1309
|
11185 // Returns object describing node/text before selection
|
bsw/jbe@1309
|
11186 // If includePrevLeaves is true returns also previous last leaf child if selection is in the beginning of current node
|
bsw/jbe@1309
|
11187 getBeforeSelection: function(includePrevLeaves) {
|
bsw/jbe@1309
|
11188 var sel = this.getSelection(),
|
bsw/jbe@1309
|
11189 startNode = (sel.isBackwards()) ? sel.focusNode : sel.anchorNode,
|
bsw/jbe@1309
|
11190 startOffset = (sel.isBackwards()) ? sel.focusOffset : sel.anchorOffset,
|
bsw/jbe@1309
|
11191 rng = this.createRange(), endNode, inTmpCaret;
|
bsw/jbe@1309
|
11192
|
bsw/jbe@1309
|
11193 // If start is textnode and all is whitespace before caret. Set start offset to 0
|
bsw/jbe@1309
|
11194 if (startNode && startNode.nodeType === 3 && (/^\s*$/).test(startNode.data.slice(0, startOffset))) {
|
bsw/jbe@1309
|
11195 startOffset = 0;
|
bsw/jbe@1309
|
11196 }
|
bsw/jbe@1309
|
11197
|
bsw/jbe@1309
|
11198 // Escape temproray helper nodes if selection in them
|
bsw/jbe@1309
|
11199 inTmpCaret = wysihtml.dom.getParentElement(startNode, { query: '._wysihtml-temp-caret-fix' }, 1);
|
bsw/jbe@1309
|
11200 if (inTmpCaret) {
|
bsw/jbe@1309
|
11201 startNode = inTmpCaret.parentNode;
|
bsw/jbe@1309
|
11202 startOffset = Array.prototype.indexOf.call(startNode.childNodes, inTmpCaret);
|
bsw/jbe@1309
|
11203 }
|
bsw/jbe@1309
|
11204
|
bsw/jbe@1309
|
11205 if (startNode) {
|
bsw/jbe@1309
|
11206 if (startOffset > 0) {
|
bsw/jbe@1309
|
11207 if (startNode.nodeType === 3) {
|
bsw/jbe@1309
|
11208 rng.setStart(startNode, 0);
|
bsw/jbe@1309
|
11209 rng.setEnd(startNode, startOffset);
|
bsw/jbe@1309
|
11210 return {
|
bsw/jbe@1309
|
11211 type: "text",
|
bsw/jbe@1309
|
11212 range: rng,
|
bsw/jbe@1309
|
11213 offset : startOffset,
|
bsw/jbe@1309
|
11214 node: startNode
|
bsw/jbe@1309
|
11215 };
|
bsw/jbe@1309
|
11216 } else {
|
bsw/jbe@1309
|
11217 rng.setStartBefore(startNode.childNodes[0]);
|
bsw/jbe@1309
|
11218 endNode = startNode.childNodes[startOffset - 1];
|
bsw/jbe@1309
|
11219 rng.setEndAfter(endNode);
|
bsw/jbe@1309
|
11220 return {
|
bsw/jbe@1309
|
11221 type: "element",
|
bsw/jbe@1309
|
11222 range: rng,
|
bsw/jbe@1309
|
11223 offset : startOffset,
|
bsw/jbe@1309
|
11224 node: endNode
|
bsw/jbe@1309
|
11225 };
|
bsw/jbe@1309
|
11226 }
|
bsw/jbe@1309
|
11227 } else {
|
bsw/jbe@1309
|
11228 rng.setStartAndEnd(startNode, 0);
|
bsw/jbe@1309
|
11229
|
bsw/jbe@1309
|
11230 if (includePrevLeaves) {
|
bsw/jbe@1309
|
11231 var prevNode = this.getPreviousNode(startNode, true),
|
bsw/jbe@1309
|
11232 prevLeaf = null;
|
bsw/jbe@1309
|
11233
|
bsw/jbe@1309
|
11234 if(prevNode) {
|
bsw/jbe@1309
|
11235 if (prevNode.nodeType === 1 && wysihtml.dom.hasClass(prevNode, this.unselectableClass)) {
|
bsw/jbe@1309
|
11236 prevLeaf = prevNode;
|
bsw/jbe@1309
|
11237 } else {
|
bsw/jbe@1309
|
11238 prevLeaf = wysihtml.dom.domNode(prevNode).lastLeafNode();
|
bsw/jbe@1309
|
11239 }
|
bsw/jbe@1309
|
11240 }
|
bsw/jbe@1309
|
11241
|
bsw/jbe@1309
|
11242 if (prevLeaf) {
|
bsw/jbe@1309
|
11243 return {
|
bsw/jbe@1309
|
11244 type: "leafnode",
|
bsw/jbe@1309
|
11245 range: rng,
|
bsw/jbe@1309
|
11246 offset : startOffset,
|
bsw/jbe@1309
|
11247 node: prevLeaf
|
bsw/jbe@1309
|
11248 };
|
bsw/jbe@1309
|
11249 }
|
bsw/jbe@1309
|
11250 }
|
bsw/jbe@1309
|
11251
|
bsw/jbe@1309
|
11252 return {
|
bsw/jbe@1309
|
11253 type: "none",
|
bsw/jbe@1309
|
11254 range: rng,
|
bsw/jbe@1309
|
11255 offset : startOffset,
|
bsw/jbe@1309
|
11256 node: startNode
|
bsw/jbe@1309
|
11257 };
|
bsw/jbe@1309
|
11258 }
|
bsw/jbe@1309
|
11259 }
|
bsw/jbe@1309
|
11260 return null;
|
bsw/jbe@1309
|
11261 },
|
bsw/jbe@1309
|
11262
|
bsw/jbe@1309
|
11263 // TODO: Figure out a method from following 2 that would work universally
|
bsw/jbe@1309
|
11264 executeAndRestoreRangy: function(method, restoreScrollPosition) {
|
bsw/jbe@1309
|
11265 var sel = rangy.saveSelection(this.win);
|
bsw/jbe@1309
|
11266 if (!sel) {
|
bsw/jbe@1309
|
11267 method();
|
bsw/jbe@1309
|
11268 } else {
|
bsw/jbe@1309
|
11269 try {
|
bsw/jbe@1309
|
11270 method();
|
bsw/jbe@1309
|
11271 } catch(e) {
|
bsw/jbe@1309
|
11272 setTimeout(function() { throw e; }, 0);
|
bsw/jbe@1309
|
11273 }
|
bsw/jbe@1309
|
11274 }
|
bsw/jbe@1309
|
11275 rangy.restoreSelection(sel);
|
bsw/jbe@1309
|
11276 },
|
bsw/jbe@1309
|
11277
|
bsw/jbe@1309
|
11278 // TODO: has problems in chrome 12. investigate block level and uneditable area inbetween
|
bsw/jbe@1309
|
11279 executeAndRestore: function(method, restoreScrollPosition) {
|
bsw/jbe@1309
|
11280 var body = this.doc.body,
|
bsw/jbe@1309
|
11281 oldScrollTop = restoreScrollPosition && body.scrollTop,
|
bsw/jbe@1309
|
11282 oldScrollLeft = restoreScrollPosition && body.scrollLeft,
|
bsw/jbe@1309
|
11283 className = "_wysihtml-temp-placeholder",
|
bsw/jbe@1309
|
11284 placeholderHtml = '<span class="' + className + '">' + wysihtml.INVISIBLE_SPACE + '</span>',
|
bsw/jbe@1309
|
11285 range = this.getRange(true),
|
bsw/jbe@1309
|
11286 caretPlaceholder,
|
bsw/jbe@1309
|
11287 newCaretPlaceholder,
|
bsw/jbe@1309
|
11288 nextSibling, prevSibling,
|
bsw/jbe@1309
|
11289 node, node2, range2,
|
bsw/jbe@1309
|
11290 newRange;
|
bsw/jbe@1309
|
11291
|
bsw/jbe@1309
|
11292 // Nothing selected, execute and say goodbye
|
bsw/jbe@1309
|
11293 if (!range) {
|
bsw/jbe@1309
|
11294 method(body, body);
|
bsw/jbe@1309
|
11295 return;
|
bsw/jbe@1309
|
11296 }
|
bsw/jbe@1309
|
11297
|
bsw/jbe@1309
|
11298 if (!range.collapsed) {
|
bsw/jbe@1309
|
11299 range2 = range.cloneRange();
|
bsw/jbe@1309
|
11300 node2 = range2.createContextualFragment(placeholderHtml);
|
bsw/jbe@1309
|
11301 range2.collapse(false);
|
bsw/jbe@1309
|
11302 range2.insertNode(node2);
|
bsw/jbe@1309
|
11303 range2.detach();
|
bsw/jbe@1309
|
11304 }
|
bsw/jbe@1309
|
11305
|
bsw/jbe@1309
|
11306 node = range.createContextualFragment(placeholderHtml);
|
bsw/jbe@1309
|
11307 range.insertNode(node);
|
bsw/jbe@1309
|
11308
|
bsw/jbe@1309
|
11309 if (node2) {
|
bsw/jbe@1309
|
11310 caretPlaceholder = this.contain.querySelectorAll("." + className);
|
bsw/jbe@1309
|
11311 range.setStartBefore(caretPlaceholder[0]);
|
bsw/jbe@1309
|
11312 range.setEndAfter(caretPlaceholder[caretPlaceholder.length -1]);
|
bsw/jbe@1309
|
11313 }
|
bsw/jbe@1309
|
11314 this.setSelection(range);
|
bsw/jbe@1309
|
11315
|
bsw/jbe@1309
|
11316 // Make sure that a potential error doesn't cause our placeholder element to be left as a placeholder
|
bsw/jbe@1309
|
11317 try {
|
bsw/jbe@1309
|
11318 method(range.startContainer, range.endContainer);
|
bsw/jbe@1309
|
11319 } catch(e) {
|
bsw/jbe@1309
|
11320 setTimeout(function() { throw e; }, 0);
|
bsw/jbe@1309
|
11321 }
|
bsw/jbe@1309
|
11322 caretPlaceholder = this.contain.querySelectorAll("." + className);
|
bsw/jbe@1309
|
11323 if (caretPlaceholder && caretPlaceholder.length) {
|
bsw/jbe@1309
|
11324 newRange = rangy.createRange(this.doc);
|
bsw/jbe@1309
|
11325 nextSibling = caretPlaceholder[0].nextSibling;
|
bsw/jbe@1309
|
11326 if (caretPlaceholder.length > 1) {
|
bsw/jbe@1309
|
11327 prevSibling = caretPlaceholder[caretPlaceholder.length -1].previousSibling;
|
bsw/jbe@1309
|
11328 }
|
bsw/jbe@1309
|
11329 if (prevSibling && nextSibling) {
|
bsw/jbe@1309
|
11330 newRange.setStartBefore(nextSibling);
|
bsw/jbe@1309
|
11331 newRange.setEndAfter(prevSibling);
|
bsw/jbe@1309
|
11332 } else {
|
bsw/jbe@1309
|
11333 newCaretPlaceholder = this.doc.createTextNode(wysihtml.INVISIBLE_SPACE);
|
bsw/jbe@1309
|
11334 dom.insert(newCaretPlaceholder).after(caretPlaceholder[0]);
|
bsw/jbe@1309
|
11335 newRange.setStartBefore(newCaretPlaceholder);
|
bsw/jbe@1309
|
11336 newRange.setEndAfter(newCaretPlaceholder);
|
bsw/jbe@1309
|
11337 }
|
bsw/jbe@1309
|
11338 this.setSelection(newRange);
|
bsw/jbe@1309
|
11339 for (var i = caretPlaceholder.length; i--;) {
|
bsw/jbe@1309
|
11340 caretPlaceholder[i].parentNode.removeChild(caretPlaceholder[i]);
|
bsw/jbe@1309
|
11341 }
|
bsw/jbe@1309
|
11342
|
bsw/jbe@1309
|
11343 } else {
|
bsw/jbe@1309
|
11344 // fallback for when all hell breaks loose
|
bsw/jbe@1309
|
11345 this.contain.focus();
|
bsw/jbe@1309
|
11346 }
|
bsw/jbe@1309
|
11347
|
bsw/jbe@1309
|
11348 if (restoreScrollPosition) {
|
bsw/jbe@1309
|
11349 body.scrollTop = oldScrollTop;
|
bsw/jbe@1309
|
11350 body.scrollLeft = oldScrollLeft;
|
bsw/jbe@1309
|
11351 }
|
bsw/jbe@1309
|
11352
|
bsw/jbe@1309
|
11353 // Remove it again, just to make sure that the placeholder is definitely out of the dom tree
|
bsw/jbe@1309
|
11354 try {
|
bsw/jbe@1309
|
11355 caretPlaceholder.parentNode.removeChild(caretPlaceholder);
|
bsw/jbe@1309
|
11356 } catch(e2) {}
|
bsw/jbe@1309
|
11357 },
|
bsw/jbe@1309
|
11358
|
bsw/jbe@1309
|
11359 set: function(node, offset) {
|
bsw/jbe@1309
|
11360 var newRange = rangy.createRange(this.doc);
|
bsw/jbe@1309
|
11361 newRange.setStart(node, offset || 0);
|
bsw/jbe@1309
|
11362 this.setSelection(newRange);
|
bsw/jbe@1309
|
11363 },
|
bsw/jbe@1309
|
11364
|
bsw/jbe@1309
|
11365 /**
|
bsw/jbe@1309
|
11366 * Insert html at the caret or selection position and move the cursor after the inserted html
|
bsw/jbe@1309
|
11367 * Replaces selection content if present
|
bsw/jbe@1309
|
11368 *
|
bsw/jbe@1309
|
11369 * @param {String} html HTML string to insert
|
bsw/jbe@1309
|
11370 * @example
|
bsw/jbe@1309
|
11371 * selection.insertHTML("<p>foobar</p>");
|
bsw/jbe@1309
|
11372 */
|
bsw/jbe@1309
|
11373 insertHTML: function(html) {
|
bsw/jbe@1309
|
11374 var range = this.getRange(),
|
bsw/jbe@1309
|
11375 node = this.doc.createElement('DIV'),
|
bsw/jbe@1309
|
11376 fragment = this.doc.createDocumentFragment(),
|
bsw/jbe@1309
|
11377 lastChild, lastEditorElement;
|
bsw/jbe@1309
|
11378
|
bsw/jbe@1309
|
11379 if (range) {
|
bsw/jbe@1309
|
11380 range.deleteContents();
|
bsw/jbe@1309
|
11381 node.innerHTML = html;
|
bsw/jbe@1309
|
11382 lastChild = node.lastChild;
|
bsw/jbe@1309
|
11383
|
bsw/jbe@1309
|
11384 while (node.firstChild) {
|
bsw/jbe@1309
|
11385 fragment.appendChild(node.firstChild);
|
bsw/jbe@1309
|
11386 }
|
bsw/jbe@1309
|
11387 range.insertNode(fragment);
|
bsw/jbe@1309
|
11388
|
bsw/jbe@1309
|
11389 lastEditorElement = this.contain.lastChild;
|
bsw/jbe@1309
|
11390 while (lastEditorElement && lastEditorElement.nodeType === 3 && lastEditorElement.previousSibling && (/^\s*$/).test(lastEditorElement.data)) {
|
bsw/jbe@1309
|
11391 lastEditorElement = lastEditorElement.previousSibling;
|
bsw/jbe@1309
|
11392 }
|
bsw/jbe@1309
|
11393
|
bsw/jbe@1309
|
11394 if (lastChild) {
|
bsw/jbe@1309
|
11395 // fixes some pad cases mostly on webkit where last nr is needed
|
bsw/jbe@1309
|
11396 if (lastEditorElement && lastChild === lastEditorElement && lastChild.nodeType === 1) {
|
bsw/jbe@1309
|
11397 this.contain.appendChild(this.doc.createElement('br'));
|
bsw/jbe@1309
|
11398 }
|
bsw/jbe@1309
|
11399 this.setAfter(lastChild);
|
bsw/jbe@1309
|
11400 }
|
bsw/jbe@1309
|
11401 }
|
bsw/jbe@1309
|
11402 },
|
bsw/jbe@1309
|
11403
|
bsw/jbe@1309
|
11404 /**
|
bsw/jbe@1309
|
11405 * Insert a node at the caret position and move the cursor behind it
|
bsw/jbe@1309
|
11406 *
|
bsw/jbe@1309
|
11407 * @param {Object} node HTML string to insert
|
bsw/jbe@1309
|
11408 * @example
|
bsw/jbe@1309
|
11409 * selection.insertNode(document.createTextNode("foobar"));
|
bsw/jbe@1309
|
11410 */
|
bsw/jbe@1309
|
11411 insertNode: function(node) {
|
bsw/jbe@1309
|
11412 var range = this.getRange();
|
bsw/jbe@1309
|
11413 if (range) {
|
bsw/jbe@1309
|
11414 range.deleteContents();
|
bsw/jbe@1309
|
11415 range.insertNode(node);
|
bsw/jbe@1309
|
11416 }
|
bsw/jbe@1309
|
11417 },
|
bsw/jbe@1309
|
11418
|
bsw/jbe@1309
|
11419 canAppendChild: function (node) {
|
bsw/jbe@1309
|
11420 var anchorNode, anchorNodeTagNameLower,
|
bsw/jbe@1309
|
11421 voidElements = ["area", "base", "br", "col", "command", "embed", "hr", "img", "input", "keygen", "link", "meta", "param", "source", "track", "wbr"],
|
bsw/jbe@1309
|
11422 range = this.getRange();
|
bsw/jbe@1309
|
11423
|
bsw/jbe@1309
|
11424 anchorNode = node || range.startContainer;
|
bsw/jbe@1309
|
11425
|
bsw/jbe@1309
|
11426 if (anchorNode) {
|
bsw/jbe@1309
|
11427 anchorNodeTagNameLower = (anchorNode.tagName || anchorNode.nodeName).toLowerCase();
|
bsw/jbe@1309
|
11428 }
|
bsw/jbe@1309
|
11429
|
bsw/jbe@1309
|
11430 return voidElements.indexOf(anchorNodeTagNameLower) === -1;
|
bsw/jbe@1309
|
11431 },
|
bsw/jbe@1309
|
11432
|
bsw/jbe@1309
|
11433 splitElementAtCaret: function (element, insertNode) {
|
bsw/jbe@1309
|
11434 var sel = this.getSelection(),
|
bsw/jbe@1309
|
11435 range, contentAfterRangeStart,
|
bsw/jbe@1309
|
11436 firstChild, lastChild, childNodes;
|
bsw/jbe@1309
|
11437
|
bsw/jbe@1309
|
11438 if (sel.rangeCount > 0) {
|
bsw/jbe@1309
|
11439 range = sel.getRangeAt(0).cloneRange(); // Create a copy of the selection range to work with
|
bsw/jbe@1309
|
11440
|
bsw/jbe@1309
|
11441 range.setEndAfter(element); // Place the end of the range after the element
|
bsw/jbe@1309
|
11442 contentAfterRangeStart = range.extractContents(); // Extract the contents of the element after the caret into a fragment
|
bsw/jbe@1309
|
11443
|
bsw/jbe@1309
|
11444 childNodes = contentAfterRangeStart.childNodes;
|
bsw/jbe@1309
|
11445
|
bsw/jbe@1309
|
11446 // Empty elements are cleaned up from extracted content
|
bsw/jbe@1309
|
11447 for (var i = childNodes.length; i --;) {
|
bsw/jbe@1309
|
11448 if (!wysihtml.dom.domNode(childNodes[i]).is.visible()) {
|
bsw/jbe@1309
|
11449 contentAfterRangeStart.removeChild(childNodes[i]);
|
bsw/jbe@1309
|
11450 }
|
bsw/jbe@1309
|
11451 }
|
bsw/jbe@1309
|
11452
|
bsw/jbe@1309
|
11453 element.parentNode.insertBefore(contentAfterRangeStart, element.nextSibling);
|
bsw/jbe@1309
|
11454
|
bsw/jbe@1309
|
11455 if (insertNode) {
|
bsw/jbe@1309
|
11456 firstChild = insertNode.firstChild || insertNode;
|
bsw/jbe@1309
|
11457 lastChild = insertNode.lastChild || insertNode;
|
bsw/jbe@1309
|
11458
|
bsw/jbe@1309
|
11459 element.parentNode.insertBefore(insertNode, element.nextSibling);
|
bsw/jbe@1309
|
11460
|
bsw/jbe@1309
|
11461 // Select inserted node contents
|
bsw/jbe@1309
|
11462 if (firstChild && lastChild) {
|
bsw/jbe@1309
|
11463 range.setStartBefore(firstChild);
|
bsw/jbe@1309
|
11464 range.setEndAfter(lastChild);
|
bsw/jbe@1309
|
11465 this.setSelection(range);
|
bsw/jbe@1309
|
11466 }
|
bsw/jbe@1309
|
11467 } else {
|
bsw/jbe@1309
|
11468 range.setStartAfter(element);
|
bsw/jbe@1309
|
11469 range.setEndAfter(element);
|
bsw/jbe@1309
|
11470 }
|
bsw/jbe@1309
|
11471
|
bsw/jbe@1309
|
11472 if (!wysihtml.dom.domNode(element).is.visible()) {
|
bsw/jbe@1309
|
11473 if (wysihtml.dom.getTextContent(element) === '') {
|
bsw/jbe@1309
|
11474 element.parentNode.removeChild(element);
|
bsw/jbe@1309
|
11475 } else {
|
bsw/jbe@1309
|
11476 element.parentNode.replaceChild(this.doc.createTextNode(" "), element);
|
bsw/jbe@1309
|
11477 }
|
bsw/jbe@1309
|
11478 }
|
bsw/jbe@1309
|
11479
|
bsw/jbe@1309
|
11480
|
bsw/jbe@1309
|
11481 }
|
bsw/jbe@1309
|
11482 },
|
bsw/jbe@1309
|
11483
|
bsw/jbe@1309
|
11484 /**
|
bsw/jbe@1309
|
11485 * Wraps current selection with the given node
|
bsw/jbe@1309
|
11486 *
|
bsw/jbe@1309
|
11487 * @param {Object} node The node to surround the selected elements with
|
bsw/jbe@1309
|
11488 */
|
bsw/jbe@1309
|
11489 surround: function(nodeOptions) {
|
bsw/jbe@1309
|
11490 var ranges = this.getOwnRanges(),
|
bsw/jbe@1309
|
11491 node, nodes = [];
|
bsw/jbe@1309
|
11492 if (ranges.length == 0) {
|
bsw/jbe@1309
|
11493 return nodes;
|
bsw/jbe@1309
|
11494 }
|
bsw/jbe@1309
|
11495
|
bsw/jbe@1309
|
11496 for (var i = ranges.length; i--;) {
|
bsw/jbe@1309
|
11497 node = this.doc.createElement(nodeOptions.nodeName);
|
bsw/jbe@1309
|
11498 nodes.push(node);
|
bsw/jbe@1309
|
11499 if (nodeOptions.className) {
|
bsw/jbe@1309
|
11500 node.className = nodeOptions.className;
|
bsw/jbe@1309
|
11501 }
|
bsw/jbe@1309
|
11502 if (nodeOptions.cssStyle) {
|
bsw/jbe@1309
|
11503 node.setAttribute('style', nodeOptions.cssStyle);
|
bsw/jbe@1309
|
11504 }
|
bsw/jbe@1309
|
11505 try {
|
bsw/jbe@1309
|
11506 // This only works when the range boundaries are not overlapping other elements
|
bsw/jbe@1309
|
11507 ranges[i].surroundContents(node);
|
bsw/jbe@1309
|
11508 this.selectNode(node);
|
bsw/jbe@1309
|
11509 } catch(e) {
|
bsw/jbe@1309
|
11510 // fallback
|
bsw/jbe@1309
|
11511 node.appendChild(ranges[i].extractContents());
|
bsw/jbe@1309
|
11512 ranges[i].insertNode(node);
|
bsw/jbe@1309
|
11513 }
|
bsw/jbe@1309
|
11514 }
|
bsw/jbe@1309
|
11515 return nodes;
|
bsw/jbe@1309
|
11516 },
|
bsw/jbe@1309
|
11517
|
bsw/jbe@1309
|
11518 /**
|
bsw/jbe@1309
|
11519 * Scroll the current caret position into the view
|
bsw/jbe@1309
|
11520 * FIXME: This is a bit hacky, there might be a smarter way of doing this
|
bsw/jbe@1309
|
11521 *
|
bsw/jbe@1309
|
11522 * @example
|
bsw/jbe@1309
|
11523 * selection.scrollIntoView();
|
bsw/jbe@1309
|
11524 */
|
bsw/jbe@1309
|
11525 scrollIntoView: function() {
|
bsw/jbe@1309
|
11526 var doc = this.doc,
|
bsw/jbe@1309
|
11527 tolerance = 5, // px
|
bsw/jbe@1309
|
11528 hasScrollBars = doc.documentElement.scrollHeight > doc.documentElement.offsetHeight,
|
bsw/jbe@1309
|
11529 tempElement = doc._wysihtmlScrollIntoViewElement = doc._wysihtmlScrollIntoViewElement || (function() {
|
bsw/jbe@1309
|
11530 var element = doc.createElement("span");
|
bsw/jbe@1309
|
11531 // The element needs content in order to be able to calculate it's position properly
|
bsw/jbe@1309
|
11532 element.innerHTML = wysihtml.INVISIBLE_SPACE;
|
bsw/jbe@1309
|
11533 return element;
|
bsw/jbe@1309
|
11534 })(),
|
bsw/jbe@1309
|
11535 offsetTop;
|
bsw/jbe@1309
|
11536
|
bsw/jbe@1309
|
11537 if (hasScrollBars) {
|
bsw/jbe@1309
|
11538 this.insertNode(tempElement);
|
bsw/jbe@1309
|
11539 offsetTop = _getCumulativeOffsetTop(tempElement);
|
bsw/jbe@1309
|
11540 tempElement.parentNode.removeChild(tempElement);
|
bsw/jbe@1309
|
11541 if (offsetTop >= (doc.body.scrollTop + doc.documentElement.offsetHeight - tolerance)) {
|
bsw/jbe@1309
|
11542 doc.body.scrollTop = offsetTop;
|
bsw/jbe@1309
|
11543 }
|
bsw/jbe@1309
|
11544 }
|
bsw/jbe@1309
|
11545 },
|
bsw/jbe@1309
|
11546
|
bsw/jbe@1309
|
11547 /**
|
bsw/jbe@1309
|
11548 * Select line where the caret is in
|
bsw/jbe@1309
|
11549 */
|
bsw/jbe@1309
|
11550 selectLine: function() {
|
bsw/jbe@1309
|
11551 var r = rangy.createRange();
|
bsw/jbe@1309
|
11552 if (wysihtml.browser.supportsSelectionModify()) {
|
bsw/jbe@1309
|
11553 this._selectLine_W3C();
|
bsw/jbe@1309
|
11554 } else if (r.nativeRange && r.nativeRange.getBoundingClientRect) {
|
bsw/jbe@1309
|
11555 // For IE Edge as it ditched the old api and did not fully implement the new one (as expected)
|
bsw/jbe@1309
|
11556 this._selectLineUniversal();
|
bsw/jbe@1309
|
11557 }
|
bsw/jbe@1309
|
11558 },
|
bsw/jbe@1309
|
11559
|
bsw/jbe@1309
|
11560 includeRangyRangeHelpers: function() {
|
bsw/jbe@1309
|
11561 var s = this.getSelection(),
|
bsw/jbe@1309
|
11562 r = s.getRangeAt(0),
|
bsw/jbe@1309
|
11563 isHelperNode = function(node) {
|
bsw/jbe@1309
|
11564 return (node && node.nodeType === 1 && node.classList.contains('rangySelectionBoundary'));
|
bsw/jbe@1309
|
11565 },
|
bsw/jbe@1309
|
11566 getNodeLength = function (node) {
|
bsw/jbe@1309
|
11567 if (node.nodeType === 1) {
|
bsw/jbe@1309
|
11568 return node.childNodes && node.childNodes.length || 0;
|
bsw/jbe@1309
|
11569 } else {
|
bsw/jbe@1309
|
11570 return node.data && node.data.length || 0;
|
bsw/jbe@1309
|
11571 }
|
bsw/jbe@1309
|
11572 },
|
bsw/jbe@1309
|
11573 anode = s.anchorNode.nodeType === 1 ? s.anchorNode.childNodes[s.anchorOffset] : s.anchorNode,
|
bsw/jbe@1309
|
11574 fnode = s.focusNode.nodeType === 1 ? s.focusNode.childNodes[s.focusOffset] : s.focusNode;
|
bsw/jbe@1309
|
11575
|
bsw/jbe@1309
|
11576 if (fnode && s.focusOffset === getNodeLength(fnode) && fnode.nextSibling && isHelperNode(fnode.nextSibling)) {
|
bsw/jbe@1309
|
11577 r.setEndAfter(fnode.nextSibling);
|
bsw/jbe@1309
|
11578 }
|
bsw/jbe@1309
|
11579 if (anode && s.anchorOffset === 0 && anode.previousSibling && isHelperNode(anode.previousSibling)) {
|
bsw/jbe@1309
|
11580 r.setStartBefore(anode.previousSibling);
|
bsw/jbe@1309
|
11581 }
|
bsw/jbe@1309
|
11582 r.select();
|
bsw/jbe@1309
|
11583 },
|
bsw/jbe@1309
|
11584
|
bsw/jbe@1309
|
11585 /**
|
bsw/jbe@1309
|
11586 * See https://developer.mozilla.org/en/DOM/Selection/modify
|
bsw/jbe@1309
|
11587 */
|
bsw/jbe@1309
|
11588 _selectLine_W3C: function() {
|
bsw/jbe@1309
|
11589 var selection = this.win.getSelection(),
|
bsw/jbe@1309
|
11590 initialBoundry = [selection.anchorNode, selection.anchorOffset, selection.focusNode, selection.focusOffset];
|
bsw/jbe@1309
|
11591
|
bsw/jbe@1309
|
11592 selection.modify("move", "left", "lineboundary");
|
bsw/jbe@1309
|
11593 selection.modify("extend", "right", "lineboundary");
|
bsw/jbe@1309
|
11594
|
bsw/jbe@1309
|
11595 // IF lineboundary extending did not change selection try universal fallback (FF fails sometimes without a reason)
|
bsw/jbe@1309
|
11596 if (selection.anchorNode === initialBoundry[0] &&
|
bsw/jbe@1309
|
11597 selection.anchorOffset === initialBoundry[1] &&
|
bsw/jbe@1309
|
11598 selection.focusNode === initialBoundry[2] &&
|
bsw/jbe@1309
|
11599 selection.focusOffset === initialBoundry[3]
|
bsw/jbe@1309
|
11600 ) {
|
bsw/jbe@1309
|
11601 this._selectLineUniversal();
|
bsw/jbe@1309
|
11602 } else {
|
bsw/jbe@1309
|
11603 this.includeRangyRangeHelpers();
|
bsw/jbe@1309
|
11604 }
|
bsw/jbe@1309
|
11605 },
|
bsw/jbe@1309
|
11606
|
bsw/jbe@1309
|
11607 // collapses selection to current line beginning or end
|
bsw/jbe@1309
|
11608 toLineBoundary: function (location, collapse) {
|
bsw/jbe@1309
|
11609 collapse = (typeof collapse === 'undefined') ? false : collapse;
|
bsw/jbe@1309
|
11610 if (wysihtml.browser.supportsSelectionModify()) {
|
bsw/jbe@1309
|
11611 var selection = this.win.getSelection();
|
bsw/jbe@1309
|
11612
|
bsw/jbe@1309
|
11613 selection.modify("extend", location, "lineboundary");
|
bsw/jbe@1309
|
11614 if (collapse) {
|
bsw/jbe@1309
|
11615 if (location === "left") {
|
bsw/jbe@1309
|
11616 selection.collapseToStart();
|
bsw/jbe@1309
|
11617 } else if (location === "right") {
|
bsw/jbe@1309
|
11618 selection.collapseToEnd();
|
bsw/jbe@1309
|
11619 }
|
bsw/jbe@1309
|
11620 }
|
bsw/jbe@1309
|
11621 }
|
bsw/jbe@1309
|
11622 },
|
bsw/jbe@1309
|
11623
|
bsw/jbe@1309
|
11624 getRangeRect: function(r) {
|
bsw/jbe@1309
|
11625 var textNode = this.doc.createTextNode("i"),
|
bsw/jbe@1309
|
11626 testNode = this.doc.createTextNode("i"),
|
bsw/jbe@1309
|
11627 rect, cr;
|
bsw/jbe@1309
|
11628
|
bsw/jbe@1309
|
11629 /*testNode.style.visibility = "hidden";
|
bsw/jbe@1309
|
11630 testNode.style.width = "0px";
|
bsw/jbe@1309
|
11631 testNode.style.display = "inline-block";
|
bsw/jbe@1309
|
11632 testNode.style.overflow = "hidden";
|
bsw/jbe@1309
|
11633 testNode.appendChild(textNode);*/
|
bsw/jbe@1309
|
11634
|
bsw/jbe@1309
|
11635 if (r.collapsed) {
|
bsw/jbe@1309
|
11636 r.insertNode(testNode);
|
bsw/jbe@1309
|
11637 r.selectNode(testNode);
|
bsw/jbe@1309
|
11638 rect = r.nativeRange.getBoundingClientRect();
|
bsw/jbe@1309
|
11639 r.deleteContents();
|
bsw/jbe@1309
|
11640
|
bsw/jbe@1309
|
11641 } else {
|
bsw/jbe@1309
|
11642 rect = r.nativeRange.getBoundingClientRect();
|
bsw/jbe@1309
|
11643 }
|
bsw/jbe@1309
|
11644
|
bsw/jbe@1309
|
11645 return rect;
|
bsw/jbe@1309
|
11646
|
bsw/jbe@1309
|
11647 },
|
bsw/jbe@1309
|
11648
|
bsw/jbe@1309
|
11649 _selectLineUniversal: function() {
|
bsw/jbe@1309
|
11650 var s = this.getSelection(),
|
bsw/jbe@1309
|
11651 r = s.getRangeAt(0),
|
bsw/jbe@1309
|
11652 rect,
|
bsw/jbe@1309
|
11653 startRange, endRange, testRange,
|
bsw/jbe@1309
|
11654 count = 0,
|
bsw/jbe@1309
|
11655 amount, testRect, found,
|
bsw/jbe@1309
|
11656 that = this,
|
bsw/jbe@1309
|
11657 isLineBreakingElement = function(el) {
|
bsw/jbe@1309
|
11658 return el && el.nodeType === 1 && (that.win.getComputedStyle(el).display === "block" || wysihtml.lang.array(['BR', 'HR']).contains(el.nodeName));
|
bsw/jbe@1309
|
11659 },
|
bsw/jbe@1309
|
11660 prevNode = function(node) {
|
bsw/jbe@1309
|
11661 var pnode = node;
|
bsw/jbe@1309
|
11662 if (pnode) {
|
bsw/jbe@1309
|
11663 while (pnode && ((pnode.nodeType === 1 && pnode.classList.contains('rangySelectionBoundary')) || (pnode.nodeType === 3 && (/^\s*$/).test(pnode.data)))) {
|
bsw/jbe@1309
|
11664 pnode = pnode.previousSibling;
|
bsw/jbe@1309
|
11665 }
|
bsw/jbe@1309
|
11666 }
|
bsw/jbe@1309
|
11667 return pnode;
|
bsw/jbe@1309
|
11668 };
|
bsw/jbe@1309
|
11669
|
bsw/jbe@1309
|
11670 startRange = r.cloneRange();
|
bsw/jbe@1309
|
11671 endRange = r.cloneRange();
|
bsw/jbe@1309
|
11672
|
bsw/jbe@1309
|
11673 if (r.collapsed) {
|
bsw/jbe@1309
|
11674 // Collapsed state can not have a bounding rect. Thus need to expand it at least by 1 character first while not crossing line boundary
|
bsw/jbe@1309
|
11675 // TODO: figure out a shorter and more readable way
|
bsw/jbe@1309
|
11676 if (r.startContainer.nodeType === 3 && r.startOffset < r.startContainer.data.length) {
|
bsw/jbe@1309
|
11677 r.moveEnd('character', 1);
|
bsw/jbe@1309
|
11678 } else if (r.startContainer.nodeType === 1 && r.startContainer.childNodes[r.startOffset] && r.startContainer.childNodes[r.startOffset].nodeType === 3 && r.startContainer.childNodes[r.startOffset].data.length > 0) {
|
bsw/jbe@1309
|
11679 r.moveEnd('character', 1);
|
bsw/jbe@1309
|
11680 } else if (
|
bsw/jbe@1309
|
11681 r.startOffset > 0 &&
|
bsw/jbe@1309
|
11682 (
|
bsw/jbe@1309
|
11683 r.startContainer.nodeType === 3 ||
|
bsw/jbe@1309
|
11684 (
|
bsw/jbe@1309
|
11685 r.startContainer.nodeType === 1 &&
|
bsw/jbe@1309
|
11686 !isLineBreakingElement(prevNode(r.startContainer.childNodes[r.startOffset - 1]))
|
bsw/jbe@1309
|
11687 )
|
bsw/jbe@1309
|
11688 )
|
bsw/jbe@1309
|
11689 ) {
|
bsw/jbe@1309
|
11690 r.moveStart('character', -1);
|
bsw/jbe@1309
|
11691 }
|
bsw/jbe@1309
|
11692 }
|
bsw/jbe@1309
|
11693 if (!r.collapsed) {
|
bsw/jbe@1309
|
11694 r.insertNode(this.doc.createTextNode(wysihtml.INVISIBLE_SPACE));
|
bsw/jbe@1309
|
11695 }
|
bsw/jbe@1309
|
11696
|
bsw/jbe@1309
|
11697 // Is probably just empty line as can not be expanded
|
bsw/jbe@1309
|
11698 rect = r.nativeRange.getBoundingClientRect();
|
bsw/jbe@1309
|
11699 // If startnode is not line break allready move the start position of range by -1 character until clientRect top changes;
|
bsw/jbe@1309
|
11700 do {
|
bsw/jbe@1309
|
11701 amount = r.moveStart('character', -1);
|
bsw/jbe@1309
|
11702 testRect = r.nativeRange.getBoundingClientRect();
|
bsw/jbe@1309
|
11703
|
bsw/jbe@1309
|
11704 if (!testRect || Math.floor(testRect.top) !== Math.floor(rect.top)) {
|
bsw/jbe@1309
|
11705 r.moveStart('character', 1);
|
bsw/jbe@1309
|
11706 found = true;
|
bsw/jbe@1309
|
11707 }
|
bsw/jbe@1309
|
11708 count++;
|
bsw/jbe@1309
|
11709 } while (amount !== 0 && !found && count < 2000);
|
bsw/jbe@1309
|
11710 count = 0;
|
bsw/jbe@1309
|
11711 found = false;
|
bsw/jbe@1309
|
11712 rect = r.nativeRange.getBoundingClientRect();
|
bsw/jbe@1309
|
11713
|
bsw/jbe@1309
|
11714 if (r.endContainer !== this.contain || (this.contain.lastChild && this.contain.childNodes[r.endOffset] !== this.contain.lastChild)) {
|
bsw/jbe@1309
|
11715 do {
|
bsw/jbe@1309
|
11716 amount = r.moveEnd('character', 1);
|
bsw/jbe@1309
|
11717 testRect = r.nativeRange.getBoundingClientRect();
|
bsw/jbe@1309
|
11718 if (!testRect || Math.floor(testRect.bottom) !== Math.floor(rect.bottom)) {
|
bsw/jbe@1309
|
11719 r.moveEnd('character', -1);
|
bsw/jbe@1309
|
11720
|
bsw/jbe@1309
|
11721 // Fix a IE line end marked by linebreak element although caret is before it
|
bsw/jbe@1309
|
11722 // If causes problems should be changed to be applied only to IE
|
bsw/jbe@1309
|
11723 if (r.endContainer && r.endContainer.nodeType === 1 && r.endContainer.childNodes[r.endOffset] && r.endContainer.childNodes[r.endOffset].nodeType === 1 && r.endContainer.childNodes[r.endOffset].nodeName === "BR" && r.endContainer.childNodes[r.endOffset].previousSibling) {
|
bsw/jbe@1309
|
11724 if (r.endContainer.childNodes[r.endOffset].previousSibling.nodeType === 1) {
|
bsw/jbe@1309
|
11725 r.setEnd(r.endContainer.childNodes[r.endOffset].previousSibling, r.endContainer.childNodes[r.endOffset].previousSibling.childNodes.length);
|
bsw/jbe@1309
|
11726 } else if (r.endContainer.childNodes[r.endOffset].previousSibling.nodeType === 3) {
|
bsw/jbe@1309
|
11727 r.setEnd(r.endContainer.childNodes[r.endOffset].previousSibling, r.endContainer.childNodes[r.endOffset].previousSibling.data.length);
|
bsw/jbe@1309
|
11728 }
|
bsw/jbe@1309
|
11729 }
|
bsw/jbe@1309
|
11730 found = true;
|
bsw/jbe@1309
|
11731 }
|
bsw/jbe@1309
|
11732 count++;
|
bsw/jbe@1309
|
11733 } while (amount !== 0 && !found && count < 2000);
|
bsw/jbe@1309
|
11734 }
|
bsw/jbe@1309
|
11735 r.select();
|
bsw/jbe@1309
|
11736 this.includeRangyRangeHelpers();
|
bsw/jbe@1309
|
11737 },
|
bsw/jbe@1309
|
11738
|
bsw/jbe@1309
|
11739 getText: function() {
|
bsw/jbe@1309
|
11740 var selection = this.getSelection();
|
bsw/jbe@1309
|
11741 return selection ? selection.toString() : "";
|
bsw/jbe@1309
|
11742 },
|
bsw/jbe@1309
|
11743
|
bsw/jbe@1309
|
11744 getNodes: function(nodeType, filter) {
|
bsw/jbe@1309
|
11745 var range = this.getRange();
|
bsw/jbe@1309
|
11746 if (range) {
|
bsw/jbe@1309
|
11747 return range.getNodes(Array.isArray(nodeType) ? nodeType : [nodeType], filter);
|
bsw/jbe@1309
|
11748 } else {
|
bsw/jbe@1309
|
11749 return [];
|
bsw/jbe@1309
|
11750 }
|
bsw/jbe@1309
|
11751 },
|
bsw/jbe@1309
|
11752
|
bsw/jbe@1309
|
11753 // Gets all the elements in selection with nodeType
|
bsw/jbe@1309
|
11754 // Ignores the elements not belonging to current editable area
|
bsw/jbe@1309
|
11755 // If filter is defined nodes must pass the filter function with true to be included in list
|
bsw/jbe@1309
|
11756 getOwnNodes: function(nodeType, filter, splitBounds) {
|
bsw/jbe@1309
|
11757 var ranges = this.getOwnRanges(),
|
bsw/jbe@1309
|
11758 nodes = [];
|
bsw/jbe@1309
|
11759 for (var r = 0, rmax = ranges.length; r < rmax; r++) {
|
bsw/jbe@1309
|
11760 if (ranges[r]) {
|
bsw/jbe@1309
|
11761 if (splitBounds) {
|
bsw/jbe@1309
|
11762 ranges[r].splitBoundaries();
|
bsw/jbe@1309
|
11763 }
|
bsw/jbe@1309
|
11764 nodes = nodes.concat(ranges[r].getNodes(Array.isArray(nodeType) ? nodeType : [nodeType], filter));
|
bsw/jbe@1309
|
11765 }
|
bsw/jbe@1309
|
11766 }
|
bsw/jbe@1309
|
11767
|
bsw/jbe@1309
|
11768 return nodes;
|
bsw/jbe@1309
|
11769 },
|
bsw/jbe@1309
|
11770
|
bsw/jbe@1309
|
11771 fixRangeOverflow: function(range) {
|
bsw/jbe@1309
|
11772 if (this.contain && this.contain.firstChild && range) {
|
bsw/jbe@1309
|
11773 var containment = range.compareNode(this.contain);
|
bsw/jbe@1309
|
11774 if (containment !== 2) {
|
bsw/jbe@1309
|
11775 if (containment === 1) {
|
bsw/jbe@1309
|
11776 range.setStartBefore(this.contain.firstChild);
|
bsw/jbe@1309
|
11777 }
|
bsw/jbe@1309
|
11778 if (containment === 0) {
|
bsw/jbe@1309
|
11779 range.setEndAfter(this.contain.lastChild);
|
bsw/jbe@1309
|
11780 }
|
bsw/jbe@1309
|
11781 if (containment === 3) {
|
bsw/jbe@1309
|
11782 range.setStartBefore(this.contain.firstChild);
|
bsw/jbe@1309
|
11783 range.setEndAfter(this.contain.lastChild);
|
bsw/jbe@1309
|
11784 }
|
bsw/jbe@1309
|
11785 } else if (this._detectInlineRangeProblems(range)) {
|
bsw/jbe@1309
|
11786 var previousElementSibling = range.endContainer.previousElementSibling;
|
bsw/jbe@1309
|
11787 if (previousElementSibling) {
|
bsw/jbe@1309
|
11788 range.setEnd(previousElementSibling, this._endOffsetForNode(previousElementSibling));
|
bsw/jbe@1309
|
11789 }
|
bsw/jbe@1309
|
11790 }
|
bsw/jbe@1309
|
11791 }
|
bsw/jbe@1309
|
11792 },
|
bsw/jbe@1309
|
11793
|
bsw/jbe@1309
|
11794 _endOffsetForNode: function(node) {
|
bsw/jbe@1309
|
11795 var range = document.createRange();
|
bsw/jbe@1309
|
11796 range.selectNodeContents(node);
|
bsw/jbe@1309
|
11797 return range.endOffset;
|
bsw/jbe@1309
|
11798 },
|
bsw/jbe@1309
|
11799
|
bsw/jbe@1309
|
11800 _detectInlineRangeProblems: function(range) {
|
bsw/jbe@1309
|
11801 var position = dom.compareDocumentPosition(range.startContainer, range.endContainer);
|
bsw/jbe@1309
|
11802 return (
|
bsw/jbe@1309
|
11803 range.endOffset == 0 &&
|
bsw/jbe@1309
|
11804 position & 4 //Node.DOCUMENT_POSITION_FOLLOWING
|
bsw/jbe@1309
|
11805 );
|
bsw/jbe@1309
|
11806 },
|
bsw/jbe@1309
|
11807
|
bsw/jbe@1309
|
11808 getRange: function(dontFix) {
|
bsw/jbe@1309
|
11809 var selection = this.getSelection(),
|
bsw/jbe@1309
|
11810 range = selection && selection.rangeCount && selection.getRangeAt(0);
|
bsw/jbe@1309
|
11811
|
bsw/jbe@1309
|
11812 if (dontFix !== true) {
|
bsw/jbe@1309
|
11813 this.fixRangeOverflow(range);
|
bsw/jbe@1309
|
11814 }
|
bsw/jbe@1309
|
11815
|
bsw/jbe@1309
|
11816 return range;
|
bsw/jbe@1309
|
11817 },
|
bsw/jbe@1309
|
11818
|
bsw/jbe@1309
|
11819 getOwnUneditables: function() {
|
bsw/jbe@1309
|
11820 var allUneditables = dom.query(this.contain, '.' + this.unselectableClass),
|
bsw/jbe@1309
|
11821 deepUneditables = dom.query(allUneditables, '.' + this.unselectableClass);
|
bsw/jbe@1309
|
11822
|
bsw/jbe@1309
|
11823 return wysihtml.lang.array(allUneditables).without(deepUneditables);
|
bsw/jbe@1309
|
11824 },
|
bsw/jbe@1309
|
11825
|
bsw/jbe@1309
|
11826 // Returns an array of ranges that belong only to this editable
|
bsw/jbe@1309
|
11827 // Needed as uneditable block in contenteditabel can split range into pieces
|
bsw/jbe@1309
|
11828 // If manipulating content reverse loop is usually needed as manipulation can shift subsequent ranges
|
bsw/jbe@1309
|
11829 getOwnRanges: function() {
|
bsw/jbe@1309
|
11830 var ranges = [],
|
bsw/jbe@1309
|
11831 r = this.getRange(),
|
bsw/jbe@1309
|
11832 tmpRanges;
|
bsw/jbe@1309
|
11833
|
bsw/jbe@1309
|
11834 if (r) { ranges.push(r); }
|
bsw/jbe@1309
|
11835
|
bsw/jbe@1309
|
11836 if (this.unselectableClass && this.contain && r) {
|
bsw/jbe@1309
|
11837 var uneditables = this.getOwnUneditables(),
|
bsw/jbe@1309
|
11838 tmpRange;
|
bsw/jbe@1309
|
11839 if (uneditables.length > 0) {
|
bsw/jbe@1309
|
11840 for (var i = 0, imax = uneditables.length; i < imax; i++) {
|
bsw/jbe@1309
|
11841 tmpRanges = [];
|
bsw/jbe@1309
|
11842 for (var j = 0, jmax = ranges.length; j < jmax; j++) {
|
bsw/jbe@1309
|
11843 if (ranges[j]) {
|
bsw/jbe@1309
|
11844 switch (ranges[j].compareNode(uneditables[i])) {
|
bsw/jbe@1309
|
11845 case 2:
|
bsw/jbe@1309
|
11846 // all selection inside uneditable. remove
|
bsw/jbe@1309
|
11847 break;
|
bsw/jbe@1309
|
11848 case 3:
|
bsw/jbe@1309
|
11849 //section begins before and ends after uneditable. spilt
|
bsw/jbe@1309
|
11850 tmpRange = ranges[j].cloneRange();
|
bsw/jbe@1309
|
11851 tmpRange.setEndBefore(uneditables[i]);
|
bsw/jbe@1309
|
11852 tmpRanges.push(tmpRange);
|
bsw/jbe@1309
|
11853
|
bsw/jbe@1309
|
11854 tmpRange = ranges[j].cloneRange();
|
bsw/jbe@1309
|
11855 tmpRange.setStartAfter(uneditables[i]);
|
bsw/jbe@1309
|
11856 tmpRanges.push(tmpRange);
|
bsw/jbe@1309
|
11857 break;
|
bsw/jbe@1309
|
11858 default:
|
bsw/jbe@1309
|
11859 // in all other cases uneditable does not touch selection. dont modify
|
bsw/jbe@1309
|
11860 tmpRanges.push(ranges[j]);
|
bsw/jbe@1309
|
11861 }
|
bsw/jbe@1309
|
11862 }
|
bsw/jbe@1309
|
11863 ranges = tmpRanges;
|
bsw/jbe@1309
|
11864 }
|
bsw/jbe@1309
|
11865 }
|
bsw/jbe@1309
|
11866 }
|
bsw/jbe@1309
|
11867 }
|
bsw/jbe@1309
|
11868 return ranges;
|
bsw/jbe@1309
|
11869 },
|
bsw/jbe@1309
|
11870
|
bsw/jbe@1309
|
11871 getSelection: function() {
|
bsw/jbe@1309
|
11872 return rangy.getSelection(this.win);
|
bsw/jbe@1309
|
11873 },
|
bsw/jbe@1309
|
11874
|
bsw/jbe@1309
|
11875 // Sets selection in document to a given range
|
bsw/jbe@1309
|
11876 // Set selection method detects if it fails to set any selection in document and returns null on fail
|
bsw/jbe@1309
|
11877 // (especially needed in webkit where some ranges just can not create selection for no reason)
|
bsw/jbe@1309
|
11878 setSelection: function(range) {
|
bsw/jbe@1309
|
11879 var selection = rangy.getSelection(this.win);
|
bsw/jbe@1309
|
11880 selection.setSingleRange(range);
|
bsw/jbe@1309
|
11881 return (selection && selection.anchorNode && selection.focusNode) ? selection : null;
|
bsw/jbe@1309
|
11882 },
|
bsw/jbe@1309
|
11883
|
bsw/jbe@1309
|
11884
|
bsw/jbe@1309
|
11885
|
bsw/jbe@1309
|
11886 // Webkit has an ancient error of not selecting all contents when uneditable block element is first or last in editable area
|
bsw/jbe@1309
|
11887 selectAll: function() {
|
bsw/jbe@1309
|
11888 var range = this.createRange(),
|
bsw/jbe@1309
|
11889 composer = this.composer,
|
bsw/jbe@1309
|
11890 that = this,
|
bsw/jbe@1309
|
11891 blankEndNode = getWebkitSelectionFixNode(this.composer.element),
|
bsw/jbe@1309
|
11892 blankStartNode = getWebkitSelectionFixNode(this.composer.element),
|
bsw/jbe@1309
|
11893 s;
|
bsw/jbe@1309
|
11894
|
bsw/jbe@1309
|
11895 var doSelect = function() {
|
bsw/jbe@1309
|
11896 range.setStart(composer.element, 0);
|
bsw/jbe@1309
|
11897 range.setEnd(composer.element, composer.element.childNodes.length);
|
bsw/jbe@1309
|
11898 s = that.setSelection(range);
|
bsw/jbe@1309
|
11899 };
|
bsw/jbe@1309
|
11900
|
bsw/jbe@1309
|
11901 var notSelected = function() {
|
bsw/jbe@1309
|
11902 return !s || (s.nativeSelection && s.nativeSelection.type && (s.nativeSelection.type === "Caret" || s.nativeSelection.type === "None"));
|
bsw/jbe@1309
|
11903 }
|
bsw/jbe@1309
|
11904
|
bsw/jbe@1309
|
11905 wysihtml.dom.removeInvisibleSpaces(this.composer.element);
|
bsw/jbe@1309
|
11906 doSelect();
|
bsw/jbe@1309
|
11907
|
bsw/jbe@1309
|
11908 if (this.composer.element.firstChild && notSelected()) {
|
bsw/jbe@1309
|
11909 // Try fixing end
|
bsw/jbe@1309
|
11910 this.composer.element.appendChild(blankEndNode);
|
bsw/jbe@1309
|
11911 doSelect();
|
bsw/jbe@1309
|
11912
|
bsw/jbe@1309
|
11913 if (notSelected()) {
|
bsw/jbe@1309
|
11914 // Remove end fix
|
bsw/jbe@1309
|
11915 blankEndNode.parentNode.removeChild(blankEndNode);
|
bsw/jbe@1309
|
11916
|
bsw/jbe@1309
|
11917 // Try fixing beginning
|
bsw/jbe@1309
|
11918 this.composer.element.insertBefore(blankStartNode, this.composer.element.firstChild);
|
bsw/jbe@1309
|
11919 doSelect();
|
bsw/jbe@1309
|
11920
|
bsw/jbe@1309
|
11921 if (notSelected()) {
|
bsw/jbe@1309
|
11922 // Try fixing both
|
bsw/jbe@1309
|
11923 this.composer.element.appendChild(blankEndNode);
|
bsw/jbe@1309
|
11924 doSelect();
|
bsw/jbe@1309
|
11925 }
|
bsw/jbe@1309
|
11926 }
|
bsw/jbe@1309
|
11927 }
|
bsw/jbe@1309
|
11928 },
|
bsw/jbe@1309
|
11929
|
bsw/jbe@1309
|
11930 createRange: function() {
|
bsw/jbe@1309
|
11931 return rangy.createRange(this.doc);
|
bsw/jbe@1309
|
11932 },
|
bsw/jbe@1309
|
11933
|
bsw/jbe@1309
|
11934 isCollapsed: function() {
|
bsw/jbe@1309
|
11935 return this.getSelection().isCollapsed;
|
bsw/jbe@1309
|
11936 },
|
bsw/jbe@1309
|
11937
|
bsw/jbe@1309
|
11938 getHtml: function() {
|
bsw/jbe@1309
|
11939 return this.getSelection().toHtml();
|
bsw/jbe@1309
|
11940 },
|
bsw/jbe@1309
|
11941
|
bsw/jbe@1309
|
11942 getPlainText: function () {
|
bsw/jbe@1309
|
11943 return this.getSelection().toString();
|
bsw/jbe@1309
|
11944 },
|
bsw/jbe@1309
|
11945
|
bsw/jbe@1309
|
11946 isEndToEndInNode: function(nodeNames) {
|
bsw/jbe@1309
|
11947 var range = this.getRange(),
|
bsw/jbe@1309
|
11948 parentElement = range.commonAncestorContainer,
|
bsw/jbe@1309
|
11949 startNode = range.startContainer,
|
bsw/jbe@1309
|
11950 endNode = range.endContainer;
|
bsw/jbe@1309
|
11951
|
bsw/jbe@1309
|
11952
|
bsw/jbe@1309
|
11953 if (parentElement.nodeType === wysihtml.TEXT_NODE) {
|
bsw/jbe@1309
|
11954 parentElement = parentElement.parentNode;
|
bsw/jbe@1309
|
11955 }
|
bsw/jbe@1309
|
11956
|
bsw/jbe@1309
|
11957 if (startNode.nodeType === wysihtml.TEXT_NODE && !(/^\s*$/).test(startNode.data.substr(range.startOffset))) {
|
bsw/jbe@1309
|
11958 return false;
|
bsw/jbe@1309
|
11959 }
|
bsw/jbe@1309
|
11960
|
bsw/jbe@1309
|
11961 if (endNode.nodeType === wysihtml.TEXT_NODE && !(/^\s*$/).test(endNode.data.substr(range.endOffset))) {
|
bsw/jbe@1309
|
11962 return false;
|
bsw/jbe@1309
|
11963 }
|
bsw/jbe@1309
|
11964
|
bsw/jbe@1309
|
11965 while (startNode && startNode !== parentElement) {
|
bsw/jbe@1309
|
11966 if (startNode.nodeType !== wysihtml.TEXT_NODE && !wysihtml.dom.contains(parentElement, startNode)) {
|
bsw/jbe@1309
|
11967 return false;
|
bsw/jbe@1309
|
11968 }
|
bsw/jbe@1309
|
11969 if (wysihtml.dom.domNode(startNode).prev({ignoreBlankTexts: true})) {
|
bsw/jbe@1309
|
11970 return false;
|
bsw/jbe@1309
|
11971 }
|
bsw/jbe@1309
|
11972 startNode = startNode.parentNode;
|
bsw/jbe@1309
|
11973 }
|
bsw/jbe@1309
|
11974
|
bsw/jbe@1309
|
11975 while (endNode && endNode !== parentElement) {
|
bsw/jbe@1309
|
11976 if (endNode.nodeType !== wysihtml.TEXT_NODE && !wysihtml.dom.contains(parentElement, endNode)) {
|
bsw/jbe@1309
|
11977 return false;
|
bsw/jbe@1309
|
11978 }
|
bsw/jbe@1309
|
11979 if (wysihtml.dom.domNode(endNode).next({ignoreBlankTexts: true})) {
|
bsw/jbe@1309
|
11980 return false;
|
bsw/jbe@1309
|
11981 }
|
bsw/jbe@1309
|
11982 endNode = endNode.parentNode;
|
bsw/jbe@1309
|
11983 }
|
bsw/jbe@1309
|
11984
|
bsw/jbe@1309
|
11985 return (wysihtml.lang.array(nodeNames).contains(parentElement.nodeName)) ? parentElement : false;
|
bsw/jbe@1309
|
11986 },
|
bsw/jbe@1309
|
11987
|
bsw/jbe@1309
|
11988 isInThisEditable: function() {
|
bsw/jbe@1309
|
11989 var sel = this.getSelection(),
|
bsw/jbe@1309
|
11990 fnode = sel.focusNode,
|
bsw/jbe@1309
|
11991 anode = sel.anchorNode;
|
bsw/jbe@1309
|
11992
|
bsw/jbe@1309
|
11993 // In IE node contains will not work for textnodes, thus taking parentNode
|
bsw/jbe@1309
|
11994 if (fnode && fnode.nodeType !== 1) {
|
bsw/jbe@1309
|
11995 fnode = fnode.parentNode;
|
bsw/jbe@1309
|
11996 }
|
bsw/jbe@1309
|
11997
|
bsw/jbe@1309
|
11998 if (anode && anode.nodeType !== 1) {
|
bsw/jbe@1309
|
11999 anode = anode.parentNode;
|
bsw/jbe@1309
|
12000 }
|
bsw/jbe@1309
|
12001
|
bsw/jbe@1309
|
12002 return anode && fnode &&
|
bsw/jbe@1309
|
12003 (wysihtml.dom.contains(this.composer.element, fnode) || this.composer.element === fnode) &&
|
bsw/jbe@1309
|
12004 (wysihtml.dom.contains(this.composer.element, anode) || this.composer.element === anode);
|
bsw/jbe@1309
|
12005 },
|
bsw/jbe@1309
|
12006
|
bsw/jbe@1309
|
12007 deselect: function() {
|
bsw/jbe@1309
|
12008 var sel = this.getSelection();
|
bsw/jbe@1309
|
12009 sel && sel.removeAllRanges();
|
bsw/jbe@1309
|
12010 }
|
bsw/jbe@1309
|
12011 });
|
bsw/jbe@1309
|
12012
|
bsw/jbe@1309
|
12013 })(wysihtml);
|
bsw/jbe@1309
|
12014
|
bsw/jbe@1309
|
12015 /**
|
bsw/jbe@1309
|
12016 * Rich Text Query/Formatting Commands
|
bsw/jbe@1309
|
12017 *
|
bsw/jbe@1309
|
12018 * @example
|
bsw/jbe@1309
|
12019 * var commands = new wysihtml.Commands(editor);
|
bsw/jbe@1309
|
12020 */
|
bsw/jbe@1309
|
12021 wysihtml.Commands = Base.extend(
|
bsw/jbe@1309
|
12022 /** @scope wysihtml.Commands.prototype */ {
|
bsw/jbe@1309
|
12023 constructor: function(editor) {
|
bsw/jbe@1309
|
12024 this.editor = editor;
|
bsw/jbe@1309
|
12025 this.composer = editor.composer;
|
bsw/jbe@1309
|
12026 this.doc = this.composer.doc;
|
bsw/jbe@1309
|
12027 },
|
bsw/jbe@1309
|
12028
|
bsw/jbe@1309
|
12029 /**
|
bsw/jbe@1309
|
12030 * Check whether the browser supports the given command
|
bsw/jbe@1309
|
12031 *
|
bsw/jbe@1309
|
12032 * @param {String} command The command string which to check (eg. "bold", "italic", "insertUnorderedList")
|
bsw/jbe@1309
|
12033 * @example
|
bsw/jbe@1309
|
12034 * commands.supports("createLink");
|
bsw/jbe@1309
|
12035 */
|
bsw/jbe@1309
|
12036 support: function(command) {
|
bsw/jbe@1309
|
12037 return wysihtml.browser.supportsCommand(this.doc, command);
|
bsw/jbe@1309
|
12038 },
|
bsw/jbe@1309
|
12039
|
bsw/jbe@1309
|
12040 /**
|
bsw/jbe@1309
|
12041 * Check whether the browser supports the given command
|
bsw/jbe@1309
|
12042 *
|
bsw/jbe@1309
|
12043 * @param {String} command The command string which to execute (eg. "bold", "italic", "insertUnorderedList")
|
bsw/jbe@1309
|
12044 * @param {String} [value] The command value parameter, needed for some commands ("createLink", "insertImage", ...), optional for commands that don't require one ("bold", "underline", ...)
|
bsw/jbe@1309
|
12045 * @example
|
bsw/jbe@1309
|
12046 * commands.exec("insertImage", "http://a1.twimg.com/profile_images/113868655/schrei_twitter_reasonably_small.jpg");
|
bsw/jbe@1309
|
12047 */
|
bsw/jbe@1309
|
12048 exec: function(command, value) {
|
bsw/jbe@1309
|
12049 var obj = wysihtml.commands[command],
|
bsw/jbe@1309
|
12050 args = wysihtml.lang.array(arguments).get(),
|
bsw/jbe@1309
|
12051 method = obj && obj.exec,
|
bsw/jbe@1309
|
12052 result = null;
|
bsw/jbe@1309
|
12053
|
bsw/jbe@1309
|
12054 // If composer ahs placeholder unset it before command
|
bsw/jbe@1309
|
12055 // Do not apply on commands that are behavioral
|
bsw/jbe@1309
|
12056 if (this.composer.hasPlaceholderSet() && !wysihtml.lang.array(['styleWithCSS', 'enableObjectResizing', 'enableInlineTableEditing']).contains(command)) {
|
bsw/jbe@1309
|
12057 this.composer.element.innerHTML = "";
|
bsw/jbe@1309
|
12058 this.composer.selection.selectNode(this.composer.element);
|
bsw/jbe@1309
|
12059 }
|
bsw/jbe@1309
|
12060
|
bsw/jbe@1309
|
12061 this.editor.fire("beforecommand:composer");
|
bsw/jbe@1309
|
12062
|
bsw/jbe@1309
|
12063 if (method) {
|
bsw/jbe@1309
|
12064 args.unshift(this.composer);
|
bsw/jbe@1309
|
12065 result = method.apply(obj, args);
|
bsw/jbe@1309
|
12066 } else {
|
bsw/jbe@1309
|
12067 try {
|
bsw/jbe@1309
|
12068 // try/catch for buggy firefox
|
bsw/jbe@1309
|
12069 result = this.doc.execCommand(command, false, value);
|
bsw/jbe@1309
|
12070 } catch(e) {}
|
bsw/jbe@1309
|
12071 }
|
bsw/jbe@1309
|
12072
|
bsw/jbe@1309
|
12073 this.editor.fire("aftercommand:composer");
|
bsw/jbe@1309
|
12074 return result;
|
bsw/jbe@1309
|
12075 },
|
bsw/jbe@1309
|
12076
|
bsw/jbe@1309
|
12077 remove: function(command, commandValue) {
|
bsw/jbe@1309
|
12078 var obj = wysihtml.commands[command],
|
bsw/jbe@1309
|
12079 args = wysihtml.lang.array(arguments).get(),
|
bsw/jbe@1309
|
12080 method = obj && obj.remove;
|
bsw/jbe@1309
|
12081 if (method) {
|
bsw/jbe@1309
|
12082 args.unshift(this.composer);
|
bsw/jbe@1309
|
12083 return method.apply(obj, args);
|
bsw/jbe@1309
|
12084 }
|
bsw/jbe@1309
|
12085 },
|
bsw/jbe@1309
|
12086
|
bsw/jbe@1309
|
12087 /**
|
bsw/jbe@1309
|
12088 * Check whether the current command is active
|
bsw/jbe@1309
|
12089 * If the caret is within a bold text, then calling this with command "bold" should return true
|
bsw/jbe@1309
|
12090 *
|
bsw/jbe@1309
|
12091 * @param {String} command The command string which to check (eg. "bold", "italic", "insertUnorderedList")
|
bsw/jbe@1309
|
12092 * @param {String} [commandValue] The command value parameter (eg. for "insertImage" the image src)
|
bsw/jbe@1309
|
12093 * @return {Boolean} Whether the command is active
|
bsw/jbe@1309
|
12094 * @example
|
bsw/jbe@1309
|
12095 * var isCurrentSelectionBold = commands.state("bold");
|
bsw/jbe@1309
|
12096 */
|
bsw/jbe@1309
|
12097 state: function(command, commandValue) {
|
bsw/jbe@1309
|
12098 var obj = wysihtml.commands[command],
|
bsw/jbe@1309
|
12099 args = wysihtml.lang.array(arguments).get(),
|
bsw/jbe@1309
|
12100 method = obj && obj.state;
|
bsw/jbe@1309
|
12101 if (method) {
|
bsw/jbe@1309
|
12102 args.unshift(this.composer);
|
bsw/jbe@1309
|
12103 return method.apply(obj, args);
|
bsw/jbe@1309
|
12104 } else {
|
bsw/jbe@1309
|
12105 try {
|
bsw/jbe@1309
|
12106 // try/catch for buggy firefox
|
bsw/jbe@1309
|
12107 return this.doc.queryCommandState(command);
|
bsw/jbe@1309
|
12108 } catch(e) {
|
bsw/jbe@1309
|
12109 return false;
|
bsw/jbe@1309
|
12110 }
|
bsw/jbe@1309
|
12111 }
|
bsw/jbe@1309
|
12112 },
|
bsw/jbe@1309
|
12113
|
bsw/jbe@1309
|
12114 /* Get command state parsed value if command has stateValue parsing function */
|
bsw/jbe@1309
|
12115 stateValue: function(command) {
|
bsw/jbe@1309
|
12116 var obj = wysihtml.commands[command],
|
bsw/jbe@1309
|
12117 args = wysihtml.lang.array(arguments).get(),
|
bsw/jbe@1309
|
12118 method = obj && obj.stateValue;
|
bsw/jbe@1309
|
12119 if (method) {
|
bsw/jbe@1309
|
12120 args.unshift(this.composer);
|
bsw/jbe@1309
|
12121 return method.apply(obj, args);
|
bsw/jbe@1309
|
12122 } else {
|
bsw/jbe@1309
|
12123 return false;
|
bsw/jbe@1309
|
12124 }
|
bsw/jbe@1309
|
12125 }
|
bsw/jbe@1309
|
12126 });
|
bsw/jbe@1309
|
12127
|
bsw/jbe@1309
|
12128 (function(wysihtml) {
|
bsw/jbe@1309
|
12129
|
bsw/jbe@1309
|
12130 var nodeOptions = {
|
bsw/jbe@1309
|
12131 nodeName: "A",
|
bsw/jbe@1309
|
12132 toggle: false
|
bsw/jbe@1309
|
12133 };
|
bsw/jbe@1309
|
12134
|
bsw/jbe@1309
|
12135 function getOptions(value) {
|
bsw/jbe@1309
|
12136 var options = typeof value === 'object' ? value : {'href': value};
|
bsw/jbe@1309
|
12137 return wysihtml.lang.object({}).merge(nodeOptions).merge({'attribute': value}).get();
|
bsw/jbe@1309
|
12138 }
|
bsw/jbe@1309
|
12139
|
bsw/jbe@1309
|
12140 wysihtml.commands.createLink = {
|
bsw/jbe@1309
|
12141 exec: function(composer, command, value) {
|
bsw/jbe@1309
|
12142 var opts = getOptions(value);
|
bsw/jbe@1309
|
12143
|
bsw/jbe@1309
|
12144 if (composer.selection.isCollapsed() && !this.state(composer, command)) {
|
bsw/jbe@1309
|
12145 var textNode = composer.doc.createTextNode(opts.attribute.href);
|
bsw/jbe@1309
|
12146 composer.selection.insertNode(textNode);
|
bsw/jbe@1309
|
12147 composer.selection.selectNode(textNode);
|
bsw/jbe@1309
|
12148 }
|
bsw/jbe@1309
|
12149 wysihtml.commands.formatInline.exec(composer, command, opts);
|
bsw/jbe@1309
|
12150 },
|
bsw/jbe@1309
|
12151
|
bsw/jbe@1309
|
12152 state: function(composer, command) {
|
bsw/jbe@1309
|
12153 return wysihtml.commands.formatInline.state(composer, command, nodeOptions);
|
bsw/jbe@1309
|
12154 }
|
bsw/jbe@1309
|
12155 };
|
bsw/jbe@1309
|
12156
|
bsw/jbe@1309
|
12157 })(wysihtml);
|
bsw/jbe@1309
|
12158
|
bsw/jbe@1309
|
12159 /* Formatblock
|
bsw/jbe@1309
|
12160 * Is used to insert block level elements
|
bsw/jbe@1309
|
12161 * It tries to solve the case that some block elements should not contain other block level elements (h1-6, p, ...)
|
bsw/jbe@1309
|
12162 *
|
bsw/jbe@1309
|
12163 */
|
bsw/jbe@1309
|
12164 (function(wysihtml) {
|
bsw/jbe@1309
|
12165
|
bsw/jbe@1309
|
12166 var dom = wysihtml.dom,
|
bsw/jbe@1309
|
12167 // When the caret is within a H1 and the H4 is invoked, the H1 should turn into H4
|
bsw/jbe@1309
|
12168 // instead of creating a H4 within a H1 which would result in semantically invalid html
|
bsw/jbe@1309
|
12169 UNNESTABLE_BLOCK_ELEMENTS = "h1, h2, h3, h4, h5, h6, p, pre",
|
bsw/jbe@1309
|
12170 BLOCK_ELEMENTS = "h1, h2, h3, h4, h5, h6, p, pre, div, blockquote",
|
bsw/jbe@1309
|
12171 INLINE_ELEMENTS = "b, big, i, small, tt, abbr, acronym, cite, code, dfn, em, kbd, strong, samp, var, a, bdo, br, q, span, sub, sup, button, label, textarea, input, select, u";
|
bsw/jbe@1309
|
12172
|
bsw/jbe@1309
|
12173 function correctOptionsForSimilarityCheck(options) {
|
bsw/jbe@1309
|
12174 return {
|
bsw/jbe@1309
|
12175 nodeName: options.nodeName || null,
|
bsw/jbe@1309
|
12176 className: (!options.classRegExp) ? options.className || null : null,
|
bsw/jbe@1309
|
12177 classRegExp: options.classRegExp || null,
|
bsw/jbe@1309
|
12178 styleProperty: options.styleProperty || null
|
bsw/jbe@1309
|
12179 };
|
bsw/jbe@1309
|
12180 }
|
bsw/jbe@1309
|
12181
|
bsw/jbe@1309
|
12182 function getRangeNode(node, offset) {
|
bsw/jbe@1309
|
12183 if (node.nodeType === 3) {
|
bsw/jbe@1309
|
12184 return node;
|
bsw/jbe@1309
|
12185 } else {
|
bsw/jbe@1309
|
12186 return node.childNodes[offset] || node;
|
bsw/jbe@1309
|
12187 }
|
bsw/jbe@1309
|
12188 }
|
bsw/jbe@1309
|
12189
|
bsw/jbe@1309
|
12190 // Returns if node is a line break
|
bsw/jbe@1309
|
12191 function isBr(n) {
|
bsw/jbe@1309
|
12192 return n && n.nodeType === 1 && n.nodeName === "BR";
|
bsw/jbe@1309
|
12193 }
|
bsw/jbe@1309
|
12194
|
bsw/jbe@1309
|
12195 // Is block level element
|
bsw/jbe@1309
|
12196 function isBlock(n, composer) {
|
bsw/jbe@1309
|
12197 return n && n.nodeType === 1 && composer.win.getComputedStyle(n).display === "block";
|
bsw/jbe@1309
|
12198 }
|
bsw/jbe@1309
|
12199
|
bsw/jbe@1309
|
12200 // Returns if node is the rangy selection bookmark element (that must not be taken into account in most situatons and is removed on selection restoring)
|
bsw/jbe@1309
|
12201 function isBookmark(n) {
|
bsw/jbe@1309
|
12202 return n && n.nodeType === 1 && n.classList.contains('rangySelectionBoundary');
|
bsw/jbe@1309
|
12203 }
|
bsw/jbe@1309
|
12204
|
bsw/jbe@1309
|
12205 // Is line breaking node
|
bsw/jbe@1309
|
12206 function isLineBreaking(n, composer) {
|
bsw/jbe@1309
|
12207 return isBr(n) || isBlock(n, composer);
|
bsw/jbe@1309
|
12208 }
|
bsw/jbe@1309
|
12209
|
bsw/jbe@1309
|
12210 // Removes empty block level elements
|
bsw/jbe@1309
|
12211 function cleanup(composer, newBlockElements) {
|
bsw/jbe@1309
|
12212 wysihtml.dom.removeInvisibleSpaces(composer.element);
|
bsw/jbe@1309
|
12213 var container = composer.element,
|
bsw/jbe@1309
|
12214 allElements = container.querySelectorAll(BLOCK_ELEMENTS),
|
bsw/jbe@1309
|
12215 noEditQuery = composer.config.classNames.uneditableContainer + ([""]).concat(BLOCK_ELEMENTS.split(',')).join(", " + composer.config.classNames.uneditableContainer + ' '),
|
bsw/jbe@1309
|
12216 uneditables = container.querySelectorAll(noEditQuery),
|
bsw/jbe@1309
|
12217 elements = wysihtml.lang.array(allElements).without(uneditables), // Lets not touch uneditable elements and their contents
|
bsw/jbe@1309
|
12218 nbIdx;
|
bsw/jbe@1309
|
12219
|
bsw/jbe@1309
|
12220 for (var i = elements.length; i--;) {
|
bsw/jbe@1309
|
12221 if (elements[i].innerHTML.replace(/[\uFEFF]/g, '') === "" && (newBlockElements.length === 0 || elements[i] !== newBlockElements[newBlockElements.length - 1])) {
|
bsw/jbe@1309
|
12222 // If cleanup removes some new block elements. remove them from newblocks array too
|
bsw/jbe@1309
|
12223 nbIdx = wysihtml.lang.array(newBlockElements).indexOf(elements[i]);
|
bsw/jbe@1309
|
12224 if (nbIdx > -1) {
|
bsw/jbe@1309
|
12225 newBlockElements.splice(nbIdx, 1);
|
bsw/jbe@1309
|
12226 }
|
bsw/jbe@1309
|
12227 elements[i].parentNode.removeChild(elements[i]);
|
bsw/jbe@1309
|
12228 }
|
bsw/jbe@1309
|
12229 }
|
bsw/jbe@1309
|
12230
|
bsw/jbe@1309
|
12231 return newBlockElements;
|
bsw/jbe@1309
|
12232 }
|
bsw/jbe@1309
|
12233
|
bsw/jbe@1309
|
12234 function defaultNodeName(composer) {
|
bsw/jbe@1309
|
12235 return composer.config.useLineBreaks ? "DIV" : "P";
|
bsw/jbe@1309
|
12236 }
|
bsw/jbe@1309
|
12237
|
bsw/jbe@1309
|
12238 // The outermost un-nestable block element parent of from node
|
bsw/jbe@1309
|
12239 function findOuterBlock(node, container, allBlocks) {
|
bsw/jbe@1309
|
12240 var n = node,
|
bsw/jbe@1309
|
12241 block = null;
|
bsw/jbe@1309
|
12242
|
bsw/jbe@1309
|
12243 while (n && container && n !== container) {
|
bsw/jbe@1309
|
12244 if (n.nodeType === 1 && n.matches(allBlocks ? BLOCK_ELEMENTS : UNNESTABLE_BLOCK_ELEMENTS)) {
|
bsw/jbe@1309
|
12245 block = n;
|
bsw/jbe@1309
|
12246 }
|
bsw/jbe@1309
|
12247 n = n.parentNode;
|
bsw/jbe@1309
|
12248 }
|
bsw/jbe@1309
|
12249
|
bsw/jbe@1309
|
12250 return block;
|
bsw/jbe@1309
|
12251 }
|
bsw/jbe@1309
|
12252
|
bsw/jbe@1309
|
12253 // Clone for splitting the inner inline element out of its parent inline elements context
|
bsw/jbe@1309
|
12254 // For example if selection is in bold and italic, clone the outer nodes and wrap these around content and return
|
bsw/jbe@1309
|
12255 function cloneOuterInlines(node, container) {
|
bsw/jbe@1309
|
12256 var n = node,
|
bsw/jbe@1309
|
12257 innerNode,
|
bsw/jbe@1309
|
12258 parentNode,
|
bsw/jbe@1309
|
12259 el = null,
|
bsw/jbe@1309
|
12260 el2;
|
bsw/jbe@1309
|
12261
|
bsw/jbe@1309
|
12262 while (n && container && n !== container) {
|
bsw/jbe@1309
|
12263 if (n.nodeType === 1 && n.matches(INLINE_ELEMENTS)) {
|
bsw/jbe@1309
|
12264 parentNode = n;
|
bsw/jbe@1309
|
12265 if (el === null) {
|
bsw/jbe@1309
|
12266 el = n.cloneNode(false);
|
bsw/jbe@1309
|
12267 innerNode = el;
|
bsw/jbe@1309
|
12268 } else {
|
bsw/jbe@1309
|
12269 el2 = n.cloneNode(false);
|
bsw/jbe@1309
|
12270 el2.appendChild(el);
|
bsw/jbe@1309
|
12271 el = el2;
|
bsw/jbe@1309
|
12272 }
|
bsw/jbe@1309
|
12273 }
|
bsw/jbe@1309
|
12274 n = n.parentNode;
|
bsw/jbe@1309
|
12275 }
|
bsw/jbe@1309
|
12276
|
bsw/jbe@1309
|
12277 return {
|
bsw/jbe@1309
|
12278 parent: parentNode,
|
bsw/jbe@1309
|
12279 outerNode: el,
|
bsw/jbe@1309
|
12280 innerNode: innerNode
|
bsw/jbe@1309
|
12281 };
|
bsw/jbe@1309
|
12282 }
|
bsw/jbe@1309
|
12283
|
bsw/jbe@1309
|
12284 // Formats an element according to options nodeName, className, styleProperty, styleValue
|
bsw/jbe@1309
|
12285 // If element is not defined, creates new element
|
bsw/jbe@1309
|
12286 // if opotions is null, remove format instead
|
bsw/jbe@1309
|
12287 function applyOptionsToElement(element, options, composer) {
|
bsw/jbe@1309
|
12288
|
bsw/jbe@1309
|
12289 if (!element) {
|
bsw/jbe@1309
|
12290 element = composer.doc.createElement(options.nodeName || defaultNodeName(composer));
|
bsw/jbe@1309
|
12291 // Add invisible space as otherwise webkit cannot set selection or range to it correctly
|
bsw/jbe@1309
|
12292 element.appendChild(composer.doc.createTextNode(wysihtml.INVISIBLE_SPACE));
|
bsw/jbe@1309
|
12293 }
|
bsw/jbe@1309
|
12294
|
bsw/jbe@1309
|
12295 if (options.nodeName && element.nodeName !== options.nodeName) {
|
bsw/jbe@1309
|
12296 element = dom.renameElement(element, options.nodeName);
|
bsw/jbe@1309
|
12297 }
|
bsw/jbe@1309
|
12298
|
bsw/jbe@1309
|
12299 // Remove similar classes before applying className
|
bsw/jbe@1309
|
12300 if (options.classRegExp) {
|
bsw/jbe@1309
|
12301 element.className = element.className.replace(options.classRegExp, "");
|
bsw/jbe@1309
|
12302 }
|
bsw/jbe@1309
|
12303 if (options.className) {
|
bsw/jbe@1309
|
12304 element.classList.add(options.className);
|
bsw/jbe@1309
|
12305 }
|
bsw/jbe@1309
|
12306
|
bsw/jbe@1309
|
12307 if (options.styleProperty && typeof options.styleValue !== "undefined") {
|
bsw/jbe@1309
|
12308 element.style[wysihtml.browser.fixStyleKey(options.styleProperty)] = options.styleValue;
|
bsw/jbe@1309
|
12309 }
|
bsw/jbe@1309
|
12310
|
bsw/jbe@1309
|
12311 return element;
|
bsw/jbe@1309
|
12312 }
|
bsw/jbe@1309
|
12313
|
bsw/jbe@1309
|
12314 // Unsets element properties by options
|
bsw/jbe@1309
|
12315 // If nodename given and matches current element, element is unwrapped or converted to default node (depending on presence of class and style attributes)
|
bsw/jbe@1309
|
12316 function removeOptionsFromElement(element, options, composer) {
|
bsw/jbe@1309
|
12317 var style, classes,
|
bsw/jbe@1309
|
12318 prevNode = element.previousSibling,
|
bsw/jbe@1309
|
12319 nextNode = element.nextSibling,
|
bsw/jbe@1309
|
12320 unwrapped = false;
|
bsw/jbe@1309
|
12321
|
bsw/jbe@1309
|
12322 if (options.styleProperty) {
|
bsw/jbe@1309
|
12323 element.style[wysihtml.browser.fixStyleKey(options.styleProperty)] = '';
|
bsw/jbe@1309
|
12324 }
|
bsw/jbe@1309
|
12325 if (options.className) {
|
bsw/jbe@1309
|
12326 element.classList.remove(options.className);
|
bsw/jbe@1309
|
12327 }
|
bsw/jbe@1309
|
12328
|
bsw/jbe@1309
|
12329 if (options.classRegExp) {
|
bsw/jbe@1309
|
12330 element.className = element.className.replace(options.classRegExp, "");
|
bsw/jbe@1309
|
12331 }
|
bsw/jbe@1309
|
12332
|
bsw/jbe@1309
|
12333 // Clean up blank class attribute
|
bsw/jbe@1309
|
12334 if (element.getAttribute('class') !== null && element.getAttribute('class').trim() === "") {
|
bsw/jbe@1309
|
12335 element.removeAttribute('class');
|
bsw/jbe@1309
|
12336 }
|
bsw/jbe@1309
|
12337
|
bsw/jbe@1309
|
12338 if (options.nodeName && element.nodeName.toLowerCase() === options.nodeName.toLowerCase()) {
|
bsw/jbe@1309
|
12339 style = element.getAttribute('style');
|
bsw/jbe@1309
|
12340 if (!style || style.trim() === '') {
|
bsw/jbe@1309
|
12341 dom.unwrap(element);
|
bsw/jbe@1309
|
12342 unwrapped = true;
|
bsw/jbe@1309
|
12343 } else {
|
bsw/jbe@1309
|
12344 element = dom.renameElement(element, defaultNodeName(composer));
|
bsw/jbe@1309
|
12345 }
|
bsw/jbe@1309
|
12346 }
|
bsw/jbe@1309
|
12347
|
bsw/jbe@1309
|
12348 // Clean up blank style attribute
|
bsw/jbe@1309
|
12349 if (element.getAttribute('style') !== null && element.getAttribute('style').trim() === "") {
|
bsw/jbe@1309
|
12350 element.removeAttribute('style');
|
bsw/jbe@1309
|
12351 }
|
bsw/jbe@1309
|
12352
|
bsw/jbe@1309
|
12353 if (unwrapped) {
|
bsw/jbe@1309
|
12354 applySurroundingLineBreaks(prevNode, nextNode, composer);
|
bsw/jbe@1309
|
12355 }
|
bsw/jbe@1309
|
12356 }
|
bsw/jbe@1309
|
12357
|
bsw/jbe@1309
|
12358 // Unwraps block level elements from inside content
|
bsw/jbe@1309
|
12359 // Useful as not all block level elements can contain other block-levels
|
bsw/jbe@1309
|
12360 function unwrapBlocksFromContent(element) {
|
bsw/jbe@1309
|
12361 var blocks = element.querySelectorAll(BLOCK_ELEMENTS) || [], // Find unnestable block elements in extracted contents
|
bsw/jbe@1309
|
12362 nextEl, prevEl;
|
bsw/jbe@1309
|
12363
|
bsw/jbe@1309
|
12364 for (var i = blocks.length; i--;) {
|
bsw/jbe@1309
|
12365 nextEl = wysihtml.dom.domNode(blocks[i]).next({nodeTypes: [1,3], ignoreBlankTexts: true}),
|
bsw/jbe@1309
|
12366 prevEl = wysihtml.dom.domNode(blocks[i]).prev({nodeTypes: [1,3], ignoreBlankTexts: true});
|
bsw/jbe@1309
|
12367
|
bsw/jbe@1309
|
12368 if (nextEl && nextEl.nodeType !== 1 && nextEl.nodeName !== 'BR') {
|
bsw/jbe@1309
|
12369 if ((blocks[i].innerHTML || blocks[i].nodeValue || '').trim() !== '') {
|
bsw/jbe@1309
|
12370 blocks[i].parentNode.insertBefore(blocks[i].ownerDocument.createElement('BR'), nextEl);
|
bsw/jbe@1309
|
12371 }
|
bsw/jbe@1309
|
12372 }
|
bsw/jbe@1309
|
12373 if (nextEl && nextEl.nodeType !== 1 && nextEl.nodeName !== 'BR') {
|
bsw/jbe@1309
|
12374 if ((blocks[i].innerHTML || blocks[i].nodeValue || '').trim() !== '') {
|
bsw/jbe@1309
|
12375 blocks[i].parentNode.insertBefore(blocks[i].ownerDocument.createElement('BR'), nextEl);
|
bsw/jbe@1309
|
12376 }
|
bsw/jbe@1309
|
12377 }
|
bsw/jbe@1309
|
12378 wysihtml.dom.unwrap(blocks[i]);
|
bsw/jbe@1309
|
12379 }
|
bsw/jbe@1309
|
12380 }
|
bsw/jbe@1309
|
12381
|
bsw/jbe@1309
|
12382 // Fix ranges that visually cover whole block element to actually cover the block
|
bsw/jbe@1309
|
12383 function fixRangeCoverage(range, composer) {
|
bsw/jbe@1309
|
12384 var node,
|
bsw/jbe@1309
|
12385 start = range.startContainer,
|
bsw/jbe@1309
|
12386 end = range.endContainer;
|
bsw/jbe@1309
|
12387
|
bsw/jbe@1309
|
12388 // If range has only one childNode and it is end to end the range, extend the range to contain the container element too
|
bsw/jbe@1309
|
12389 // This ensures the wrapper node is modified and optios added to it
|
bsw/jbe@1309
|
12390 if (start && start.nodeType === 1 && start === end) {
|
bsw/jbe@1309
|
12391 if (start.firstChild === start.lastChild && range.endOffset === 1) {
|
bsw/jbe@1309
|
12392 if (start !== composer.element && start.nodeName !== 'LI' && start.nodeName !== 'TD') {
|
bsw/jbe@1309
|
12393 range.setStartBefore(start);
|
bsw/jbe@1309
|
12394 range.setEndAfter(end);
|
bsw/jbe@1309
|
12395 }
|
bsw/jbe@1309
|
12396 }
|
bsw/jbe@1309
|
12397 return;
|
bsw/jbe@1309
|
12398 }
|
bsw/jbe@1309
|
12399
|
bsw/jbe@1309
|
12400 // If range starts outside of node and ends inside at textrange and covers the whole node visually, extend end to cover the node end too
|
bsw/jbe@1309
|
12401 if (start && start.nodeType === 1 && end.nodeType === 3) {
|
bsw/jbe@1309
|
12402 if (start.firstChild === end && range.endOffset === end.data.length) {
|
bsw/jbe@1309
|
12403 if (start !== composer.element && start.nodeName !== 'LI' && start.nodeName !== 'TD') {
|
bsw/jbe@1309
|
12404 range.setEndAfter(start);
|
bsw/jbe@1309
|
12405 }
|
bsw/jbe@1309
|
12406 }
|
bsw/jbe@1309
|
12407 return;
|
bsw/jbe@1309
|
12408 }
|
bsw/jbe@1309
|
12409
|
bsw/jbe@1309
|
12410 // If range ends outside of node and starts inside at textrange and covers the whole node visually, extend start to cover the node start too
|
bsw/jbe@1309
|
12411 if (end && end.nodeType === 1 && start.nodeType === 3) {
|
bsw/jbe@1309
|
12412 if (end.firstChild === start && range.startOffset === 0) {
|
bsw/jbe@1309
|
12413 if (end !== composer.element && end.nodeName !== 'LI' && end.nodeName !== 'TD') {
|
bsw/jbe@1309
|
12414 range.setStartBefore(end);
|
bsw/jbe@1309
|
12415 }
|
bsw/jbe@1309
|
12416 }
|
bsw/jbe@1309
|
12417 return;
|
bsw/jbe@1309
|
12418 }
|
bsw/jbe@1309
|
12419
|
bsw/jbe@1309
|
12420 // If range covers a whole textnode and the textnode is the only child of node, extend range to node
|
bsw/jbe@1309
|
12421 if (start && start.nodeType === 3 && start === end && start.parentNode.childNodes.length === 1) {
|
bsw/jbe@1309
|
12422 if (range.endOffset == end.data.length && range.startOffset === 0) {
|
bsw/jbe@1309
|
12423 node = start.parentNode;
|
bsw/jbe@1309
|
12424 if (node !== composer.element && node.nodeName !== 'LI' && node.nodeName !== 'TD') {
|
bsw/jbe@1309
|
12425 range.setStartBefore(node);
|
bsw/jbe@1309
|
12426 range.setEndAfter(node);
|
bsw/jbe@1309
|
12427 }
|
bsw/jbe@1309
|
12428 }
|
bsw/jbe@1309
|
12429 return;
|
bsw/jbe@1309
|
12430 }
|
bsw/jbe@1309
|
12431 }
|
bsw/jbe@1309
|
12432
|
bsw/jbe@1309
|
12433 // Scans ranges array for insertion points that are not allowed to insert block tags fixes/splits illegal ranges
|
bsw/jbe@1309
|
12434 // Some places do not allow block level elements inbetween (inside ul and outside li)
|
bsw/jbe@1309
|
12435 // TODO: might need extending for other nodes besides li (maybe dd,dl,dt)
|
bsw/jbe@1309
|
12436 function fixNotPermittedInsertionPoints(ranges) {
|
bsw/jbe@1309
|
12437 var newRanges = [],
|
bsw/jbe@1309
|
12438 lis, j, maxj, tmpRange, rangePos, closestLI;
|
bsw/jbe@1309
|
12439
|
bsw/jbe@1309
|
12440 for (var i = 0, maxi = ranges.length; i < maxi; i++) {
|
bsw/jbe@1309
|
12441
|
bsw/jbe@1309
|
12442 // Fixes range start and end positions if inside UL or OL element (outside of LI)
|
bsw/jbe@1309
|
12443 if (ranges[i].startContainer.nodeType === 1 && ranges[i].startContainer.matches('ul, ol')) {
|
bsw/jbe@1309
|
12444 ranges[i].setStart(ranges[i].startContainer.childNodes[ranges[i].startOffset], 0);
|
bsw/jbe@1309
|
12445 }
|
bsw/jbe@1309
|
12446 if (ranges[i].endContainer.nodeType === 1 && ranges[i].endContainer.matches('ul, ol')) {
|
bsw/jbe@1309
|
12447 closestLI = ranges[i].endContainer.childNodes[Math.max(ranges[i].endOffset - 1, 0)];
|
bsw/jbe@1309
|
12448 if (closestLI.childNodes) {
|
bsw/jbe@1309
|
12449 ranges[i].setEnd(closestLI, closestLI.childNodes.length);
|
bsw/jbe@1309
|
12450 }
|
bsw/jbe@1309
|
12451 }
|
bsw/jbe@1309
|
12452
|
bsw/jbe@1309
|
12453 // Get all LI eleemnts in selection (fully or partially covered)
|
bsw/jbe@1309
|
12454 // And make sure ranges are either inside LI or outside UL/OL
|
bsw/jbe@1309
|
12455 // Split and add new ranges as needed to cover same range content
|
bsw/jbe@1309
|
12456 // TODO: Needs improvement to accept DL, DD, DT
|
bsw/jbe@1309
|
12457 lis = ranges[i].getNodes([1], function(node) {
|
bsw/jbe@1309
|
12458 return node.nodeName === "LI";
|
bsw/jbe@1309
|
12459 });
|
bsw/jbe@1309
|
12460 if (lis.length > 0) {
|
bsw/jbe@1309
|
12461
|
bsw/jbe@1309
|
12462 for (j = 0, maxj = lis.length; j < maxj; j++) {
|
bsw/jbe@1309
|
12463 rangePos = ranges[i].compareNode(lis[j]);
|
bsw/jbe@1309
|
12464
|
bsw/jbe@1309
|
12465 // Fixes start of range that crosses LI border
|
bsw/jbe@1309
|
12466 if (rangePos === ranges[i].NODE_AFTER || rangePos === ranges[i].NODE_INSIDE) {
|
bsw/jbe@1309
|
12467 // Range starts before and ends inside the node
|
bsw/jbe@1309
|
12468
|
bsw/jbe@1309
|
12469 tmpRange = ranges[i].cloneRange();
|
bsw/jbe@1309
|
12470 closestLI = wysihtml.dom.domNode(lis[j]).prev({nodeTypes: [1]});
|
bsw/jbe@1309
|
12471
|
bsw/jbe@1309
|
12472 if (closestLI) {
|
bsw/jbe@1309
|
12473 tmpRange.setEnd(closestLI, closestLI.childNodes.length);
|
bsw/jbe@1309
|
12474 } else if (lis[j].closest('ul, ol')) {
|
bsw/jbe@1309
|
12475 tmpRange.setEndBefore(lis[j].closest('ul, ol'));
|
bsw/jbe@1309
|
12476 } else {
|
bsw/jbe@1309
|
12477 tmpRange.setEndBefore(lis[j]);
|
bsw/jbe@1309
|
12478 }
|
bsw/jbe@1309
|
12479 newRanges.push(tmpRange);
|
bsw/jbe@1309
|
12480 ranges[i].setStart(lis[j], 0);
|
bsw/jbe@1309
|
12481 }
|
bsw/jbe@1309
|
12482
|
bsw/jbe@1309
|
12483 // Fixes end of range that crosses li border
|
bsw/jbe@1309
|
12484 if (rangePos === ranges[i].NODE_BEFORE || rangePos === ranges[i].NODE_INSIDE) {
|
bsw/jbe@1309
|
12485 // Range starts inside the node and ends after node
|
bsw/jbe@1309
|
12486
|
bsw/jbe@1309
|
12487 tmpRange = ranges[i].cloneRange();
|
bsw/jbe@1309
|
12488 tmpRange.setEnd(lis[j], lis[j].childNodes.length);
|
bsw/jbe@1309
|
12489 newRanges.push(tmpRange);
|
bsw/jbe@1309
|
12490
|
bsw/jbe@1309
|
12491 // Find next LI in list and if present set range to it, else
|
bsw/jbe@1309
|
12492 closestLI = wysihtml.dom.domNode(lis[j]).next({nodeTypes: [1]});
|
bsw/jbe@1309
|
12493 if (closestLI) {
|
bsw/jbe@1309
|
12494 ranges[i].setStart(closestLI, 0);
|
bsw/jbe@1309
|
12495 } else if (lis[j].closest('ul, ol')) {
|
bsw/jbe@1309
|
12496 ranges[i].setStartAfter(lis[j].closest('ul, ol'));
|
bsw/jbe@1309
|
12497 } else {
|
bsw/jbe@1309
|
12498 ranges[i].setStartAfter(lis[j]);
|
bsw/jbe@1309
|
12499 }
|
bsw/jbe@1309
|
12500 }
|
bsw/jbe@1309
|
12501 }
|
bsw/jbe@1309
|
12502 newRanges.push(ranges[i]);
|
bsw/jbe@1309
|
12503 } else {
|
bsw/jbe@1309
|
12504 newRanges.push(ranges[i]);
|
bsw/jbe@1309
|
12505 }
|
bsw/jbe@1309
|
12506 }
|
bsw/jbe@1309
|
12507 return newRanges;
|
bsw/jbe@1309
|
12508 }
|
bsw/jbe@1309
|
12509
|
bsw/jbe@1309
|
12510 // Return options object with nodeName set if original did not have any
|
bsw/jbe@1309
|
12511 // Node name is set to local or global default
|
bsw/jbe@1309
|
12512 function getOptionsWithNodename(options, defaultName, composer) {
|
bsw/jbe@1309
|
12513 var correctedOptions = (options) ? wysihtml.lang.object(options).clone(true) : null;
|
bsw/jbe@1309
|
12514 if (correctedOptions) {
|
bsw/jbe@1309
|
12515 correctedOptions.nodeName = correctedOptions.nodeName || defaultName || defaultNodeName(composer);
|
bsw/jbe@1309
|
12516 }
|
bsw/jbe@1309
|
12517 return correctedOptions;
|
bsw/jbe@1309
|
12518 }
|
bsw/jbe@1309
|
12519
|
bsw/jbe@1309
|
12520 // Injects document fragment to range ensuring outer elements are split to a place where block elements are allowed to be inserted
|
bsw/jbe@1309
|
12521 // Also wraps empty clones of split parent tags around fragment to keep formatting
|
bsw/jbe@1309
|
12522 // If firstOuterBlock is given assume that instead of finding outer (useful for solving cases of some blocks are allowed into others while others are not)
|
bsw/jbe@1309
|
12523 function injectFragmentToRange(fragment, range, composer, firstOuterBlock) {
|
bsw/jbe@1309
|
12524 var rangeStartContainer = range.startContainer,
|
bsw/jbe@1309
|
12525 firstOuterBlock = firstOuterBlock || findOuterBlock(rangeStartContainer, composer.element, true),
|
bsw/jbe@1309
|
12526 outerInlines, first, last, prev, next;
|
bsw/jbe@1309
|
12527
|
bsw/jbe@1309
|
12528 if (firstOuterBlock) {
|
bsw/jbe@1309
|
12529 // If selection starts inside un-nestable block, split-escape the unnestable point and insert node between
|
bsw/jbe@1309
|
12530 first = fragment.firstChild;
|
bsw/jbe@1309
|
12531 last = fragment.lastChild;
|
bsw/jbe@1309
|
12532
|
bsw/jbe@1309
|
12533 composer.selection.splitElementAtCaret(firstOuterBlock, fragment);
|
bsw/jbe@1309
|
12534
|
bsw/jbe@1309
|
12535 next = wysihtml.dom.domNode(last).next({nodeTypes: [1,3], ignoreBlankTexts: true});
|
bsw/jbe@1309
|
12536 prev = wysihtml.dom.domNode(first).prev({nodeTypes: [1,3], ignoreBlankTexts: true});
|
bsw/jbe@1309
|
12537
|
bsw/jbe@1309
|
12538 if (first && !isLineBreaking(first, composer) && prev && !isLineBreaking(prev, composer)) {
|
bsw/jbe@1309
|
12539 first.parentNode.insertBefore(composer.doc.createElement('br'), first);
|
bsw/jbe@1309
|
12540 }
|
bsw/jbe@1309
|
12541
|
bsw/jbe@1309
|
12542 if (last && !isLineBreaking(last, composer) && next && !isLineBreaking(next, composer)) {
|
bsw/jbe@1309
|
12543 next.parentNode.insertBefore(composer.doc.createElement('br'), next);
|
bsw/jbe@1309
|
12544 }
|
bsw/jbe@1309
|
12545
|
bsw/jbe@1309
|
12546 } else {
|
bsw/jbe@1309
|
12547 // Ensure node does not get inserted into an inline where it is not allowed
|
bsw/jbe@1309
|
12548 outerInlines = cloneOuterInlines(rangeStartContainer, composer.element);
|
bsw/jbe@1309
|
12549 if (outerInlines.outerNode && outerInlines.innerNode && outerInlines.parent) {
|
bsw/jbe@1309
|
12550 if (fragment.childNodes.length === 1) {
|
bsw/jbe@1309
|
12551 while(fragment.firstChild.firstChild) {
|
bsw/jbe@1309
|
12552 outerInlines.innerNode.appendChild(fragment.firstChild.firstChild);
|
bsw/jbe@1309
|
12553 }
|
bsw/jbe@1309
|
12554 fragment.firstChild.appendChild(outerInlines.outerNode);
|
bsw/jbe@1309
|
12555 }
|
bsw/jbe@1309
|
12556 composer.selection.splitElementAtCaret(outerInlines.parent, fragment);
|
bsw/jbe@1309
|
12557 } else {
|
bsw/jbe@1309
|
12558 var fc = fragment.firstChild,
|
bsw/jbe@1309
|
12559 lc = fragment.lastChild;
|
bsw/jbe@1309
|
12560
|
bsw/jbe@1309
|
12561 range.insertNode(fragment);
|
bsw/jbe@1309
|
12562 // restore range position as it might get lost in webkit sometimes
|
bsw/jbe@1309
|
12563 range.setStartBefore(fc);
|
bsw/jbe@1309
|
12564 range.setEndAfter(lc);
|
bsw/jbe@1309
|
12565 }
|
bsw/jbe@1309
|
12566 }
|
bsw/jbe@1309
|
12567 }
|
bsw/jbe@1309
|
12568
|
bsw/jbe@1309
|
12569 // Removes all block formatting from range
|
bsw/jbe@1309
|
12570 function clearRangeBlockFromating(range, closestBlockName, composer) {
|
bsw/jbe@1309
|
12571 var r = range.cloneRange(),
|
bsw/jbe@1309
|
12572 prevNode = getRangeNode(r.startContainer, r.startOffset).previousSibling,
|
bsw/jbe@1309
|
12573 nextNode = getRangeNode(r.endContainer, r.endOffset).nextSibling,
|
bsw/jbe@1309
|
12574 content = r.extractContents(),
|
bsw/jbe@1309
|
12575 fragment = composer.doc.createDocumentFragment(),
|
bsw/jbe@1309
|
12576 children, blocks,
|
bsw/jbe@1309
|
12577 first = true;
|
bsw/jbe@1309
|
12578
|
bsw/jbe@1309
|
12579 while(content.firstChild) {
|
bsw/jbe@1309
|
12580 // Iterate over all selection content first level childNodes
|
bsw/jbe@1309
|
12581 if (content.firstChild.nodeType === 1 && content.firstChild.matches(BLOCK_ELEMENTS)) {
|
bsw/jbe@1309
|
12582 // If node is a block element
|
bsw/jbe@1309
|
12583 // Split block formating and add new block to wrap caret
|
bsw/jbe@1309
|
12584
|
bsw/jbe@1309
|
12585 unwrapBlocksFromContent(content.firstChild);
|
bsw/jbe@1309
|
12586 children = wysihtml.dom.unwrap(content.firstChild);
|
bsw/jbe@1309
|
12587
|
bsw/jbe@1309
|
12588 // Add line break before if needed
|
bsw/jbe@1309
|
12589 if (children.length > 0) {
|
bsw/jbe@1309
|
12590 if (
|
bsw/jbe@1309
|
12591 (fragment.lastChild && (fragment.lastChild.nodeType !== 1 || !isLineBreaking(fragment.lastChild, composer))) ||
|
bsw/jbe@1309
|
12592 (!fragment.lastChild && prevNode && (prevNode.nodeType !== 1 || isLineBreaking(prevNode, composer)))
|
bsw/jbe@1309
|
12593 ){
|
bsw/jbe@1309
|
12594 fragment.appendChild(composer.doc.createElement('BR'));
|
bsw/jbe@1309
|
12595 }
|
bsw/jbe@1309
|
12596 }
|
bsw/jbe@1309
|
12597
|
bsw/jbe@1309
|
12598 for (var c = 0, cmax = children.length; c < cmax; c++) {
|
bsw/jbe@1309
|
12599 fragment.appendChild(children[c]);
|
bsw/jbe@1309
|
12600 }
|
bsw/jbe@1309
|
12601
|
bsw/jbe@1309
|
12602 // Add line break after if needed
|
bsw/jbe@1309
|
12603 if (children.length > 0) {
|
bsw/jbe@1309
|
12604 if (fragment.lastChild.nodeType !== 1 || !isLineBreaking(fragment.lastChild, composer)) {
|
bsw/jbe@1309
|
12605 if (nextNode || fragment.lastChild !== content.lastChild) {
|
bsw/jbe@1309
|
12606 fragment.appendChild(composer.doc.createElement('BR'));
|
bsw/jbe@1309
|
12607 }
|
bsw/jbe@1309
|
12608 }
|
bsw/jbe@1309
|
12609 }
|
bsw/jbe@1309
|
12610
|
bsw/jbe@1309
|
12611 } else {
|
bsw/jbe@1309
|
12612 fragment.appendChild(content.firstChild);
|
bsw/jbe@1309
|
12613 }
|
bsw/jbe@1309
|
12614
|
bsw/jbe@1309
|
12615 first = false;
|
bsw/jbe@1309
|
12616 }
|
bsw/jbe@1309
|
12617 blocks = wysihtml.lang.array(fragment.childNodes).get();
|
bsw/jbe@1309
|
12618 injectFragmentToRange(fragment, r, composer);
|
bsw/jbe@1309
|
12619 return blocks;
|
bsw/jbe@1309
|
12620 }
|
bsw/jbe@1309
|
12621
|
bsw/jbe@1309
|
12622 // When block node is inserted, look surrounding nodes and remove surplous linebreak tags (as block format breaks line itself)
|
bsw/jbe@1309
|
12623 function removeSurroundingLineBreaks(prevNode, nextNode, composer) {
|
bsw/jbe@1309
|
12624 var prevPrev = prevNode && wysihtml.dom.domNode(prevNode).prev({nodeTypes: [1,3], ignoreBlankTexts: true});
|
bsw/jbe@1309
|
12625 if (isBr(nextNode)) {
|
bsw/jbe@1309
|
12626 nextNode.parentNode.removeChild(nextNode);
|
bsw/jbe@1309
|
12627 }
|
bsw/jbe@1309
|
12628 if (isBr(prevNode) && (!prevPrev || prevPrev.nodeType !== 1 || composer.win.getComputedStyle(prevPrev).display !== "block")) {
|
bsw/jbe@1309
|
12629 prevNode.parentNode.removeChild(prevNode);
|
bsw/jbe@1309
|
12630 }
|
bsw/jbe@1309
|
12631 }
|
bsw/jbe@1309
|
12632
|
bsw/jbe@1309
|
12633 function applySurroundingLineBreaks(prevNode, nextNode, composer) {
|
bsw/jbe@1309
|
12634 var prevPrev;
|
bsw/jbe@1309
|
12635
|
bsw/jbe@1309
|
12636 if (prevNode && isBookmark(prevNode)) {
|
bsw/jbe@1309
|
12637 prevNode = prevNode.previousSibling;
|
bsw/jbe@1309
|
12638 }
|
bsw/jbe@1309
|
12639 if (nextNode && isBookmark(nextNode)) {
|
bsw/jbe@1309
|
12640 nextNode = nextNode.nextSibling;
|
bsw/jbe@1309
|
12641 }
|
bsw/jbe@1309
|
12642
|
bsw/jbe@1309
|
12643 prevPrev = prevNode && prevNode.previousSibling;
|
bsw/jbe@1309
|
12644
|
bsw/jbe@1309
|
12645 if (prevNode && (prevNode.nodeType !== 1 || (composer.win.getComputedStyle(prevNode).display !== "block" && !isBr(prevNode))) && prevNode.parentNode) {
|
bsw/jbe@1309
|
12646 prevNode.parentNode.insertBefore(composer.doc.createElement('br'), prevNode.nextSibling);
|
bsw/jbe@1309
|
12647 }
|
bsw/jbe@1309
|
12648
|
bsw/jbe@1309
|
12649 if (nextNode && (nextNode.nodeType !== 1 || composer.win.getComputedStyle(nextNode).display !== "block") && nextNode.parentNode) {
|
bsw/jbe@1309
|
12650 nextNode.parentNode.insertBefore(composer.doc.createElement('br'), nextNode);
|
bsw/jbe@1309
|
12651 }
|
bsw/jbe@1309
|
12652 }
|
bsw/jbe@1309
|
12653
|
bsw/jbe@1309
|
12654 var isWhitespaceBefore = function (textNode, offset) {
|
bsw/jbe@1309
|
12655 var str = textNode.data ? textNode.data.slice(0, offset) : "";
|
bsw/jbe@1309
|
12656 return (/^\s*$/).test(str);
|
bsw/jbe@1309
|
12657 }
|
bsw/jbe@1309
|
12658
|
bsw/jbe@1309
|
12659 var isWhitespaceAfter = function (textNode, offset) {
|
bsw/jbe@1309
|
12660 var str = textNode.data ? textNode.data.slice(offset) : "";
|
bsw/jbe@1309
|
12661 return (/^\s*$/).test(str);
|
bsw/jbe@1309
|
12662 }
|
bsw/jbe@1309
|
12663
|
bsw/jbe@1309
|
12664 var trimBlankTextsAndBreaks = function(fragment) {
|
bsw/jbe@1309
|
12665 if (fragment) {
|
bsw/jbe@1309
|
12666 while (fragment.firstChild && fragment.firstChild.nodeType === 3 && (/^\s*$/).test(fragment.firstChild.data) && fragment.lastChild !== fragment.firstChild) {
|
bsw/jbe@1309
|
12667 fragment.removeChild(fragment.firstChild);
|
bsw/jbe@1309
|
12668 }
|
bsw/jbe@1309
|
12669
|
bsw/jbe@1309
|
12670 while (fragment.lastChild && fragment.lastChild.nodeType === 3 && (/^\s*$/).test(fragment.lastChild.data) && fragment.lastChild !== fragment.firstChild) {
|
bsw/jbe@1309
|
12671 fragment.removeChild(fragment.lastChild);
|
bsw/jbe@1309
|
12672 }
|
bsw/jbe@1309
|
12673
|
bsw/jbe@1309
|
12674 if (fragment.firstChild && fragment.firstChild.nodeType === 1 && fragment.firstChild.nodeName === "BR" && fragment.lastChild !== fragment.firstChild) {
|
bsw/jbe@1309
|
12675 fragment.removeChild(fragment.firstChild);
|
bsw/jbe@1309
|
12676 }
|
bsw/jbe@1309
|
12677
|
bsw/jbe@1309
|
12678 if (fragment.lastChild && fragment.lastChild.nodeType === 1 && fragment.lastChild.nodeName === "BR" && fragment.lastChild !== fragment.firstChild) {
|
bsw/jbe@1309
|
12679 fragment.removeChild(fragment.lastChild);
|
bsw/jbe@1309
|
12680 }
|
bsw/jbe@1309
|
12681 }
|
bsw/jbe@1309
|
12682 }
|
bsw/jbe@1309
|
12683
|
bsw/jbe@1309
|
12684 // Wrap the range with a block level element
|
bsw/jbe@1309
|
12685 // If element is one of unnestable block elements (ex: h2 inside h1), split nodes and insert between so nesting does not occur
|
bsw/jbe@1309
|
12686 function wrapRangeWithElement(range, options, closestBlockName, composer) {
|
bsw/jbe@1309
|
12687 var similarOptions = options ? correctOptionsForSimilarityCheck(options) : null,
|
bsw/jbe@1309
|
12688 r = range.cloneRange(),
|
bsw/jbe@1309
|
12689 rangeStartContainer = r.startContainer,
|
bsw/jbe@1309
|
12690 startNode = getRangeNode(r.startContainer, r.startOffset),
|
bsw/jbe@1309
|
12691 endNode = getRangeNode(r.endContainer, r.endOffset),
|
bsw/jbe@1309
|
12692 prevNode = (r.startContainer === startNode && startNode.nodeType === 3 && !isWhitespaceBefore(startNode, r.startOffset)) ? startNode : wysihtml.dom.domNode(startNode).prev({nodeTypes: [1,3], ignoreBlankTexts: true}),
|
bsw/jbe@1309
|
12693 nextNode = (
|
bsw/jbe@1309
|
12694 (
|
bsw/jbe@1309
|
12695 r.endContainer.nodeType === 1 &&
|
bsw/jbe@1309
|
12696 r.endContainer.childNodes[r.endOffset] === endNode &&
|
bsw/jbe@1309
|
12697 (
|
bsw/jbe@1309
|
12698 endNode.nodeType === 1 ||
|
bsw/jbe@1309
|
12699 !isWhitespaceAfter(endNode, r.endOffset) &&
|
bsw/jbe@1309
|
12700 !wysihtml.dom.domNode(endNode).is.rangyBookmark()
|
bsw/jbe@1309
|
12701 )
|
bsw/jbe@1309
|
12702 ) || (
|
bsw/jbe@1309
|
12703 r.endContainer === endNode &&
|
bsw/jbe@1309
|
12704 endNode.nodeType === 3 &&
|
bsw/jbe@1309
|
12705 !isWhitespaceAfter(endNode, r.endOffset)
|
bsw/jbe@1309
|
12706 )
|
bsw/jbe@1309
|
12707 ) ? endNode : wysihtml.dom.domNode(endNode).next({nodeTypes: [1,3], ignoreBlankTexts: true}),
|
bsw/jbe@1309
|
12708 content = r.extractContents(),
|
bsw/jbe@1309
|
12709 fragment = composer.doc.createDocumentFragment(),
|
bsw/jbe@1309
|
12710 similarOuterBlock = similarOptions ? wysihtml.dom.getParentElement(rangeStartContainer, similarOptions, null, composer.element) : null,
|
bsw/jbe@1309
|
12711 splitAllBlocks = !closestBlockName || !options || (options.nodeName === "BLOCKQUOTE" && closestBlockName === "BLOCKQUOTE"),
|
bsw/jbe@1309
|
12712 firstOuterBlock = similarOuterBlock || findOuterBlock(rangeStartContainer, composer.element, splitAllBlocks), // The outermost un-nestable block element parent of selection start
|
bsw/jbe@1309
|
12713 wrapper, blocks, children,
|
bsw/jbe@1309
|
12714 firstc, lastC;
|
bsw/jbe@1309
|
12715
|
bsw/jbe@1309
|
12716 if (wysihtml.dom.domNode(nextNode).is.rangyBookmark()) {
|
bsw/jbe@1309
|
12717 endNode = nextNode;
|
bsw/jbe@1309
|
12718 nextNode = endNode.nextSibling;
|
bsw/jbe@1309
|
12719 }
|
bsw/jbe@1309
|
12720
|
bsw/jbe@1309
|
12721 trimBlankTextsAndBreaks(content);
|
bsw/jbe@1309
|
12722
|
bsw/jbe@1309
|
12723 if (options && options.nodeName === "BLOCKQUOTE") {
|
bsw/jbe@1309
|
12724
|
bsw/jbe@1309
|
12725 // If blockquote is to be inserted no quessing just add it as outermost block on line or selection
|
bsw/jbe@1309
|
12726 var tmpEl = applyOptionsToElement(null, options, composer);
|
bsw/jbe@1309
|
12727 tmpEl.appendChild(content);
|
bsw/jbe@1309
|
12728 fragment.appendChild(tmpEl);
|
bsw/jbe@1309
|
12729 blocks = [tmpEl];
|
bsw/jbe@1309
|
12730
|
bsw/jbe@1309
|
12731 } else {
|
bsw/jbe@1309
|
12732
|
bsw/jbe@1309
|
12733 if (!content.firstChild) {
|
bsw/jbe@1309
|
12734 // IF selection is caret (can happen if line is empty) add format around tag
|
bsw/jbe@1309
|
12735 fragment.appendChild(applyOptionsToElement(null, options, composer));
|
bsw/jbe@1309
|
12736 } else {
|
bsw/jbe@1309
|
12737
|
bsw/jbe@1309
|
12738 while(content.firstChild) {
|
bsw/jbe@1309
|
12739 // Iterate over all selection content first level childNodes
|
bsw/jbe@1309
|
12740
|
bsw/jbe@1309
|
12741 if (content.firstChild.nodeType == 1 && content.firstChild.matches(BLOCK_ELEMENTS)) {
|
bsw/jbe@1309
|
12742
|
bsw/jbe@1309
|
12743 // If node is a block element
|
bsw/jbe@1309
|
12744 // Escape(split) block formatting at caret
|
bsw/jbe@1309
|
12745 applyOptionsToElement(content.firstChild, options, composer);
|
bsw/jbe@1309
|
12746 if (content.firstChild.matches(UNNESTABLE_BLOCK_ELEMENTS)) {
|
bsw/jbe@1309
|
12747 unwrapBlocksFromContent(content.firstChild);
|
bsw/jbe@1309
|
12748 }
|
bsw/jbe@1309
|
12749 fragment.appendChild(content.firstChild);
|
bsw/jbe@1309
|
12750
|
bsw/jbe@1309
|
12751 } else {
|
bsw/jbe@1309
|
12752
|
bsw/jbe@1309
|
12753 // Wrap subsequent non-block nodes inside new block element
|
bsw/jbe@1309
|
12754 wrapper = applyOptionsToElement(null, getOptionsWithNodename(options, closestBlockName, composer), composer);
|
bsw/jbe@1309
|
12755 while(content.firstChild && (content.firstChild.nodeType !== 1 || !content.firstChild.matches(BLOCK_ELEMENTS))) {
|
bsw/jbe@1309
|
12756 if (content.firstChild.nodeType == 1 && wrapper.matches(UNNESTABLE_BLOCK_ELEMENTS)) {
|
bsw/jbe@1309
|
12757 unwrapBlocksFromContent(content.firstChild);
|
bsw/jbe@1309
|
12758 }
|
bsw/jbe@1309
|
12759 wrapper.appendChild(content.firstChild);
|
bsw/jbe@1309
|
12760 }
|
bsw/jbe@1309
|
12761 fragment.appendChild(wrapper);
|
bsw/jbe@1309
|
12762 }
|
bsw/jbe@1309
|
12763 }
|
bsw/jbe@1309
|
12764 }
|
bsw/jbe@1309
|
12765
|
bsw/jbe@1309
|
12766 blocks = wysihtml.lang.array(fragment.childNodes).get();
|
bsw/jbe@1309
|
12767 }
|
bsw/jbe@1309
|
12768 injectFragmentToRange(fragment, r, composer, firstOuterBlock);
|
bsw/jbe@1309
|
12769 removeSurroundingLineBreaks(prevNode, nextNode, composer);
|
bsw/jbe@1309
|
12770
|
bsw/jbe@1309
|
12771 // Fix webkit madness by inserting linebreak rangy after cursor marker to blank last block
|
bsw/jbe@1309
|
12772 // (if it contains rangy bookmark, so selection can be restored later correctly)
|
bsw/jbe@1309
|
12773 if (blocks.length > 0 &&
|
bsw/jbe@1309
|
12774 (
|
bsw/jbe@1309
|
12775 typeof blocks[blocks.length - 1].lastChild === "undefined" || wysihtml.dom.domNode(blocks[blocks.length - 1].lastChild).is.rangyBookmark()
|
bsw/jbe@1309
|
12776 )
|
bsw/jbe@1309
|
12777 ) {
|
bsw/jbe@1309
|
12778 blocks[blocks.length - 1].appendChild(composer.doc.createElement('br'));
|
bsw/jbe@1309
|
12779 }
|
bsw/jbe@1309
|
12780 return blocks;
|
bsw/jbe@1309
|
12781 }
|
bsw/jbe@1309
|
12782
|
bsw/jbe@1309
|
12783 // Find closest block level element
|
bsw/jbe@1309
|
12784 function getParentBlockNodeName(element, composer) {
|
bsw/jbe@1309
|
12785 var parentNode = wysihtml.dom.getParentElement(element, {
|
bsw/jbe@1309
|
12786 query: BLOCK_ELEMENTS
|
bsw/jbe@1309
|
12787 }, null, composer.element);
|
bsw/jbe@1309
|
12788
|
bsw/jbe@1309
|
12789 return (parentNode) ? parentNode.nodeName : null;
|
bsw/jbe@1309
|
12790 }
|
bsw/jbe@1309
|
12791
|
bsw/jbe@1309
|
12792 // Expands caret to cover the closest block that:
|
bsw/jbe@1309
|
12793 // * cannot contain other block level elements (h1-6,p, etc)
|
bsw/jbe@1309
|
12794 // * Has the same nodeName that is to be inserted
|
bsw/jbe@1309
|
12795 // * has insertingNodeName
|
bsw/jbe@1309
|
12796 // * is DIV if insertingNodeName is not present
|
bsw/jbe@1309
|
12797 //
|
bsw/jbe@1309
|
12798 // If nothing found selects the current line
|
bsw/jbe@1309
|
12799 function expandCaretToBlock(composer, insertingNodeName) {
|
bsw/jbe@1309
|
12800 var parent = wysihtml.dom.getParentElement(composer.selection.getOwnRanges()[0].startContainer, {
|
bsw/jbe@1309
|
12801 query: UNNESTABLE_BLOCK_ELEMENTS + ', ' + (insertingNodeName ? insertingNodeName.toLowerCase() : 'div'),
|
bsw/jbe@1309
|
12802 }, null, composer.element),
|
bsw/jbe@1309
|
12803 range;
|
bsw/jbe@1309
|
12804
|
bsw/jbe@1309
|
12805 if (parent) {
|
bsw/jbe@1309
|
12806 range = composer.selection.createRange();
|
bsw/jbe@1309
|
12807 range.selectNode(parent);
|
bsw/jbe@1309
|
12808 composer.selection.setSelection(range);
|
bsw/jbe@1309
|
12809 } else if (!composer.isEmpty()) {
|
bsw/jbe@1309
|
12810 composer.selection.selectLine();
|
bsw/jbe@1309
|
12811 }
|
bsw/jbe@1309
|
12812 }
|
bsw/jbe@1309
|
12813
|
bsw/jbe@1309
|
12814 // Set selection to begin inside first created block element (beginning of it) and end inside (and after content) of last block element
|
bsw/jbe@1309
|
12815 // TODO: Checking nodetype might be unnescescary as nodes inserted by formatBlock are nodetype 1 anyway
|
bsw/jbe@1309
|
12816 function selectElements(newBlockElements, composer) {
|
bsw/jbe@1309
|
12817 var range = composer.selection.createRange(),
|
bsw/jbe@1309
|
12818 lastEl = newBlockElements[newBlockElements.length - 1],
|
bsw/jbe@1309
|
12819 lastOffset = (lastEl.nodeType === 1 && lastEl.childNodes) ? lastEl.childNodes.length | 0 : lastEl.length || 0;
|
bsw/jbe@1309
|
12820
|
bsw/jbe@1309
|
12821 range.setStart(newBlockElements[0], 0);
|
bsw/jbe@1309
|
12822 range.setEnd(lastEl, lastOffset);
|
bsw/jbe@1309
|
12823 range.select();
|
bsw/jbe@1309
|
12824 }
|
bsw/jbe@1309
|
12825
|
bsw/jbe@1309
|
12826 // Get all ranges from selection (takes out uneditables and out of editor parts) and apply format to each
|
bsw/jbe@1309
|
12827 // Return created/modified block level elements
|
bsw/jbe@1309
|
12828 // Method can be either "apply" or "remove"
|
bsw/jbe@1309
|
12829 function formatSelection(method, composer, options) {
|
bsw/jbe@1309
|
12830 var ranges = composer.selection.getOwnRanges(),
|
bsw/jbe@1309
|
12831 newBlockElements = [],
|
bsw/jbe@1309
|
12832 closestBlockName;
|
bsw/jbe@1309
|
12833
|
bsw/jbe@1309
|
12834 // Some places do not allow block level elements inbetween (inside ul and outside li, inside table and outside of td/th)
|
bsw/jbe@1309
|
12835 ranges = fixNotPermittedInsertionPoints(ranges);
|
bsw/jbe@1309
|
12836
|
bsw/jbe@1309
|
12837 for (var i = ranges.length; i--;) {
|
bsw/jbe@1309
|
12838 fixRangeCoverage(ranges[i], composer);
|
bsw/jbe@1309
|
12839 closestBlockName = getParentBlockNodeName(ranges[i].startContainer, composer);
|
bsw/jbe@1309
|
12840 if (method === "remove") {
|
bsw/jbe@1309
|
12841 newBlockElements = newBlockElements.concat(clearRangeBlockFromating(ranges[i], closestBlockName, composer));
|
bsw/jbe@1309
|
12842 } else {
|
bsw/jbe@1309
|
12843 newBlockElements = newBlockElements.concat(wrapRangeWithElement(ranges[i], options, closestBlockName, composer));
|
bsw/jbe@1309
|
12844 }
|
bsw/jbe@1309
|
12845 }
|
bsw/jbe@1309
|
12846 return newBlockElements;
|
bsw/jbe@1309
|
12847 }
|
bsw/jbe@1309
|
12848
|
bsw/jbe@1309
|
12849 // If properties is passed as a string, look for tag with that tagName/query
|
bsw/jbe@1309
|
12850 function parseOptions(options) {
|
bsw/jbe@1309
|
12851 if (typeof options === "string") {
|
bsw/jbe@1309
|
12852 options = {
|
bsw/jbe@1309
|
12853 nodeName: options.toUpperCase()
|
bsw/jbe@1309
|
12854 };
|
bsw/jbe@1309
|
12855 }
|
bsw/jbe@1309
|
12856 return options;
|
bsw/jbe@1309
|
12857 }
|
bsw/jbe@1309
|
12858
|
bsw/jbe@1309
|
12859 function caretIsOnEmptyLine(composer) {
|
bsw/jbe@1309
|
12860 var caretInfo;
|
bsw/jbe@1309
|
12861 if (composer.selection.isCollapsed()) {
|
bsw/jbe@1309
|
12862 caretInfo = composer.selection.getNodesNearCaret();
|
bsw/jbe@1309
|
12863 if (caretInfo && caretInfo.caretNode) {
|
bsw/jbe@1309
|
12864 if (
|
bsw/jbe@1309
|
12865 // caret is allready breaknode
|
bsw/jbe@1309
|
12866 wysihtml.dom.domNode(caretInfo.caretNode).is.lineBreak() ||
|
bsw/jbe@1309
|
12867 // caret is textnode
|
bsw/jbe@1309
|
12868 (caretInfo.caretNode.nodeType === 3 && caretInfo.textOffset === 0 && (!caretInfo.prevNode || wysihtml.dom.domNode(caretInfo.prevNode).is.lineBreak())) ||
|
bsw/jbe@1309
|
12869 // Caret is temprorary rangy selection marker
|
bsw/jbe@1309
|
12870 (caretInfo.caretNode.nodeType === 1 && caretInfo.caretNode.classList.contains('rangySelectionBoundary') &&
|
bsw/jbe@1309
|
12871 (!caretInfo.prevNode || wysihtml.dom.domNode(caretInfo.prevNode).is.lineBreak() || wysihtml.dom.domNode(caretInfo.prevNode).is.block()) &&
|
bsw/jbe@1309
|
12872 (!caretInfo.nextNode || wysihtml.dom.domNode(caretInfo.nextNode).is.lineBreak() || wysihtml.dom.domNode(caretInfo.nextNode).is.block())
|
bsw/jbe@1309
|
12873 )
|
bsw/jbe@1309
|
12874 ) {
|
bsw/jbe@1309
|
12875 return true;
|
bsw/jbe@1309
|
12876 }
|
bsw/jbe@1309
|
12877 }
|
bsw/jbe@1309
|
12878 }
|
bsw/jbe@1309
|
12879 return false;
|
bsw/jbe@1309
|
12880 }
|
bsw/jbe@1309
|
12881
|
bsw/jbe@1309
|
12882 wysihtml.commands.formatBlock = {
|
bsw/jbe@1309
|
12883 exec: function(composer, command, options) {
|
bsw/jbe@1309
|
12884 options = parseOptions(options);
|
bsw/jbe@1309
|
12885 var newBlockElements = [],
|
bsw/jbe@1309
|
12886 ranges, range, bookmark, state, closestBlockName;
|
bsw/jbe@1309
|
12887
|
bsw/jbe@1309
|
12888 // Find if current format state is active if options.toggle is set as true
|
bsw/jbe@1309
|
12889 // In toggle case active state elemets are formatted instead of working directly on selection
|
bsw/jbe@1309
|
12890 if (options && options.toggle) {
|
bsw/jbe@1309
|
12891 state = this.state(composer, command, options);
|
bsw/jbe@1309
|
12892 }
|
bsw/jbe@1309
|
12893 if (state) {
|
bsw/jbe@1309
|
12894 // Remove format from state nodes if toggle set and state on and selection is collapsed
|
bsw/jbe@1309
|
12895 bookmark = rangy.saveSelection(composer.win);
|
bsw/jbe@1309
|
12896 for (var j = 0, jmax = state.length; j < jmax; j++) {
|
bsw/jbe@1309
|
12897 removeOptionsFromElement(state[j], options, composer);
|
bsw/jbe@1309
|
12898 }
|
bsw/jbe@1309
|
12899
|
bsw/jbe@1309
|
12900 } else {
|
bsw/jbe@1309
|
12901 // If selection is caret expand it to cover nearest suitable block element or row if none found
|
bsw/jbe@1309
|
12902 if (composer.selection.isCollapsed()) {
|
bsw/jbe@1309
|
12903 bookmark = rangy.saveSelection(composer.win);
|
bsw/jbe@1309
|
12904 if (caretIsOnEmptyLine(composer)) {
|
bsw/jbe@1309
|
12905 composer.selection.selectLine();
|
bsw/jbe@1309
|
12906 } else {
|
bsw/jbe@1309
|
12907 expandCaretToBlock(composer, options && options.nodeName ? options.nodeName.toUpperCase() : undefined);
|
bsw/jbe@1309
|
12908 }
|
bsw/jbe@1309
|
12909 }
|
bsw/jbe@1309
|
12910 if (options) {
|
bsw/jbe@1309
|
12911 newBlockElements = formatSelection("apply", composer, options);
|
bsw/jbe@1309
|
12912 } else {
|
bsw/jbe@1309
|
12913 // Options == null means block formatting should be removed from selection
|
bsw/jbe@1309
|
12914 newBlockElements = formatSelection("remove", composer);
|
bsw/jbe@1309
|
12915 }
|
bsw/jbe@1309
|
12916
|
bsw/jbe@1309
|
12917 }
|
bsw/jbe@1309
|
12918
|
bsw/jbe@1309
|
12919 // Remove empty block elements that may be left behind
|
bsw/jbe@1309
|
12920 // Also remove them from new blocks list
|
bsw/jbe@1309
|
12921 newBlockElements = cleanup(composer, newBlockElements);
|
bsw/jbe@1309
|
12922
|
bsw/jbe@1309
|
12923 // Restore selection
|
bsw/jbe@1309
|
12924 if (bookmark) {
|
bsw/jbe@1309
|
12925 rangy.restoreSelection(bookmark);
|
bsw/jbe@1309
|
12926 } else {
|
bsw/jbe@1309
|
12927 selectElements(newBlockElements, composer);
|
bsw/jbe@1309
|
12928 }
|
bsw/jbe@1309
|
12929 },
|
bsw/jbe@1309
|
12930
|
bsw/jbe@1309
|
12931 // Removes all block formatting from selection
|
bsw/jbe@1309
|
12932 remove: function(composer, command, options) {
|
bsw/jbe@1309
|
12933 options = parseOptions(options);
|
bsw/jbe@1309
|
12934 var newBlockElements, bookmark;
|
bsw/jbe@1309
|
12935
|
bsw/jbe@1309
|
12936 // If selection is caret expand it to cover nearest suitable block element or row if none found
|
bsw/jbe@1309
|
12937 if (composer.selection.isCollapsed()) {
|
bsw/jbe@1309
|
12938 bookmark = rangy.saveSelection(composer.win);
|
bsw/jbe@1309
|
12939 expandCaretToBlock(composer, options && options.nodeName ? options.nodeName.toUpperCase() : undefined);
|
bsw/jbe@1309
|
12940 }
|
bsw/jbe@1309
|
12941
|
bsw/jbe@1309
|
12942 newBlockElements = formatSelection("remove", composer);
|
bsw/jbe@1309
|
12943 newBlockElements = cleanup(composer, newBlockElements);
|
bsw/jbe@1309
|
12944
|
bsw/jbe@1309
|
12945 // Restore selection
|
bsw/jbe@1309
|
12946 if (bookmark) {
|
bsw/jbe@1309
|
12947 rangy.restoreSelection(bookmark);
|
bsw/jbe@1309
|
12948 } else {
|
bsw/jbe@1309
|
12949 selectElements(newBlockElements, composer);
|
bsw/jbe@1309
|
12950 }
|
bsw/jbe@1309
|
12951 },
|
bsw/jbe@1309
|
12952
|
bsw/jbe@1309
|
12953 // If options as null is passed returns status describing all block level elements
|
bsw/jbe@1309
|
12954 state: function(composer, command, options) {
|
bsw/jbe@1309
|
12955 options = parseOptions(options);
|
bsw/jbe@1309
|
12956
|
bsw/jbe@1309
|
12957 var nodes = composer.selection.filterElements((function (element) { // Finds matching elements inside selection
|
bsw/jbe@1309
|
12958 return wysihtml.dom.domNode(element).test(options || { query: BLOCK_ELEMENTS });
|
bsw/jbe@1309
|
12959 }).bind(this)),
|
bsw/jbe@1309
|
12960 parentNodes = composer.selection.getSelectedOwnNodes(),
|
bsw/jbe@1309
|
12961 parent;
|
bsw/jbe@1309
|
12962
|
bsw/jbe@1309
|
12963 // Finds matching elements that are parents of selection and adds to nodes list
|
bsw/jbe@1309
|
12964 for (var i = 0, maxi = parentNodes.length; i < maxi; i++) {
|
bsw/jbe@1309
|
12965 parent = dom.getParentElement(parentNodes[i], options || { query: BLOCK_ELEMENTS }, null, composer.element);
|
bsw/jbe@1309
|
12966 if (parent && nodes.indexOf(parent) === -1) {
|
bsw/jbe@1309
|
12967 nodes.push(parent);
|
bsw/jbe@1309
|
12968 }
|
bsw/jbe@1309
|
12969 }
|
bsw/jbe@1309
|
12970
|
bsw/jbe@1309
|
12971 return (nodes.length === 0) ? false : nodes;
|
bsw/jbe@1309
|
12972 }
|
bsw/jbe@1309
|
12973
|
bsw/jbe@1309
|
12974 };
|
bsw/jbe@1309
|
12975 })(wysihtml);
|
bsw/jbe@1309
|
12976
|
bsw/jbe@1309
|
12977 /**
|
bsw/jbe@1309
|
12978 * Unifies all inline tags additions and removals
|
bsw/jbe@1309
|
12979 * See https://github.com/Voog/wysihtml/pull/169 for specification of action
|
bsw/jbe@1309
|
12980 */
|
bsw/jbe@1309
|
12981
|
bsw/jbe@1309
|
12982 (function(wysihtml) {
|
bsw/jbe@1309
|
12983
|
bsw/jbe@1309
|
12984 var defaultTag = "SPAN",
|
bsw/jbe@1309
|
12985 INLINE_ELEMENTS = "b, big, i, small, tt, abbr, acronym, cite, code, dfn, em, kbd, strong, samp, var, a, bdo, br, q, span, sub, sup, button, label, textarea, input, select, u",
|
bsw/jbe@1309
|
12986 queryAliasMap = {
|
bsw/jbe@1309
|
12987 "b": "b, strong",
|
bsw/jbe@1309
|
12988 "strong": "b, strong",
|
bsw/jbe@1309
|
12989 "em": "em, i",
|
bsw/jbe@1309
|
12990 "i": "em, i"
|
bsw/jbe@1309
|
12991 };
|
bsw/jbe@1309
|
12992
|
bsw/jbe@1309
|
12993 function hasNoClass(element) {
|
bsw/jbe@1309
|
12994 return (/^\s*$/).test(element.className);
|
bsw/jbe@1309
|
12995 }
|
bsw/jbe@1309
|
12996
|
bsw/jbe@1309
|
12997 function hasNoStyle(element) {
|
bsw/jbe@1309
|
12998 return !element.getAttribute('style') || (/^\s*$/).test(element.getAttribute('style'));
|
bsw/jbe@1309
|
12999 }
|
bsw/jbe@1309
|
13000
|
bsw/jbe@1309
|
13001 // Associative arrays in javascript are really objects and do not have length defined
|
bsw/jbe@1309
|
13002 // Thus have to check emptyness in a different way
|
bsw/jbe@1309
|
13003 function hasNoAttributes(element) {
|
bsw/jbe@1309
|
13004 var attr = wysihtml.dom.getAttributes(element);
|
bsw/jbe@1309
|
13005 return wysihtml.lang.object(attr).isEmpty();
|
bsw/jbe@1309
|
13006 }
|
bsw/jbe@1309
|
13007
|
bsw/jbe@1309
|
13008 // compares two nodes if they are semantically the same
|
bsw/jbe@1309
|
13009 // Used in cleanup to find consequent semantically similar elements for merge
|
bsw/jbe@1309
|
13010 function isSameNode(element1, element2) {
|
bsw/jbe@1309
|
13011 var classes1, classes2,
|
bsw/jbe@1309
|
13012 attr1, attr2;
|
bsw/jbe@1309
|
13013
|
bsw/jbe@1309
|
13014 if (element1.nodeType !== 1 || element2.nodeType !== 1) {
|
bsw/jbe@1309
|
13015 return false;
|
bsw/jbe@1309
|
13016 }
|
bsw/jbe@1309
|
13017
|
bsw/jbe@1309
|
13018 if (element1.nodeName !== element2.nodeName) {
|
bsw/jbe@1309
|
13019 return false;
|
bsw/jbe@1309
|
13020 }
|
bsw/jbe@1309
|
13021
|
bsw/jbe@1309
|
13022 classes1 = element1.className.trim().replace(/\s+/g, ' ').split(' ');
|
bsw/jbe@1309
|
13023 classes2 = element2.className.trim().replace(/\s+/g, ' ').split(' ');
|
bsw/jbe@1309
|
13024 if (wysihtml.lang.array(classes1).without(classes2).length > 0) {
|
bsw/jbe@1309
|
13025 return false;
|
bsw/jbe@1309
|
13026 }
|
bsw/jbe@1309
|
13027
|
bsw/jbe@1309
|
13028 attr1 = wysihtml.dom.getAttributes(element1);
|
bsw/jbe@1309
|
13029 attr2 = wysihtml.dom.getAttributes(element2);
|
bsw/jbe@1309
|
13030
|
bsw/jbe@1309
|
13031 if (attr1.length !== attr2.length || !wysihtml.lang.object(wysihtml.lang.object(attr1).difference(attr2)).isEmpty()) {
|
bsw/jbe@1309
|
13032 return false;
|
bsw/jbe@1309
|
13033 }
|
bsw/jbe@1309
|
13034
|
bsw/jbe@1309
|
13035 return true;
|
bsw/jbe@1309
|
13036 }
|
bsw/jbe@1309
|
13037
|
bsw/jbe@1309
|
13038 function createWrapNode(textNode, options) {
|
bsw/jbe@1309
|
13039 var nodeName = options && options.nodeName || defaultTag,
|
bsw/jbe@1309
|
13040 element = textNode.ownerDocument.createElement(nodeName);
|
bsw/jbe@1309
|
13041
|
bsw/jbe@1309
|
13042 // Remove similar classes before applying className
|
bsw/jbe@1309
|
13043 if (options.classRegExp) {
|
bsw/jbe@1309
|
13044 element.className = element.className.replace(options.classRegExp, "");
|
bsw/jbe@1309
|
13045 }
|
bsw/jbe@1309
|
13046
|
bsw/jbe@1309
|
13047 if (options.className) {
|
bsw/jbe@1309
|
13048 element.classList.add(options.className);
|
bsw/jbe@1309
|
13049 }
|
bsw/jbe@1309
|
13050
|
bsw/jbe@1309
|
13051 if (options.styleProperty && typeof options.styleValue !== "undefined") {
|
bsw/jbe@1309
|
13052 element.style[wysihtml.browser.fixStyleKey(options.styleProperty)] = options.styleValue;
|
bsw/jbe@1309
|
13053 }
|
bsw/jbe@1309
|
13054
|
bsw/jbe@1309
|
13055 if (options.attribute) {
|
bsw/jbe@1309
|
13056 if (typeof options.attribute === "object") {
|
bsw/jbe@1309
|
13057 for (var a in options.attribute) {
|
bsw/jbe@1309
|
13058 if (options.attribute.hasOwnProperty(a)) {
|
bsw/jbe@1309
|
13059 element.setAttribute(a, options.attribute[a]);
|
bsw/jbe@1309
|
13060 }
|
bsw/jbe@1309
|
13061 }
|
bsw/jbe@1309
|
13062 } else if (typeof options.attributeValue !== "undefined") {
|
bsw/jbe@1309
|
13063 element.setAttribute(options.attribute, options.attributeValue);
|
bsw/jbe@1309
|
13064 }
|
bsw/jbe@1309
|
13065 }
|
bsw/jbe@1309
|
13066
|
bsw/jbe@1309
|
13067 return element;
|
bsw/jbe@1309
|
13068 }
|
bsw/jbe@1309
|
13069
|
bsw/jbe@1309
|
13070 // Tests if attr2 list contains all attributes present in attr1
|
bsw/jbe@1309
|
13071 // Note: attr 1 can have more attributes than attr2
|
bsw/jbe@1309
|
13072 function containsSameAttributes(attr1, attr2) {
|
bsw/jbe@1309
|
13073 for (var a in attr1) {
|
bsw/jbe@1309
|
13074 if (attr1.hasOwnProperty(a)) {
|
bsw/jbe@1309
|
13075 if (typeof attr2[a] === undefined || attr2[a] !== attr1[a]) {
|
bsw/jbe@1309
|
13076 return false;
|
bsw/jbe@1309
|
13077 }
|
bsw/jbe@1309
|
13078 }
|
bsw/jbe@1309
|
13079 }
|
bsw/jbe@1309
|
13080 return true;
|
bsw/jbe@1309
|
13081 }
|
bsw/jbe@1309
|
13082
|
bsw/jbe@1309
|
13083 // If attrbutes and values are the same > remove
|
bsw/jbe@1309
|
13084 // if attributes or values
|
bsw/jbe@1309
|
13085 function updateElementAttributes(element, newAttributes, toggle) {
|
bsw/jbe@1309
|
13086 var attr = wysihtml.dom.getAttributes(element),
|
bsw/jbe@1309
|
13087 fullContain = containsSameAttributes(newAttributes, attr),
|
bsw/jbe@1309
|
13088 attrDifference = wysihtml.lang.object(attr).difference(newAttributes),
|
bsw/jbe@1309
|
13089 a, b;
|
bsw/jbe@1309
|
13090
|
bsw/jbe@1309
|
13091 if (fullContain && toggle !== false) {
|
bsw/jbe@1309
|
13092 for (a in newAttributes) {
|
bsw/jbe@1309
|
13093 if (newAttributes.hasOwnProperty(a)) {
|
bsw/jbe@1309
|
13094 element.removeAttribute(a);
|
bsw/jbe@1309
|
13095 }
|
bsw/jbe@1309
|
13096 }
|
bsw/jbe@1309
|
13097 } else {
|
bsw/jbe@1309
|
13098
|
bsw/jbe@1309
|
13099 /*if (!wysihtml.lang.object(attrDifference).isEmpty()) {
|
bsw/jbe@1309
|
13100 for (b in attrDifference) {
|
bsw/jbe@1309
|
13101 if (attrDifference.hasOwnProperty(b)) {
|
bsw/jbe@1309
|
13102 element.removeAttribute(b);
|
bsw/jbe@1309
|
13103 }
|
bsw/jbe@1309
|
13104 }
|
bsw/jbe@1309
|
13105 }*/
|
bsw/jbe@1309
|
13106
|
bsw/jbe@1309
|
13107 for (a in newAttributes) {
|
bsw/jbe@1309
|
13108 if (newAttributes.hasOwnProperty(a)) {
|
bsw/jbe@1309
|
13109 element.setAttribute(a, newAttributes[a]);
|
bsw/jbe@1309
|
13110 }
|
bsw/jbe@1309
|
13111 }
|
bsw/jbe@1309
|
13112 }
|
bsw/jbe@1309
|
13113 }
|
bsw/jbe@1309
|
13114
|
bsw/jbe@1309
|
13115 function updateFormatOfElement(element, options) {
|
bsw/jbe@1309
|
13116 var attr, newNode, a, newAttributes, nodeNameQuery, nodeQueryMatch;
|
bsw/jbe@1309
|
13117
|
bsw/jbe@1309
|
13118 if (options.className) {
|
bsw/jbe@1309
|
13119 if (options.toggle !== false && element.classList.contains(options.className)) {
|
bsw/jbe@1309
|
13120 element.classList.remove(options.className);
|
bsw/jbe@1309
|
13121 } else {
|
bsw/jbe@1309
|
13122 if (options.classRegExp) {
|
bsw/jbe@1309
|
13123 element.className = element.className.replace(options.classRegExp, '');
|
bsw/jbe@1309
|
13124 }
|
bsw/jbe@1309
|
13125 element.classList.add(options.className);
|
bsw/jbe@1309
|
13126 }
|
bsw/jbe@1309
|
13127 if (hasNoClass(element)) {
|
bsw/jbe@1309
|
13128 element.removeAttribute('class');
|
bsw/jbe@1309
|
13129 }
|
bsw/jbe@1309
|
13130 }
|
bsw/jbe@1309
|
13131
|
bsw/jbe@1309
|
13132 // change/remove style
|
bsw/jbe@1309
|
13133 if (options.styleProperty) {
|
bsw/jbe@1309
|
13134 if (options.toggle !== false && element.style[wysihtml.browser.fixStyleKey(options.styleProperty)].trim().replace(/, /g, ",") === options.styleValue) {
|
bsw/jbe@1309
|
13135 element.style[wysihtml.browser.fixStyleKey(options.styleProperty)] = '';
|
bsw/jbe@1309
|
13136 } else {
|
bsw/jbe@1309
|
13137 element.style[wysihtml.browser.fixStyleKey(options.styleProperty)] = options.styleValue;
|
bsw/jbe@1309
|
13138 }
|
bsw/jbe@1309
|
13139 }
|
bsw/jbe@1309
|
13140 if (hasNoStyle(element)) {
|
bsw/jbe@1309
|
13141 element.removeAttribute('style');
|
bsw/jbe@1309
|
13142 }
|
bsw/jbe@1309
|
13143
|
bsw/jbe@1309
|
13144 if (options.attribute) {
|
bsw/jbe@1309
|
13145 if (typeof options.attribute === "object") {
|
bsw/jbe@1309
|
13146 newAttributes = options.attribute;
|
bsw/jbe@1309
|
13147 } else {
|
bsw/jbe@1309
|
13148 newAttributes = {};
|
bsw/jbe@1309
|
13149 newAttributes[options.attribute] = options.attributeValue || '';
|
bsw/jbe@1309
|
13150 }
|
bsw/jbe@1309
|
13151 updateElementAttributes(element, newAttributes, options.toggle);
|
bsw/jbe@1309
|
13152 }
|
bsw/jbe@1309
|
13153
|
bsw/jbe@1309
|
13154
|
bsw/jbe@1309
|
13155 // Handle similar semantically same elements (queryAliasMap)
|
bsw/jbe@1309
|
13156 nodeNameQuery = options.nodeName ? queryAliasMap[options.nodeName.toLowerCase()] || options.nodeName.toLowerCase() : null;
|
bsw/jbe@1309
|
13157 nodeQueryMatch = nodeNameQuery ? wysihtml.dom.domNode(element).test({ query: nodeNameQuery }) : false;
|
bsw/jbe@1309
|
13158
|
bsw/jbe@1309
|
13159 // Unwrap element if no attributes present and node name given
|
bsw/jbe@1309
|
13160 // or no attributes and if no nodename set but node is the default
|
bsw/jbe@1309
|
13161 if (!options.nodeName || options.nodeName === defaultTag || nodeQueryMatch) {
|
bsw/jbe@1309
|
13162 if (
|
bsw/jbe@1309
|
13163 ((options.toggle !== false && nodeQueryMatch) || (!options.nodeName && element.nodeName === defaultTag)) &&
|
bsw/jbe@1309
|
13164 hasNoClass(element) && hasNoStyle(element) && hasNoAttributes(element)
|
bsw/jbe@1309
|
13165 ) {
|
bsw/jbe@1309
|
13166 wysihtml.dom.unwrap(element);
|
bsw/jbe@1309
|
13167 }
|
bsw/jbe@1309
|
13168
|
bsw/jbe@1309
|
13169 }
|
bsw/jbe@1309
|
13170 }
|
bsw/jbe@1309
|
13171
|
bsw/jbe@1309
|
13172 // Fetch all textnodes in selection
|
bsw/jbe@1309
|
13173 // Empty textnodes are ignored except the one containing text caret
|
bsw/jbe@1309
|
13174 function getSelectedTextNodes(selection, splitBounds) {
|
bsw/jbe@1309
|
13175 var textNodes = [];
|
bsw/jbe@1309
|
13176
|
bsw/jbe@1309
|
13177 if (!selection.isCollapsed()) {
|
bsw/jbe@1309
|
13178 textNodes = textNodes.concat(selection.getOwnNodes([3], function(node) {
|
bsw/jbe@1309
|
13179 // Exclude empty nodes except caret node
|
bsw/jbe@1309
|
13180 return (!wysihtml.dom.domNode(node).is.emptyTextNode());
|
bsw/jbe@1309
|
13181 }, splitBounds));
|
bsw/jbe@1309
|
13182 }
|
bsw/jbe@1309
|
13183
|
bsw/jbe@1309
|
13184 return textNodes;
|
bsw/jbe@1309
|
13185 }
|
bsw/jbe@1309
|
13186
|
bsw/jbe@1309
|
13187 function findSimilarTextNodeWrapper(textNode, options, container, exact) {
|
bsw/jbe@1309
|
13188 var node = textNode,
|
bsw/jbe@1309
|
13189 similarOptions = exact ? options : correctOptionsForSimilarityCheck(options);
|
bsw/jbe@1309
|
13190
|
bsw/jbe@1309
|
13191 do {
|
bsw/jbe@1309
|
13192 if (node.nodeType === 1 && isSimilarNode(node, similarOptions)) {
|
bsw/jbe@1309
|
13193 return node;
|
bsw/jbe@1309
|
13194 }
|
bsw/jbe@1309
|
13195 node = node.parentNode;
|
bsw/jbe@1309
|
13196 } while (node && node !== container);
|
bsw/jbe@1309
|
13197
|
bsw/jbe@1309
|
13198 return null;
|
bsw/jbe@1309
|
13199 }
|
bsw/jbe@1309
|
13200
|
bsw/jbe@1309
|
13201 function correctOptionsForSimilarityCheck(options) {
|
bsw/jbe@1309
|
13202 return {
|
bsw/jbe@1309
|
13203 nodeName: options.nodeName || null,
|
bsw/jbe@1309
|
13204 className: (!options.classRegExp) ? options.className || null : null,
|
bsw/jbe@1309
|
13205 classRegExp: options.classRegExp || null,
|
bsw/jbe@1309
|
13206 styleProperty: options.styleProperty || null
|
bsw/jbe@1309
|
13207 };
|
bsw/jbe@1309
|
13208 }
|
bsw/jbe@1309
|
13209
|
bsw/jbe@1309
|
13210 // Finds inline node with similar nodeName/style/className
|
bsw/jbe@1309
|
13211 // If nodeName is specified inline node with the same (or alias) nodeName is expected to prove similar regardless of attributes
|
bsw/jbe@1309
|
13212 function isSimilarNode(node, options) {
|
bsw/jbe@1309
|
13213 var o;
|
bsw/jbe@1309
|
13214 if (options.nodeName) {
|
bsw/jbe@1309
|
13215 var query = queryAliasMap[options.nodeName.toLowerCase()] || options.nodeName.toLowerCase();
|
bsw/jbe@1309
|
13216 return wysihtml.dom.domNode(node).test({ query: query });
|
bsw/jbe@1309
|
13217 } else {
|
bsw/jbe@1309
|
13218 o = wysihtml.lang.object(options).clone();
|
bsw/jbe@1309
|
13219 o.query = INLINE_ELEMENTS; // make sure only inline elements with styles and classes are counted
|
bsw/jbe@1309
|
13220 return wysihtml.dom.domNode(node).test(o);
|
bsw/jbe@1309
|
13221 }
|
bsw/jbe@1309
|
13222 }
|
bsw/jbe@1309
|
13223
|
bsw/jbe@1309
|
13224 function selectRange(composer, range) {
|
bsw/jbe@1309
|
13225 var d = document.documentElement || document.body,
|
bsw/jbe@1309
|
13226 oldScrollTop = d.scrollTop,
|
bsw/jbe@1309
|
13227 oldScrollLeft = d.scrollLeft,
|
bsw/jbe@1309
|
13228 selection = rangy.getSelection(composer.win);
|
bsw/jbe@1309
|
13229
|
bsw/jbe@1309
|
13230 rangy.getSelection(composer.win).removeAllRanges();
|
bsw/jbe@1309
|
13231
|
bsw/jbe@1309
|
13232 // IE looses focus of contenteditable on removeallranges and can not set new selection unless contenteditable is focused again
|
bsw/jbe@1309
|
13233 try {
|
bsw/jbe@1309
|
13234 rangy.getSelection(composer.win).addRange(range);
|
bsw/jbe@1309
|
13235 } catch (e) {}
|
bsw/jbe@1309
|
13236 if (!composer.doc.activeElement || !wysihtml.dom.contains(composer.element, composer.doc.activeElement)) {
|
bsw/jbe@1309
|
13237 composer.element.focus();
|
bsw/jbe@1309
|
13238 d.scrollTop = oldScrollTop;
|
bsw/jbe@1309
|
13239 d.scrollLeft = oldScrollLeft;
|
bsw/jbe@1309
|
13240 rangy.getSelection(composer.win).addRange(range);
|
bsw/jbe@1309
|
13241 }
|
bsw/jbe@1309
|
13242 }
|
bsw/jbe@1309
|
13243
|
bsw/jbe@1309
|
13244 function selectTextNodes(textNodes, composer) {
|
bsw/jbe@1309
|
13245 var range = rangy.createRange(composer.doc),
|
bsw/jbe@1309
|
13246 lastText = textNodes[textNodes.length - 1];
|
bsw/jbe@1309
|
13247
|
bsw/jbe@1309
|
13248 if (textNodes[0] && lastText) {
|
bsw/jbe@1309
|
13249 range.setStart(textNodes[0], 0);
|
bsw/jbe@1309
|
13250 range.setEnd(lastText, lastText.length);
|
bsw/jbe@1309
|
13251 selectRange(composer, range);
|
bsw/jbe@1309
|
13252 }
|
bsw/jbe@1309
|
13253
|
bsw/jbe@1309
|
13254 }
|
bsw/jbe@1309
|
13255
|
bsw/jbe@1309
|
13256 function selectTextNode(composer, node, start, end) {
|
bsw/jbe@1309
|
13257 var range = rangy.createRange(composer.doc);
|
bsw/jbe@1309
|
13258 if (node) {
|
bsw/jbe@1309
|
13259 range.setStart(node, start);
|
bsw/jbe@1309
|
13260 range.setEnd(node, typeof end !== 'undefined' ? end : start);
|
bsw/jbe@1309
|
13261 selectRange(composer, range);
|
bsw/jbe@1309
|
13262 }
|
bsw/jbe@1309
|
13263 }
|
bsw/jbe@1309
|
13264
|
bsw/jbe@1309
|
13265 function getState(composer, options, exact) {
|
bsw/jbe@1309
|
13266 var searchNodes = getSelectedTextNodes(composer.selection),
|
bsw/jbe@1309
|
13267 nodes = [],
|
bsw/jbe@1309
|
13268 partial = false,
|
bsw/jbe@1309
|
13269 node, range, caretNode;
|
bsw/jbe@1309
|
13270
|
bsw/jbe@1309
|
13271 if (composer.selection.isInThisEditable()) {
|
bsw/jbe@1309
|
13272
|
bsw/jbe@1309
|
13273 if (searchNodes.length === 0 && composer.selection.isCollapsed()) {
|
bsw/jbe@1309
|
13274 caretNode = composer.selection.getSelection().anchorNode;
|
bsw/jbe@1309
|
13275 if (!caretNode) {
|
bsw/jbe@1309
|
13276 // selection not in editor
|
bsw/jbe@1309
|
13277 return {
|
bsw/jbe@1309
|
13278 nodes: [],
|
bsw/jbe@1309
|
13279 partial: false
|
bsw/jbe@1309
|
13280 };
|
bsw/jbe@1309
|
13281 }
|
bsw/jbe@1309
|
13282 if (caretNode.nodeType === 3) {
|
bsw/jbe@1309
|
13283 searchNodes = [caretNode];
|
bsw/jbe@1309
|
13284 }
|
bsw/jbe@1309
|
13285 }
|
bsw/jbe@1309
|
13286
|
bsw/jbe@1309
|
13287 // Handle collapsed selection caret
|
bsw/jbe@1309
|
13288 if (!searchNodes.length) {
|
bsw/jbe@1309
|
13289 range = composer.selection.getOwnRanges()[0];
|
bsw/jbe@1309
|
13290 if (range) {
|
bsw/jbe@1309
|
13291 searchNodes = [range.endContainer];
|
bsw/jbe@1309
|
13292 }
|
bsw/jbe@1309
|
13293 }
|
bsw/jbe@1309
|
13294
|
bsw/jbe@1309
|
13295 for (var i = 0, maxi = searchNodes.length; i < maxi; i++) {
|
bsw/jbe@1309
|
13296 node = findSimilarTextNodeWrapper(searchNodes[i], options, composer.element, exact);
|
bsw/jbe@1309
|
13297 if (node) {
|
bsw/jbe@1309
|
13298 nodes.push(node);
|
bsw/jbe@1309
|
13299 } else {
|
bsw/jbe@1309
|
13300 partial = true;
|
bsw/jbe@1309
|
13301 }
|
bsw/jbe@1309
|
13302 }
|
bsw/jbe@1309
|
13303
|
bsw/jbe@1309
|
13304 }
|
bsw/jbe@1309
|
13305
|
bsw/jbe@1309
|
13306 return {
|
bsw/jbe@1309
|
13307 nodes: nodes,
|
bsw/jbe@1309
|
13308 partial: partial
|
bsw/jbe@1309
|
13309 };
|
bsw/jbe@1309
|
13310 }
|
bsw/jbe@1309
|
13311
|
bsw/jbe@1309
|
13312 // Returns if caret is inside a word in textnode (not on boundary)
|
bsw/jbe@1309
|
13313 // If selection anchornode is not text node, returns false
|
bsw/jbe@1309
|
13314 function caretIsInsideWord(selection) {
|
bsw/jbe@1309
|
13315 var anchor, offset, beforeChar, afterChar;
|
bsw/jbe@1309
|
13316 if (selection) {
|
bsw/jbe@1309
|
13317 anchor = selection.anchorNode;
|
bsw/jbe@1309
|
13318 offset = selection.anchorOffset;
|
bsw/jbe@1309
|
13319 if (anchor && anchor.nodeType === 3 && offset > 0 && offset < anchor.data.length) {
|
bsw/jbe@1309
|
13320 beforeChar = anchor.data[offset - 1];
|
bsw/jbe@1309
|
13321 afterChar = anchor.data[offset];
|
bsw/jbe@1309
|
13322 return (/\w/).test(beforeChar) && (/\w/).test(afterChar);
|
bsw/jbe@1309
|
13323 }
|
bsw/jbe@1309
|
13324 }
|
bsw/jbe@1309
|
13325 return false;
|
bsw/jbe@1309
|
13326 }
|
bsw/jbe@1309
|
13327
|
bsw/jbe@1309
|
13328 // Returns a range and textnode containing object from caret position covering a whole word
|
bsw/jbe@1309
|
13329 // wordOffsety describes the original position of caret in the new textNode
|
bsw/jbe@1309
|
13330 // Caret has to be inside a textNode.
|
bsw/jbe@1309
|
13331 function getRangeForWord(selection) {
|
bsw/jbe@1309
|
13332 var anchor, offset, doc, range, offsetStart, offsetEnd, beforeChar, afterChar,
|
bsw/jbe@1309
|
13333 txtNodes = [];
|
bsw/jbe@1309
|
13334 if (selection) {
|
bsw/jbe@1309
|
13335 anchor = selection.anchorNode;
|
bsw/jbe@1309
|
13336 offset = offsetStart = offsetEnd = selection.anchorOffset;
|
bsw/jbe@1309
|
13337 doc = anchor.ownerDocument;
|
bsw/jbe@1309
|
13338 range = rangy.createRange(doc);
|
bsw/jbe@1309
|
13339
|
bsw/jbe@1309
|
13340 if (anchor && anchor.nodeType === 3) {
|
bsw/jbe@1309
|
13341
|
bsw/jbe@1309
|
13342 while (offsetStart > 0 && (/\w/).test(anchor.data[offsetStart - 1])) {
|
bsw/jbe@1309
|
13343 offsetStart--;
|
bsw/jbe@1309
|
13344 }
|
bsw/jbe@1309
|
13345
|
bsw/jbe@1309
|
13346 while (offsetEnd < anchor.data.length && (/\w/).test(anchor.data[offsetEnd])) {
|
bsw/jbe@1309
|
13347 offsetEnd++;
|
bsw/jbe@1309
|
13348 }
|
bsw/jbe@1309
|
13349
|
bsw/jbe@1309
|
13350 range.setStartAndEnd(anchor, offsetStart, offsetEnd);
|
bsw/jbe@1309
|
13351 range.splitBoundaries();
|
bsw/jbe@1309
|
13352 txtNodes = range.getNodes([3], function(node) {
|
bsw/jbe@1309
|
13353 return (!wysihtml.dom.domNode(node).is.emptyTextNode());
|
bsw/jbe@1309
|
13354 });
|
bsw/jbe@1309
|
13355
|
bsw/jbe@1309
|
13356 return {
|
bsw/jbe@1309
|
13357 wordOffset: offset - offsetStart,
|
bsw/jbe@1309
|
13358 range: range,
|
bsw/jbe@1309
|
13359 textNode: txtNodes[0]
|
bsw/jbe@1309
|
13360 };
|
bsw/jbe@1309
|
13361
|
bsw/jbe@1309
|
13362 }
|
bsw/jbe@1309
|
13363 }
|
bsw/jbe@1309
|
13364 return false;
|
bsw/jbe@1309
|
13365 }
|
bsw/jbe@1309
|
13366
|
bsw/jbe@1309
|
13367 // Contents of 2 elements are merged to fitst element. second element is removed as consequence
|
bsw/jbe@1309
|
13368 function mergeContents(element1, element2) {
|
bsw/jbe@1309
|
13369 while (element2.firstChild) {
|
bsw/jbe@1309
|
13370 element1.appendChild(element2.firstChild);
|
bsw/jbe@1309
|
13371 }
|
bsw/jbe@1309
|
13372 element2.parentNode.removeChild(element2);
|
bsw/jbe@1309
|
13373 }
|
bsw/jbe@1309
|
13374
|
bsw/jbe@1309
|
13375 function mergeConsequentSimilarElements(elements) {
|
bsw/jbe@1309
|
13376 for (var i = elements.length; i--;) {
|
bsw/jbe@1309
|
13377
|
bsw/jbe@1309
|
13378 if (elements[i] && elements[i].parentNode) { // Test if node is not allready removed in cleanup
|
bsw/jbe@1309
|
13379
|
bsw/jbe@1309
|
13380 if (elements[i].nextSibling && isSameNode(elements[i], elements[i].nextSibling)) {
|
bsw/jbe@1309
|
13381 mergeContents(elements[i], elements[i].nextSibling);
|
bsw/jbe@1309
|
13382 }
|
bsw/jbe@1309
|
13383
|
bsw/jbe@1309
|
13384 if (elements[i].previousSibling && isSameNode(elements[i] , elements[i].previousSibling)) {
|
bsw/jbe@1309
|
13385 mergeContents(elements[i].previousSibling, elements[i]);
|
bsw/jbe@1309
|
13386 }
|
bsw/jbe@1309
|
13387
|
bsw/jbe@1309
|
13388 }
|
bsw/jbe@1309
|
13389 }
|
bsw/jbe@1309
|
13390 }
|
bsw/jbe@1309
|
13391
|
bsw/jbe@1309
|
13392 function cleanupAndSetSelection(composer, textNodes, options) {
|
bsw/jbe@1309
|
13393 if (textNodes.length > 0) {
|
bsw/jbe@1309
|
13394 selectTextNodes(textNodes, composer);
|
bsw/jbe@1309
|
13395 }
|
bsw/jbe@1309
|
13396 mergeConsequentSimilarElements(getState(composer, options).nodes);
|
bsw/jbe@1309
|
13397 if (textNodes.length > 0) {
|
bsw/jbe@1309
|
13398 selectTextNodes(textNodes, composer);
|
bsw/jbe@1309
|
13399 }
|
bsw/jbe@1309
|
13400 }
|
bsw/jbe@1309
|
13401
|
bsw/jbe@1309
|
13402 function cleanupAndSetCaret(composer, textNode, offset, options) {
|
bsw/jbe@1309
|
13403 selectTextNode(composer, textNode, offset);
|
bsw/jbe@1309
|
13404 mergeConsequentSimilarElements(getState(composer, options).nodes);
|
bsw/jbe@1309
|
13405 selectTextNode(composer, textNode, offset);
|
bsw/jbe@1309
|
13406 }
|
bsw/jbe@1309
|
13407
|
bsw/jbe@1309
|
13408 // Formats a textnode with given options
|
bsw/jbe@1309
|
13409 function formatTextNode(textNode, options) {
|
bsw/jbe@1309
|
13410 var wrapNode = createWrapNode(textNode, options);
|
bsw/jbe@1309
|
13411
|
bsw/jbe@1309
|
13412 textNode.parentNode.insertBefore(wrapNode, textNode);
|
bsw/jbe@1309
|
13413 wrapNode.appendChild(textNode);
|
bsw/jbe@1309
|
13414 }
|
bsw/jbe@1309
|
13415
|
bsw/jbe@1309
|
13416 // Changes/toggles format of a textnode
|
bsw/jbe@1309
|
13417 function unformatTextNode(textNode, composer, options) {
|
bsw/jbe@1309
|
13418 var container = composer.element,
|
bsw/jbe@1309
|
13419 wrapNode = findSimilarTextNodeWrapper(textNode, options, container),
|
bsw/jbe@1309
|
13420 newWrapNode;
|
bsw/jbe@1309
|
13421
|
bsw/jbe@1309
|
13422 if (wrapNode) {
|
bsw/jbe@1309
|
13423 newWrapNode = wrapNode.cloneNode(false);
|
bsw/jbe@1309
|
13424
|
bsw/jbe@1309
|
13425 wysihtml.dom.domNode(textNode).escapeParent(wrapNode, newWrapNode);
|
bsw/jbe@1309
|
13426 updateFormatOfElement(newWrapNode, options);
|
bsw/jbe@1309
|
13427 }
|
bsw/jbe@1309
|
13428 }
|
bsw/jbe@1309
|
13429
|
bsw/jbe@1309
|
13430 // Removes the format around textnode
|
bsw/jbe@1309
|
13431 function removeFormatFromTextNode(textNode, composer, options) {
|
bsw/jbe@1309
|
13432 var container = composer.element,
|
bsw/jbe@1309
|
13433 wrapNode = findSimilarTextNodeWrapper(textNode, options, container);
|
bsw/jbe@1309
|
13434
|
bsw/jbe@1309
|
13435 if (wrapNode) {
|
bsw/jbe@1309
|
13436 wysihtml.dom.domNode(textNode).escapeParent(wrapNode);
|
bsw/jbe@1309
|
13437 }
|
bsw/jbe@1309
|
13438 }
|
bsw/jbe@1309
|
13439
|
bsw/jbe@1309
|
13440 // Creates node around caret formated with options
|
bsw/jbe@1309
|
13441 function formatTextRange(range, composer, options) {
|
bsw/jbe@1309
|
13442 var wrapNode = createWrapNode(range.endContainer, options);
|
bsw/jbe@1309
|
13443
|
bsw/jbe@1309
|
13444 range.surroundContents(wrapNode);
|
bsw/jbe@1309
|
13445 composer.selection.selectNode(wrapNode);
|
bsw/jbe@1309
|
13446 }
|
bsw/jbe@1309
|
13447
|
bsw/jbe@1309
|
13448 // Changes/toggles format of whole selection
|
bsw/jbe@1309
|
13449 function updateFormat(composer, textNodes, state, options) {
|
bsw/jbe@1309
|
13450 var exactState = getState(composer, options, true),
|
bsw/jbe@1309
|
13451 selection = composer.selection.getSelection(),
|
bsw/jbe@1309
|
13452 wordObj, textNode, newNode, i;
|
bsw/jbe@1309
|
13453
|
bsw/jbe@1309
|
13454 if (!textNodes.length) {
|
bsw/jbe@1309
|
13455 // Selection is caret
|
bsw/jbe@1309
|
13456
|
bsw/jbe@1309
|
13457
|
bsw/jbe@1309
|
13458 if (options.toggle !== false) {
|
bsw/jbe@1309
|
13459 if (caretIsInsideWord(selection)) {
|
bsw/jbe@1309
|
13460
|
bsw/jbe@1309
|
13461 // Unformat whole word
|
bsw/jbe@1309
|
13462 wordObj = getRangeForWord(selection);
|
bsw/jbe@1309
|
13463 textNode = wordObj.textNode;
|
bsw/jbe@1309
|
13464 unformatTextNode(wordObj.textNode, composer, options);
|
bsw/jbe@1309
|
13465 cleanupAndSetCaret(composer, wordObj.textNode, wordObj.wordOffset, options);
|
bsw/jbe@1309
|
13466
|
bsw/jbe@1309
|
13467 } else {
|
bsw/jbe@1309
|
13468
|
bsw/jbe@1309
|
13469 // Escape caret out of format
|
bsw/jbe@1309
|
13470 textNode = composer.doc.createTextNode(wysihtml.INVISIBLE_SPACE);
|
bsw/jbe@1309
|
13471 newNode = state.nodes[0].cloneNode(false);
|
bsw/jbe@1309
|
13472 newNode.appendChild(textNode);
|
bsw/jbe@1309
|
13473 composer.selection.splitElementAtCaret(state.nodes[0], newNode);
|
bsw/jbe@1309
|
13474 updateFormatOfElement(newNode, options);
|
bsw/jbe@1309
|
13475 cleanupAndSetSelection(composer, [textNode], options);
|
bsw/jbe@1309
|
13476 var s = composer.selection.getSelection();
|
bsw/jbe@1309
|
13477 if (s.anchorNode && s.focusNode) {
|
bsw/jbe@1309
|
13478 // Has an error in IE when collapsing selection. probably from rangy
|
bsw/jbe@1309
|
13479 try {
|
bsw/jbe@1309
|
13480 s.collapseToEnd();
|
bsw/jbe@1309
|
13481 } catch (e) {}
|
bsw/jbe@1309
|
13482 }
|
bsw/jbe@1309
|
13483 }
|
bsw/jbe@1309
|
13484 } else {
|
bsw/jbe@1309
|
13485 // In non-toggle mode the closest state element has to be found and the state updated differently
|
bsw/jbe@1309
|
13486 for (i = state.nodes.length; i--;) {
|
bsw/jbe@1309
|
13487 updateFormatOfElement(state.nodes[i], options);
|
bsw/jbe@1309
|
13488 }
|
bsw/jbe@1309
|
13489 }
|
bsw/jbe@1309
|
13490
|
bsw/jbe@1309
|
13491 } else {
|
bsw/jbe@1309
|
13492
|
bsw/jbe@1309
|
13493 if (!exactState.partial && options.toggle !== false) {
|
bsw/jbe@1309
|
13494
|
bsw/jbe@1309
|
13495 // If whole selection (all textnodes) are in the applied format
|
bsw/jbe@1309
|
13496 // remove the format from selection
|
bsw/jbe@1309
|
13497 // Non-toggle mode never removes. Remove has to be called explicitly
|
bsw/jbe@1309
|
13498 for (i = textNodes.length; i--;) {
|
bsw/jbe@1309
|
13499 unformatTextNode(textNodes[i], composer, options);
|
bsw/jbe@1309
|
13500 }
|
bsw/jbe@1309
|
13501
|
bsw/jbe@1309
|
13502 } else {
|
bsw/jbe@1309
|
13503
|
bsw/jbe@1309
|
13504 // Selection is partially in format
|
bsw/jbe@1309
|
13505 // change it to new if format if textnode allreafy in similar state
|
bsw/jbe@1309
|
13506 // else just apply
|
bsw/jbe@1309
|
13507
|
bsw/jbe@1309
|
13508 for (i = textNodes.length; i--;) {
|
bsw/jbe@1309
|
13509
|
bsw/jbe@1309
|
13510 if (findSimilarTextNodeWrapper(textNodes[i], options, composer.element)) {
|
bsw/jbe@1309
|
13511 unformatTextNode(textNodes[i], composer, options);
|
bsw/jbe@1309
|
13512 }
|
bsw/jbe@1309
|
13513
|
bsw/jbe@1309
|
13514 if (!findSimilarTextNodeWrapper(textNodes[i], options, composer.element)) {
|
bsw/jbe@1309
|
13515 formatTextNode(textNodes[i], options);
|
bsw/jbe@1309
|
13516 }
|
bsw/jbe@1309
|
13517 }
|
bsw/jbe@1309
|
13518
|
bsw/jbe@1309
|
13519 }
|
bsw/jbe@1309
|
13520
|
bsw/jbe@1309
|
13521 cleanupAndSetSelection(composer, textNodes, options);
|
bsw/jbe@1309
|
13522 }
|
bsw/jbe@1309
|
13523 }
|
bsw/jbe@1309
|
13524
|
bsw/jbe@1309
|
13525 // Removes format from selection
|
bsw/jbe@1309
|
13526 function removeFormat(composer, textNodes, state, options) {
|
bsw/jbe@1309
|
13527 var textNode, textOffset, newNode, i,
|
bsw/jbe@1309
|
13528 selection = composer.selection.getSelection();
|
bsw/jbe@1309
|
13529
|
bsw/jbe@1309
|
13530 if (!textNodes.length) {
|
bsw/jbe@1309
|
13531 textNode = selection.anchorNode;
|
bsw/jbe@1309
|
13532 textOffset = selection.anchorOffset;
|
bsw/jbe@1309
|
13533
|
bsw/jbe@1309
|
13534 for (i = state.nodes.length; i--;) {
|
bsw/jbe@1309
|
13535 wysihtml.dom.unwrap(state.nodes[i]);
|
bsw/jbe@1309
|
13536 }
|
bsw/jbe@1309
|
13537
|
bsw/jbe@1309
|
13538 cleanupAndSetCaret(composer, textNode, textOffset, options);
|
bsw/jbe@1309
|
13539 } else {
|
bsw/jbe@1309
|
13540 for (i = textNodes.length; i--;) {
|
bsw/jbe@1309
|
13541 removeFormatFromTextNode(textNodes[i], composer, options);
|
bsw/jbe@1309
|
13542 }
|
bsw/jbe@1309
|
13543 cleanupAndSetSelection(composer, textNodes, options);
|
bsw/jbe@1309
|
13544 }
|
bsw/jbe@1309
|
13545 }
|
bsw/jbe@1309
|
13546
|
bsw/jbe@1309
|
13547 // Adds format to selection
|
bsw/jbe@1309
|
13548 function applyFormat(composer, textNodes, options) {
|
bsw/jbe@1309
|
13549 var wordObj, i,
|
bsw/jbe@1309
|
13550 selection = composer.selection.getSelection();
|
bsw/jbe@1309
|
13551
|
bsw/jbe@1309
|
13552 if (!textNodes.length) {
|
bsw/jbe@1309
|
13553 // Handle collapsed selection caret and return
|
bsw/jbe@1309
|
13554 if (caretIsInsideWord(selection)) {
|
bsw/jbe@1309
|
13555
|
bsw/jbe@1309
|
13556 wordObj = getRangeForWord(selection);
|
bsw/jbe@1309
|
13557 formatTextNode(wordObj.textNode, options);
|
bsw/jbe@1309
|
13558 cleanupAndSetCaret(composer, wordObj.textNode, wordObj.wordOffset, options);
|
bsw/jbe@1309
|
13559
|
bsw/jbe@1309
|
13560 } else {
|
bsw/jbe@1309
|
13561 var r = composer.selection.getOwnRanges()[0];
|
bsw/jbe@1309
|
13562 if (r) {
|
bsw/jbe@1309
|
13563 formatTextRange(r, composer, options);
|
bsw/jbe@1309
|
13564 }
|
bsw/jbe@1309
|
13565 }
|
bsw/jbe@1309
|
13566
|
bsw/jbe@1309
|
13567 } else {
|
bsw/jbe@1309
|
13568 // Handle textnodes in selection and apply format
|
bsw/jbe@1309
|
13569 for (i = textNodes.length; i--;) {
|
bsw/jbe@1309
|
13570 formatTextNode(textNodes[i], options);
|
bsw/jbe@1309
|
13571 }
|
bsw/jbe@1309
|
13572 cleanupAndSetSelection(composer, textNodes, options);
|
bsw/jbe@1309
|
13573 }
|
bsw/jbe@1309
|
13574 }
|
bsw/jbe@1309
|
13575
|
bsw/jbe@1309
|
13576 // If properties is passed as a string, correct options with that nodeName
|
bsw/jbe@1309
|
13577 function fixOptions(options) {
|
bsw/jbe@1309
|
13578 options = (typeof options === "string") ? { nodeName: options } : options;
|
bsw/jbe@1309
|
13579 if (options.nodeName) { options.nodeName = options.nodeName.toUpperCase(); }
|
bsw/jbe@1309
|
13580 return options;
|
bsw/jbe@1309
|
13581 }
|
bsw/jbe@1309
|
13582
|
bsw/jbe@1309
|
13583 wysihtml.commands.formatInline = {
|
bsw/jbe@1309
|
13584
|
bsw/jbe@1309
|
13585 // Basics:
|
bsw/jbe@1309
|
13586 // In case of plain text or inline state not set wrap all non-empty textnodes with
|
bsw/jbe@1309
|
13587 // In case a similar inline wrapper node is detected on one of textnodes, the wrapper node is changed (if fully contained) or split and changed (partially contained)
|
bsw/jbe@1309
|
13588 // In case of changing mode every textnode is addressed separatly
|
bsw/jbe@1309
|
13589 exec: function(composer, command, options) {
|
bsw/jbe@1309
|
13590 options = fixOptions(options);
|
bsw/jbe@1309
|
13591
|
bsw/jbe@1309
|
13592 // Join adjactent textnodes first
|
bsw/jbe@1309
|
13593 composer.element.normalize();
|
bsw/jbe@1309
|
13594
|
bsw/jbe@1309
|
13595 var textNodes = getSelectedTextNodes(composer.selection, true),
|
bsw/jbe@1309
|
13596 state = getState(composer, options);
|
bsw/jbe@1309
|
13597 if (state.nodes.length > 0) {
|
bsw/jbe@1309
|
13598 // Text allready has the format applied
|
bsw/jbe@1309
|
13599 updateFormat(composer, textNodes, state, options);
|
bsw/jbe@1309
|
13600 } else {
|
bsw/jbe@1309
|
13601 // Selection is not in the applied format
|
bsw/jbe@1309
|
13602 applyFormat(composer, textNodes, options);
|
bsw/jbe@1309
|
13603 }
|
bsw/jbe@1309
|
13604 composer.element.normalize();
|
bsw/jbe@1309
|
13605 },
|
bsw/jbe@1309
|
13606
|
bsw/jbe@1309
|
13607 remove: function(composer, command, options) {
|
bsw/jbe@1309
|
13608 options = fixOptions(options);
|
bsw/jbe@1309
|
13609 composer.element.normalize();
|
bsw/jbe@1309
|
13610
|
bsw/jbe@1309
|
13611 var textNodes = getSelectedTextNodes(composer.selection, true),
|
bsw/jbe@1309
|
13612 state = getState(composer, options);
|
bsw/jbe@1309
|
13613
|
bsw/jbe@1309
|
13614 if (state.nodes.length > 0) {
|
bsw/jbe@1309
|
13615 // Text allready has the format applied
|
bsw/jbe@1309
|
13616 removeFormat(composer, textNodes, state, options);
|
bsw/jbe@1309
|
13617 }
|
bsw/jbe@1309
|
13618
|
bsw/jbe@1309
|
13619 composer.element.normalize();
|
bsw/jbe@1309
|
13620 },
|
bsw/jbe@1309
|
13621
|
bsw/jbe@1309
|
13622 state: function(composer, command, options) {
|
bsw/jbe@1309
|
13623 options = fixOptions(options);
|
bsw/jbe@1309
|
13624 var nodes = getState(composer, options, true).nodes;
|
bsw/jbe@1309
|
13625 return (nodes.length === 0) ? false : nodes;
|
bsw/jbe@1309
|
13626 }
|
bsw/jbe@1309
|
13627 };
|
bsw/jbe@1309
|
13628
|
bsw/jbe@1309
|
13629 })(wysihtml);
|
bsw/jbe@1309
|
13630
|
bsw/jbe@1309
|
13631 (function(wysihtml){
|
bsw/jbe@1309
|
13632 wysihtml.commands.indentList = {
|
bsw/jbe@1309
|
13633 exec: function(composer, command, value) {
|
bsw/jbe@1309
|
13634 var listEls = composer.selection.getSelectionParentsByTag('LI');
|
bsw/jbe@1309
|
13635 if (listEls) {
|
bsw/jbe@1309
|
13636 return this.tryToPushLiLevel(listEls, composer.selection);
|
bsw/jbe@1309
|
13637 }
|
bsw/jbe@1309
|
13638 return false;
|
bsw/jbe@1309
|
13639 },
|
bsw/jbe@1309
|
13640
|
bsw/jbe@1309
|
13641 state: function(composer, command) {
|
bsw/jbe@1309
|
13642 return false;
|
bsw/jbe@1309
|
13643 },
|
bsw/jbe@1309
|
13644
|
bsw/jbe@1309
|
13645 tryToPushLiLevel: function(liNodes, selection) {
|
bsw/jbe@1309
|
13646 var listTag, list, prevLi, liNode, prevLiList,
|
bsw/jbe@1309
|
13647 found = false;
|
bsw/jbe@1309
|
13648
|
bsw/jbe@1309
|
13649 selection.executeAndRestoreRangy(function() {
|
bsw/jbe@1309
|
13650
|
bsw/jbe@1309
|
13651 for (var i = liNodes.length; i--;) {
|
bsw/jbe@1309
|
13652 liNode = liNodes[i];
|
bsw/jbe@1309
|
13653 listTag = (liNode.parentNode.nodeName === 'OL') ? 'OL' : 'UL';
|
bsw/jbe@1309
|
13654 list = liNode.ownerDocument.createElement(listTag);
|
bsw/jbe@1309
|
13655 prevLi = wysihtml.dom.domNode(liNode).prev({nodeTypes: [wysihtml.ELEMENT_NODE]});
|
bsw/jbe@1309
|
13656 prevLiList = (prevLi) ? prevLi.querySelector('ul, ol') : null;
|
bsw/jbe@1309
|
13657
|
bsw/jbe@1309
|
13658 if (prevLi) {
|
bsw/jbe@1309
|
13659 if (prevLiList) {
|
bsw/jbe@1309
|
13660 prevLiList.appendChild(liNode);
|
bsw/jbe@1309
|
13661 } else {
|
bsw/jbe@1309
|
13662 list.appendChild(liNode);
|
bsw/jbe@1309
|
13663 prevLi.appendChild(list);
|
bsw/jbe@1309
|
13664 }
|
bsw/jbe@1309
|
13665 found = true;
|
bsw/jbe@1309
|
13666 }
|
bsw/jbe@1309
|
13667 }
|
bsw/jbe@1309
|
13668
|
bsw/jbe@1309
|
13669 });
|
bsw/jbe@1309
|
13670 return found;
|
bsw/jbe@1309
|
13671 }
|
bsw/jbe@1309
|
13672 };
|
bsw/jbe@1309
|
13673 }(wysihtml));
|
bsw/jbe@1309
|
13674
|
bsw/jbe@1309
|
13675 (function(wysihtml){
|
bsw/jbe@1309
|
13676 wysihtml.commands.insertHTML = {
|
bsw/jbe@1309
|
13677 exec: function(composer, command, html) {
|
bsw/jbe@1309
|
13678 composer.selection.insertHTML(html);
|
bsw/jbe@1309
|
13679 },
|
bsw/jbe@1309
|
13680
|
bsw/jbe@1309
|
13681 state: function() {
|
bsw/jbe@1309
|
13682 return false;
|
bsw/jbe@1309
|
13683 }
|
bsw/jbe@1309
|
13684 };
|
bsw/jbe@1309
|
13685 }(wysihtml));
|
bsw/jbe@1309
|
13686
|
bsw/jbe@1309
|
13687 (function(wysihtml) {
|
bsw/jbe@1309
|
13688 var LINE_BREAK = "<br>" + (wysihtml.browser.needsSpaceAfterLineBreak() ? " " : "");
|
bsw/jbe@1309
|
13689
|
bsw/jbe@1309
|
13690 wysihtml.commands.insertLineBreak = {
|
bsw/jbe@1309
|
13691 exec: function(composer, command) {
|
bsw/jbe@1309
|
13692 composer.selection.insertHTML(LINE_BREAK);
|
bsw/jbe@1309
|
13693 },
|
bsw/jbe@1309
|
13694
|
bsw/jbe@1309
|
13695 state: function() {
|
bsw/jbe@1309
|
13696 return false;
|
bsw/jbe@1309
|
13697 }
|
bsw/jbe@1309
|
13698 };
|
bsw/jbe@1309
|
13699 })(wysihtml);
|
bsw/jbe@1309
|
13700
|
bsw/jbe@1309
|
13701 wysihtml.commands.insertList = (function(wysihtml) {
|
bsw/jbe@1309
|
13702
|
bsw/jbe@1309
|
13703 var isNode = function(node, name) {
|
bsw/jbe@1309
|
13704 if (node && node.nodeName) {
|
bsw/jbe@1309
|
13705 if (typeof name === 'string') {
|
bsw/jbe@1309
|
13706 name = [name];
|
bsw/jbe@1309
|
13707 }
|
bsw/jbe@1309
|
13708 for (var n = name.length; n--;) {
|
bsw/jbe@1309
|
13709 if (node.nodeName === name[n]) {
|
bsw/jbe@1309
|
13710 return true;
|
bsw/jbe@1309
|
13711 }
|
bsw/jbe@1309
|
13712 }
|
bsw/jbe@1309
|
13713 }
|
bsw/jbe@1309
|
13714 return false;
|
bsw/jbe@1309
|
13715 };
|
bsw/jbe@1309
|
13716
|
bsw/jbe@1309
|
13717 var findListEl = function(node, nodeName, composer) {
|
bsw/jbe@1309
|
13718 var ret = {
|
bsw/jbe@1309
|
13719 el: null,
|
bsw/jbe@1309
|
13720 other: false
|
bsw/jbe@1309
|
13721 };
|
bsw/jbe@1309
|
13722
|
bsw/jbe@1309
|
13723 if (node) {
|
bsw/jbe@1309
|
13724 var parentLi = wysihtml.dom.getParentElement(node, { query: "li" }, false, composer.element),
|
bsw/jbe@1309
|
13725 otherNodeName = (nodeName === "UL") ? "OL" : "UL";
|
bsw/jbe@1309
|
13726
|
bsw/jbe@1309
|
13727 if (isNode(node, nodeName)) {
|
bsw/jbe@1309
|
13728 ret.el = node;
|
bsw/jbe@1309
|
13729 } else if (isNode(node, otherNodeName)) {
|
bsw/jbe@1309
|
13730 ret = {
|
bsw/jbe@1309
|
13731 el: node,
|
bsw/jbe@1309
|
13732 other: true
|
bsw/jbe@1309
|
13733 };
|
bsw/jbe@1309
|
13734 } else if (parentLi) {
|
bsw/jbe@1309
|
13735 if (isNode(parentLi.parentNode, nodeName)) {
|
bsw/jbe@1309
|
13736 ret.el = parentLi.parentNode;
|
bsw/jbe@1309
|
13737 } else if (isNode(parentLi.parentNode, otherNodeName)) {
|
bsw/jbe@1309
|
13738 ret = {
|
bsw/jbe@1309
|
13739 el : parentLi.parentNode,
|
bsw/jbe@1309
|
13740 other: true
|
bsw/jbe@1309
|
13741 };
|
bsw/jbe@1309
|
13742 }
|
bsw/jbe@1309
|
13743 }
|
bsw/jbe@1309
|
13744 }
|
bsw/jbe@1309
|
13745
|
bsw/jbe@1309
|
13746 // do not count list elements outside of composer
|
bsw/jbe@1309
|
13747 if (ret.el && !composer.element.contains(ret.el)) {
|
bsw/jbe@1309
|
13748 ret.el = null;
|
bsw/jbe@1309
|
13749 }
|
bsw/jbe@1309
|
13750
|
bsw/jbe@1309
|
13751 return ret;
|
bsw/jbe@1309
|
13752 };
|
bsw/jbe@1309
|
13753
|
bsw/jbe@1309
|
13754 var handleSameTypeList = function(el, nodeName, composer) {
|
bsw/jbe@1309
|
13755 var otherNodeName = (nodeName === "UL") ? "OL" : "UL",
|
bsw/jbe@1309
|
13756 otherLists, innerLists;
|
bsw/jbe@1309
|
13757 // Unwrap list
|
bsw/jbe@1309
|
13758 // <ul><li>foo</li><li>bar</li></ul>
|
bsw/jbe@1309
|
13759 // becomes:
|
bsw/jbe@1309
|
13760 // foo<br>bar<br>
|
bsw/jbe@1309
|
13761
|
bsw/jbe@1309
|
13762 composer.selection.executeAndRestoreRangy(function() {
|
bsw/jbe@1309
|
13763 otherLists = getListsInSelection(otherNodeName, composer);
|
bsw/jbe@1309
|
13764 if (otherLists.length) {
|
bsw/jbe@1309
|
13765 for (var l = otherLists.length; l--;) {
|
bsw/jbe@1309
|
13766 wysihtml.dom.renameElement(otherLists[l], nodeName.toLowerCase());
|
bsw/jbe@1309
|
13767 }
|
bsw/jbe@1309
|
13768 } else {
|
bsw/jbe@1309
|
13769 innerLists = getListsInSelection(['OL', 'UL'], composer);
|
bsw/jbe@1309
|
13770 for (var i = innerLists.length; i--;) {
|
bsw/jbe@1309
|
13771 wysihtml.dom.resolveList(innerLists[i], composer.config.useLineBreaks);
|
bsw/jbe@1309
|
13772 }
|
bsw/jbe@1309
|
13773 if (innerLists.length === 0) {
|
bsw/jbe@1309
|
13774 wysihtml.dom.resolveList(el, composer.config.useLineBreaks);
|
bsw/jbe@1309
|
13775 }
|
bsw/jbe@1309
|
13776 }
|
bsw/jbe@1309
|
13777 });
|
bsw/jbe@1309
|
13778 };
|
bsw/jbe@1309
|
13779
|
bsw/jbe@1309
|
13780 var handleOtherTypeList = function(el, nodeName, composer) {
|
bsw/jbe@1309
|
13781 var otherNodeName = (nodeName === "UL") ? "OL" : "UL";
|
bsw/jbe@1309
|
13782 // Turn an ordered list into an unordered list
|
bsw/jbe@1309
|
13783 // <ol><li>foo</li><li>bar</li></ol>
|
bsw/jbe@1309
|
13784 // becomes:
|
bsw/jbe@1309
|
13785 // <ul><li>foo</li><li>bar</li></ul>
|
bsw/jbe@1309
|
13786 // Also rename other lists in selection
|
bsw/jbe@1309
|
13787 composer.selection.executeAndRestoreRangy(function() {
|
bsw/jbe@1309
|
13788 var renameLists = [el].concat(getListsInSelection(otherNodeName, composer));
|
bsw/jbe@1309
|
13789
|
bsw/jbe@1309
|
13790 // All selection inner lists get renamed too
|
bsw/jbe@1309
|
13791 for (var l = renameLists.length; l--;) {
|
bsw/jbe@1309
|
13792 wysihtml.dom.renameElement(renameLists[l], nodeName.toLowerCase());
|
bsw/jbe@1309
|
13793 }
|
bsw/jbe@1309
|
13794 });
|
bsw/jbe@1309
|
13795 };
|
bsw/jbe@1309
|
13796
|
bsw/jbe@1309
|
13797 var getListsInSelection = function(nodeName, composer) {
|
bsw/jbe@1309
|
13798 var ranges = composer.selection.getOwnRanges(),
|
bsw/jbe@1309
|
13799 renameLists = [];
|
bsw/jbe@1309
|
13800
|
bsw/jbe@1309
|
13801 for (var r = ranges.length; r--;) {
|
bsw/jbe@1309
|
13802 renameLists = renameLists.concat(ranges[r].getNodes([1], function(node) {
|
bsw/jbe@1309
|
13803 return isNode(node, nodeName);
|
bsw/jbe@1309
|
13804 }));
|
bsw/jbe@1309
|
13805 }
|
bsw/jbe@1309
|
13806
|
bsw/jbe@1309
|
13807 return renameLists;
|
bsw/jbe@1309
|
13808 };
|
bsw/jbe@1309
|
13809
|
bsw/jbe@1309
|
13810 var createListFallback = function(nodeName, composer) {
|
bsw/jbe@1309
|
13811 var sel = rangy.saveSelection(composer.win);
|
bsw/jbe@1309
|
13812
|
bsw/jbe@1309
|
13813 // Fallback for Create list
|
bsw/jbe@1309
|
13814 var tempClassName = "_wysihtml-temp-" + new Date().getTime(),
|
bsw/jbe@1309
|
13815 isEmpty, list;
|
bsw/jbe@1309
|
13816
|
bsw/jbe@1309
|
13817 composer.commands.exec("formatBlock", {
|
bsw/jbe@1309
|
13818 "nodeName": "div",
|
bsw/jbe@1309
|
13819 "className": tempClassName
|
bsw/jbe@1309
|
13820 });
|
bsw/jbe@1309
|
13821
|
bsw/jbe@1309
|
13822 var tempElement = composer.element.querySelector("." + tempClassName);
|
bsw/jbe@1309
|
13823
|
bsw/jbe@1309
|
13824 // This space causes new lists to never break on enter
|
bsw/jbe@1309
|
13825 var INVISIBLE_SPACE_REG_EXP = /\uFEFF/g;
|
bsw/jbe@1309
|
13826 tempElement.innerHTML = tempElement.innerHTML.replace(wysihtml.INVISIBLE_SPACE_REG_EXP, "");
|
bsw/jbe@1309
|
13827 if (tempElement) {
|
bsw/jbe@1309
|
13828 isEmpty = (/^(\s|(<br>))+$/i).test(tempElement.innerHTML);
|
bsw/jbe@1309
|
13829 list = wysihtml.dom.convertToList(tempElement, nodeName.toLowerCase(), composer.parent.config.classNames.uneditableContainer);
|
bsw/jbe@1309
|
13830 if (sel) {
|
bsw/jbe@1309
|
13831 rangy.restoreSelection(sel);
|
bsw/jbe@1309
|
13832 }
|
bsw/jbe@1309
|
13833 if (isEmpty) {
|
bsw/jbe@1309
|
13834 composer.selection.selectNode(list.querySelector("li"), true);
|
bsw/jbe@1309
|
13835 }
|
bsw/jbe@1309
|
13836 }
|
bsw/jbe@1309
|
13837 };
|
bsw/jbe@1309
|
13838
|
bsw/jbe@1309
|
13839 return {
|
bsw/jbe@1309
|
13840 exec: function(composer, command, nodeName) {
|
bsw/jbe@1309
|
13841 var doc = composer.doc,
|
bsw/jbe@1309
|
13842 cmd = (nodeName === "OL") ? "insertOrderedList" : "insertUnorderedList",
|
bsw/jbe@1309
|
13843 s = composer.selection.getSelection(),
|
bsw/jbe@1309
|
13844 anode = s.anchorNode.nodeType === 1 && s.anchorNode.firstChild ? s.anchorNode.childNodes[s.anchorOffset] : s.anchorNode,
|
bsw/jbe@1309
|
13845 fnode = s.focusNode.nodeType === 1 && s.focusNode.firstChild ? s.focusNode.childNodes[s.focusOffset] || s.focusNode.lastChild : s.focusNode,
|
bsw/jbe@1309
|
13846 selectedNode, list;
|
bsw/jbe@1309
|
13847
|
bsw/jbe@1309
|
13848 if (s.isBackwards()) {
|
bsw/jbe@1309
|
13849 // swap variables
|
bsw/jbe@1309
|
13850 anode = [fnode, fnode = anode][0];
|
bsw/jbe@1309
|
13851 }
|
bsw/jbe@1309
|
13852
|
bsw/jbe@1309
|
13853 if (wysihtml.dom.domNode(fnode).is.emptyTextNode(true) && fnode) {
|
bsw/jbe@1309
|
13854 fnode = wysihtml.dom.domNode(fnode).prev({nodeTypes: [1,3], ignoreBlankTexts: true});
|
bsw/jbe@1309
|
13855 }
|
bsw/jbe@1309
|
13856 if (wysihtml.dom.domNode(anode).is.emptyTextNode(true) && anode) {
|
bsw/jbe@1309
|
13857 anode = wysihtml.dom.domNode(anode).next({nodeTypes: [1,3], ignoreBlankTexts: true});
|
bsw/jbe@1309
|
13858 }
|
bsw/jbe@1309
|
13859
|
bsw/jbe@1309
|
13860 if (anode && fnode) {
|
bsw/jbe@1309
|
13861 if (anode === fnode) {
|
bsw/jbe@1309
|
13862 selectedNode = anode;
|
bsw/jbe@1309
|
13863 } else {
|
bsw/jbe@1309
|
13864 selectedNode = wysihtml.dom.domNode(anode).commonAncestor(fnode, composer.element);
|
bsw/jbe@1309
|
13865 }
|
bsw/jbe@1309
|
13866 } else {
|
bsw/jbe@1309
|
13867 selectedNode = composer.selection.getSelectedNode();
|
bsw/jbe@1309
|
13868 }
|
bsw/jbe@1309
|
13869
|
bsw/jbe@1309
|
13870 list = findListEl(selectedNode, nodeName, composer);
|
bsw/jbe@1309
|
13871
|
bsw/jbe@1309
|
13872 if (!list.el) {
|
bsw/jbe@1309
|
13873 if (composer.commands.support(cmd)) {
|
bsw/jbe@1309
|
13874 doc.execCommand(cmd, false, null);
|
bsw/jbe@1309
|
13875 } else {
|
bsw/jbe@1309
|
13876 createListFallback(nodeName, composer);
|
bsw/jbe@1309
|
13877 }
|
bsw/jbe@1309
|
13878 } else if (list.other) {
|
bsw/jbe@1309
|
13879 handleOtherTypeList(list.el, nodeName, composer);
|
bsw/jbe@1309
|
13880 } else {
|
bsw/jbe@1309
|
13881 handleSameTypeList(list.el, nodeName, composer);
|
bsw/jbe@1309
|
13882 }
|
bsw/jbe@1309
|
13883 },
|
bsw/jbe@1309
|
13884
|
bsw/jbe@1309
|
13885 state: function(composer, command, nodeName) {
|
bsw/jbe@1309
|
13886 var selectedNode = composer.selection.getSelectedNode(),
|
bsw/jbe@1309
|
13887 list = findListEl(selectedNode, nodeName, composer);
|
bsw/jbe@1309
|
13888
|
bsw/jbe@1309
|
13889 return (list.el && !list.other) ? list.el : false;
|
bsw/jbe@1309
|
13890 }
|
bsw/jbe@1309
|
13891 };
|
bsw/jbe@1309
|
13892
|
bsw/jbe@1309
|
13893 })(wysihtml);
|
bsw/jbe@1309
|
13894
|
bsw/jbe@1309
|
13895 (function(wysihtml){
|
bsw/jbe@1309
|
13896
|
bsw/jbe@1309
|
13897 wysihtml.commands.outdentList = {
|
bsw/jbe@1309
|
13898 exec: function(composer, command, value) {
|
bsw/jbe@1309
|
13899 var listEls = composer.selection.getSelectionParentsByTag('LI');
|
bsw/jbe@1309
|
13900 if (listEls) {
|
bsw/jbe@1309
|
13901 return this.tryToPullLiLevel(listEls, composer);
|
bsw/jbe@1309
|
13902 }
|
bsw/jbe@1309
|
13903 return false;
|
bsw/jbe@1309
|
13904 },
|
bsw/jbe@1309
|
13905
|
bsw/jbe@1309
|
13906 state: function(composer, command) {
|
bsw/jbe@1309
|
13907 return false;
|
bsw/jbe@1309
|
13908 },
|
bsw/jbe@1309
|
13909
|
bsw/jbe@1309
|
13910 tryToPullLiLevel: function(liNodes, composer) {
|
bsw/jbe@1309
|
13911 var listNode, outerListNode, outerLiNode, list, prevLi, liNode, afterList,
|
bsw/jbe@1309
|
13912 found = false,
|
bsw/jbe@1309
|
13913 that = this;
|
bsw/jbe@1309
|
13914
|
bsw/jbe@1309
|
13915 composer.selection.executeAndRestoreRangy(function() {
|
bsw/jbe@1309
|
13916
|
bsw/jbe@1309
|
13917 for (var i = liNodes.length; i--;) {
|
bsw/jbe@1309
|
13918 liNode = liNodes[i];
|
bsw/jbe@1309
|
13919 if (liNode.parentNode) {
|
bsw/jbe@1309
|
13920 listNode = liNode.parentNode;
|
bsw/jbe@1309
|
13921
|
bsw/jbe@1309
|
13922 if (listNode.tagName === 'OL' || listNode.tagName === 'UL') {
|
bsw/jbe@1309
|
13923 found = true;
|
bsw/jbe@1309
|
13924
|
bsw/jbe@1309
|
13925 outerListNode = wysihtml.dom.getParentElement(listNode.parentNode, { query: 'ol, ul' }, false, composer.element);
|
bsw/jbe@1309
|
13926 outerLiNode = wysihtml.dom.getParentElement(listNode.parentNode, { query: 'li' }, false, composer.element);
|
bsw/jbe@1309
|
13927
|
bsw/jbe@1309
|
13928 if (outerListNode && outerLiNode) {
|
bsw/jbe@1309
|
13929
|
bsw/jbe@1309
|
13930 if (liNode.nextSibling) {
|
bsw/jbe@1309
|
13931 afterList = that.getAfterList(listNode, liNode);
|
bsw/jbe@1309
|
13932 liNode.appendChild(afterList);
|
bsw/jbe@1309
|
13933 }
|
bsw/jbe@1309
|
13934 outerListNode.insertBefore(liNode, outerLiNode.nextSibling);
|
bsw/jbe@1309
|
13935
|
bsw/jbe@1309
|
13936 } else {
|
bsw/jbe@1309
|
13937
|
bsw/jbe@1309
|
13938 if (liNode.nextSibling) {
|
bsw/jbe@1309
|
13939 afterList = that.getAfterList(listNode, liNode);
|
bsw/jbe@1309
|
13940 liNode.appendChild(afterList);
|
bsw/jbe@1309
|
13941 }
|
bsw/jbe@1309
|
13942
|
bsw/jbe@1309
|
13943 for (var j = liNode.childNodes.length; j--;) {
|
bsw/jbe@1309
|
13944 listNode.parentNode.insertBefore(liNode.childNodes[j], listNode.nextSibling);
|
bsw/jbe@1309
|
13945 }
|
bsw/jbe@1309
|
13946
|
bsw/jbe@1309
|
13947 listNode.parentNode.insertBefore(document.createElement('br'), listNode.nextSibling);
|
bsw/jbe@1309
|
13948 liNode.parentNode.removeChild(liNode);
|
bsw/jbe@1309
|
13949
|
bsw/jbe@1309
|
13950 }
|
bsw/jbe@1309
|
13951
|
bsw/jbe@1309
|
13952 // cleanup
|
bsw/jbe@1309
|
13953 if (listNode.childNodes.length === 0) {
|
bsw/jbe@1309
|
13954 listNode.parentNode.removeChild(listNode);
|
bsw/jbe@1309
|
13955 }
|
bsw/jbe@1309
|
13956 }
|
bsw/jbe@1309
|
13957 }
|
bsw/jbe@1309
|
13958 }
|
bsw/jbe@1309
|
13959
|
bsw/jbe@1309
|
13960 });
|
bsw/jbe@1309
|
13961 return found;
|
bsw/jbe@1309
|
13962 },
|
bsw/jbe@1309
|
13963
|
bsw/jbe@1309
|
13964 getAfterList: function(listNode, liNode) {
|
bsw/jbe@1309
|
13965 var nodeName = listNode.nodeName,
|
bsw/jbe@1309
|
13966 newList = document.createElement(nodeName);
|
bsw/jbe@1309
|
13967
|
bsw/jbe@1309
|
13968 while (liNode.nextSibling) {
|
bsw/jbe@1309
|
13969 newList.appendChild(liNode.nextSibling);
|
bsw/jbe@1309
|
13970 }
|
bsw/jbe@1309
|
13971 return newList;
|
bsw/jbe@1309
|
13972 }
|
bsw/jbe@1309
|
13973
|
bsw/jbe@1309
|
13974 };
|
bsw/jbe@1309
|
13975 }(wysihtml));
|
bsw/jbe@1309
|
13976
|
bsw/jbe@1309
|
13977 (function(wysihtml){
|
bsw/jbe@1309
|
13978 wysihtml.commands.redo = {
|
bsw/jbe@1309
|
13979 exec: function(composer) {
|
bsw/jbe@1309
|
13980 return composer.undoManager.redo();
|
bsw/jbe@1309
|
13981 },
|
bsw/jbe@1309
|
13982
|
bsw/jbe@1309
|
13983 state: function(composer) {
|
bsw/jbe@1309
|
13984 return false;
|
bsw/jbe@1309
|
13985 }
|
bsw/jbe@1309
|
13986 };
|
bsw/jbe@1309
|
13987 }(wysihtml));
|
bsw/jbe@1309
|
13988
|
bsw/jbe@1309
|
13989 (function(wysihtml) {
|
bsw/jbe@1309
|
13990
|
bsw/jbe@1309
|
13991 var nodeOptions = {
|
bsw/jbe@1309
|
13992 nodeName: "A"
|
bsw/jbe@1309
|
13993 };
|
bsw/jbe@1309
|
13994
|
bsw/jbe@1309
|
13995 wysihtml.commands.removeLink = {
|
bsw/jbe@1309
|
13996 exec: function(composer, command) {
|
bsw/jbe@1309
|
13997 wysihtml.commands.formatInline.remove(composer, command, nodeOptions);
|
bsw/jbe@1309
|
13998 },
|
bsw/jbe@1309
|
13999
|
bsw/jbe@1309
|
14000 state: function(composer, command) {
|
bsw/jbe@1309
|
14001 return wysihtml.commands.formatInline.state(composer, command, nodeOptions);
|
bsw/jbe@1309
|
14002 }
|
bsw/jbe@1309
|
14003 };
|
bsw/jbe@1309
|
14004
|
bsw/jbe@1309
|
14005 })(wysihtml);
|
bsw/jbe@1309
|
14006
|
bsw/jbe@1309
|
14007 (function(wysihtml){
|
bsw/jbe@1309
|
14008 wysihtml.commands.undo = {
|
bsw/jbe@1309
|
14009 exec: function(composer) {
|
bsw/jbe@1309
|
14010 return composer.undoManager.undo();
|
bsw/jbe@1309
|
14011 },
|
bsw/jbe@1309
|
14012
|
bsw/jbe@1309
|
14013 state: function(composer) {
|
bsw/jbe@1309
|
14014 return false;
|
bsw/jbe@1309
|
14015 }
|
bsw/jbe@1309
|
14016 };
|
bsw/jbe@1309
|
14017 }(wysihtml));
|
bsw/jbe@1309
|
14018
|
bsw/jbe@1309
|
14019 /**
|
bsw/jbe@1309
|
14020 * Undo Manager for wysihtml
|
bsw/jbe@1309
|
14021 * slightly inspired by http://rniwa.com/editing/undomanager.html#the-undomanager-interface
|
bsw/jbe@1309
|
14022 */
|
bsw/jbe@1309
|
14023 (function(wysihtml) {
|
bsw/jbe@1309
|
14024 var Z_KEY = 90,
|
bsw/jbe@1309
|
14025 Y_KEY = 89,
|
bsw/jbe@1309
|
14026 BACKSPACE_KEY = 8,
|
bsw/jbe@1309
|
14027 DELETE_KEY = 46,
|
bsw/jbe@1309
|
14028 MAX_HISTORY_ENTRIES = 25,
|
bsw/jbe@1309
|
14029 DATA_ATTR_NODE = "data-wysihtml-selection-node",
|
bsw/jbe@1309
|
14030 DATA_ATTR_OFFSET = "data-wysihtml-selection-offset",
|
bsw/jbe@1309
|
14031 UNDO_HTML = '<span id="_wysihtml-undo" class="_wysihtml-temp">' + wysihtml.INVISIBLE_SPACE + '</span>',
|
bsw/jbe@1309
|
14032 REDO_HTML = '<span id="_wysihtml-redo" class="_wysihtml-temp">' + wysihtml.INVISIBLE_SPACE + '</span>',
|
bsw/jbe@1309
|
14033 dom = wysihtml.dom;
|
bsw/jbe@1309
|
14034
|
bsw/jbe@1309
|
14035 function cleanTempElements(doc) {
|
bsw/jbe@1309
|
14036 var tempElement;
|
bsw/jbe@1309
|
14037 while (tempElement = doc.querySelector("._wysihtml-temp")) {
|
bsw/jbe@1309
|
14038 tempElement.parentNode.removeChild(tempElement);
|
bsw/jbe@1309
|
14039 }
|
bsw/jbe@1309
|
14040 }
|
bsw/jbe@1309
|
14041
|
bsw/jbe@1309
|
14042 wysihtml.UndoManager = wysihtml.lang.Dispatcher.extend(
|
bsw/jbe@1309
|
14043 /** @scope wysihtml.UndoManager.prototype */ {
|
bsw/jbe@1309
|
14044 constructor: function(editor) {
|
bsw/jbe@1309
|
14045 this.editor = editor;
|
bsw/jbe@1309
|
14046 this.composer = editor.composer;
|
bsw/jbe@1309
|
14047 this.element = this.composer.element;
|
bsw/jbe@1309
|
14048
|
bsw/jbe@1309
|
14049 this.position = 0;
|
bsw/jbe@1309
|
14050 this.historyStr = [];
|
bsw/jbe@1309
|
14051 this.historyDom = [];
|
bsw/jbe@1309
|
14052
|
bsw/jbe@1309
|
14053 this.transact();
|
bsw/jbe@1309
|
14054
|
bsw/jbe@1309
|
14055 this._observe();
|
bsw/jbe@1309
|
14056 },
|
bsw/jbe@1309
|
14057
|
bsw/jbe@1309
|
14058 _observe: function() {
|
bsw/jbe@1309
|
14059 var that = this,
|
bsw/jbe@1309
|
14060 doc = this.composer.sandbox.getDocument(),
|
bsw/jbe@1309
|
14061 lastKey;
|
bsw/jbe@1309
|
14062
|
bsw/jbe@1309
|
14063 // Catch CTRL+Z and CTRL+Y
|
bsw/jbe@1309
|
14064 dom.observe(this.element, "keydown", function(event) {
|
bsw/jbe@1309
|
14065 if (event.altKey || (!event.ctrlKey && !event.metaKey)) {
|
bsw/jbe@1309
|
14066 return;
|
bsw/jbe@1309
|
14067 }
|
bsw/jbe@1309
|
14068
|
bsw/jbe@1309
|
14069 var keyCode = event.keyCode,
|
bsw/jbe@1309
|
14070 isUndo = keyCode === Z_KEY && !event.shiftKey,
|
bsw/jbe@1309
|
14071 isRedo = (keyCode === Z_KEY && event.shiftKey) || (keyCode === Y_KEY);
|
bsw/jbe@1309
|
14072
|
bsw/jbe@1309
|
14073 if (isUndo) {
|
bsw/jbe@1309
|
14074 that.undo();
|
bsw/jbe@1309
|
14075 event.preventDefault();
|
bsw/jbe@1309
|
14076 } else if (isRedo) {
|
bsw/jbe@1309
|
14077 that.redo();
|
bsw/jbe@1309
|
14078 event.preventDefault();
|
bsw/jbe@1309
|
14079 }
|
bsw/jbe@1309
|
14080 });
|
bsw/jbe@1309
|
14081
|
bsw/jbe@1309
|
14082 // Catch delete and backspace
|
bsw/jbe@1309
|
14083 dom.observe(this.element, "keydown", function(event) {
|
bsw/jbe@1309
|
14084 var keyCode = event.keyCode;
|
bsw/jbe@1309
|
14085 if (keyCode === lastKey) {
|
bsw/jbe@1309
|
14086 return;
|
bsw/jbe@1309
|
14087 }
|
bsw/jbe@1309
|
14088
|
bsw/jbe@1309
|
14089 lastKey = keyCode;
|
bsw/jbe@1309
|
14090
|
bsw/jbe@1309
|
14091 if (keyCode === BACKSPACE_KEY || keyCode === DELETE_KEY) {
|
bsw/jbe@1309
|
14092 that.transact();
|
bsw/jbe@1309
|
14093 }
|
bsw/jbe@1309
|
14094 });
|
bsw/jbe@1309
|
14095
|
bsw/jbe@1309
|
14096 this.editor
|
bsw/jbe@1309
|
14097 .on("newword:composer", function() {
|
bsw/jbe@1309
|
14098 that.transact();
|
bsw/jbe@1309
|
14099 })
|
bsw/jbe@1309
|
14100
|
bsw/jbe@1309
|
14101 .on("beforecommand:composer", function() {
|
bsw/jbe@1309
|
14102 that.transact();
|
bsw/jbe@1309
|
14103 });
|
bsw/jbe@1309
|
14104 },
|
bsw/jbe@1309
|
14105
|
bsw/jbe@1309
|
14106 transact: function() {
|
bsw/jbe@1309
|
14107 var previousHtml = this.historyStr[this.position - 1],
|
bsw/jbe@1309
|
14108 currentHtml = this.composer.getValue(false, false),
|
bsw/jbe@1309
|
14109 composerIsVisible = this.element.offsetWidth > 0 && this.element.offsetHeight > 0,
|
bsw/jbe@1309
|
14110 range, node, offset, element, position;
|
bsw/jbe@1309
|
14111
|
bsw/jbe@1309
|
14112 if (currentHtml === previousHtml) {
|
bsw/jbe@1309
|
14113 return;
|
bsw/jbe@1309
|
14114 }
|
bsw/jbe@1309
|
14115
|
bsw/jbe@1309
|
14116 var length = this.historyStr.length = this.historyDom.length = this.position;
|
bsw/jbe@1309
|
14117 if (length > MAX_HISTORY_ENTRIES) {
|
bsw/jbe@1309
|
14118 this.historyStr.shift();
|
bsw/jbe@1309
|
14119 this.historyDom.shift();
|
bsw/jbe@1309
|
14120 this.position--;
|
bsw/jbe@1309
|
14121 }
|
bsw/jbe@1309
|
14122
|
bsw/jbe@1309
|
14123 this.position++;
|
bsw/jbe@1309
|
14124
|
bsw/jbe@1309
|
14125 if (composerIsVisible) {
|
bsw/jbe@1309
|
14126 // Do not start saving selection if composer is not visible
|
bsw/jbe@1309
|
14127 range = this.composer.selection.getRange();
|
bsw/jbe@1309
|
14128 node = (range && range.startContainer) ? range.startContainer : this.element;
|
bsw/jbe@1309
|
14129 offset = (range && range.startOffset) ? range.startOffset : 0;
|
bsw/jbe@1309
|
14130
|
bsw/jbe@1309
|
14131 if (node.nodeType === wysihtml.ELEMENT_NODE) {
|
bsw/jbe@1309
|
14132 element = node;
|
bsw/jbe@1309
|
14133 } else {
|
bsw/jbe@1309
|
14134 element = node.parentNode;
|
bsw/jbe@1309
|
14135 position = this.getChildNodeIndex(element, node);
|
bsw/jbe@1309
|
14136 }
|
bsw/jbe@1309
|
14137
|
bsw/jbe@1309
|
14138 element.setAttribute(DATA_ATTR_OFFSET, offset);
|
bsw/jbe@1309
|
14139 if (typeof(position) !== "undefined") {
|
bsw/jbe@1309
|
14140 element.setAttribute(DATA_ATTR_NODE, position);
|
bsw/jbe@1309
|
14141 }
|
bsw/jbe@1309
|
14142 }
|
bsw/jbe@1309
|
14143
|
bsw/jbe@1309
|
14144 var clone = this.element.cloneNode(!!currentHtml);
|
bsw/jbe@1309
|
14145 this.historyDom.push(clone);
|
bsw/jbe@1309
|
14146 this.historyStr.push(currentHtml);
|
bsw/jbe@1309
|
14147
|
bsw/jbe@1309
|
14148 if (element) {
|
bsw/jbe@1309
|
14149 element.removeAttribute(DATA_ATTR_OFFSET);
|
bsw/jbe@1309
|
14150 element.removeAttribute(DATA_ATTR_NODE);
|
bsw/jbe@1309
|
14151 }
|
bsw/jbe@1309
|
14152
|
bsw/jbe@1309
|
14153 },
|
bsw/jbe@1309
|
14154
|
bsw/jbe@1309
|
14155 undo: function() {
|
bsw/jbe@1309
|
14156 this.transact();
|
bsw/jbe@1309
|
14157
|
bsw/jbe@1309
|
14158 if (!this.undoPossible()) {
|
bsw/jbe@1309
|
14159 return;
|
bsw/jbe@1309
|
14160 }
|
bsw/jbe@1309
|
14161
|
bsw/jbe@1309
|
14162 this.set(this.historyDom[--this.position - 1]);
|
bsw/jbe@1309
|
14163 this.editor.fire("undo:composer");
|
bsw/jbe@1309
|
14164 },
|
bsw/jbe@1309
|
14165
|
bsw/jbe@1309
|
14166 redo: function() {
|
bsw/jbe@1309
|
14167 if (!this.redoPossible()) {
|
bsw/jbe@1309
|
14168 return;
|
bsw/jbe@1309
|
14169 }
|
bsw/jbe@1309
|
14170
|
bsw/jbe@1309
|
14171 this.set(this.historyDom[++this.position - 1]);
|
bsw/jbe@1309
|
14172 this.editor.fire("redo:composer");
|
bsw/jbe@1309
|
14173 },
|
bsw/jbe@1309
|
14174
|
bsw/jbe@1309
|
14175 undoPossible: function() {
|
bsw/jbe@1309
|
14176 return this.position > 1;
|
bsw/jbe@1309
|
14177 },
|
bsw/jbe@1309
|
14178
|
bsw/jbe@1309
|
14179 redoPossible: function() {
|
bsw/jbe@1309
|
14180 return this.position < this.historyStr.length;
|
bsw/jbe@1309
|
14181 },
|
bsw/jbe@1309
|
14182
|
bsw/jbe@1309
|
14183 set: function(historyEntry) {
|
bsw/jbe@1309
|
14184 this.element.innerHTML = "";
|
bsw/jbe@1309
|
14185
|
bsw/jbe@1309
|
14186 var i = 0,
|
bsw/jbe@1309
|
14187 childNodes = historyEntry.childNodes,
|
bsw/jbe@1309
|
14188 length = historyEntry.childNodes.length;
|
bsw/jbe@1309
|
14189
|
bsw/jbe@1309
|
14190 for (; i<length; i++) {
|
bsw/jbe@1309
|
14191 this.element.appendChild(childNodes[i].cloneNode(true));
|
bsw/jbe@1309
|
14192 }
|
bsw/jbe@1309
|
14193
|
bsw/jbe@1309
|
14194 // Restore selection
|
bsw/jbe@1309
|
14195 var offset,
|
bsw/jbe@1309
|
14196 node,
|
bsw/jbe@1309
|
14197 position;
|
bsw/jbe@1309
|
14198
|
bsw/jbe@1309
|
14199 if (historyEntry.hasAttribute(DATA_ATTR_OFFSET)) {
|
bsw/jbe@1309
|
14200 offset = historyEntry.getAttribute(DATA_ATTR_OFFSET);
|
bsw/jbe@1309
|
14201 position = historyEntry.getAttribute(DATA_ATTR_NODE);
|
bsw/jbe@1309
|
14202 node = this.element;
|
bsw/jbe@1309
|
14203 } else {
|
bsw/jbe@1309
|
14204 node = this.element.querySelector("[" + DATA_ATTR_OFFSET + "]") || this.element;
|
bsw/jbe@1309
|
14205 offset = node.getAttribute(DATA_ATTR_OFFSET);
|
bsw/jbe@1309
|
14206 position = node.getAttribute(DATA_ATTR_NODE);
|
bsw/jbe@1309
|
14207 node.removeAttribute(DATA_ATTR_OFFSET);
|
bsw/jbe@1309
|
14208 node.removeAttribute(DATA_ATTR_NODE);
|
bsw/jbe@1309
|
14209 }
|
bsw/jbe@1309
|
14210
|
bsw/jbe@1309
|
14211 if (position !== null) {
|
bsw/jbe@1309
|
14212 node = this.getChildNodeByIndex(node, +position);
|
bsw/jbe@1309
|
14213 }
|
bsw/jbe@1309
|
14214
|
bsw/jbe@1309
|
14215 this.composer.selection.set(node, offset);
|
bsw/jbe@1309
|
14216 },
|
bsw/jbe@1309
|
14217
|
bsw/jbe@1309
|
14218 getChildNodeIndex: function(parent, child) {
|
bsw/jbe@1309
|
14219 var i = 0,
|
bsw/jbe@1309
|
14220 childNodes = parent.childNodes,
|
bsw/jbe@1309
|
14221 length = childNodes.length;
|
bsw/jbe@1309
|
14222 for (; i<length; i++) {
|
bsw/jbe@1309
|
14223 if (childNodes[i] === child) {
|
bsw/jbe@1309
|
14224 return i;
|
bsw/jbe@1309
|
14225 }
|
bsw/jbe@1309
|
14226 }
|
bsw/jbe@1309
|
14227 },
|
bsw/jbe@1309
|
14228
|
bsw/jbe@1309
|
14229 getChildNodeByIndex: function(parent, index) {
|
bsw/jbe@1309
|
14230 return parent.childNodes[index];
|
bsw/jbe@1309
|
14231 }
|
bsw/jbe@1309
|
14232 });
|
bsw/jbe@1309
|
14233 })(wysihtml);
|
bsw/jbe@1309
|
14234
|
bsw/jbe@1309
|
14235 /**
|
bsw/jbe@1309
|
14236 * TODO: the following methods still need unit test coverage
|
bsw/jbe@1309
|
14237 */
|
bsw/jbe@1309
|
14238 wysihtml.views.View = Base.extend(
|
bsw/jbe@1309
|
14239 /** @scope wysihtml.views.View.prototype */ {
|
bsw/jbe@1309
|
14240 constructor: function(parent, textareaElement, config) {
|
bsw/jbe@1309
|
14241 this.parent = parent;
|
bsw/jbe@1309
|
14242 this.element = textareaElement;
|
bsw/jbe@1309
|
14243 this.config = config;
|
bsw/jbe@1309
|
14244 if (!this.config.noTextarea) {
|
bsw/jbe@1309
|
14245 this._observeViewChange();
|
bsw/jbe@1309
|
14246 }
|
bsw/jbe@1309
|
14247 },
|
bsw/jbe@1309
|
14248
|
bsw/jbe@1309
|
14249 _observeViewChange: function() {
|
bsw/jbe@1309
|
14250 var that = this;
|
bsw/jbe@1309
|
14251 this.parent.on("beforeload", function() {
|
bsw/jbe@1309
|
14252 that.parent.on("change_view", function(view) {
|
bsw/jbe@1309
|
14253 if (view === that.name) {
|
bsw/jbe@1309
|
14254 that.parent.currentView = that;
|
bsw/jbe@1309
|
14255 that.show();
|
bsw/jbe@1309
|
14256 // Using tiny delay here to make sure that the placeholder is set before focusing
|
bsw/jbe@1309
|
14257 setTimeout(function() { that.focus(); }, 0);
|
bsw/jbe@1309
|
14258 } else {
|
bsw/jbe@1309
|
14259 that.hide();
|
bsw/jbe@1309
|
14260 }
|
bsw/jbe@1309
|
14261 });
|
bsw/jbe@1309
|
14262 });
|
bsw/jbe@1309
|
14263 },
|
bsw/jbe@1309
|
14264
|
bsw/jbe@1309
|
14265 focus: function() {
|
bsw/jbe@1309
|
14266 if (this.element && this.element.ownerDocument && this.element.ownerDocument.querySelector(":focus") === this.element) {
|
bsw/jbe@1309
|
14267 return;
|
bsw/jbe@1309
|
14268 }
|
bsw/jbe@1309
|
14269
|
bsw/jbe@1309
|
14270 try { if(this.element) { this.element.focus(); } } catch(e) {}
|
bsw/jbe@1309
|
14271 },
|
bsw/jbe@1309
|
14272
|
bsw/jbe@1309
|
14273 hide: function() {
|
bsw/jbe@1309
|
14274 this.element.style.display = "none";
|
bsw/jbe@1309
|
14275 },
|
bsw/jbe@1309
|
14276
|
bsw/jbe@1309
|
14277 show: function() {
|
bsw/jbe@1309
|
14278 this.element.style.display = "";
|
bsw/jbe@1309
|
14279 },
|
bsw/jbe@1309
|
14280
|
bsw/jbe@1309
|
14281 disable: function() {
|
bsw/jbe@1309
|
14282 this.element.setAttribute("disabled", "disabled");
|
bsw/jbe@1309
|
14283 },
|
bsw/jbe@1309
|
14284
|
bsw/jbe@1309
|
14285 enable: function() {
|
bsw/jbe@1309
|
14286 this.element.removeAttribute("disabled");
|
bsw/jbe@1309
|
14287 }
|
bsw/jbe@1309
|
14288 });
|
bsw/jbe@1309
|
14289
|
bsw/jbe@1309
|
14290 (function(wysihtml) {
|
bsw/jbe@1309
|
14291 var dom = wysihtml.dom,
|
bsw/jbe@1309
|
14292 browser = wysihtml.browser;
|
bsw/jbe@1309
|
14293
|
bsw/jbe@1309
|
14294 wysihtml.views.Composer = wysihtml.views.View.extend(
|
bsw/jbe@1309
|
14295 /** @scope wysihtml.views.Composer.prototype */ {
|
bsw/jbe@1309
|
14296 name: "composer",
|
bsw/jbe@1309
|
14297
|
bsw/jbe@1309
|
14298 constructor: function(parent, editableElement, config) {
|
bsw/jbe@1309
|
14299 this.base(parent, editableElement, config);
|
bsw/jbe@1309
|
14300 if (!this.config.noTextarea) {
|
bsw/jbe@1309
|
14301 this.textarea = this.parent.textarea;
|
bsw/jbe@1309
|
14302 } else {
|
bsw/jbe@1309
|
14303 this.editableArea = editableElement;
|
bsw/jbe@1309
|
14304 }
|
bsw/jbe@1309
|
14305 if (this.config.contentEditableMode) {
|
bsw/jbe@1309
|
14306 this._initContentEditableArea();
|
bsw/jbe@1309
|
14307 } else {
|
bsw/jbe@1309
|
14308 this._initSandbox();
|
bsw/jbe@1309
|
14309 }
|
bsw/jbe@1309
|
14310 },
|
bsw/jbe@1309
|
14311
|
bsw/jbe@1309
|
14312 clear: function() {
|
bsw/jbe@1309
|
14313 this.element.innerHTML = browser.displaysCaretInEmptyContentEditableCorrectly() ? "" : "<br>";
|
bsw/jbe@1309
|
14314 },
|
bsw/jbe@1309
|
14315
|
bsw/jbe@1309
|
14316 getValue: function(parse, clearInternals) {
|
bsw/jbe@1309
|
14317 var value = this.isEmpty() ? "" : wysihtml.quirks.getCorrectInnerHTML(this.element);
|
bsw/jbe@1309
|
14318 if (parse !== false) {
|
bsw/jbe@1309
|
14319 value = this.parent.parse(value, (clearInternals === false) ? false : true);
|
bsw/jbe@1309
|
14320 }
|
bsw/jbe@1309
|
14321 return value;
|
bsw/jbe@1309
|
14322 },
|
bsw/jbe@1309
|
14323
|
bsw/jbe@1309
|
14324 setValue: function(html, parse) {
|
bsw/jbe@1309
|
14325 if (parse !== false) {
|
bsw/jbe@1309
|
14326 html = this.parent.parse(html);
|
bsw/jbe@1309
|
14327 }
|
bsw/jbe@1309
|
14328
|
bsw/jbe@1309
|
14329 try {
|
bsw/jbe@1309
|
14330 this.element.innerHTML = html;
|
bsw/jbe@1309
|
14331 } catch (e) {
|
bsw/jbe@1309
|
14332 this.element.innerText = html;
|
bsw/jbe@1309
|
14333 }
|
bsw/jbe@1309
|
14334 },
|
bsw/jbe@1309
|
14335
|
bsw/jbe@1309
|
14336 cleanUp: function(rules) {
|
bsw/jbe@1309
|
14337 var bookmark;
|
bsw/jbe@1309
|
14338 if (this.selection && this.selection.isInThisEditable()) {
|
bsw/jbe@1309
|
14339 bookmark = rangy.saveSelection(this.win);
|
bsw/jbe@1309
|
14340 }
|
bsw/jbe@1309
|
14341 this.parent.parse(this.element, undefined, rules);
|
bsw/jbe@1309
|
14342 if (bookmark) {
|
bsw/jbe@1309
|
14343 rangy.restoreSelection(bookmark);
|
bsw/jbe@1309
|
14344 }
|
bsw/jbe@1309
|
14345 },
|
bsw/jbe@1309
|
14346
|
bsw/jbe@1309
|
14347 show: function() {
|
bsw/jbe@1309
|
14348 this.editableArea.style.display = this._displayStyle || "";
|
bsw/jbe@1309
|
14349
|
bsw/jbe@1309
|
14350 if (!this.config.noTextarea && !this.textarea.element.disabled) {
|
bsw/jbe@1309
|
14351 // Firefox needs this, otherwise contentEditable becomes uneditable
|
bsw/jbe@1309
|
14352 this.disable();
|
bsw/jbe@1309
|
14353 this.enable();
|
bsw/jbe@1309
|
14354 }
|
bsw/jbe@1309
|
14355 },
|
bsw/jbe@1309
|
14356
|
bsw/jbe@1309
|
14357 hide: function() {
|
bsw/jbe@1309
|
14358 this._displayStyle = dom.getStyle("display").from(this.editableArea);
|
bsw/jbe@1309
|
14359 if (this._displayStyle === "none") {
|
bsw/jbe@1309
|
14360 this._displayStyle = null;
|
bsw/jbe@1309
|
14361 }
|
bsw/jbe@1309
|
14362 this.editableArea.style.display = "none";
|
bsw/jbe@1309
|
14363 },
|
bsw/jbe@1309
|
14364
|
bsw/jbe@1309
|
14365 disable: function() {
|
bsw/jbe@1309
|
14366 this.parent.fire("disable:composer");
|
bsw/jbe@1309
|
14367 this.element.removeAttribute("contentEditable");
|
bsw/jbe@1309
|
14368 },
|
bsw/jbe@1309
|
14369
|
bsw/jbe@1309
|
14370 enable: function() {
|
bsw/jbe@1309
|
14371 this.parent.fire("enable:composer");
|
bsw/jbe@1309
|
14372 this.element.setAttribute("contentEditable", "true");
|
bsw/jbe@1309
|
14373 },
|
bsw/jbe@1309
|
14374
|
bsw/jbe@1309
|
14375 focus: function(setToEnd) {
|
bsw/jbe@1309
|
14376 // IE 8 fires the focus event after .focus()
|
bsw/jbe@1309
|
14377 // This is needed by our simulate_placeholder.js to work
|
bsw/jbe@1309
|
14378 // therefore we clear it ourselves this time
|
bsw/jbe@1309
|
14379 if (wysihtml.browser.doesAsyncFocus() && this.hasPlaceholderSet()) {
|
bsw/jbe@1309
|
14380 this.clear();
|
bsw/jbe@1309
|
14381 }
|
bsw/jbe@1309
|
14382
|
bsw/jbe@1309
|
14383 this.base();
|
bsw/jbe@1309
|
14384
|
bsw/jbe@1309
|
14385 var lastChild = this.element.lastChild;
|
bsw/jbe@1309
|
14386 if (setToEnd && lastChild && this.selection) {
|
bsw/jbe@1309
|
14387 if (lastChild.nodeName === "BR") {
|
bsw/jbe@1309
|
14388 this.selection.setBefore(this.element.lastChild);
|
bsw/jbe@1309
|
14389 } else {
|
bsw/jbe@1309
|
14390 this.selection.setAfter(this.element.lastChild);
|
bsw/jbe@1309
|
14391 }
|
bsw/jbe@1309
|
14392 }
|
bsw/jbe@1309
|
14393 },
|
bsw/jbe@1309
|
14394
|
bsw/jbe@1309
|
14395 getScrollPos: function() {
|
bsw/jbe@1309
|
14396 if (this.doc && this.win) {
|
bsw/jbe@1309
|
14397 var pos = {};
|
bsw/jbe@1309
|
14398
|
bsw/jbe@1309
|
14399 if (typeof this.win.pageYOffset !== "undefined") {
|
bsw/jbe@1309
|
14400 pos.y = this.win.pageYOffset;
|
bsw/jbe@1309
|
14401 } else {
|
bsw/jbe@1309
|
14402 pos.y = (this.doc.documentElement || this.doc.body.parentNode || this.doc.body).scrollTop;
|
bsw/jbe@1309
|
14403 }
|
bsw/jbe@1309
|
14404
|
bsw/jbe@1309
|
14405 if (typeof this.win.pageXOffset !== "undefined") {
|
bsw/jbe@1309
|
14406 pos.x = this.win.pageXOffset;
|
bsw/jbe@1309
|
14407 } else {
|
bsw/jbe@1309
|
14408 pos.x = (this.doc.documentElement || this.doc.body.parentNode || this.doc.body).scrollLeft;
|
bsw/jbe@1309
|
14409 }
|
bsw/jbe@1309
|
14410
|
bsw/jbe@1309
|
14411 return pos;
|
bsw/jbe@1309
|
14412 }
|
bsw/jbe@1309
|
14413 },
|
bsw/jbe@1309
|
14414
|
bsw/jbe@1309
|
14415 setScrollPos: function(pos) {
|
bsw/jbe@1309
|
14416 if (pos && typeof pos.x !== "undefined" && typeof pos.y !== "undefined") {
|
bsw/jbe@1309
|
14417 this.win.scrollTo(pos.x, pos.y);
|
bsw/jbe@1309
|
14418 }
|
bsw/jbe@1309
|
14419 },
|
bsw/jbe@1309
|
14420
|
bsw/jbe@1309
|
14421 getTextContent: function() {
|
bsw/jbe@1309
|
14422 return dom.getTextContent(this.element);
|
bsw/jbe@1309
|
14423 },
|
bsw/jbe@1309
|
14424
|
bsw/jbe@1309
|
14425 hasPlaceholderSet: function() {
|
bsw/jbe@1309
|
14426 return this.getTextContent() == ((this.config.noTextarea) ? this.editableArea.getAttribute("data-placeholder") : this.textarea.element.getAttribute("placeholder")) && this.placeholderSet;
|
bsw/jbe@1309
|
14427 },
|
bsw/jbe@1309
|
14428
|
bsw/jbe@1309
|
14429 isEmpty: function() {
|
bsw/jbe@1309
|
14430 var innerHTML = this.element.innerHTML.toLowerCase();
|
bsw/jbe@1309
|
14431 return (/^(\s|<br>|<\/br>|<p>|<\/p>)*$/i).test(innerHTML) ||
|
bsw/jbe@1309
|
14432 innerHTML === "" ||
|
bsw/jbe@1309
|
14433 innerHTML === "<br>" ||
|
bsw/jbe@1309
|
14434 innerHTML === "<p></p>" ||
|
bsw/jbe@1309
|
14435 innerHTML === "<p><br></p>" ||
|
bsw/jbe@1309
|
14436 this.hasPlaceholderSet();
|
bsw/jbe@1309
|
14437 },
|
bsw/jbe@1309
|
14438
|
bsw/jbe@1309
|
14439 _initContentEditableArea: function() {
|
bsw/jbe@1309
|
14440 var that = this;
|
bsw/jbe@1309
|
14441 if (this.config.noTextarea) {
|
bsw/jbe@1309
|
14442 this.sandbox = new dom.ContentEditableArea(function() {
|
bsw/jbe@1309
|
14443 that._create();
|
bsw/jbe@1309
|
14444 }, {
|
bsw/jbe@1309
|
14445 className: this.config.classNames.sandbox
|
bsw/jbe@1309
|
14446 }, this.editableArea);
|
bsw/jbe@1309
|
14447 } else {
|
bsw/jbe@1309
|
14448 this.sandbox = new dom.ContentEditableArea(function() {
|
bsw/jbe@1309
|
14449 that._create();
|
bsw/jbe@1309
|
14450 }, {
|
bsw/jbe@1309
|
14451 className: this.config.classNames.sandbox
|
bsw/jbe@1309
|
14452 });
|
bsw/jbe@1309
|
14453 this.editableArea = this.sandbox.getContentEditable();
|
bsw/jbe@1309
|
14454 dom.insert(this.editableArea).after(this.textarea.element);
|
bsw/jbe@1309
|
14455 this._createWysiwygFormField();
|
bsw/jbe@1309
|
14456 }
|
bsw/jbe@1309
|
14457 },
|
bsw/jbe@1309
|
14458
|
bsw/jbe@1309
|
14459 _initSandbox: function() {
|
bsw/jbe@1309
|
14460 var that = this;
|
bsw/jbe@1309
|
14461 this.sandbox = new dom.Sandbox(function() {
|
bsw/jbe@1309
|
14462 that._create();
|
bsw/jbe@1309
|
14463 }, {
|
bsw/jbe@1309
|
14464 stylesheets: this.config.stylesheets,
|
bsw/jbe@1309
|
14465 className: this.config.classNames.sandbox
|
bsw/jbe@1309
|
14466 });
|
bsw/jbe@1309
|
14467 this.editableArea = this.sandbox.getIframe();
|
bsw/jbe@1309
|
14468
|
bsw/jbe@1309
|
14469 var textareaElement = this.textarea.element;
|
bsw/jbe@1309
|
14470 dom.insert(this.editableArea).after(textareaElement);
|
bsw/jbe@1309
|
14471
|
bsw/jbe@1309
|
14472 this._createWysiwygFormField();
|
bsw/jbe@1309
|
14473 },
|
bsw/jbe@1309
|
14474
|
bsw/jbe@1309
|
14475 // Creates hidden field which tells the server after submit, that the user used an wysiwyg editor
|
bsw/jbe@1309
|
14476 _createWysiwygFormField: function() {
|
bsw/jbe@1309
|
14477 if (this.textarea.element.form) {
|
bsw/jbe@1309
|
14478 var hiddenField = document.createElement("input");
|
bsw/jbe@1309
|
14479 hiddenField.type = "hidden";
|
bsw/jbe@1309
|
14480 hiddenField.name = "_wysihtml_mode";
|
bsw/jbe@1309
|
14481 hiddenField.value = 1;
|
bsw/jbe@1309
|
14482 dom.insert(hiddenField).after(this.textarea.element);
|
bsw/jbe@1309
|
14483 }
|
bsw/jbe@1309
|
14484 },
|
bsw/jbe@1309
|
14485
|
bsw/jbe@1309
|
14486 _create: function() {
|
bsw/jbe@1309
|
14487 var that = this;
|
bsw/jbe@1309
|
14488 this.doc = this.sandbox.getDocument();
|
bsw/jbe@1309
|
14489 this.win = this.sandbox.getWindow();
|
bsw/jbe@1309
|
14490 this.element = (this.config.contentEditableMode) ? this.sandbox.getContentEditable() : this.doc.body;
|
bsw/jbe@1309
|
14491 if (!this.config.noTextarea) {
|
bsw/jbe@1309
|
14492 this.textarea = this.parent.textarea;
|
bsw/jbe@1309
|
14493 this.element.innerHTML = this.textarea.getValue(true, false);
|
bsw/jbe@1309
|
14494 } else {
|
bsw/jbe@1309
|
14495 this.cleanUp(); // cleans contenteditable on initiation as it may contain html
|
bsw/jbe@1309
|
14496 }
|
bsw/jbe@1309
|
14497
|
bsw/jbe@1309
|
14498 // Make sure our selection handler is ready
|
bsw/jbe@1309
|
14499 this.selection = new wysihtml.Selection(this.parent, this.element, this.config.classNames.uneditableContainer);
|
bsw/jbe@1309
|
14500
|
bsw/jbe@1309
|
14501 // Make sure commands dispatcher is ready
|
bsw/jbe@1309
|
14502 this.commands = new wysihtml.Commands(this.parent);
|
bsw/jbe@1309
|
14503
|
bsw/jbe@1309
|
14504 if (!this.config.noTextarea) {
|
bsw/jbe@1309
|
14505 dom.copyAttributes([
|
bsw/jbe@1309
|
14506 "className", "spellcheck", "title", "lang", "dir", "accessKey"
|
bsw/jbe@1309
|
14507 ]).from(this.textarea.element).to(this.element);
|
bsw/jbe@1309
|
14508 }
|
bsw/jbe@1309
|
14509
|
bsw/jbe@1309
|
14510 this._initAutoLinking();
|
bsw/jbe@1309
|
14511
|
bsw/jbe@1309
|
14512 dom.addClass(this.element, this.config.classNames.composer);
|
bsw/jbe@1309
|
14513 //
|
bsw/jbe@1309
|
14514 // Make the editor look like the original textarea, by syncing styles
|
bsw/jbe@1309
|
14515 if (this.config.style && !this.config.contentEditableMode) {
|
bsw/jbe@1309
|
14516 this.style();
|
bsw/jbe@1309
|
14517 }
|
bsw/jbe@1309
|
14518
|
bsw/jbe@1309
|
14519 this.observe();
|
bsw/jbe@1309
|
14520
|
bsw/jbe@1309
|
14521 var name = this.config.name;
|
bsw/jbe@1309
|
14522 if (name) {
|
bsw/jbe@1309
|
14523 dom.addClass(this.element, name);
|
bsw/jbe@1309
|
14524 if (!this.config.contentEditableMode) { dom.addClass(this.editableArea, name); }
|
bsw/jbe@1309
|
14525 }
|
bsw/jbe@1309
|
14526
|
bsw/jbe@1309
|
14527 this.enable();
|
bsw/jbe@1309
|
14528
|
bsw/jbe@1309
|
14529 if (!this.config.noTextarea && this.textarea.element.disabled) {
|
bsw/jbe@1309
|
14530 this.disable();
|
bsw/jbe@1309
|
14531 }
|
bsw/jbe@1309
|
14532
|
bsw/jbe@1309
|
14533 // Simulate html5 placeholder attribute on contentEditable element
|
bsw/jbe@1309
|
14534 var placeholderText = typeof(this.config.placeholder) === "string"
|
bsw/jbe@1309
|
14535 ? this.config.placeholder
|
bsw/jbe@1309
|
14536 : ((this.config.noTextarea) ? this.editableArea.getAttribute("data-placeholder") : this.textarea.element.getAttribute("placeholder"));
|
bsw/jbe@1309
|
14537 if (placeholderText) {
|
bsw/jbe@1309
|
14538 dom.simulatePlaceholder(this.parent, this, placeholderText, this.config.classNames.placeholder);
|
bsw/jbe@1309
|
14539 }
|
bsw/jbe@1309
|
14540
|
bsw/jbe@1309
|
14541 // Make sure that the browser avoids using inline styles whenever possible
|
bsw/jbe@1309
|
14542 this.commands.exec("styleWithCSS", false);
|
bsw/jbe@1309
|
14543
|
bsw/jbe@1309
|
14544 this._initObjectResizing();
|
bsw/jbe@1309
|
14545 this._initUndoManager();
|
bsw/jbe@1309
|
14546 this._initLineBreaking();
|
bsw/jbe@1309
|
14547
|
bsw/jbe@1309
|
14548 // Simulate html5 autofocus on contentEditable element
|
bsw/jbe@1309
|
14549 // This doesn't work on IOS (5.1.1)
|
bsw/jbe@1309
|
14550 if (!this.config.noTextarea && (this.textarea.element.hasAttribute("autofocus") || document.querySelector(":focus") == this.textarea.element) && !browser.isIos()) {
|
bsw/jbe@1309
|
14551 setTimeout(function() { that.focus(true); }, 100);
|
bsw/jbe@1309
|
14552 }
|
bsw/jbe@1309
|
14553
|
bsw/jbe@1309
|
14554 // IE sometimes leaves a single paragraph, which can't be removed by the user
|
bsw/jbe@1309
|
14555 if (!browser.clearsContentEditableCorrectly()) {
|
bsw/jbe@1309
|
14556 wysihtml.quirks.ensureProperClearing(this);
|
bsw/jbe@1309
|
14557 }
|
bsw/jbe@1309
|
14558
|
bsw/jbe@1309
|
14559 // Set up a sync that makes sure that textarea and editor have the same content
|
bsw/jbe@1309
|
14560 if (this.initSync && this.config.sync) {
|
bsw/jbe@1309
|
14561 this.initSync();
|
bsw/jbe@1309
|
14562 }
|
bsw/jbe@1309
|
14563
|
bsw/jbe@1309
|
14564 // Okay hide the textarea, we are ready to go
|
bsw/jbe@1309
|
14565 if (!this.config.noTextarea) { this.textarea.hide(); }
|
bsw/jbe@1309
|
14566
|
bsw/jbe@1309
|
14567 // Fire global (before-)load event
|
bsw/jbe@1309
|
14568 this.parent.fire("beforeload").fire("load");
|
bsw/jbe@1309
|
14569 },
|
bsw/jbe@1309
|
14570
|
bsw/jbe@1309
|
14571 _initAutoLinking: function() {
|
bsw/jbe@1309
|
14572 var that = this,
|
bsw/jbe@1309
|
14573 supportsDisablingOfAutoLinking = browser.canDisableAutoLinking(),
|
bsw/jbe@1309
|
14574 supportsAutoLinking = browser.doesAutoLinkingInContentEditable();
|
bsw/jbe@1309
|
14575
|
bsw/jbe@1309
|
14576 if (supportsDisablingOfAutoLinking) {
|
bsw/jbe@1309
|
14577 this.commands.exec("AutoUrlDetect", false, false);
|
bsw/jbe@1309
|
14578 }
|
bsw/jbe@1309
|
14579
|
bsw/jbe@1309
|
14580 if (!this.config.autoLink) {
|
bsw/jbe@1309
|
14581 return;
|
bsw/jbe@1309
|
14582 }
|
bsw/jbe@1309
|
14583
|
bsw/jbe@1309
|
14584 // Only do the auto linking by ourselves when the browser doesn't support auto linking
|
bsw/jbe@1309
|
14585 // OR when he supports auto linking but we were able to turn it off (IE9+)
|
bsw/jbe@1309
|
14586 if (!supportsAutoLinking || (supportsAutoLinking && supportsDisablingOfAutoLinking)) {
|
bsw/jbe@1309
|
14587 this.parent.on("newword:composer", function() {
|
bsw/jbe@1309
|
14588 if (dom.getTextContent(that.element).match(dom.autoLink.URL_REG_EXP)) {
|
bsw/jbe@1309
|
14589 var nodeWithSelection = that.selection.getSelectedNode(),
|
bsw/jbe@1309
|
14590 uneditables = that.element.querySelectorAll("." + that.config.classNames.uneditableContainer),
|
bsw/jbe@1309
|
14591 isInUneditable = false;
|
bsw/jbe@1309
|
14592
|
bsw/jbe@1309
|
14593 for (var i = uneditables.length; i--;) {
|
bsw/jbe@1309
|
14594 if (wysihtml.dom.contains(uneditables[i], nodeWithSelection)) {
|
bsw/jbe@1309
|
14595 isInUneditable = true;
|
bsw/jbe@1309
|
14596 }
|
bsw/jbe@1309
|
14597 }
|
bsw/jbe@1309
|
14598
|
bsw/jbe@1309
|
14599 if (!isInUneditable) dom.autoLink(nodeWithSelection, [that.config.classNames.uneditableContainer]);
|
bsw/jbe@1309
|
14600 }
|
bsw/jbe@1309
|
14601 });
|
bsw/jbe@1309
|
14602
|
bsw/jbe@1309
|
14603 dom.observe(this.element, "blur", function() {
|
bsw/jbe@1309
|
14604 dom.autoLink(that.element, [that.config.classNames.uneditableContainer]);
|
bsw/jbe@1309
|
14605 });
|
bsw/jbe@1309
|
14606 }
|
bsw/jbe@1309
|
14607
|
bsw/jbe@1309
|
14608 // Assuming we have the following:
|
bsw/jbe@1309
|
14609 // <a href="http://www.google.de">http://www.google.de</a>
|
bsw/jbe@1309
|
14610 // If a user now changes the url in the innerHTML we want to make sure that
|
bsw/jbe@1309
|
14611 // it's synchronized with the href attribute (as long as the innerHTML is still a url)
|
bsw/jbe@1309
|
14612 var // Use a live NodeList to check whether there are any links in the document
|
bsw/jbe@1309
|
14613 links = this.sandbox.getDocument().getElementsByTagName("a"),
|
bsw/jbe@1309
|
14614 // The autoLink helper method reveals a reg exp to detect correct urls
|
bsw/jbe@1309
|
14615 urlRegExp = dom.autoLink.URL_REG_EXP,
|
bsw/jbe@1309
|
14616 getTextContent = function(element) {
|
bsw/jbe@1309
|
14617 var textContent = wysihtml.lang.string(dom.getTextContent(element)).trim();
|
bsw/jbe@1309
|
14618 if (textContent.substr(0, 4) === "www.") {
|
bsw/jbe@1309
|
14619 textContent = "http://" + textContent;
|
bsw/jbe@1309
|
14620 }
|
bsw/jbe@1309
|
14621 return textContent;
|
bsw/jbe@1309
|
14622 };
|
bsw/jbe@1309
|
14623
|
bsw/jbe@1309
|
14624 dom.observe(this.element, "keydown", function(event) {
|
bsw/jbe@1309
|
14625 if (!links.length) {
|
bsw/jbe@1309
|
14626 return;
|
bsw/jbe@1309
|
14627 }
|
bsw/jbe@1309
|
14628
|
bsw/jbe@1309
|
14629 var selectedNode = that.selection.getSelectedNode(event.target.ownerDocument),
|
bsw/jbe@1309
|
14630 link = dom.getParentElement(selectedNode, { query: "a" }, 4),
|
bsw/jbe@1309
|
14631 textContent;
|
bsw/jbe@1309
|
14632
|
bsw/jbe@1309
|
14633 if (!link) {
|
bsw/jbe@1309
|
14634 return;
|
bsw/jbe@1309
|
14635 }
|
bsw/jbe@1309
|
14636
|
bsw/jbe@1309
|
14637 textContent = getTextContent(link);
|
bsw/jbe@1309
|
14638 // keydown is fired before the actual content is changed
|
bsw/jbe@1309
|
14639 // therefore we set a timeout to change the href
|
bsw/jbe@1309
|
14640 setTimeout(function() {
|
bsw/jbe@1309
|
14641 var newTextContent = getTextContent(link);
|
bsw/jbe@1309
|
14642 if (newTextContent === textContent) {
|
bsw/jbe@1309
|
14643 return;
|
bsw/jbe@1309
|
14644 }
|
bsw/jbe@1309
|
14645
|
bsw/jbe@1309
|
14646 // Only set href when new href looks like a valid url
|
bsw/jbe@1309
|
14647 if (newTextContent.match(urlRegExp)) {
|
bsw/jbe@1309
|
14648 link.setAttribute("href", newTextContent);
|
bsw/jbe@1309
|
14649 }
|
bsw/jbe@1309
|
14650 }, 0);
|
bsw/jbe@1309
|
14651 });
|
bsw/jbe@1309
|
14652 },
|
bsw/jbe@1309
|
14653
|
bsw/jbe@1309
|
14654 _initObjectResizing: function() {
|
bsw/jbe@1309
|
14655 this.commands.exec("enableObjectResizing", true);
|
bsw/jbe@1309
|
14656
|
bsw/jbe@1309
|
14657 // IE sets inline styles after resizing objects
|
bsw/jbe@1309
|
14658 // The following lines make sure that the width/height css properties
|
bsw/jbe@1309
|
14659 // are copied over to the width/height attributes
|
bsw/jbe@1309
|
14660 if (browser.supportsEvent("resizeend")) {
|
bsw/jbe@1309
|
14661 var properties = ["width", "height"],
|
bsw/jbe@1309
|
14662 propertiesLength = properties.length,
|
bsw/jbe@1309
|
14663 element = this.element;
|
bsw/jbe@1309
|
14664
|
bsw/jbe@1309
|
14665 dom.observe(element, "resizeend", function(event) {
|
bsw/jbe@1309
|
14666 var target = event.target || event.srcElement,
|
bsw/jbe@1309
|
14667 style = target.style,
|
bsw/jbe@1309
|
14668 i = 0,
|
bsw/jbe@1309
|
14669 property;
|
bsw/jbe@1309
|
14670
|
bsw/jbe@1309
|
14671 if (target.nodeName !== "IMG") {
|
bsw/jbe@1309
|
14672 return;
|
bsw/jbe@1309
|
14673 }
|
bsw/jbe@1309
|
14674
|
bsw/jbe@1309
|
14675 for (; i<propertiesLength; i++) {
|
bsw/jbe@1309
|
14676 property = properties[i];
|
bsw/jbe@1309
|
14677 if (style[property]) {
|
bsw/jbe@1309
|
14678 target.setAttribute(property, parseInt(style[property], 10));
|
bsw/jbe@1309
|
14679 style[property] = "";
|
bsw/jbe@1309
|
14680 }
|
bsw/jbe@1309
|
14681 }
|
bsw/jbe@1309
|
14682
|
bsw/jbe@1309
|
14683 // After resizing IE sometimes forgets to remove the old resize handles
|
bsw/jbe@1309
|
14684 wysihtml.quirks.redraw(element);
|
bsw/jbe@1309
|
14685 });
|
bsw/jbe@1309
|
14686 }
|
bsw/jbe@1309
|
14687 },
|
bsw/jbe@1309
|
14688
|
bsw/jbe@1309
|
14689 _initUndoManager: function() {
|
bsw/jbe@1309
|
14690 this.undoManager = new wysihtml.UndoManager(this.parent);
|
bsw/jbe@1309
|
14691 },
|
bsw/jbe@1309
|
14692
|
bsw/jbe@1309
|
14693 _initLineBreaking: function() {
|
bsw/jbe@1309
|
14694 var that = this,
|
bsw/jbe@1309
|
14695 USE_NATIVE_LINE_BREAK_INSIDE_TAGS = "li, p, h1, h2, h3, h4, h5, h6",
|
bsw/jbe@1309
|
14696 LIST_TAGS = "ul, ol, menu";
|
bsw/jbe@1309
|
14697
|
bsw/jbe@1309
|
14698 function adjust(selectedNode) {
|
bsw/jbe@1309
|
14699 var parentElement = dom.getParentElement(selectedNode, { query: "p, div" }, 2);
|
bsw/jbe@1309
|
14700 if (parentElement && dom.contains(that.element, parentElement)) {
|
bsw/jbe@1309
|
14701 that.selection.executeAndRestoreRangy(function() {
|
bsw/jbe@1309
|
14702 if (that.config.useLineBreaks) {
|
bsw/jbe@1309
|
14703 if (!parentElement.firstChild || (parentElement.firstChild === parentElement.lastChild && parentElement.firstChild.nodeType === 1 && parentElement.firstChild.classList.contains('rangySelectionBoundary'))) {
|
bsw/jbe@1309
|
14704 parentElement.appendChild(that.doc.createElement('br'));
|
bsw/jbe@1309
|
14705 }
|
bsw/jbe@1309
|
14706 dom.replaceWithChildNodes(parentElement);
|
bsw/jbe@1309
|
14707 } else if (parentElement.nodeName !== "P") {
|
bsw/jbe@1309
|
14708 dom.renameElement(parentElement, "p");
|
bsw/jbe@1309
|
14709 }
|
bsw/jbe@1309
|
14710 });
|
bsw/jbe@1309
|
14711 }
|
bsw/jbe@1309
|
14712 }
|
bsw/jbe@1309
|
14713
|
bsw/jbe@1309
|
14714 // Ensures when editor is empty and not line breaks mode, the inital state has a paragraph in it on focus with caret inside paragraph
|
bsw/jbe@1309
|
14715 if (!this.config.useLineBreaks) {
|
bsw/jbe@1309
|
14716 dom.observe(this.element, ["focus"], function() {
|
bsw/jbe@1309
|
14717 if (that.isEmpty()) {
|
bsw/jbe@1309
|
14718 setTimeout(function() {
|
bsw/jbe@1309
|
14719 var paragraph = that.doc.createElement("P");
|
bsw/jbe@1309
|
14720 that.element.innerHTML = "";
|
bsw/jbe@1309
|
14721 that.element.appendChild(paragraph);
|
bsw/jbe@1309
|
14722 if (!browser.displaysCaretInEmptyContentEditableCorrectly()) {
|
bsw/jbe@1309
|
14723 paragraph.innerHTML = "<br>";
|
bsw/jbe@1309
|
14724 that.selection.setBefore(paragraph.firstChild);
|
bsw/jbe@1309
|
14725 } else {
|
bsw/jbe@1309
|
14726 that.selection.selectNode(paragraph, true);
|
bsw/jbe@1309
|
14727 }
|
bsw/jbe@1309
|
14728 }, 0);
|
bsw/jbe@1309
|
14729 }
|
bsw/jbe@1309
|
14730 });
|
bsw/jbe@1309
|
14731 }
|
bsw/jbe@1309
|
14732
|
bsw/jbe@1309
|
14733 dom.observe(this.element, "keydown", function(event) {
|
bsw/jbe@1309
|
14734 var keyCode = event.keyCode;
|
bsw/jbe@1309
|
14735
|
bsw/jbe@1309
|
14736 if (event.shiftKey || event.ctrlKey || event.defaultPrevented) {
|
bsw/jbe@1309
|
14737 return;
|
bsw/jbe@1309
|
14738 }
|
bsw/jbe@1309
|
14739
|
bsw/jbe@1309
|
14740 if (keyCode !== wysihtml.ENTER_KEY && keyCode !== wysihtml.BACKSPACE_KEY) {
|
bsw/jbe@1309
|
14741 return;
|
bsw/jbe@1309
|
14742 }
|
bsw/jbe@1309
|
14743 var blockElement = dom.getParentElement(that.selection.getSelectedNode(), { query: USE_NATIVE_LINE_BREAK_INSIDE_TAGS }, 4);
|
bsw/jbe@1309
|
14744 if (blockElement) {
|
bsw/jbe@1309
|
14745 setTimeout(function() {
|
bsw/jbe@1309
|
14746 // Unwrap paragraph after leaving a list or a H1-6
|
bsw/jbe@1309
|
14747 var selectedNode = that.selection.getSelectedNode(),
|
bsw/jbe@1309
|
14748 list;
|
bsw/jbe@1309
|
14749
|
bsw/jbe@1309
|
14750 if (blockElement.nodeName === "LI") {
|
bsw/jbe@1309
|
14751 if (!selectedNode) {
|
bsw/jbe@1309
|
14752 return;
|
bsw/jbe@1309
|
14753 }
|
bsw/jbe@1309
|
14754
|
bsw/jbe@1309
|
14755 list = dom.getParentElement(selectedNode, { query: LIST_TAGS }, 2);
|
bsw/jbe@1309
|
14756
|
bsw/jbe@1309
|
14757 if (!list) {
|
bsw/jbe@1309
|
14758 adjust(selectedNode);
|
bsw/jbe@1309
|
14759 }
|
bsw/jbe@1309
|
14760 }
|
bsw/jbe@1309
|
14761
|
bsw/jbe@1309
|
14762 if (keyCode === wysihtml.ENTER_KEY && blockElement.nodeName.match(/^H[1-6]$/)) {
|
bsw/jbe@1309
|
14763 adjust(selectedNode);
|
bsw/jbe@1309
|
14764 }
|
bsw/jbe@1309
|
14765 }, 0);
|
bsw/jbe@1309
|
14766 return;
|
bsw/jbe@1309
|
14767 }
|
bsw/jbe@1309
|
14768 if (that.config.useLineBreaks && keyCode === wysihtml.ENTER_KEY && !wysihtml.browser.insertsLineBreaksOnReturn()) {
|
bsw/jbe@1309
|
14769 event.preventDefault();
|
bsw/jbe@1309
|
14770 that.commands.exec("insertLineBreak");
|
bsw/jbe@1309
|
14771 }
|
bsw/jbe@1309
|
14772 });
|
bsw/jbe@1309
|
14773 }
|
bsw/jbe@1309
|
14774 });
|
bsw/jbe@1309
|
14775 })(wysihtml);
|
bsw/jbe@1309
|
14776
|
bsw/jbe@1309
|
14777 (function(wysihtml) {
|
bsw/jbe@1309
|
14778 var dom = wysihtml.dom,
|
bsw/jbe@1309
|
14779 doc = document,
|
bsw/jbe@1309
|
14780 win = window,
|
bsw/jbe@1309
|
14781 HOST_TEMPLATE = doc.createElement("div"),
|
bsw/jbe@1309
|
14782 /**
|
bsw/jbe@1309
|
14783 * Styles to copy from textarea to the composer element
|
bsw/jbe@1309
|
14784 */
|
bsw/jbe@1309
|
14785 TEXT_FORMATTING = [
|
bsw/jbe@1309
|
14786 "background-color",
|
bsw/jbe@1309
|
14787 "color", "cursor",
|
bsw/jbe@1309
|
14788 "font-family", "font-size", "font-style", "font-variant", "font-weight",
|
bsw/jbe@1309
|
14789 "line-height", "letter-spacing",
|
bsw/jbe@1309
|
14790 "text-align", "text-decoration", "text-indent", "text-rendering",
|
bsw/jbe@1309
|
14791 "word-break", "word-wrap", "word-spacing"
|
bsw/jbe@1309
|
14792 ],
|
bsw/jbe@1309
|
14793 /**
|
bsw/jbe@1309
|
14794 * Styles to copy from textarea to the iframe
|
bsw/jbe@1309
|
14795 */
|
bsw/jbe@1309
|
14796 BOX_FORMATTING = [
|
bsw/jbe@1309
|
14797 "background-color",
|
bsw/jbe@1309
|
14798 "border-collapse",
|
bsw/jbe@1309
|
14799 "border-bottom-color", "border-bottom-style", "border-bottom-width",
|
bsw/jbe@1309
|
14800 "border-left-color", "border-left-style", "border-left-width",
|
bsw/jbe@1309
|
14801 "border-right-color", "border-right-style", "border-right-width",
|
bsw/jbe@1309
|
14802 "border-top-color", "border-top-style", "border-top-width",
|
bsw/jbe@1309
|
14803 "clear", "display", "float",
|
bsw/jbe@1309
|
14804 "margin-bottom", "margin-left", "margin-right", "margin-top",
|
bsw/jbe@1309
|
14805 "outline-color", "outline-offset", "outline-width", "outline-style",
|
bsw/jbe@1309
|
14806 "padding-left", "padding-right", "padding-top", "padding-bottom",
|
bsw/jbe@1309
|
14807 "position", "top", "left", "right", "bottom", "z-index",
|
bsw/jbe@1309
|
14808 "vertical-align", "text-align",
|
bsw/jbe@1309
|
14809 "-webkit-box-sizing", "-moz-box-sizing", "-ms-box-sizing", "box-sizing",
|
bsw/jbe@1309
|
14810 "-webkit-box-shadow", "-moz-box-shadow", "-ms-box-shadow","box-shadow",
|
bsw/jbe@1309
|
14811 "-webkit-border-top-right-radius", "-moz-border-radius-topright", "border-top-right-radius",
|
bsw/jbe@1309
|
14812 "-webkit-border-bottom-right-radius", "-moz-border-radius-bottomright", "border-bottom-right-radius",
|
bsw/jbe@1309
|
14813 "-webkit-border-bottom-left-radius", "-moz-border-radius-bottomleft", "border-bottom-left-radius",
|
bsw/jbe@1309
|
14814 "-webkit-border-top-left-radius", "-moz-border-radius-topleft", "border-top-left-radius",
|
bsw/jbe@1309
|
14815 "width", "height"
|
bsw/jbe@1309
|
14816 ],
|
bsw/jbe@1309
|
14817 ADDITIONAL_CSS_RULES = [
|
bsw/jbe@1309
|
14818 "html { height: 100%; }",
|
bsw/jbe@1309
|
14819 "body { height: 100%; padding: 1px 0 0 0; margin: -1px 0 0 0; }",
|
bsw/jbe@1309
|
14820 "body > p:first-child { margin-top: 0; }",
|
bsw/jbe@1309
|
14821 "._wysihtml-temp { display: none; }",
|
bsw/jbe@1309
|
14822 wysihtml.browser.isGecko ?
|
bsw/jbe@1309
|
14823 "body.placeholder { color: graytext !important; }" :
|
bsw/jbe@1309
|
14824 "body.placeholder { color: #a9a9a9 !important; }",
|
bsw/jbe@1309
|
14825 // Ensure that user see's broken images and can delete them
|
bsw/jbe@1309
|
14826 "img:-moz-broken { -moz-force-broken-image-icon: 1; height: 24px; width: 24px; }"
|
bsw/jbe@1309
|
14827 ];
|
bsw/jbe@1309
|
14828
|
bsw/jbe@1309
|
14829 /**
|
bsw/jbe@1309
|
14830 * With "setActive" IE offers a smart way of focusing elements without scrolling them into view:
|
bsw/jbe@1309
|
14831 * http://msdn.microsoft.com/en-us/library/ms536738(v=vs.85).aspx
|
bsw/jbe@1309
|
14832 *
|
bsw/jbe@1309
|
14833 * Other browsers need a more hacky way: (pssst don't tell my mama)
|
bsw/jbe@1309
|
14834 * In order to prevent the element being scrolled into view when focusing it, we simply
|
bsw/jbe@1309
|
14835 * move it out of the scrollable area, focus it, and reset it's position
|
bsw/jbe@1309
|
14836 */
|
bsw/jbe@1309
|
14837 var focusWithoutScrolling = function(element) {
|
bsw/jbe@1309
|
14838 if (element.setActive) {
|
bsw/jbe@1309
|
14839 // Following line could cause a js error when the textarea is invisible
|
bsw/jbe@1309
|
14840 // See https://github.com/xing/wysihtml5/issues/9
|
bsw/jbe@1309
|
14841 try { element.setActive(); } catch(e) {}
|
bsw/jbe@1309
|
14842 } else {
|
bsw/jbe@1309
|
14843 var elementStyle = element.style,
|
bsw/jbe@1309
|
14844 originalScrollTop = doc.documentElement.scrollTop || doc.body.scrollTop,
|
bsw/jbe@1309
|
14845 originalScrollLeft = doc.documentElement.scrollLeft || doc.body.scrollLeft,
|
bsw/jbe@1309
|
14846 originalStyles = {
|
bsw/jbe@1309
|
14847 position: elementStyle.position,
|
bsw/jbe@1309
|
14848 top: elementStyle.top,
|
bsw/jbe@1309
|
14849 left: elementStyle.left,
|
bsw/jbe@1309
|
14850 WebkitUserSelect: elementStyle.WebkitUserSelect
|
bsw/jbe@1309
|
14851 };
|
bsw/jbe@1309
|
14852
|
bsw/jbe@1309
|
14853 dom.setStyles({
|
bsw/jbe@1309
|
14854 position: "absolute",
|
bsw/jbe@1309
|
14855 top: "-99999px",
|
bsw/jbe@1309
|
14856 left: "-99999px",
|
bsw/jbe@1309
|
14857 // Don't ask why but temporarily setting -webkit-user-select to none makes the whole thing performing smoother
|
bsw/jbe@1309
|
14858 WebkitUserSelect: "none"
|
bsw/jbe@1309
|
14859 }).on(element);
|
bsw/jbe@1309
|
14860
|
bsw/jbe@1309
|
14861 element.focus();
|
bsw/jbe@1309
|
14862
|
bsw/jbe@1309
|
14863 dom.setStyles(originalStyles).on(element);
|
bsw/jbe@1309
|
14864
|
bsw/jbe@1309
|
14865 if (win.scrollTo) {
|
bsw/jbe@1309
|
14866 // Some browser extensions unset this method to prevent annoyances
|
bsw/jbe@1309
|
14867 // "Better PopUp Blocker" for Chrome http://code.google.com/p/betterpopupblocker/source/browse/trunk/blockStart.js#100
|
bsw/jbe@1309
|
14868 // Issue: http://code.google.com/p/betterpopupblocker/issues/detail?id=1
|
bsw/jbe@1309
|
14869 win.scrollTo(originalScrollLeft, originalScrollTop);
|
bsw/jbe@1309
|
14870 }
|
bsw/jbe@1309
|
14871 }
|
bsw/jbe@1309
|
14872 };
|
bsw/jbe@1309
|
14873
|
bsw/jbe@1309
|
14874
|
bsw/jbe@1309
|
14875 wysihtml.views.Composer.prototype.style = function() {
|
bsw/jbe@1309
|
14876 var that = this,
|
bsw/jbe@1309
|
14877 originalActiveElement = doc.querySelector(":focus"),
|
bsw/jbe@1309
|
14878 textareaElement = this.textarea.element,
|
bsw/jbe@1309
|
14879 hasPlaceholder = textareaElement.hasAttribute("placeholder"),
|
bsw/jbe@1309
|
14880 originalPlaceholder = hasPlaceholder && textareaElement.getAttribute("placeholder"),
|
bsw/jbe@1309
|
14881 originalDisplayValue = textareaElement.style.display,
|
bsw/jbe@1309
|
14882 originalDisabled = textareaElement.disabled,
|
bsw/jbe@1309
|
14883 displayValueForCopying;
|
bsw/jbe@1309
|
14884
|
bsw/jbe@1309
|
14885 this.focusStylesHost = HOST_TEMPLATE.cloneNode(false);
|
bsw/jbe@1309
|
14886 this.blurStylesHost = HOST_TEMPLATE.cloneNode(false);
|
bsw/jbe@1309
|
14887 this.disabledStylesHost = HOST_TEMPLATE.cloneNode(false);
|
bsw/jbe@1309
|
14888
|
bsw/jbe@1309
|
14889 // Remove placeholder before copying (as the placeholder has an affect on the computed style)
|
bsw/jbe@1309
|
14890 if (hasPlaceholder) {
|
bsw/jbe@1309
|
14891 textareaElement.removeAttribute("placeholder");
|
bsw/jbe@1309
|
14892 }
|
bsw/jbe@1309
|
14893
|
bsw/jbe@1309
|
14894 if (textareaElement === originalActiveElement) {
|
bsw/jbe@1309
|
14895 textareaElement.blur();
|
bsw/jbe@1309
|
14896 }
|
bsw/jbe@1309
|
14897
|
bsw/jbe@1309
|
14898 // enable for copying styles
|
bsw/jbe@1309
|
14899 textareaElement.disabled = false;
|
bsw/jbe@1309
|
14900
|
bsw/jbe@1309
|
14901 // set textarea to display="none" to get cascaded styles via getComputedStyle
|
bsw/jbe@1309
|
14902 textareaElement.style.display = displayValueForCopying = "none";
|
bsw/jbe@1309
|
14903
|
bsw/jbe@1309
|
14904 if ((textareaElement.getAttribute("rows") && dom.getStyle("height").from(textareaElement) === "auto") ||
|
bsw/jbe@1309
|
14905 (textareaElement.getAttribute("cols") && dom.getStyle("width").from(textareaElement) === "auto")) {
|
bsw/jbe@1309
|
14906 textareaElement.style.display = displayValueForCopying = originalDisplayValue;
|
bsw/jbe@1309
|
14907 }
|
bsw/jbe@1309
|
14908
|
bsw/jbe@1309
|
14909 // --------- iframe styles (has to be set before editor styles, otherwise IE9 sets wrong fontFamily on blurStylesHost) ---------
|
bsw/jbe@1309
|
14910 dom.copyStyles(BOX_FORMATTING).from(textareaElement).to(this.editableArea).andTo(this.blurStylesHost);
|
bsw/jbe@1309
|
14911
|
bsw/jbe@1309
|
14912 // --------- editor styles ---------
|
bsw/jbe@1309
|
14913 dom.copyStyles(TEXT_FORMATTING).from(textareaElement).to(this.element).andTo(this.blurStylesHost);
|
bsw/jbe@1309
|
14914
|
bsw/jbe@1309
|
14915 // --------- apply standard rules ---------
|
bsw/jbe@1309
|
14916 dom.insertCSS(ADDITIONAL_CSS_RULES).into(this.element.ownerDocument);
|
bsw/jbe@1309
|
14917
|
bsw/jbe@1309
|
14918 // --------- :disabled styles ---------
|
bsw/jbe@1309
|
14919 textareaElement.disabled = true;
|
bsw/jbe@1309
|
14920 dom.copyStyles(BOX_FORMATTING).from(textareaElement).to(this.disabledStylesHost);
|
bsw/jbe@1309
|
14921 dom.copyStyles(TEXT_FORMATTING).from(textareaElement).to(this.disabledStylesHost);
|
bsw/jbe@1309
|
14922 textareaElement.disabled = originalDisabled;
|
bsw/jbe@1309
|
14923
|
bsw/jbe@1309
|
14924 // --------- :focus styles ---------
|
bsw/jbe@1309
|
14925 textareaElement.style.display = originalDisplayValue;
|
bsw/jbe@1309
|
14926 focusWithoutScrolling(textareaElement);
|
bsw/jbe@1309
|
14927 textareaElement.style.display = displayValueForCopying;
|
bsw/jbe@1309
|
14928
|
bsw/jbe@1309
|
14929 dom.copyStyles(BOX_FORMATTING).from(textareaElement).to(this.focusStylesHost);
|
bsw/jbe@1309
|
14930 dom.copyStyles(TEXT_FORMATTING).from(textareaElement).to(this.focusStylesHost);
|
bsw/jbe@1309
|
14931
|
bsw/jbe@1309
|
14932 // reset textarea
|
bsw/jbe@1309
|
14933 textareaElement.style.display = originalDisplayValue;
|
bsw/jbe@1309
|
14934
|
bsw/jbe@1309
|
14935 dom.copyStyles(["display"]).from(textareaElement).to(this.editableArea);
|
bsw/jbe@1309
|
14936
|
bsw/jbe@1309
|
14937 // Make sure that we don't change the display style of the iframe when copying styles oblur/onfocus
|
bsw/jbe@1309
|
14938 // this is needed for when the change_view event is fired where the iframe is hidden and then
|
bsw/jbe@1309
|
14939 // the blur event fires and re-displays it
|
bsw/jbe@1309
|
14940 var boxFormattingStyles = wysihtml.lang.array(BOX_FORMATTING).without(["display"]);
|
bsw/jbe@1309
|
14941
|
bsw/jbe@1309
|
14942 // --------- restore focus ---------
|
bsw/jbe@1309
|
14943 if (originalActiveElement) {
|
bsw/jbe@1309
|
14944 focusWithoutScrolling(originalActiveElement);
|
bsw/jbe@1309
|
14945 } else {
|
bsw/jbe@1309
|
14946 textareaElement.blur();
|
bsw/jbe@1309
|
14947 }
|
bsw/jbe@1309
|
14948
|
bsw/jbe@1309
|
14949 // --------- restore placeholder ---------
|
bsw/jbe@1309
|
14950 if (hasPlaceholder) {
|
bsw/jbe@1309
|
14951 textareaElement.setAttribute("placeholder", originalPlaceholder);
|
bsw/jbe@1309
|
14952 }
|
bsw/jbe@1309
|
14953
|
bsw/jbe@1309
|
14954 // --------- Sync focus/blur styles ---------
|
bsw/jbe@1309
|
14955 this.parent.on("focus:composer", function() {
|
bsw/jbe@1309
|
14956 dom.copyStyles(boxFormattingStyles) .from(that.focusStylesHost).to(that.editableArea);
|
bsw/jbe@1309
|
14957 dom.copyStyles(TEXT_FORMATTING) .from(that.focusStylesHost).to(that.element);
|
bsw/jbe@1309
|
14958 });
|
bsw/jbe@1309
|
14959
|
bsw/jbe@1309
|
14960 this.parent.on("blur:composer", function() {
|
bsw/jbe@1309
|
14961 dom.copyStyles(boxFormattingStyles) .from(that.blurStylesHost).to(that.editableArea);
|
bsw/jbe@1309
|
14962 dom.copyStyles(TEXT_FORMATTING) .from(that.blurStylesHost).to(that.element);
|
bsw/jbe@1309
|
14963 });
|
bsw/jbe@1309
|
14964
|
bsw/jbe@1309
|
14965 this.parent.observe("disable:composer", function() {
|
bsw/jbe@1309
|
14966 dom.copyStyles(boxFormattingStyles) .from(that.disabledStylesHost).to(that.editableArea);
|
bsw/jbe@1309
|
14967 dom.copyStyles(TEXT_FORMATTING) .from(that.disabledStylesHost).to(that.element);
|
bsw/jbe@1309
|
14968 });
|
bsw/jbe@1309
|
14969
|
bsw/jbe@1309
|
14970 this.parent.observe("enable:composer", function() {
|
bsw/jbe@1309
|
14971 dom.copyStyles(boxFormattingStyles) .from(that.blurStylesHost).to(that.editableArea);
|
bsw/jbe@1309
|
14972 dom.copyStyles(TEXT_FORMATTING) .from(that.blurStylesHost).to(that.element);
|
bsw/jbe@1309
|
14973 });
|
bsw/jbe@1309
|
14974
|
bsw/jbe@1309
|
14975 return this;
|
bsw/jbe@1309
|
14976 };
|
bsw/jbe@1309
|
14977 })(wysihtml);
|
bsw/jbe@1309
|
14978
|
bsw/jbe@1309
|
14979 /**
|
bsw/jbe@1309
|
14980 * Taking care of events
|
bsw/jbe@1309
|
14981 * - Simulating 'change' event on contentEditable element
|
bsw/jbe@1309
|
14982 * - Handling drag & drop logic
|
bsw/jbe@1309
|
14983 * - Catch paste events
|
bsw/jbe@1309
|
14984 * - Dispatch proprietary newword:composer event
|
bsw/jbe@1309
|
14985 * - Keyboard shortcuts
|
bsw/jbe@1309
|
14986 */
|
bsw/jbe@1309
|
14987 (function(wysihtml) {
|
bsw/jbe@1309
|
14988 var dom = wysihtml.dom,
|
bsw/jbe@1309
|
14989 domNode = dom.domNode,
|
bsw/jbe@1309
|
14990 browser = wysihtml.browser,
|
bsw/jbe@1309
|
14991 /**
|
bsw/jbe@1309
|
14992 * Map keyCodes to query commands
|
bsw/jbe@1309
|
14993 */
|
bsw/jbe@1309
|
14994 shortcuts = {
|
bsw/jbe@1309
|
14995 "66": "bold", // B
|
bsw/jbe@1309
|
14996 "73": "italic", // I
|
bsw/jbe@1309
|
14997 "85": "underline" // U
|
bsw/jbe@1309
|
14998 };
|
bsw/jbe@1309
|
14999
|
bsw/jbe@1309
|
15000 var actions = {
|
bsw/jbe@1309
|
15001
|
bsw/jbe@1309
|
15002 // Adds multiple eventlisteners to target, bound to one callback
|
bsw/jbe@1309
|
15003 // TODO: If needed elsewhere make it part of wysihtml.dom or sth
|
bsw/jbe@1309
|
15004 addListeners: function (target, events, callback) {
|
bsw/jbe@1309
|
15005 for(var i = 0, max = events.length; i < max; i++) {
|
bsw/jbe@1309
|
15006 target.addEventListener(events[i], callback, false);
|
bsw/jbe@1309
|
15007 }
|
bsw/jbe@1309
|
15008 },
|
bsw/jbe@1309
|
15009
|
bsw/jbe@1309
|
15010 // Removes multiple eventlisteners from target, bound to one callback
|
bsw/jbe@1309
|
15011 // TODO: If needed elsewhere make it part of wysihtml.dom or sth
|
bsw/jbe@1309
|
15012 removeListeners: function (target, events, callback) {
|
bsw/jbe@1309
|
15013 for(var i = 0, max = events.length; i < max; i++) {
|
bsw/jbe@1309
|
15014 target.removeEventListener(events[i], callback, false);
|
bsw/jbe@1309
|
15015 }
|
bsw/jbe@1309
|
15016 },
|
bsw/jbe@1309
|
15017
|
bsw/jbe@1309
|
15018 // Override for giving user ability to delete last line break in table cell
|
bsw/jbe@1309
|
15019 fixLastBrDeletionInTable: function(composer, force) {
|
bsw/jbe@1309
|
15020 if (composer.selection.caretIsInTheEndOfNode()) {
|
bsw/jbe@1309
|
15021 var sel = composer.selection.getSelection(),
|
bsw/jbe@1309
|
15022 aNode = sel.anchorNode;
|
bsw/jbe@1309
|
15023 if (aNode && aNode.nodeType === 1 && (wysihtml.dom.getParentElement(aNode, {query: 'td, th'}, false, composer.element) || force)) {
|
bsw/jbe@1309
|
15024 var nextNode = aNode.childNodes[sel.anchorOffset];
|
bsw/jbe@1309
|
15025 if (nextNode && nextNode.nodeType === 1 & nextNode.nodeName === "BR") {
|
bsw/jbe@1309
|
15026 nextNode.parentNode.removeChild(nextNode);
|
bsw/jbe@1309
|
15027 return true;
|
bsw/jbe@1309
|
15028 }
|
bsw/jbe@1309
|
15029 }
|
bsw/jbe@1309
|
15030 }
|
bsw/jbe@1309
|
15031 return false;
|
bsw/jbe@1309
|
15032 },
|
bsw/jbe@1309
|
15033
|
bsw/jbe@1309
|
15034 // If found an uneditable before caret then notify it before deletion
|
bsw/jbe@1309
|
15035 handleUneditableDeletion: function(composer) {
|
bsw/jbe@1309
|
15036 var before = composer.selection.getBeforeSelection(true);
|
bsw/jbe@1309
|
15037 if (before && (before.type === "element" || before.type === "leafnode") && before.node.nodeType === 1 && before.node.classList.contains(composer.config.classNames.uneditableContainer)) {
|
bsw/jbe@1309
|
15038 if (actions.fixLastBrDeletionInTable(composer, true)) {
|
bsw/jbe@1309
|
15039 return true;
|
bsw/jbe@1309
|
15040 }
|
bsw/jbe@1309
|
15041 try {
|
bsw/jbe@1309
|
15042 var ev = new CustomEvent("wysihtml:uneditable:delete", {bubbles: true, cancelable: false});
|
bsw/jbe@1309
|
15043 before.node.dispatchEvent(ev);
|
bsw/jbe@1309
|
15044 } catch (err) {}
|
bsw/jbe@1309
|
15045 before.node.parentNode.removeChild(before.node);
|
bsw/jbe@1309
|
15046 return true;
|
bsw/jbe@1309
|
15047 }
|
bsw/jbe@1309
|
15048 return false;
|
bsw/jbe@1309
|
15049 },
|
bsw/jbe@1309
|
15050
|
bsw/jbe@1309
|
15051 // Deletion with caret in the beginning of headings and other block elvel elements needs special attention
|
bsw/jbe@1309
|
15052 // Not allways does it concate text to previous block node correctly (browsers do unexpected miracles here especially webkit)
|
bsw/jbe@1309
|
15053 fixDeleteInTheBeginningOfBlock: function(composer) {
|
bsw/jbe@1309
|
15054 var selection = composer.selection,
|
bsw/jbe@1309
|
15055 prevNode = selection.getPreviousNode();
|
bsw/jbe@1309
|
15056
|
bsw/jbe@1309
|
15057 if (selection.caretIsFirstInSelection(wysihtml.browser.usesControlRanges()) && prevNode) {
|
bsw/jbe@1309
|
15058 if (prevNode.nodeType === 1 &&
|
bsw/jbe@1309
|
15059 wysihtml.dom.domNode(prevNode).is.block() &&
|
bsw/jbe@1309
|
15060 !domNode(prevNode).test({
|
bsw/jbe@1309
|
15061 query: "ol, ul, table, tr, dl"
|
bsw/jbe@1309
|
15062 })
|
bsw/jbe@1309
|
15063 ) {
|
bsw/jbe@1309
|
15064 if ((/^\s*$/).test(prevNode.textContent || prevNode.innerText)) {
|
bsw/jbe@1309
|
15065 // If heading is empty remove the heading node
|
bsw/jbe@1309
|
15066 prevNode.parentNode.removeChild(prevNode);
|
bsw/jbe@1309
|
15067 return true;
|
bsw/jbe@1309
|
15068 } else {
|
bsw/jbe@1309
|
15069 if (prevNode.lastChild) {
|
bsw/jbe@1309
|
15070 var selNode = prevNode.lastChild,
|
bsw/jbe@1309
|
15071 selectedNode = selection.getSelectedNode(),
|
bsw/jbe@1309
|
15072 commonAncestorNode = domNode(prevNode).commonAncestor(selectedNode, composer.element),
|
bsw/jbe@1309
|
15073 curNode = wysihtml.dom.getParentElement(selectedNode, {
|
bsw/jbe@1309
|
15074 query: "h1, h2, h3, h4, h5, h6, p, pre, div, blockquote"
|
bsw/jbe@1309
|
15075 }, false, commonAncestorNode || composer.element);
|
bsw/jbe@1309
|
15076
|
bsw/jbe@1309
|
15077 if (curNode) {
|
bsw/jbe@1309
|
15078 domNode(curNode).transferContentTo(prevNode, true);
|
bsw/jbe@1309
|
15079 selection.setAfter(selNode);
|
bsw/jbe@1309
|
15080 return true;
|
bsw/jbe@1309
|
15081 } else if (wysihtml.browser.usesControlRanges()) {
|
bsw/jbe@1309
|
15082 selectedNode = selection.getCaretNode();
|
bsw/jbe@1309
|
15083 domNode(selectedNode).transferContentTo(prevNode, true);
|
bsw/jbe@1309
|
15084 selection.setAfter(selNode);
|
bsw/jbe@1309
|
15085 return true;
|
bsw/jbe@1309
|
15086 }
|
bsw/jbe@1309
|
15087 }
|
bsw/jbe@1309
|
15088 }
|
bsw/jbe@1309
|
15089 }
|
bsw/jbe@1309
|
15090 }
|
bsw/jbe@1309
|
15091 return false;
|
bsw/jbe@1309
|
15092 },
|
bsw/jbe@1309
|
15093
|
bsw/jbe@1309
|
15094 /* In IE when deleting with caret at the begining of LI, list gets broken into half instead of merging the LI with previous */
|
bsw/jbe@1309
|
15095 /* This does not match other browsers an is less intuitive from UI standpoint, thus has to be fixed */
|
bsw/jbe@1309
|
15096 fixDeleteInTheBeginningOfLi: function(composer) {
|
bsw/jbe@1309
|
15097 if (wysihtml.browser.hasLiDeletingProblem()) {
|
bsw/jbe@1309
|
15098 var selection = composer.selection.getSelection(),
|
bsw/jbe@1309
|
15099 aNode = selection.anchorNode,
|
bsw/jbe@1309
|
15100 listNode, prevNode, firstNode,
|
bsw/jbe@1309
|
15101 isInBeginnig = composer.selection.caretIsFirstInSelection(),
|
bsw/jbe@1309
|
15102 prevNode,
|
bsw/jbe@1309
|
15103 intermediaryNode;
|
bsw/jbe@1309
|
15104
|
bsw/jbe@1309
|
15105 // Fix caret at the beginnig of first textNode in LI
|
bsw/jbe@1309
|
15106 if (aNode.nodeType === 3 && selection.anchorOffset === 0 && aNode === aNode.parentNode.firstChild) {
|
bsw/jbe@1309
|
15107 aNode = aNode.parentNode;
|
bsw/jbe@1309
|
15108 isInBeginnig = true;
|
bsw/jbe@1309
|
15109 }
|
bsw/jbe@1309
|
15110
|
bsw/jbe@1309
|
15111 if (isInBeginnig && aNode && aNode.nodeType === 1 && aNode.nodeName === "LI") {
|
bsw/jbe@1309
|
15112 prevNode = domNode(aNode).prev({nodeTypes: [1,3], ignoreBlankTexts: true});
|
bsw/jbe@1309
|
15113 if (!prevNode && aNode.parentNode && (aNode.parentNode.nodeName === "UL" || aNode.parentNode.nodeName === "OL")) {
|
bsw/jbe@1309
|
15114 prevNode = domNode(aNode.parentNode).prev({nodeTypes: [1,3], ignoreBlankTexts: true});
|
bsw/jbe@1309
|
15115 intermediaryNode = aNode.parentNode;
|
bsw/jbe@1309
|
15116 }
|
bsw/jbe@1309
|
15117 if (prevNode) {
|
bsw/jbe@1309
|
15118 firstNode = aNode.firstChild;
|
bsw/jbe@1309
|
15119 domNode(aNode).transferContentTo(prevNode, true);
|
bsw/jbe@1309
|
15120
|
bsw/jbe@1309
|
15121 if (intermediaryNode && intermediaryNode.children.length === 0){
|
bsw/jbe@1309
|
15122 intermediaryNode.remove();
|
bsw/jbe@1309
|
15123 }
|
bsw/jbe@1309
|
15124
|
bsw/jbe@1309
|
15125 if (firstNode) {
|
bsw/jbe@1309
|
15126 composer.selection.setBefore(firstNode);
|
bsw/jbe@1309
|
15127 } else if (prevNode) {
|
bsw/jbe@1309
|
15128 if (prevNode.nodeType === 1) {
|
bsw/jbe@1309
|
15129 if (prevNode.lastChild) {
|
bsw/jbe@1309
|
15130 composer.selection.setAfter(prevNode.lastChild);
|
bsw/jbe@1309
|
15131 } else {
|
bsw/jbe@1309
|
15132 composer.selection.selectNode(prevNode);
|
bsw/jbe@1309
|
15133 }
|
bsw/jbe@1309
|
15134 } else {
|
bsw/jbe@1309
|
15135 composer.selection.setAfter(prevNode);
|
bsw/jbe@1309
|
15136 }
|
bsw/jbe@1309
|
15137 }
|
bsw/jbe@1309
|
15138 return true;
|
bsw/jbe@1309
|
15139 }
|
bsw/jbe@1309
|
15140 }
|
bsw/jbe@1309
|
15141 }
|
bsw/jbe@1309
|
15142 return false;
|
bsw/jbe@1309
|
15143 },
|
bsw/jbe@1309
|
15144
|
bsw/jbe@1309
|
15145 fixDeleteInTheBeginningOfControlSelection: function(composer) {
|
bsw/jbe@1309
|
15146 var selection = composer.selection,
|
bsw/jbe@1309
|
15147 prevNode = selection.getPreviousNode(),
|
bsw/jbe@1309
|
15148 selectedNode = selection.getSelectedNode(),
|
bsw/jbe@1309
|
15149 afterCaretNode;
|
bsw/jbe@1309
|
15150
|
bsw/jbe@1309
|
15151 if (selection.caretIsFirstInSelection()) {
|
bsw/jbe@1309
|
15152 if (selectedNode.nodeType === 3) {
|
bsw/jbe@1309
|
15153 selectedNode = selectedNode.parentNode;
|
bsw/jbe@1309
|
15154 }
|
bsw/jbe@1309
|
15155 afterCaretNode = selectedNode.firstChild;
|
bsw/jbe@1309
|
15156 domNode(selectedNode).transferContentTo(prevNode, true);
|
bsw/jbe@1309
|
15157 if (afterCaretNode) {
|
bsw/jbe@1309
|
15158 composer.selection.setBefore(afterCaretNode);
|
bsw/jbe@1309
|
15159 }
|
bsw/jbe@1309
|
15160 return true;
|
bsw/jbe@1309
|
15161 }
|
bsw/jbe@1309
|
15162 return false;
|
bsw/jbe@1309
|
15163 },
|
bsw/jbe@1309
|
15164
|
bsw/jbe@1309
|
15165 // Fixes some misbehaviours of enters in linebreaks mode (natively a bit unsupported feature)
|
bsw/jbe@1309
|
15166 // Returns true if some corrections is applied so events know when to prevent default
|
bsw/jbe@1309
|
15167 doLineBreaksModeEnterWithCaret: function(composer) {
|
bsw/jbe@1309
|
15168 var breakNodes = "p, pre, div, blockquote",
|
bsw/jbe@1309
|
15169 caretInfo, parent, txtNode,
|
bsw/jbe@1309
|
15170 ret = false;
|
bsw/jbe@1309
|
15171
|
bsw/jbe@1309
|
15172 caretInfo = composer.selection.getNodesNearCaret();
|
bsw/jbe@1309
|
15173 if (caretInfo) {
|
bsw/jbe@1309
|
15174
|
bsw/jbe@1309
|
15175 if (caretInfo.caretNode || caretInfo.nextNode) {
|
bsw/jbe@1309
|
15176 parent = dom.getParentElement(caretInfo.caretNode || caretInfo.nextNode, { query: breakNodes }, 2);
|
bsw/jbe@1309
|
15177 if (parent === composer.element) {
|
bsw/jbe@1309
|
15178 parent = undefined;
|
bsw/jbe@1309
|
15179 }
|
bsw/jbe@1309
|
15180 }
|
bsw/jbe@1309
|
15181
|
bsw/jbe@1309
|
15182 if (parent && caretInfo.caretNode) {
|
bsw/jbe@1309
|
15183 if (domNode(caretInfo.caretNode).is.lineBreak()) {
|
bsw/jbe@1309
|
15184
|
bsw/jbe@1309
|
15185 if (composer.config.doubleLineBreakEscapesBlock) {
|
bsw/jbe@1309
|
15186 // Double enter (enter on blank line) exits block element in useLineBreaks mode.
|
bsw/jbe@1309
|
15187 ret = true;
|
bsw/jbe@1309
|
15188 caretInfo.caretNode.parentNode.removeChild(caretInfo.caretNode);
|
bsw/jbe@1309
|
15189
|
bsw/jbe@1309
|
15190 // Ensure surplous line breaks are not added to preceding element
|
bsw/jbe@1309
|
15191 if (domNode(caretInfo.nextNode).is.lineBreak()) {
|
bsw/jbe@1309
|
15192 caretInfo.nextNode.parentNode.removeChild(caretInfo.nextNode);
|
bsw/jbe@1309
|
15193 }
|
bsw/jbe@1309
|
15194
|
bsw/jbe@1309
|
15195 var brNode = composer.doc.createElement('br');
|
bsw/jbe@1309
|
15196 if (domNode(caretInfo.nextNode).is.lineBreak() && caretInfo.nextNode === parent.lastChild) {
|
bsw/jbe@1309
|
15197 parent.parentNode.insertBefore(brNode, parent.nextSibling);
|
bsw/jbe@1309
|
15198 } else {
|
bsw/jbe@1309
|
15199 composer.selection.splitElementAtCaret(parent, brNode);
|
bsw/jbe@1309
|
15200 }
|
bsw/jbe@1309
|
15201
|
bsw/jbe@1309
|
15202 // Ensure surplous blank lines are not added to preceding element
|
bsw/jbe@1309
|
15203 if (caretInfo.nextNode && caretInfo.nextNode.nodeType === 3) {
|
bsw/jbe@1309
|
15204 // Replaces blank lines at the beginning of textnode
|
bsw/jbe@1309
|
15205 caretInfo.nextNode.data = caretInfo.nextNode.data.replace(/^ *[\r\n]+/, '');
|
bsw/jbe@1309
|
15206 }
|
bsw/jbe@1309
|
15207 composer.selection.setBefore(brNode);
|
bsw/jbe@1309
|
15208 }
|
bsw/jbe@1309
|
15209
|
bsw/jbe@1309
|
15210 } else if (caretInfo.caretNode.nodeType === 3 && wysihtml.browser.hasCaretBlockElementIssue() && caretInfo.textOffset === caretInfo.caretNode.data.length && !caretInfo.nextNode) {
|
bsw/jbe@1309
|
15211
|
bsw/jbe@1309
|
15212 // This fixes annoying webkit issue when you press enter at the end of a block then seemingly nothing happens.
|
bsw/jbe@1309
|
15213 // in reality one line break is generated and cursor is reported after it, but when entering something cursor jumps before the br
|
bsw/jbe@1309
|
15214 ret = true;
|
bsw/jbe@1309
|
15215 var br1 = composer.doc.createElement('br'),
|
bsw/jbe@1309
|
15216 br2 = composer.doc.createElement('br'),
|
bsw/jbe@1309
|
15217 f = composer.doc.createDocumentFragment();
|
bsw/jbe@1309
|
15218 f.appendChild(br1);
|
bsw/jbe@1309
|
15219 f.appendChild(br2);
|
bsw/jbe@1309
|
15220 composer.selection.insertNode(f);
|
bsw/jbe@1309
|
15221 composer.selection.setBefore(br2);
|
bsw/jbe@1309
|
15222
|
bsw/jbe@1309
|
15223 }
|
bsw/jbe@1309
|
15224 }
|
bsw/jbe@1309
|
15225 }
|
bsw/jbe@1309
|
15226 return ret;
|
bsw/jbe@1309
|
15227 }
|
bsw/jbe@1309
|
15228 };
|
bsw/jbe@1309
|
15229
|
bsw/jbe@1309
|
15230 var handleDeleteKeyPress = function(event, composer) {
|
bsw/jbe@1309
|
15231 var selection = composer.selection,
|
bsw/jbe@1309
|
15232 element = composer.element;
|
bsw/jbe@1309
|
15233
|
bsw/jbe@1309
|
15234 if (selection.isCollapsed()) {
|
bsw/jbe@1309
|
15235 /**
|
bsw/jbe@1309
|
15236 * when the editor is empty in useLineBreaks = false mode, preserve
|
bsw/jbe@1309
|
15237 * the default value in it which is <p><br></p>
|
bsw/jbe@1309
|
15238 */
|
bsw/jbe@1309
|
15239 if (composer.isEmpty() && !composer.config.useLineBreaks) {
|
bsw/jbe@1309
|
15240 event.preventDefault();
|
bsw/jbe@1309
|
15241 return;
|
bsw/jbe@1309
|
15242 }
|
bsw/jbe@1309
|
15243 if (actions.handleUneditableDeletion(composer)) {
|
bsw/jbe@1309
|
15244 event.preventDefault();
|
bsw/jbe@1309
|
15245 return;
|
bsw/jbe@1309
|
15246 }
|
bsw/jbe@1309
|
15247 if (actions.fixDeleteInTheBeginningOfLi(composer)) {
|
bsw/jbe@1309
|
15248 event.preventDefault();
|
bsw/jbe@1309
|
15249 return;
|
bsw/jbe@1309
|
15250 }
|
bsw/jbe@1309
|
15251 if (actions.fixDeleteInTheBeginningOfBlock(composer)) {
|
bsw/jbe@1309
|
15252 event.preventDefault();
|
bsw/jbe@1309
|
15253 return;
|
bsw/jbe@1309
|
15254 }
|
bsw/jbe@1309
|
15255 if (actions.fixLastBrDeletionInTable(composer)) {
|
bsw/jbe@1309
|
15256 event.preventDefault();
|
bsw/jbe@1309
|
15257 return;
|
bsw/jbe@1309
|
15258 }
|
bsw/jbe@1309
|
15259 if (wysihtml.browser.usesControlRanges()) {
|
bsw/jbe@1309
|
15260 if (actions.fixDeleteInTheBeginningOfControlSelection(composer)) {
|
bsw/jbe@1309
|
15261 event.preventDefault();
|
bsw/jbe@1309
|
15262 return;
|
bsw/jbe@1309
|
15263 }
|
bsw/jbe@1309
|
15264 }
|
bsw/jbe@1309
|
15265 } else {
|
bsw/jbe@1309
|
15266 if (selection.containsUneditable()) {
|
bsw/jbe@1309
|
15267 event.preventDefault();
|
bsw/jbe@1309
|
15268 selection.deleteContents();
|
bsw/jbe@1309
|
15269 }
|
bsw/jbe@1309
|
15270 }
|
bsw/jbe@1309
|
15271 };
|
bsw/jbe@1309
|
15272
|
bsw/jbe@1309
|
15273 var handleEnterKeyPress = function(event, composer) {
|
bsw/jbe@1309
|
15274 if (composer.config.useLineBreaks && !event.shiftKey && !event.ctrlKey) {
|
bsw/jbe@1309
|
15275 // Fixes some misbehaviours of enters in linebreaks mode (natively a bit unsupported feature)
|
bsw/jbe@1309
|
15276
|
bsw/jbe@1309
|
15277 var breakNodes = "p, pre, div, blockquote",
|
bsw/jbe@1309
|
15278 caretInfo, parent, txtNode;
|
bsw/jbe@1309
|
15279
|
bsw/jbe@1309
|
15280 if (composer.selection.isCollapsed()) {
|
bsw/jbe@1309
|
15281 if (actions.doLineBreaksModeEnterWithCaret(composer)) {
|
bsw/jbe@1309
|
15282 event.preventDefault();
|
bsw/jbe@1309
|
15283 }
|
bsw/jbe@1309
|
15284 }
|
bsw/jbe@1309
|
15285 }
|
bsw/jbe@1309
|
15286
|
bsw/jbe@1309
|
15287 if (browser.hasCaretAtLinkEndInsertionProblems() && composer.selection.caretIsInTheEndOfNode()) {
|
bsw/jbe@1309
|
15288 var target = composer.selection.getSelectedNode(true),
|
bsw/jbe@1309
|
15289 targetEl = (target && target.nodeType === 3) ? target.parentNode : target, // target guaranteed to be an Element
|
bsw/jbe@1309
|
15290 invisibleSpace, space;
|
bsw/jbe@1309
|
15291
|
bsw/jbe@1309
|
15292 if (targetEl && targetEl.closest('a') && target.nodeType === 3 && target === targetEl.lastChild) {
|
bsw/jbe@1309
|
15293 // Seems like enter was pressed and caret was at the end of link node
|
bsw/jbe@1309
|
15294 // This means user wants to escape the link now (caret is last in link node too).
|
bsw/jbe@1309
|
15295 composer.selection.setAfter(targetEl);
|
bsw/jbe@1309
|
15296 }
|
bsw/jbe@1309
|
15297 }
|
bsw/jbe@1309
|
15298 };
|
bsw/jbe@1309
|
15299
|
bsw/jbe@1309
|
15300 var handleTabKeyDown = function(composer, element, shiftKey) {
|
bsw/jbe@1309
|
15301 if (!composer.selection.isCollapsed()) {
|
bsw/jbe@1309
|
15302 composer.selection.deleteContents();
|
bsw/jbe@1309
|
15303 } else if (composer.selection.caretIsInTheBeginnig('li')) {
|
bsw/jbe@1309
|
15304 if (shiftKey) {
|
bsw/jbe@1309
|
15305 if (composer.commands.exec('outdentList')) return;
|
bsw/jbe@1309
|
15306 } else {
|
bsw/jbe@1309
|
15307 if (composer.commands.exec('indentList')) return;
|
bsw/jbe@1309
|
15308 }
|
bsw/jbe@1309
|
15309 }
|
bsw/jbe@1309
|
15310
|
bsw/jbe@1309
|
15311 // Is   close enough to tab. Could not find enough counter arguments for now.
|
bsw/jbe@1309
|
15312 composer.commands.exec("insertHTML", " ");
|
bsw/jbe@1309
|
15313 };
|
bsw/jbe@1309
|
15314
|
bsw/jbe@1309
|
15315 var handleDomNodeRemoved = function(event) {
|
bsw/jbe@1309
|
15316 if (this.domNodeRemovedInterval) {
|
bsw/jbe@1309
|
15317 clearInterval(domNodeRemovedInterval);
|
bsw/jbe@1309
|
15318 }
|
bsw/jbe@1309
|
15319 this.parent.fire("destroy:composer");
|
bsw/jbe@1309
|
15320 };
|
bsw/jbe@1309
|
15321
|
bsw/jbe@1309
|
15322 // Listens to "drop", "paste", "mouseup", "focus", "keyup" events and fires
|
bsw/jbe@1309
|
15323 var handleUserInteraction = function (event) {
|
bsw/jbe@1309
|
15324 this.parent.fire("beforeinteraction", event).fire("beforeinteraction:composer", event);
|
bsw/jbe@1309
|
15325 setTimeout((function() {
|
bsw/jbe@1309
|
15326 this.parent.fire("interaction", event).fire("interaction:composer", event);
|
bsw/jbe@1309
|
15327 }).bind(this), 0);
|
bsw/jbe@1309
|
15328 };
|
bsw/jbe@1309
|
15329
|
bsw/jbe@1309
|
15330 var handleFocus = function(event) {
|
bsw/jbe@1309
|
15331 this.parent.fire("focus", event).fire("focus:composer", event);
|
bsw/jbe@1309
|
15332
|
bsw/jbe@1309
|
15333 // Delay storing of state until all focus handler are fired
|
bsw/jbe@1309
|
15334 // especially the one which resets the placeholder
|
bsw/jbe@1309
|
15335 setTimeout((function() {
|
bsw/jbe@1309
|
15336 this.focusState = this.getValue(false, false);
|
bsw/jbe@1309
|
15337 }).bind(this), 0);
|
bsw/jbe@1309
|
15338 };
|
bsw/jbe@1309
|
15339
|
bsw/jbe@1309
|
15340 var handleBlur = function(event) {
|
bsw/jbe@1309
|
15341 if (this.focusState !== this.getValue(false, false)) {
|
bsw/jbe@1309
|
15342 //create change event if supported (all except IE8)
|
bsw/jbe@1309
|
15343 var changeevent = event;
|
bsw/jbe@1309
|
15344 if(typeof Object.create == 'function') {
|
bsw/jbe@1309
|
15345 changeevent = Object.create(event, { type: { value: 'change' } });
|
bsw/jbe@1309
|
15346 }
|
bsw/jbe@1309
|
15347 this.parent.fire("change", changeevent).fire("change:composer", changeevent);
|
bsw/jbe@1309
|
15348 }
|
bsw/jbe@1309
|
15349 this.parent.fire("blur", event).fire("blur:composer", event);
|
bsw/jbe@1309
|
15350 };
|
bsw/jbe@1309
|
15351
|
bsw/jbe@1309
|
15352 var handlePaste = function(event) {
|
bsw/jbe@1309
|
15353 this.parent.fire(event.type, event).fire(event.type + ":composer", event);
|
bsw/jbe@1309
|
15354 if (event.type === "paste") {
|
bsw/jbe@1309
|
15355 setTimeout((function() {
|
bsw/jbe@1309
|
15356 this.parent.fire("newword:composer");
|
bsw/jbe@1309
|
15357 }).bind(this), 0);
|
bsw/jbe@1309
|
15358 }
|
bsw/jbe@1309
|
15359 };
|
bsw/jbe@1309
|
15360
|
bsw/jbe@1309
|
15361 var handleCopy = function(event) {
|
bsw/jbe@1309
|
15362 if (this.config.copyedFromMarking) {
|
bsw/jbe@1309
|
15363 // If supported the copied source can be based directly on selection
|
bsw/jbe@1309
|
15364 // Very useful for webkit based browsers where copy will otherwise contain a lot of code and styles based on whatever and not actually in selection.
|
bsw/jbe@1309
|
15365 if (wysihtml.browser.supportsModernPaste()) {
|
bsw/jbe@1309
|
15366 event.clipboardData.setData("text/html", this.config.copyedFromMarking + this.selection.getHtml());
|
bsw/jbe@1309
|
15367 event.clipboardData.setData("text/plain", this.selection.getPlainText());
|
bsw/jbe@1309
|
15368 event.preventDefault();
|
bsw/jbe@1309
|
15369 }
|
bsw/jbe@1309
|
15370 this.parent.fire(event.type, event).fire(event.type + ":composer", event);
|
bsw/jbe@1309
|
15371 }
|
bsw/jbe@1309
|
15372 };
|
bsw/jbe@1309
|
15373
|
bsw/jbe@1309
|
15374 var handleKeyUp = function(event) {
|
bsw/jbe@1309
|
15375 var keyCode = event.keyCode;
|
bsw/jbe@1309
|
15376 if (keyCode === wysihtml.SPACE_KEY || keyCode === wysihtml.ENTER_KEY) {
|
bsw/jbe@1309
|
15377 this.parent.fire("newword:composer");
|
bsw/jbe@1309
|
15378 }
|
bsw/jbe@1309
|
15379 };
|
bsw/jbe@1309
|
15380
|
bsw/jbe@1309
|
15381 var handleMouseDown = function(event) {
|
bsw/jbe@1309
|
15382 if (!browser.canSelectImagesInContentEditable()) {
|
bsw/jbe@1309
|
15383 // Make sure that images are selected when clicking on them
|
bsw/jbe@1309
|
15384 var target = event.target,
|
bsw/jbe@1309
|
15385 allImages = this.element.querySelectorAll('img'),
|
bsw/jbe@1309
|
15386 notMyImages = this.element.querySelectorAll('.' + this.config.classNames.uneditableContainer + ' img'),
|
bsw/jbe@1309
|
15387 myImages = wysihtml.lang.array(allImages).without(notMyImages);
|
bsw/jbe@1309
|
15388
|
bsw/jbe@1309
|
15389 if (target.nodeName === "IMG" && wysihtml.lang.array(myImages).contains(target)) {
|
bsw/jbe@1309
|
15390 this.selection.selectNode(target);
|
bsw/jbe@1309
|
15391 }
|
bsw/jbe@1309
|
15392 }
|
bsw/jbe@1309
|
15393
|
bsw/jbe@1309
|
15394 // Saves mousedown position for IE controlSelect fix
|
bsw/jbe@1309
|
15395 if (wysihtml.browser.usesControlRanges()) {
|
bsw/jbe@1309
|
15396 this.selection.lastMouseDownPos = {x: event.clientX, y: event.clientY};
|
bsw/jbe@1309
|
15397 setTimeout(function() {
|
bsw/jbe@1309
|
15398 delete this.selection.lastMouseDownPos;
|
bsw/jbe@1309
|
15399 }.bind(this), 0);
|
bsw/jbe@1309
|
15400 }
|
bsw/jbe@1309
|
15401 };
|
bsw/jbe@1309
|
15402
|
bsw/jbe@1309
|
15403 // IE has this madness of control selects of overflowed and some other elements (weird box around element on selection and second click selects text)
|
bsw/jbe@1309
|
15404 // This fix handles the second click problem by adding cursor to the right position under cursor inside when controlSelection is made
|
bsw/jbe@1309
|
15405 var handleIEControlSelect = function(event) {
|
bsw/jbe@1309
|
15406 var target = event.target,
|
bsw/jbe@1309
|
15407 pos = this.selection.lastMouseDownPos;
|
bsw/jbe@1309
|
15408 if (pos) {
|
bsw/jbe@1309
|
15409 var caretPosition = document.body.createTextRange();
|
bsw/jbe@1309
|
15410 setTimeout(function() {
|
bsw/jbe@1309
|
15411 try {
|
bsw/jbe@1309
|
15412 caretPosition.moveToPoint(pos.x, pos.y);
|
bsw/jbe@1309
|
15413 caretPosition.select();
|
bsw/jbe@1309
|
15414 } catch (e) {}
|
bsw/jbe@1309
|
15415 }.bind(this), 0);
|
bsw/jbe@1309
|
15416 }
|
bsw/jbe@1309
|
15417 };
|
bsw/jbe@1309
|
15418
|
bsw/jbe@1309
|
15419 var handleClick = function(event) {
|
bsw/jbe@1309
|
15420 if (this.config.classNames.uneditableContainer) {
|
bsw/jbe@1309
|
15421 // If uneditables is configured, makes clicking on uneditable move caret after clicked element (so it can be deleted like text)
|
bsw/jbe@1309
|
15422 // If uneditable needs text selection itself event.stopPropagation can be used to prevent this behaviour
|
bsw/jbe@1309
|
15423 var uneditable = wysihtml.dom.getParentElement(event.target, { query: "." + this.config.classNames.uneditableContainer }, false, this.element);
|
bsw/jbe@1309
|
15424 if (uneditable) {
|
bsw/jbe@1309
|
15425 this.selection.setAfter(uneditable);
|
bsw/jbe@1309
|
15426 }
|
bsw/jbe@1309
|
15427 }
|
bsw/jbe@1309
|
15428 };
|
bsw/jbe@1309
|
15429
|
bsw/jbe@1309
|
15430 var handleDrop = function(event) {
|
bsw/jbe@1309
|
15431 if (!browser.canSelectImagesInContentEditable()) {
|
bsw/jbe@1309
|
15432 // TODO: if I knew how to get dropped elements list from event I could limit it to only IMG element case
|
bsw/jbe@1309
|
15433 setTimeout((function() {
|
bsw/jbe@1309
|
15434 this.selection.getSelection().removeAllRanges();
|
bsw/jbe@1309
|
15435 }).bind(this), 0);
|
bsw/jbe@1309
|
15436 }
|
bsw/jbe@1309
|
15437 };
|
bsw/jbe@1309
|
15438
|
bsw/jbe@1309
|
15439 var handleKeyDown = function(event) {
|
bsw/jbe@1309
|
15440 var keyCode = event.keyCode,
|
bsw/jbe@1309
|
15441 command = shortcuts[keyCode],
|
bsw/jbe@1309
|
15442 target = this.selection.getSelectedNode(true),
|
bsw/jbe@1309
|
15443 targetEl = (target && target.nodeType === 3) ? target.parentNode : target, // target guaranteed to be an Element
|
bsw/jbe@1309
|
15444 parent;
|
bsw/jbe@1309
|
15445
|
bsw/jbe@1309
|
15446 // Select all (meta/ctrl + a)
|
bsw/jbe@1309
|
15447 if ((event.ctrlKey || event.metaKey) && !event.altKey && keyCode === 65) {
|
bsw/jbe@1309
|
15448 this.selection.selectAll();
|
bsw/jbe@1309
|
15449 event.preventDefault();
|
bsw/jbe@1309
|
15450 return;
|
bsw/jbe@1309
|
15451 }
|
bsw/jbe@1309
|
15452
|
bsw/jbe@1309
|
15453 // Shortcut logic
|
bsw/jbe@1309
|
15454 if ((event.ctrlKey || event.metaKey) && !event.altKey && command) {
|
bsw/jbe@1309
|
15455 this.commands.exec(command);
|
bsw/jbe@1309
|
15456 event.preventDefault();
|
bsw/jbe@1309
|
15457 }
|
bsw/jbe@1309
|
15458
|
bsw/jbe@1309
|
15459 if (keyCode === wysihtml.BACKSPACE_KEY) {
|
bsw/jbe@1309
|
15460 // Delete key override for special cases
|
bsw/jbe@1309
|
15461 handleDeleteKeyPress(event, this);
|
bsw/jbe@1309
|
15462 }
|
bsw/jbe@1309
|
15463
|
bsw/jbe@1309
|
15464 // Make sure that when pressing backspace/delete on selected images deletes the image and it's anchor
|
bsw/jbe@1309
|
15465 if (keyCode === wysihtml.BACKSPACE_KEY || keyCode === wysihtml.DELETE_KEY) {
|
bsw/jbe@1309
|
15466 if (target && target.nodeName === "IMG") {
|
bsw/jbe@1309
|
15467 event.preventDefault();
|
bsw/jbe@1309
|
15468 parent = target.parentNode;
|
bsw/jbe@1309
|
15469 parent.removeChild(target);// delete the <img>
|
bsw/jbe@1309
|
15470 // And it's parent <a> too if it hasn't got any other child nodes
|
bsw/jbe@1309
|
15471 if (parent.nodeName === "A" && !parent.firstChild) {
|
bsw/jbe@1309
|
15472 parent.parentNode.removeChild(parent);
|
bsw/jbe@1309
|
15473 }
|
bsw/jbe@1309
|
15474 setTimeout((function() {
|
bsw/jbe@1309
|
15475 wysihtml.quirks.redraw(this.element);
|
bsw/jbe@1309
|
15476 }).bind(this), 0);
|
bsw/jbe@1309
|
15477 }
|
bsw/jbe@1309
|
15478 }
|
bsw/jbe@1309
|
15479
|
bsw/jbe@1309
|
15480 if (this.config.handleTabKey && keyCode === wysihtml.TAB_KEY) {
|
bsw/jbe@1309
|
15481 // TAB key handling
|
bsw/jbe@1309
|
15482 event.preventDefault();
|
bsw/jbe@1309
|
15483 handleTabKeyDown(this, this.element, event.shiftKey);
|
bsw/jbe@1309
|
15484 }
|
bsw/jbe@1309
|
15485
|
bsw/jbe@1309
|
15486 if (keyCode === wysihtml.ENTER_KEY) {
|
bsw/jbe@1309
|
15487 handleEnterKeyPress(event, this);
|
bsw/jbe@1309
|
15488 }
|
bsw/jbe@1309
|
15489
|
bsw/jbe@1309
|
15490 };
|
bsw/jbe@1309
|
15491
|
bsw/jbe@1309
|
15492 var handleKeyPress = function(event) {
|
bsw/jbe@1309
|
15493
|
bsw/jbe@1309
|
15494 // This block should run only if some character is inserted (nor command keys like delete, backspace, enter, etc.)
|
bsw/jbe@1309
|
15495 if (event.which !== 0) {
|
bsw/jbe@1309
|
15496
|
bsw/jbe@1309
|
15497 // Test if caret is last in a link in webkit and try to fix webkit problem,
|
bsw/jbe@1309
|
15498 // that all inserted content is added outside of link.
|
bsw/jbe@1309
|
15499 // This issue was added as a not thought through fix for getting caret after link in contenteditable if it is last in editable area.
|
bsw/jbe@1309
|
15500 // Allthough it fixes this minor case it actually introduces a cascade of problems when editing links.
|
bsw/jbe@1309
|
15501 // The standard approachi in other wysiwygs seems as a step backwards - introducing a separate modal for managing links content text.
|
bsw/jbe@1309
|
15502 // I find it to be too big of a tradeoff in terms of expected simple UI flow, thus trying to fight against it.
|
bsw/jbe@1309
|
15503 // Also adds link escaping by double space with caret at the end of link for all browsers
|
bsw/jbe@1309
|
15504
|
bsw/jbe@1309
|
15505 if (this.selection.caretIsInTheEndOfNode()) {
|
bsw/jbe@1309
|
15506 var target = this.selection.getSelectedNode(true),
|
bsw/jbe@1309
|
15507 targetEl = (target && target.nodeType === 3) ? target.parentNode : target, // target guaranteed to be an Element
|
bsw/jbe@1309
|
15508 invisibleSpace, space;
|
bsw/jbe@1309
|
15509
|
bsw/jbe@1309
|
15510 if (targetEl && targetEl.closest('a') && target === targetEl.lastChild) {
|
bsw/jbe@1309
|
15511
|
bsw/jbe@1309
|
15512 if (event.which !== 32 || this.selection.caretIsInTheEndOfNode(true) && browser.hasCaretAtLinkEndInsertionProblems()) {
|
bsw/jbe@1309
|
15513 // Executed if there is no whitespace before caret in textnode in case of pressing space.
|
bsw/jbe@1309
|
15514 // Whitespace before marks that user wants to escape the node by pressing double space.
|
bsw/jbe@1309
|
15515 // Otherwise insert the character in the link not out as it would like to go natively
|
bsw/jbe@1309
|
15516
|
bsw/jbe@1309
|
15517 invisibleSpace = this.doc.createTextNode(wysihtml.INVISIBLE_SPACE);
|
bsw/jbe@1309
|
15518 this.selection.insertNode(invisibleSpace);
|
bsw/jbe@1309
|
15519 this.selection.setBefore(invisibleSpace);
|
bsw/jbe@1309
|
15520 setTimeout(function() {
|
bsw/jbe@1309
|
15521
|
bsw/jbe@1309
|
15522 if (invisibleSpace.textContent.length > 1) {
|
bsw/jbe@1309
|
15523 invisibleSpace.textContent = invisibleSpace.textContent.replace(wysihtml.INVISIBLE_SPACE_REG_EXP, '');
|
bsw/jbe@1309
|
15524 this.selection.setAfter(invisibleSpace);
|
bsw/jbe@1309
|
15525 } else {
|
bsw/jbe@1309
|
15526 invisibleSpace.remove();
|
bsw/jbe@1309
|
15527 }
|
bsw/jbe@1309
|
15528
|
bsw/jbe@1309
|
15529 }.bind(this), 0);
|
bsw/jbe@1309
|
15530 } else if (event.which === 32) {
|
bsw/jbe@1309
|
15531 // Seems like space was pressed and there was a space before the caret allready
|
bsw/jbe@1309
|
15532 // This means user wants to escape the link now (caret is last in link node too) so we let the native browser do it-s job and escape.
|
bsw/jbe@1309
|
15533 // But lets move the trailing space too out of link if present
|
bsw/jbe@1309
|
15534
|
bsw/jbe@1309
|
15535 if (target.nodeType === 3 && (/[\u00A0 ]$/).test(target.textContent)) {
|
bsw/jbe@1309
|
15536
|
bsw/jbe@1309
|
15537 target.textContent = target.textContent.replace(/[\u00A0 ]$/, '');
|
bsw/jbe@1309
|
15538 space = this.doc.createTextNode(' ');
|
bsw/jbe@1309
|
15539 targetEl.parentNode.insertBefore(space, targetEl.nextSibling);
|
bsw/jbe@1309
|
15540 this.selection.setAfter(space, false);
|
bsw/jbe@1309
|
15541 event.preventDefault();
|
bsw/jbe@1309
|
15542
|
bsw/jbe@1309
|
15543 }
|
bsw/jbe@1309
|
15544 }
|
bsw/jbe@1309
|
15545 }
|
bsw/jbe@1309
|
15546 }
|
bsw/jbe@1309
|
15547 }
|
bsw/jbe@1309
|
15548 }
|
bsw/jbe@1309
|
15549
|
bsw/jbe@1309
|
15550 var handleIframeFocus = function(event) {
|
bsw/jbe@1309
|
15551 setTimeout((function() {
|
bsw/jbe@1309
|
15552 if (this.doc.querySelector(":focus") !== this.element) {
|
bsw/jbe@1309
|
15553 this.focus();
|
bsw/jbe@1309
|
15554 }
|
bsw/jbe@1309
|
15555 }).bind(this), 0);
|
bsw/jbe@1309
|
15556 };
|
bsw/jbe@1309
|
15557
|
bsw/jbe@1309
|
15558 var handleIframeBlur = function(event) {
|
bsw/jbe@1309
|
15559 setTimeout((function() {
|
bsw/jbe@1309
|
15560 this.selection.getSelection().removeAllRanges();
|
bsw/jbe@1309
|
15561 }).bind(this), 0);
|
bsw/jbe@1309
|
15562 };
|
bsw/jbe@1309
|
15563
|
bsw/jbe@1309
|
15564 // Testing requires actions to be accessible from out of scope
|
bsw/jbe@1309
|
15565 wysihtml.views.Composer.prototype.observeActions = actions;
|
bsw/jbe@1309
|
15566
|
bsw/jbe@1309
|
15567 wysihtml.views.Composer.prototype.observe = function() {
|
bsw/jbe@1309
|
15568 var that = this,
|
bsw/jbe@1309
|
15569 container = (this.sandbox.getIframe) ? this.sandbox.getIframe() : this.sandbox.getContentEditable(),
|
bsw/jbe@1309
|
15570 element = this.element,
|
bsw/jbe@1309
|
15571 focusBlurElement = (browser.supportsEventsInIframeCorrectly() || this.sandbox.getContentEditable) ? this.element : this.sandbox.getWindow();
|
bsw/jbe@1309
|
15572
|
bsw/jbe@1309
|
15573 this.focusState = this.getValue(false, false);
|
bsw/jbe@1309
|
15574 this.actions = actions;
|
bsw/jbe@1309
|
15575
|
bsw/jbe@1309
|
15576 // --------- destroy:composer event ---------
|
bsw/jbe@1309
|
15577 container.addEventListener(["DOMNodeRemoved"], handleDomNodeRemoved.bind(this), false);
|
bsw/jbe@1309
|
15578
|
bsw/jbe@1309
|
15579 // DOMNodeRemoved event is not supported in IE 8
|
bsw/jbe@1309
|
15580 // TODO: try to figure out a polyfill style fix, so it could be transferred to polyfills and removed if ie8 is not needed
|
bsw/jbe@1309
|
15581 if (!browser.supportsMutationEvents()) {
|
bsw/jbe@1309
|
15582 this.domNodeRemovedInterval = setInterval(function() {
|
bsw/jbe@1309
|
15583 if (!dom.contains(document.documentElement, container)) {
|
bsw/jbe@1309
|
15584 handleDomNodeRemoved.call(this);
|
bsw/jbe@1309
|
15585 }
|
bsw/jbe@1309
|
15586 }, 250);
|
bsw/jbe@1309
|
15587 }
|
bsw/jbe@1309
|
15588
|
bsw/jbe@1309
|
15589 actions.addListeners(focusBlurElement, ['drop', 'paste', 'mouseup', 'focus', 'keyup'], handleUserInteraction.bind(this));
|
bsw/jbe@1309
|
15590 focusBlurElement.addEventListener('focus', handleFocus.bind(this), false);
|
bsw/jbe@1309
|
15591 focusBlurElement.addEventListener('blur', handleBlur.bind(this), false);
|
bsw/jbe@1309
|
15592
|
bsw/jbe@1309
|
15593 actions.addListeners(this.element, ['drop', 'paste', 'beforepaste'], handlePaste.bind(this), false);
|
bsw/jbe@1309
|
15594 this.element.addEventListener('copy', handleCopy.bind(this), false);
|
bsw/jbe@1309
|
15595 this.element.addEventListener('mousedown', handleMouseDown.bind(this), false);
|
bsw/jbe@1309
|
15596 this.element.addEventListener('click', handleClick.bind(this), false);
|
bsw/jbe@1309
|
15597 this.element.addEventListener('drop', handleDrop.bind(this), false);
|
bsw/jbe@1309
|
15598 this.element.addEventListener('keyup', handleKeyUp.bind(this), false);
|
bsw/jbe@1309
|
15599 this.element.addEventListener('keydown', handleKeyDown.bind(this), false);
|
bsw/jbe@1309
|
15600 this.element.addEventListener('keypress', handleKeyPress.bind(this), false);
|
bsw/jbe@1309
|
15601
|
bsw/jbe@1309
|
15602 // IE controlselect madness fix
|
bsw/jbe@1309
|
15603 if (wysihtml.browser.usesControlRanges()) {
|
bsw/jbe@1309
|
15604 this.element.addEventListener('mscontrolselect', handleIEControlSelect.bind(this), false);
|
bsw/jbe@1309
|
15605 }
|
bsw/jbe@1309
|
15606
|
bsw/jbe@1309
|
15607 this.element.addEventListener("dragenter", (function() {
|
bsw/jbe@1309
|
15608 this.parent.fire("unset_placeholder");
|
bsw/jbe@1309
|
15609 }).bind(this), false);
|
bsw/jbe@1309
|
15610
|
bsw/jbe@1309
|
15611 };
|
bsw/jbe@1309
|
15612 })(wysihtml);
|
bsw/jbe@1309
|
15613
|
bsw/jbe@1309
|
15614 /**
|
bsw/jbe@1309
|
15615 * Class that takes care that the value of the composer and the textarea is always in sync
|
bsw/jbe@1309
|
15616 */
|
bsw/jbe@1309
|
15617 (function(wysihtml) {
|
bsw/jbe@1309
|
15618 var INTERVAL = 400;
|
bsw/jbe@1309
|
15619
|
bsw/jbe@1309
|
15620 wysihtml.views.Synchronizer = Base.extend(
|
bsw/jbe@1309
|
15621 /** @scope wysihtml.views.Synchronizer.prototype */ {
|
bsw/jbe@1309
|
15622
|
bsw/jbe@1309
|
15623 constructor: function(editor, textarea, composer) {
|
bsw/jbe@1309
|
15624 this.editor = editor;
|
bsw/jbe@1309
|
15625 this.textarea = textarea;
|
bsw/jbe@1309
|
15626 this.composer = composer;
|
bsw/jbe@1309
|
15627
|
bsw/jbe@1309
|
15628 this._observe();
|
bsw/jbe@1309
|
15629 },
|
bsw/jbe@1309
|
15630
|
bsw/jbe@1309
|
15631 /**
|
bsw/jbe@1309
|
15632 * Sync html from composer to textarea
|
bsw/jbe@1309
|
15633 * Takes care of placeholders
|
bsw/jbe@1309
|
15634 * @param {Boolean} shouldParseHtml Whether the html should be sanitized before inserting it into the textarea
|
bsw/jbe@1309
|
15635 */
|
bsw/jbe@1309
|
15636 fromComposerToTextarea: function(shouldParseHtml) {
|
bsw/jbe@1309
|
15637 this.textarea.setValue(wysihtml.lang.string(this.composer.getValue(false, false)).trim(), shouldParseHtml);
|
bsw/jbe@1309
|
15638 },
|
bsw/jbe@1309
|
15639
|
bsw/jbe@1309
|
15640 /**
|
bsw/jbe@1309
|
15641 * Sync value of textarea to composer
|
bsw/jbe@1309
|
15642 * Takes care of placeholders
|
bsw/jbe@1309
|
15643 * @param {Boolean} shouldParseHtml Whether the html should be sanitized before inserting it into the composer
|
bsw/jbe@1309
|
15644 */
|
bsw/jbe@1309
|
15645 fromTextareaToComposer: function(shouldParseHtml) {
|
bsw/jbe@1309
|
15646 var textareaValue = this.textarea.getValue(false, false);
|
bsw/jbe@1309
|
15647 if (textareaValue) {
|
bsw/jbe@1309
|
15648 this.composer.setValue(textareaValue, shouldParseHtml);
|
bsw/jbe@1309
|
15649 } else {
|
bsw/jbe@1309
|
15650 this.composer.clear();
|
bsw/jbe@1309
|
15651 this.editor.fire("set_placeholder");
|
bsw/jbe@1309
|
15652 }
|
bsw/jbe@1309
|
15653 },
|
bsw/jbe@1309
|
15654
|
bsw/jbe@1309
|
15655 /**
|
bsw/jbe@1309
|
15656 * Invoke syncing based on view state
|
bsw/jbe@1309
|
15657 * @param {Boolean} shouldParseHtml Whether the html should be sanitized before inserting it into the composer/textarea
|
bsw/jbe@1309
|
15658 */
|
bsw/jbe@1309
|
15659 sync: function(shouldParseHtml) {
|
bsw/jbe@1309
|
15660 if (this.editor.currentView.name === "textarea") {
|
bsw/jbe@1309
|
15661 this.fromTextareaToComposer(shouldParseHtml);
|
bsw/jbe@1309
|
15662 } else {
|
bsw/jbe@1309
|
15663 this.fromComposerToTextarea(shouldParseHtml);
|
bsw/jbe@1309
|
15664 }
|
bsw/jbe@1309
|
15665 },
|
bsw/jbe@1309
|
15666
|
bsw/jbe@1309
|
15667 /**
|
bsw/jbe@1309
|
15668 * Initializes interval-based syncing
|
bsw/jbe@1309
|
15669 * also makes sure that on-submit the composer's content is synced with the textarea
|
bsw/jbe@1309
|
15670 * immediately when the form gets submitted
|
bsw/jbe@1309
|
15671 */
|
bsw/jbe@1309
|
15672 _observe: function() {
|
bsw/jbe@1309
|
15673 var interval,
|
bsw/jbe@1309
|
15674 that = this,
|
bsw/jbe@1309
|
15675 form = this.textarea.element.form,
|
bsw/jbe@1309
|
15676 startInterval = function() {
|
bsw/jbe@1309
|
15677 interval = setInterval(function() { that.fromComposerToTextarea(); }, INTERVAL);
|
bsw/jbe@1309
|
15678 },
|
bsw/jbe@1309
|
15679 stopInterval = function() {
|
bsw/jbe@1309
|
15680 clearInterval(interval);
|
bsw/jbe@1309
|
15681 interval = null;
|
bsw/jbe@1309
|
15682 };
|
bsw/jbe@1309
|
15683
|
bsw/jbe@1309
|
15684 startInterval();
|
bsw/jbe@1309
|
15685
|
bsw/jbe@1309
|
15686 if (form) {
|
bsw/jbe@1309
|
15687 // If the textarea is in a form make sure that after onreset and onsubmit the composer
|
bsw/jbe@1309
|
15688 // has the correct state
|
bsw/jbe@1309
|
15689 wysihtml.dom.observe(form, "submit", function() {
|
bsw/jbe@1309
|
15690 that.sync(true);
|
bsw/jbe@1309
|
15691 });
|
bsw/jbe@1309
|
15692 wysihtml.dom.observe(form, "reset", function() {
|
bsw/jbe@1309
|
15693 setTimeout(function() { that.fromTextareaToComposer(); }, 0);
|
bsw/jbe@1309
|
15694 });
|
bsw/jbe@1309
|
15695 }
|
bsw/jbe@1309
|
15696
|
bsw/jbe@1309
|
15697 this.editor.on("change_view", function(view) {
|
bsw/jbe@1309
|
15698 if (view === "composer" && !interval) {
|
bsw/jbe@1309
|
15699 that.fromTextareaToComposer(true);
|
bsw/jbe@1309
|
15700 startInterval();
|
bsw/jbe@1309
|
15701 } else if (view === "textarea") {
|
bsw/jbe@1309
|
15702 that.fromComposerToTextarea(true);
|
bsw/jbe@1309
|
15703 stopInterval();
|
bsw/jbe@1309
|
15704 }
|
bsw/jbe@1309
|
15705 });
|
bsw/jbe@1309
|
15706
|
bsw/jbe@1309
|
15707 this.editor.on("destroy:composer", stopInterval);
|
bsw/jbe@1309
|
15708 }
|
bsw/jbe@1309
|
15709 });
|
bsw/jbe@1309
|
15710 })(wysihtml);
|
bsw/jbe@1309
|
15711
|
bsw/jbe@1309
|
15712 (function(wysihtml) {
|
bsw/jbe@1309
|
15713
|
bsw/jbe@1309
|
15714 wysihtml.views.SourceView = Base.extend(
|
bsw/jbe@1309
|
15715 /** @scope wysihtml.views.SourceView.prototype */ {
|
bsw/jbe@1309
|
15716
|
bsw/jbe@1309
|
15717 constructor: function(editor, composer) {
|
bsw/jbe@1309
|
15718 this.editor = editor;
|
bsw/jbe@1309
|
15719 this.composer = composer;
|
bsw/jbe@1309
|
15720
|
bsw/jbe@1309
|
15721 this._observe();
|
bsw/jbe@1309
|
15722 },
|
bsw/jbe@1309
|
15723
|
bsw/jbe@1309
|
15724 switchToTextarea: function(shouldParseHtml) {
|
bsw/jbe@1309
|
15725 var composerStyles = this.composer.win.getComputedStyle(this.composer.element),
|
bsw/jbe@1309
|
15726 width = parseFloat(composerStyles.width),
|
bsw/jbe@1309
|
15727 height = Math.max(parseFloat(composerStyles.height), 100);
|
bsw/jbe@1309
|
15728
|
bsw/jbe@1309
|
15729 if (!this.textarea) {
|
bsw/jbe@1309
|
15730 this.textarea = this.composer.doc.createElement('textarea');
|
bsw/jbe@1309
|
15731 this.textarea.className = "wysihtml-source-view";
|
bsw/jbe@1309
|
15732 }
|
bsw/jbe@1309
|
15733 this.textarea.style.width = width + 'px';
|
bsw/jbe@1309
|
15734 this.textarea.style.height = height + 'px';
|
bsw/jbe@1309
|
15735 this.textarea.value = this.editor.getValue(shouldParseHtml, true);
|
bsw/jbe@1309
|
15736 this.composer.element.parentNode.insertBefore(this.textarea, this.composer.element);
|
bsw/jbe@1309
|
15737 this.editor.currentView = "source";
|
bsw/jbe@1309
|
15738 this.composer.element.style.display = 'none';
|
bsw/jbe@1309
|
15739 },
|
bsw/jbe@1309
|
15740
|
bsw/jbe@1309
|
15741 switchToComposer: function(shouldParseHtml) {
|
bsw/jbe@1309
|
15742 var textareaValue = this.textarea.value;
|
bsw/jbe@1309
|
15743 if (textareaValue) {
|
bsw/jbe@1309
|
15744 this.composer.setValue(textareaValue, shouldParseHtml);
|
bsw/jbe@1309
|
15745 } else {
|
bsw/jbe@1309
|
15746 this.composer.clear();
|
bsw/jbe@1309
|
15747 this.editor.fire("set_placeholder");
|
bsw/jbe@1309
|
15748 }
|
bsw/jbe@1309
|
15749 this.textarea.parentNode.removeChild(this.textarea);
|
bsw/jbe@1309
|
15750 this.editor.currentView = this.composer;
|
bsw/jbe@1309
|
15751 this.composer.element.style.display = '';
|
bsw/jbe@1309
|
15752 },
|
bsw/jbe@1309
|
15753
|
bsw/jbe@1309
|
15754 _observe: function() {
|
bsw/jbe@1309
|
15755 this.editor.on("change_view", function(view) {
|
bsw/jbe@1309
|
15756 if (view === "composer") {
|
bsw/jbe@1309
|
15757 this.switchToComposer(true);
|
bsw/jbe@1309
|
15758 } else if (view === "textarea") {
|
bsw/jbe@1309
|
15759 this.switchToTextarea(true);
|
bsw/jbe@1309
|
15760 }
|
bsw/jbe@1309
|
15761 }.bind(this));
|
bsw/jbe@1309
|
15762 }
|
bsw/jbe@1309
|
15763
|
bsw/jbe@1309
|
15764 });
|
bsw/jbe@1309
|
15765
|
bsw/jbe@1309
|
15766 })(wysihtml);
|
bsw/jbe@1309
|
15767
|
bsw/jbe@1309
|
15768 wysihtml.views.Textarea = wysihtml.views.View.extend(
|
bsw/jbe@1309
|
15769 /** @scope wysihtml.views.Textarea.prototype */ {
|
bsw/jbe@1309
|
15770 name: "textarea",
|
bsw/jbe@1309
|
15771
|
bsw/jbe@1309
|
15772 constructor: function(parent, textareaElement, config) {
|
bsw/jbe@1309
|
15773 this.base(parent, textareaElement, config);
|
bsw/jbe@1309
|
15774
|
bsw/jbe@1309
|
15775 this._observe();
|
bsw/jbe@1309
|
15776 },
|
bsw/jbe@1309
|
15777
|
bsw/jbe@1309
|
15778 clear: function() {
|
bsw/jbe@1309
|
15779 this.element.value = "";
|
bsw/jbe@1309
|
15780 },
|
bsw/jbe@1309
|
15781
|
bsw/jbe@1309
|
15782 getValue: function(parse) {
|
bsw/jbe@1309
|
15783 var value = this.isEmpty() ? "" : this.element.value;
|
bsw/jbe@1309
|
15784 if (parse !== false) {
|
bsw/jbe@1309
|
15785 value = this.parent.parse(value);
|
bsw/jbe@1309
|
15786 }
|
bsw/jbe@1309
|
15787 return value;
|
bsw/jbe@1309
|
15788 },
|
bsw/jbe@1309
|
15789
|
bsw/jbe@1309
|
15790 setValue: function(html, parse) {
|
bsw/jbe@1309
|
15791 if (parse !== false) {
|
bsw/jbe@1309
|
15792 html = this.parent.parse(html);
|
bsw/jbe@1309
|
15793 }
|
bsw/jbe@1309
|
15794 this.element.value = html;
|
bsw/jbe@1309
|
15795 },
|
bsw/jbe@1309
|
15796
|
bsw/jbe@1309
|
15797 cleanUp: function(rules) {
|
bsw/jbe@1309
|
15798 var html = this.parent.parse(this.element.value, undefined, rules);
|
bsw/jbe@1309
|
15799 this.element.value = html;
|
bsw/jbe@1309
|
15800 },
|
bsw/jbe@1309
|
15801
|
bsw/jbe@1309
|
15802 hasPlaceholderSet: function() {
|
bsw/jbe@1309
|
15803 var supportsPlaceholder = wysihtml.browser.supportsPlaceholderAttributeOn(this.element),
|
bsw/jbe@1309
|
15804 placeholderText = this.element.getAttribute("placeholder") || null,
|
bsw/jbe@1309
|
15805 value = this.element.value,
|
bsw/jbe@1309
|
15806 isEmpty = !value;
|
bsw/jbe@1309
|
15807 return (supportsPlaceholder && isEmpty) || (value === placeholderText);
|
bsw/jbe@1309
|
15808 },
|
bsw/jbe@1309
|
15809
|
bsw/jbe@1309
|
15810 isEmpty: function() {
|
bsw/jbe@1309
|
15811 return !wysihtml.lang.string(this.element.value).trim() || this.hasPlaceholderSet();
|
bsw/jbe@1309
|
15812 },
|
bsw/jbe@1309
|
15813
|
bsw/jbe@1309
|
15814 _observe: function() {
|
bsw/jbe@1309
|
15815 var element = this.element,
|
bsw/jbe@1309
|
15816 parent = this.parent,
|
bsw/jbe@1309
|
15817 eventMapping = {
|
bsw/jbe@1309
|
15818 focusin: "focus",
|
bsw/jbe@1309
|
15819 focusout: "blur"
|
bsw/jbe@1309
|
15820 },
|
bsw/jbe@1309
|
15821 /**
|
bsw/jbe@1309
|
15822 * Calling focus() or blur() on an element doesn't synchronously trigger the attached focus/blur events
|
bsw/jbe@1309
|
15823 * This is the case for focusin and focusout, so let's use them whenever possible, kkthxbai
|
bsw/jbe@1309
|
15824 */
|
bsw/jbe@1309
|
15825 events = wysihtml.browser.supportsEvent("focusin") ? ["focusin", "focusout", "change"] : ["focus", "blur", "change"];
|
bsw/jbe@1309
|
15826
|
bsw/jbe@1309
|
15827 parent.on("beforeload", function() {
|
bsw/jbe@1309
|
15828 wysihtml.dom.observe(element, events, function(event) {
|
bsw/jbe@1309
|
15829 var eventName = eventMapping[event.type] || event.type;
|
bsw/jbe@1309
|
15830 parent.fire(eventName).fire(eventName + ":textarea");
|
bsw/jbe@1309
|
15831 });
|
bsw/jbe@1309
|
15832
|
bsw/jbe@1309
|
15833 wysihtml.dom.observe(element, ["paste", "drop"], function() {
|
bsw/jbe@1309
|
15834 setTimeout(function() { parent.fire("paste").fire("paste:textarea"); }, 0);
|
bsw/jbe@1309
|
15835 });
|
bsw/jbe@1309
|
15836 });
|
bsw/jbe@1309
|
15837 }
|
bsw/jbe@1309
|
15838 });
|
bsw/jbe@1309
|
15839
|
bsw/jbe@1309
|
15840 /**
|
bsw/jbe@1309
|
15841 * WYSIHTML Editor
|
bsw/jbe@1309
|
15842 *
|
bsw/jbe@1309
|
15843 * @param {Element} editableElement Reference to the textarea which should be turned into a rich text interface
|
bsw/jbe@1309
|
15844 * @param {Object} [config] See defaults object below for explanation of each individual config option
|
bsw/jbe@1309
|
15845 *
|
bsw/jbe@1309
|
15846 * @events
|
bsw/jbe@1309
|
15847 * load
|
bsw/jbe@1309
|
15848 * beforeload (for internal use only)
|
bsw/jbe@1309
|
15849 * focus
|
bsw/jbe@1309
|
15850 * focus:composer
|
bsw/jbe@1309
|
15851 * focus:textarea
|
bsw/jbe@1309
|
15852 * blur
|
bsw/jbe@1309
|
15853 * blur:composer
|
bsw/jbe@1309
|
15854 * blur:textarea
|
bsw/jbe@1309
|
15855 * change
|
bsw/jbe@1309
|
15856 * change:composer
|
bsw/jbe@1309
|
15857 * change:textarea
|
bsw/jbe@1309
|
15858 * paste
|
bsw/jbe@1309
|
15859 * paste:composer
|
bsw/jbe@1309
|
15860 * paste:textarea
|
bsw/jbe@1309
|
15861 * newword:composer
|
bsw/jbe@1309
|
15862 * destroy:composer
|
bsw/jbe@1309
|
15863 * undo:composer
|
bsw/jbe@1309
|
15864 * redo:composer
|
bsw/jbe@1309
|
15865 * beforecommand:composer
|
bsw/jbe@1309
|
15866 * aftercommand:composer
|
bsw/jbe@1309
|
15867 * enable:composer
|
bsw/jbe@1309
|
15868 * disable:composer
|
bsw/jbe@1309
|
15869 * change_view
|
bsw/jbe@1309
|
15870 */
|
bsw/jbe@1309
|
15871 (function(wysihtml) {
|
bsw/jbe@1309
|
15872 var undef;
|
bsw/jbe@1309
|
15873
|
bsw/jbe@1309
|
15874 wysihtml.Editor = wysihtml.lang.Dispatcher.extend({
|
bsw/jbe@1309
|
15875 /** @scope wysihtml.Editor.prototype */
|
bsw/jbe@1309
|
15876 defaults: {
|
bsw/jbe@1309
|
15877 // Give the editor a name, the name will also be set as class name on the iframe and on the iframe's body
|
bsw/jbe@1309
|
15878 name: undef,
|
bsw/jbe@1309
|
15879 // Whether the editor should look like the textarea (by adopting styles)
|
bsw/jbe@1309
|
15880 style: true,
|
bsw/jbe@1309
|
15881 // Whether urls, entered by the user should automatically become clickable-links
|
bsw/jbe@1309
|
15882 autoLink: true,
|
bsw/jbe@1309
|
15883 // Tab key inserts tab into text as default behaviour. It can be disabled to regain keyboard navigation
|
bsw/jbe@1309
|
15884 handleTabKey: true,
|
bsw/jbe@1309
|
15885 // Object which includes parser rules to apply when html gets cleaned
|
bsw/jbe@1309
|
15886 // See parser_rules/*.js for examples
|
bsw/jbe@1309
|
15887 parserRules: { tags: { br: {}, span: {}, div: {}, p: {}, b: {}, i: {}, u: {} }, classes: {} },
|
bsw/jbe@1309
|
15888 // Object which includes parser when the user inserts content via copy & paste. If null parserRules will be used instead
|
bsw/jbe@1309
|
15889 pasteParserRulesets: null,
|
bsw/jbe@1309
|
15890 // Parser method to use when the user inserts content
|
bsw/jbe@1309
|
15891 parser: wysihtml.dom.parse,
|
bsw/jbe@1309
|
15892 // By default wysihtml will insert a <br> for line breaks, set this to false to use <p>
|
bsw/jbe@1309
|
15893 useLineBreaks: true,
|
bsw/jbe@1309
|
15894 // Double enter (enter on blank line) exits block element in useLineBreaks mode.
|
bsw/jbe@1309
|
15895 // It enables a way of escaping out of block elements and splitting block elements
|
bsw/jbe@1309
|
15896 doubleLineBreakEscapesBlock: true,
|
bsw/jbe@1309
|
15897 // Array (or single string) of stylesheet urls to be loaded in the editor's iframe
|
bsw/jbe@1309
|
15898 stylesheets: [],
|
bsw/jbe@1309
|
15899 // Placeholder text to use, defaults to the placeholder attribute on the textarea element
|
bsw/jbe@1309
|
15900 placeholderText: undef,
|
bsw/jbe@1309
|
15901 // Whether the rich text editor should be rendered on touch devices (wysihtml >= 0.3.0 comes with basic support for iOS 5)
|
bsw/jbe@1309
|
15902 supportTouchDevices: true,
|
bsw/jbe@1309
|
15903 // Whether senseless <span> elements (empty or without attributes) should be removed/replaced with their content
|
bsw/jbe@1309
|
15904 cleanUp: true,
|
bsw/jbe@1309
|
15905 // Whether to use div instead of secure iframe
|
bsw/jbe@1309
|
15906 contentEditableMode: false,
|
bsw/jbe@1309
|
15907 classNames: {
|
bsw/jbe@1309
|
15908 // Class name which should be set on the contentEditable element in the created sandbox iframe, can be styled via the 'stylesheets' option
|
bsw/jbe@1309
|
15909 composer: "wysihtml-editor",
|
bsw/jbe@1309
|
15910 // Class name to add to the body when the wysihtml editor is supported
|
bsw/jbe@1309
|
15911 body: "wysihtml-supported",
|
bsw/jbe@1309
|
15912 // classname added to editable area element (iframe/div) on creation
|
bsw/jbe@1309
|
15913 sandbox: "wysihtml-sandbox",
|
bsw/jbe@1309
|
15914 // class on editable area with placeholder
|
bsw/jbe@1309
|
15915 placeholder: "wysihtml-placeholder",
|
bsw/jbe@1309
|
15916 // Classname of container that editor should not touch and pass through
|
bsw/jbe@1309
|
15917 uneditableContainer: "wysihtml-uneditable-container"
|
bsw/jbe@1309
|
15918 },
|
bsw/jbe@1309
|
15919 // Browsers that support copied source handling will get a marking of the origin of the copied source (for determinig code cleanup rules on paste)
|
bsw/jbe@1309
|
15920 // Also copied source is based directly on selection -
|
bsw/jbe@1309
|
15921 // (very useful for webkit based browsers where copy will otherwise contain a lot of code and styles based on whatever and not actually in selection).
|
bsw/jbe@1309
|
15922 // If falsy value is passed source override is also disabled
|
bsw/jbe@1309
|
15923 copyedFromMarking: '<meta name="copied-from" content="wysihtml">'
|
bsw/jbe@1309
|
15924 },
|
bsw/jbe@1309
|
15925
|
bsw/jbe@1309
|
15926 constructor: function(editableElement, config) {
|
bsw/jbe@1309
|
15927 this.editableElement = typeof(editableElement) === "string" ? document.getElementById(editableElement) : editableElement;
|
bsw/jbe@1309
|
15928 this.config = wysihtml.lang.object({}).merge(this.defaults).merge(config).get();
|
bsw/jbe@1309
|
15929 this._isCompatible = wysihtml.browser.supported();
|
bsw/jbe@1309
|
15930
|
bsw/jbe@1309
|
15931 // merge classNames
|
bsw/jbe@1309
|
15932 if (config && config.classNames) {
|
bsw/jbe@1309
|
15933 wysihtml.lang.object(this.config.classNames).merge(config.classNames);
|
bsw/jbe@1309
|
15934 }
|
bsw/jbe@1309
|
15935
|
bsw/jbe@1309
|
15936 if (this.editableElement.nodeName.toLowerCase() != "textarea") {
|
bsw/jbe@1309
|
15937 this.config.contentEditableMode = true;
|
bsw/jbe@1309
|
15938 this.config.noTextarea = true;
|
bsw/jbe@1309
|
15939 }
|
bsw/jbe@1309
|
15940 if (!this.config.noTextarea) {
|
bsw/jbe@1309
|
15941 this.textarea = new wysihtml.views.Textarea(this, this.editableElement, this.config);
|
bsw/jbe@1309
|
15942 this.currentView = this.textarea;
|
bsw/jbe@1309
|
15943 }
|
bsw/jbe@1309
|
15944
|
bsw/jbe@1309
|
15945 // Sort out unsupported/unwanted browsers here
|
bsw/jbe@1309
|
15946 if (!this._isCompatible || (!this.config.supportTouchDevices && wysihtml.browser.isTouchDevice())) {
|
bsw/jbe@1309
|
15947 var that = this;
|
bsw/jbe@1309
|
15948 setTimeout(function() { that.fire("beforeload").fire("load"); }, 0);
|
bsw/jbe@1309
|
15949 return;
|
bsw/jbe@1309
|
15950 }
|
bsw/jbe@1309
|
15951
|
bsw/jbe@1309
|
15952 // Add class name to body, to indicate that the editor is supported
|
bsw/jbe@1309
|
15953 wysihtml.dom.addClass(document.body, this.config.classNames.body);
|
bsw/jbe@1309
|
15954
|
bsw/jbe@1309
|
15955 this.composer = new wysihtml.views.Composer(this, this.editableElement, this.config);
|
bsw/jbe@1309
|
15956 this.currentView = this.composer;
|
bsw/jbe@1309
|
15957
|
bsw/jbe@1309
|
15958 if (typeof(this.config.parser) === "function") {
|
bsw/jbe@1309
|
15959 this._initParser();
|
bsw/jbe@1309
|
15960 }
|
bsw/jbe@1309
|
15961
|
bsw/jbe@1309
|
15962 this.on("beforeload", this.handleBeforeLoad);
|
bsw/jbe@1309
|
15963 },
|
bsw/jbe@1309
|
15964
|
bsw/jbe@1309
|
15965 handleBeforeLoad: function() {
|
bsw/jbe@1309
|
15966 if (!this.config.noTextarea) {
|
bsw/jbe@1309
|
15967 this.synchronizer = new wysihtml.views.Synchronizer(this, this.textarea, this.composer);
|
bsw/jbe@1309
|
15968 } else {
|
bsw/jbe@1309
|
15969 this.sourceView = new wysihtml.views.SourceView(this, this.composer);
|
bsw/jbe@1309
|
15970 }
|
bsw/jbe@1309
|
15971 this.runEditorExtenders();
|
bsw/jbe@1309
|
15972 },
|
bsw/jbe@1309
|
15973
|
bsw/jbe@1309
|
15974 runEditorExtenders: function() {
|
bsw/jbe@1309
|
15975 wysihtml.editorExtenders.forEach(function(extender) {
|
bsw/jbe@1309
|
15976 extender(this);
|
bsw/jbe@1309
|
15977 }.bind(this));
|
bsw/jbe@1309
|
15978 },
|
bsw/jbe@1309
|
15979
|
bsw/jbe@1309
|
15980 isCompatible: function() {
|
bsw/jbe@1309
|
15981 return this._isCompatible;
|
bsw/jbe@1309
|
15982 },
|
bsw/jbe@1309
|
15983
|
bsw/jbe@1309
|
15984 clear: function() {
|
bsw/jbe@1309
|
15985 this.currentView.clear();
|
bsw/jbe@1309
|
15986 return this;
|
bsw/jbe@1309
|
15987 },
|
bsw/jbe@1309
|
15988
|
bsw/jbe@1309
|
15989 getValue: function(parse, clearInternals) {
|
bsw/jbe@1309
|
15990 return this.currentView.getValue(parse, clearInternals);
|
bsw/jbe@1309
|
15991 },
|
bsw/jbe@1309
|
15992
|
bsw/jbe@1309
|
15993 setValue: function(html, parse) {
|
bsw/jbe@1309
|
15994 this.fire("unset_placeholder");
|
bsw/jbe@1309
|
15995
|
bsw/jbe@1309
|
15996 if (!html) {
|
bsw/jbe@1309
|
15997 return this.clear();
|
bsw/jbe@1309
|
15998 }
|
bsw/jbe@1309
|
15999
|
bsw/jbe@1309
|
16000 this.currentView.setValue(html, parse);
|
bsw/jbe@1309
|
16001 return this;
|
bsw/jbe@1309
|
16002 },
|
bsw/jbe@1309
|
16003
|
bsw/jbe@1309
|
16004 cleanUp: function(rules) {
|
bsw/jbe@1309
|
16005 this.currentView.cleanUp(rules);
|
bsw/jbe@1309
|
16006 },
|
bsw/jbe@1309
|
16007
|
bsw/jbe@1309
|
16008 focus: function(setToEnd) {
|
bsw/jbe@1309
|
16009 this.currentView.focus(setToEnd);
|
bsw/jbe@1309
|
16010 return this;
|
bsw/jbe@1309
|
16011 },
|
bsw/jbe@1309
|
16012
|
bsw/jbe@1309
|
16013 /**
|
bsw/jbe@1309
|
16014 * Deactivate editor (make it readonly)
|
bsw/jbe@1309
|
16015 */
|
bsw/jbe@1309
|
16016 disable: function() {
|
bsw/jbe@1309
|
16017 this.currentView.disable();
|
bsw/jbe@1309
|
16018 return this;
|
bsw/jbe@1309
|
16019 },
|
bsw/jbe@1309
|
16020
|
bsw/jbe@1309
|
16021 /**
|
bsw/jbe@1309
|
16022 * Activate editor
|
bsw/jbe@1309
|
16023 */
|
bsw/jbe@1309
|
16024 enable: function() {
|
bsw/jbe@1309
|
16025 this.currentView.enable();
|
bsw/jbe@1309
|
16026 return this;
|
bsw/jbe@1309
|
16027 },
|
bsw/jbe@1309
|
16028
|
bsw/jbe@1309
|
16029 isEmpty: function() {
|
bsw/jbe@1309
|
16030 return this.currentView.isEmpty();
|
bsw/jbe@1309
|
16031 },
|
bsw/jbe@1309
|
16032
|
bsw/jbe@1309
|
16033 hasPlaceholderSet: function() {
|
bsw/jbe@1309
|
16034 return this.currentView.hasPlaceholderSet();
|
bsw/jbe@1309
|
16035 },
|
bsw/jbe@1309
|
16036
|
bsw/jbe@1309
|
16037 destroy: function() {
|
bsw/jbe@1309
|
16038 if (this.composer && this.composer.sandbox) {
|
bsw/jbe@1309
|
16039 this.composer.sandbox.destroy();
|
bsw/jbe@1309
|
16040 }
|
bsw/jbe@1309
|
16041 this.fire("destroy:composer");
|
bsw/jbe@1309
|
16042 this.off();
|
bsw/jbe@1309
|
16043 },
|
bsw/jbe@1309
|
16044
|
bsw/jbe@1309
|
16045 parse: function(htmlOrElement, clearInternals, customRules) {
|
bsw/jbe@1309
|
16046 var parseContext = (this.config.contentEditableMode) ? document : ((this.composer) ? this.composer.sandbox.getDocument() : null);
|
bsw/jbe@1309
|
16047 var returnValue = this.config.parser(htmlOrElement, {
|
bsw/jbe@1309
|
16048 "rules": customRules || this.config.parserRules,
|
bsw/jbe@1309
|
16049 "cleanUp": this.config.cleanUp,
|
bsw/jbe@1309
|
16050 "context": parseContext,
|
bsw/jbe@1309
|
16051 "uneditableClass": this.config.classNames.uneditableContainer,
|
bsw/jbe@1309
|
16052 "clearInternals" : clearInternals
|
bsw/jbe@1309
|
16053 });
|
bsw/jbe@1309
|
16054 if (typeof(htmlOrElement) === "object") {
|
bsw/jbe@1309
|
16055 wysihtml.quirks.redraw(htmlOrElement);
|
bsw/jbe@1309
|
16056 }
|
bsw/jbe@1309
|
16057 return returnValue;
|
bsw/jbe@1309
|
16058 },
|
bsw/jbe@1309
|
16059
|
bsw/jbe@1309
|
16060 /**
|
bsw/jbe@1309
|
16061 * Prepare html parser logic
|
bsw/jbe@1309
|
16062 * - Observes for paste and drop
|
bsw/jbe@1309
|
16063 */
|
bsw/jbe@1309
|
16064 _initParser: function() {
|
bsw/jbe@1309
|
16065 var oldHtml;
|
bsw/jbe@1309
|
16066
|
bsw/jbe@1309
|
16067 if (wysihtml.browser.supportsModernPaste()) {
|
bsw/jbe@1309
|
16068 this.on("paste:composer", function(event) {
|
bsw/jbe@1309
|
16069 event.preventDefault();
|
bsw/jbe@1309
|
16070 oldHtml = wysihtml.dom.getPastedHtml(event);
|
bsw/jbe@1309
|
16071 if (oldHtml) {
|
bsw/jbe@1309
|
16072 this._cleanAndPaste(oldHtml);
|
bsw/jbe@1309
|
16073 }
|
bsw/jbe@1309
|
16074 }.bind(this));
|
bsw/jbe@1309
|
16075
|
bsw/jbe@1309
|
16076 } else {
|
bsw/jbe@1309
|
16077 this.on("beforepaste:composer", function(event) {
|
bsw/jbe@1309
|
16078 event.preventDefault();
|
bsw/jbe@1309
|
16079 var scrollPos = this.composer.getScrollPos();
|
bsw/jbe@1309
|
16080
|
bsw/jbe@1309
|
16081 wysihtml.dom.getPastedHtmlWithDiv(this.composer, function(pastedHTML) {
|
bsw/jbe@1309
|
16082 if (pastedHTML) {
|
bsw/jbe@1309
|
16083 this._cleanAndPaste(pastedHTML);
|
bsw/jbe@1309
|
16084 }
|
bsw/jbe@1309
|
16085 this.composer.setScrollPos(scrollPos);
|
bsw/jbe@1309
|
16086 }.bind(this));
|
bsw/jbe@1309
|
16087
|
bsw/jbe@1309
|
16088 }.bind(this));
|
bsw/jbe@1309
|
16089 }
|
bsw/jbe@1309
|
16090 },
|
bsw/jbe@1309
|
16091
|
bsw/jbe@1309
|
16092 _cleanAndPaste: function (oldHtml) {
|
bsw/jbe@1309
|
16093 var cleanHtml = wysihtml.quirks.cleanPastedHTML(oldHtml, {
|
bsw/jbe@1309
|
16094 "referenceNode": this.composer.element,
|
bsw/jbe@1309
|
16095 "rules": this.config.pasteParserRulesets || [{"set": this.config.parserRules}],
|
bsw/jbe@1309
|
16096 "uneditableClass": this.config.classNames.uneditableContainer
|
bsw/jbe@1309
|
16097 });
|
bsw/jbe@1309
|
16098 this.composer.selection.deleteContents();
|
bsw/jbe@1309
|
16099 this.composer.selection.insertHTML(cleanHtml);
|
bsw/jbe@1309
|
16100 }
|
bsw/jbe@1309
|
16101 });
|
bsw/jbe@1309
|
16102 })(wysihtml);
|