1 | define([ |
---|
2 | "dojo/_base/declare", // declare |
---|
3 | "dojo/dom-construct", // domConstruct.destroy domConstruct.place |
---|
4 | "dojo/keys", // keys.ENTER |
---|
5 | "dojo/_base/lang", |
---|
6 | "dojo/on", |
---|
7 | "dojo/sniff", // has("ie") has("mozilla") has("webkit") |
---|
8 | "dojo/_base/window", // win.withGlobal |
---|
9 | "dojo/window", // winUtils.scrollIntoView |
---|
10 | "../_Plugin", |
---|
11 | "../RichText", |
---|
12 | "../range", |
---|
13 | "../../_base/focus" |
---|
14 | ], function(declare, domConstruct, keys, lang, on, has, win, winUtils, _Plugin, RichText, rangeapi, baseFocus){ |
---|
15 | |
---|
16 | // module: |
---|
17 | // dijit/_editor/plugins/EnterKeyHandling |
---|
18 | |
---|
19 | return declare("dijit._editor.plugins.EnterKeyHandling", _Plugin, { |
---|
20 | // summary: |
---|
21 | // This plugin tries to make all browsers behave consistently with regard to |
---|
22 | // how ENTER behaves in the editor window. It traps the ENTER key and alters |
---|
23 | // the way DOM is constructed in certain cases to try to commonize the generated |
---|
24 | // DOM and behaviors across browsers. |
---|
25 | // |
---|
26 | // description: |
---|
27 | // This plugin has three modes: |
---|
28 | // |
---|
29 | // - blockNodeForEnter=BR |
---|
30 | // - blockNodeForEnter=DIV |
---|
31 | // - blockNodeForEnter=P |
---|
32 | // |
---|
33 | // In blockNodeForEnter=P, the ENTER key starts a new |
---|
34 | // paragraph, and shift-ENTER starts a new line in the current paragraph. |
---|
35 | // For example, the input: |
---|
36 | // |
---|
37 | // | first paragraph <shift-ENTER> |
---|
38 | // | second line of first paragraph <ENTER> |
---|
39 | // | second paragraph |
---|
40 | // |
---|
41 | // will generate: |
---|
42 | // |
---|
43 | // | <p> |
---|
44 | // | first paragraph |
---|
45 | // | <br/> |
---|
46 | // | second line of first paragraph |
---|
47 | // | </p> |
---|
48 | // | <p> |
---|
49 | // | second paragraph |
---|
50 | // | </p> |
---|
51 | // |
---|
52 | // In BR and DIV mode, the ENTER key conceptually goes to a new line in the |
---|
53 | // current paragraph, and users conceptually create a new paragraph by pressing ENTER twice. |
---|
54 | // For example, if the user enters text into an editor like this: |
---|
55 | // |
---|
56 | // | one <ENTER> |
---|
57 | // | two <ENTER> |
---|
58 | // | three <ENTER> |
---|
59 | // | <ENTER> |
---|
60 | // | four <ENTER> |
---|
61 | // | five <ENTER> |
---|
62 | // | six <ENTER> |
---|
63 | // |
---|
64 | // It will appear on the screen as two 'paragraphs' of three lines each. Markupwise, this generates: |
---|
65 | // |
---|
66 | // BR: |
---|
67 | // | one<br/> |
---|
68 | // | two<br/> |
---|
69 | // | three<br/> |
---|
70 | // | <br/> |
---|
71 | // | four<br/> |
---|
72 | // | five<br/> |
---|
73 | // | six<br/> |
---|
74 | // |
---|
75 | // DIV: |
---|
76 | // | <div>one</div> |
---|
77 | // | <div>two</div> |
---|
78 | // | <div>three</div> |
---|
79 | // | <div> </div> |
---|
80 | // | <div>four</div> |
---|
81 | // | <div>five</div> |
---|
82 | // | <div>six</div> |
---|
83 | |
---|
84 | // blockNodeForEnter: String |
---|
85 | // This property decides the behavior of Enter key. It can be either P, |
---|
86 | // DIV, BR, or empty (which means disable this feature). Anything else |
---|
87 | // will trigger errors. The default is 'BR' |
---|
88 | // |
---|
89 | // See class description for more details. |
---|
90 | blockNodeForEnter: 'BR', |
---|
91 | |
---|
92 | constructor: function(args){ |
---|
93 | if(args){ |
---|
94 | if("blockNodeForEnter" in args){ |
---|
95 | args.blockNodeForEnter = args.blockNodeForEnter.toUpperCase(); |
---|
96 | } |
---|
97 | lang.mixin(this, args); |
---|
98 | } |
---|
99 | }, |
---|
100 | |
---|
101 | setEditor: function(editor){ |
---|
102 | // Overrides _Plugin.setEditor(). |
---|
103 | if(this.editor === editor){ |
---|
104 | return; |
---|
105 | } |
---|
106 | this.editor = editor; |
---|
107 | if(this.blockNodeForEnter == 'BR'){ |
---|
108 | // While Moz has a mode tht mostly works, it's still a little different, |
---|
109 | // So, try to just have a common mode and be consistent. Which means |
---|
110 | // we need to enable customUndo, if not already enabled. |
---|
111 | this.editor.customUndo = true; |
---|
112 | editor.onLoadDeferred.then(lang.hitch(this, function(d){ |
---|
113 | this.own(on(editor.document, "keydown", lang.hitch(this, function(e){ |
---|
114 | if(e.keyCode == keys.ENTER){ |
---|
115 | // Just do it manually. The handleEnterKey has a shift mode that |
---|
116 | // Always acts like <br>, so just use it. |
---|
117 | var ne = lang.mixin({}, e); |
---|
118 | ne.shiftKey = true; |
---|
119 | if(!this.handleEnterKey(ne)){ |
---|
120 | e.stopPropagation(); |
---|
121 | e.preventDefault(); |
---|
122 | } |
---|
123 | } |
---|
124 | }))); |
---|
125 | if(has("ie") >= 9 && has("ie") <= 10){ |
---|
126 | this.own(on(editor.document, "paste", lang.hitch(this, function(e){ |
---|
127 | setTimeout(lang.hitch(this, function(){ |
---|
128 | // Use the old range/selection code to kick IE 9 into updating |
---|
129 | // its range by moving it back, then forward, one 'character'. |
---|
130 | var r = this.editor.document.selection.createRange(); |
---|
131 | r.move('character', -1); |
---|
132 | r.select(); |
---|
133 | r.move('character', 1); |
---|
134 | r.select(); |
---|
135 | }), 0); |
---|
136 | }))); |
---|
137 | } |
---|
138 | return d; |
---|
139 | })); |
---|
140 | }else if(this.blockNodeForEnter){ |
---|
141 | // add enter key handler |
---|
142 | var h = lang.hitch(this, "handleEnterKey"); |
---|
143 | editor.addKeyHandler(13, 0, 0, h); //enter |
---|
144 | editor.addKeyHandler(13, 0, 1, h); //shift+enter |
---|
145 | this.own(this.editor.on('KeyPressed', lang.hitch(this, 'onKeyPressed'))); |
---|
146 | } |
---|
147 | }, |
---|
148 | onKeyPressed: function(){ |
---|
149 | // summary: |
---|
150 | // Handler for after the user has pressed a key, and the display has been updated. |
---|
151 | // Connected to RichText's onKeyPressed() method. |
---|
152 | // tags: |
---|
153 | // private |
---|
154 | if(this._checkListLater){ |
---|
155 | if(win.withGlobal(this.editor.window, 'isCollapsed', baseFocus)){ // TODO: stop using withGlobal(), and baseFocus |
---|
156 | var liparent = this.editor.selection.getAncestorElement('LI'); |
---|
157 | if(!liparent){ |
---|
158 | // circulate the undo detection code by calling RichText::execCommand directly |
---|
159 | RichText.prototype.execCommand.call(this.editor, 'formatblock', this.blockNodeForEnter); |
---|
160 | // set the innerHTML of the new block node |
---|
161 | var block = this.editor.selection.getAncestorElement(this.blockNodeForEnter); |
---|
162 | if(block){ |
---|
163 | block.innerHTML = this.bogusHtmlContent; |
---|
164 | if(has("ie") <= 9){ |
---|
165 | // move to the start by moving backwards one char |
---|
166 | var r = this.editor.document.selection.createRange(); |
---|
167 | r.move('character', -1); |
---|
168 | r.select(); |
---|
169 | } |
---|
170 | }else{ |
---|
171 | console.error('onKeyPressed: Cannot find the new block node'); // FIXME |
---|
172 | } |
---|
173 | }else{ |
---|
174 | if(has("mozilla")){ |
---|
175 | if(liparent.parentNode.parentNode.nodeName == 'LI'){ |
---|
176 | liparent = liparent.parentNode.parentNode; |
---|
177 | } |
---|
178 | } |
---|
179 | var fc = liparent.firstChild; |
---|
180 | if(fc && fc.nodeType == 1 && (fc.nodeName == 'UL' || fc.nodeName == 'OL')){ |
---|
181 | liparent.insertBefore(fc.ownerDocument.createTextNode('\xA0'), fc); |
---|
182 | var newrange = rangeapi.create(this.editor.window); |
---|
183 | newrange.setStart(liparent.firstChild, 0); |
---|
184 | var selection = rangeapi.getSelection(this.editor.window, true); |
---|
185 | selection.removeAllRanges(); |
---|
186 | selection.addRange(newrange); |
---|
187 | } |
---|
188 | } |
---|
189 | } |
---|
190 | this._checkListLater = false; |
---|
191 | } |
---|
192 | if(this._pressedEnterInBlock){ |
---|
193 | // the new created is the original current P, so we have previousSibling below |
---|
194 | if(this._pressedEnterInBlock.previousSibling){ |
---|
195 | this.removeTrailingBr(this._pressedEnterInBlock.previousSibling); |
---|
196 | } |
---|
197 | delete this._pressedEnterInBlock; |
---|
198 | } |
---|
199 | }, |
---|
200 | |
---|
201 | // bogusHtmlContent: [private] String |
---|
202 | // HTML to stick into a new empty block |
---|
203 | bogusHtmlContent: ' ', // |
---|
204 | |
---|
205 | // blockNodes: [private] Regex |
---|
206 | // Regex for testing if a given tag is a block level (display:block) tag |
---|
207 | blockNodes: /^(?:P|H1|H2|H3|H4|H5|H6|LI)$/, |
---|
208 | |
---|
209 | handleEnterKey: function(e){ |
---|
210 | // summary: |
---|
211 | // Handler for enter key events when blockNodeForEnter is DIV or P. |
---|
212 | // description: |
---|
213 | // Manually handle enter key event to make the behavior consistent across |
---|
214 | // all supported browsers. See class description for details. |
---|
215 | // tags: |
---|
216 | // private |
---|
217 | |
---|
218 | var selection, range, newrange, startNode, endNode, brNode, doc = this.editor.document, br, rs, txt; |
---|
219 | if(e.shiftKey){ // shift+enter always generates <br> |
---|
220 | var parent = this.editor.selection.getParentElement(); |
---|
221 | var header = rangeapi.getAncestor(parent, this.blockNodes); |
---|
222 | if(header){ |
---|
223 | if(header.tagName == 'LI'){ |
---|
224 | return true; // let browser handle |
---|
225 | } |
---|
226 | selection = rangeapi.getSelection(this.editor.window); |
---|
227 | range = selection.getRangeAt(0); |
---|
228 | if(!range.collapsed){ |
---|
229 | range.deleteContents(); |
---|
230 | selection = rangeapi.getSelection(this.editor.window); |
---|
231 | range = selection.getRangeAt(0); |
---|
232 | } |
---|
233 | if(rangeapi.atBeginningOfContainer(header, range.startContainer, range.startOffset)){ |
---|
234 | br = doc.createElement('br'); |
---|
235 | newrange = rangeapi.create(this.editor.window); |
---|
236 | header.insertBefore(br, header.firstChild); |
---|
237 | newrange.setStartAfter(br); |
---|
238 | selection.removeAllRanges(); |
---|
239 | selection.addRange(newrange); |
---|
240 | }else if(rangeapi.atEndOfContainer(header, range.startContainer, range.startOffset)){ |
---|
241 | newrange = rangeapi.create(this.editor.window); |
---|
242 | br = doc.createElement('br'); |
---|
243 | header.appendChild(br); |
---|
244 | header.appendChild(doc.createTextNode('\xA0')); |
---|
245 | newrange.setStart(header.lastChild, 0); |
---|
246 | selection.removeAllRanges(); |
---|
247 | selection.addRange(newrange); |
---|
248 | }else{ |
---|
249 | rs = range.startContainer; |
---|
250 | if(rs && rs.nodeType == 3){ |
---|
251 | // Text node, we have to split it. |
---|
252 | txt = rs.nodeValue; |
---|
253 | startNode = doc.createTextNode(txt.substring(0, range.startOffset)); |
---|
254 | endNode = doc.createTextNode(txt.substring(range.startOffset)); |
---|
255 | brNode = doc.createElement("br"); |
---|
256 | |
---|
257 | if(endNode.nodeValue == "" && has("webkit")){ |
---|
258 | endNode = doc.createTextNode('\xA0') |
---|
259 | } |
---|
260 | domConstruct.place(startNode, rs, "after"); |
---|
261 | domConstruct.place(brNode, startNode, "after"); |
---|
262 | domConstruct.place(endNode, brNode, "after"); |
---|
263 | domConstruct.destroy(rs); |
---|
264 | newrange = rangeapi.create(this.editor.window); |
---|
265 | newrange.setStart(endNode, 0); |
---|
266 | selection.removeAllRanges(); |
---|
267 | selection.addRange(newrange); |
---|
268 | return false; |
---|
269 | } |
---|
270 | return true; // let browser handle |
---|
271 | } |
---|
272 | }else{ |
---|
273 | selection = rangeapi.getSelection(this.editor.window); |
---|
274 | if(selection.rangeCount){ |
---|
275 | range = selection.getRangeAt(0); |
---|
276 | if(range && range.startContainer){ |
---|
277 | if(!range.collapsed){ |
---|
278 | range.deleteContents(); |
---|
279 | selection = rangeapi.getSelection(this.editor.window); |
---|
280 | range = selection.getRangeAt(0); |
---|
281 | } |
---|
282 | rs = range.startContainer; |
---|
283 | if(rs && rs.nodeType == 3){ |
---|
284 | // Text node, we have to split it. |
---|
285 | |
---|
286 | var offset = range.startOffset; |
---|
287 | if(rs.length < offset){ |
---|
288 | //We are not splitting the right node, try to locate the correct one |
---|
289 | ret = this._adjustNodeAndOffset(rs, offset); |
---|
290 | rs = ret.node; |
---|
291 | offset = ret.offset; |
---|
292 | } |
---|
293 | txt = rs.nodeValue; |
---|
294 | |
---|
295 | startNode = doc.createTextNode(txt.substring(0, offset)); |
---|
296 | endNode = doc.createTextNode(txt.substring(offset)); |
---|
297 | brNode = doc.createElement("br"); |
---|
298 | |
---|
299 | if(!endNode.length){ |
---|
300 | // Create dummy text with a   to go after the BR, to prevent IE crash. |
---|
301 | // See https://bugs.dojotoolkit.org/ticket/12008 for details. |
---|
302 | endNode = doc.createTextNode('\xA0'); |
---|
303 | } |
---|
304 | |
---|
305 | if(startNode.length){ |
---|
306 | domConstruct.place(startNode, rs, "after"); |
---|
307 | }else{ |
---|
308 | startNode = rs; |
---|
309 | } |
---|
310 | domConstruct.place(brNode, startNode, "after"); |
---|
311 | domConstruct.place(endNode, brNode, "after"); |
---|
312 | domConstruct.destroy(rs); |
---|
313 | newrange = rangeapi.create(this.editor.window); |
---|
314 | newrange.setStart(endNode, 0); |
---|
315 | newrange.setEnd(endNode, endNode.length); |
---|
316 | selection.removeAllRanges(); |
---|
317 | selection.addRange(newrange); |
---|
318 | this.editor.selection.collapse(true); |
---|
319 | }else{ |
---|
320 | var targetNode; |
---|
321 | if(range.startOffset >= 0){ |
---|
322 | targetNode = rs.childNodes[range.startOffset]; |
---|
323 | } |
---|
324 | var brNode = doc.createElement("br"); |
---|
325 | var endNode = doc.createTextNode('\xA0'); |
---|
326 | if(!targetNode){ |
---|
327 | rs.appendChild(brNode); |
---|
328 | rs.appendChild(endNode); |
---|
329 | }else{ |
---|
330 | domConstruct.place(brNode, targetNode, "before"); |
---|
331 | domConstruct.place(endNode, brNode, "after"); |
---|
332 | } |
---|
333 | newrange = rangeapi.create(this.editor.window); |
---|
334 | newrange.setStart(endNode, 0); |
---|
335 | newrange.setEnd(endNode, endNode.length); |
---|
336 | selection.removeAllRanges(); |
---|
337 | selection.addRange(newrange); |
---|
338 | this.editor.selection.collapse(true); |
---|
339 | } |
---|
340 | // \xA0 dummy text node remains, but is stripped before get("value") |
---|
341 | // by RichText._stripTrailingEmptyNodes(). Still, could we just use a plain |
---|
342 | // space (" ") instead? |
---|
343 | } |
---|
344 | }else{ |
---|
345 | // don't change this: do not call this.execCommand, as that may have other logic in subclass |
---|
346 | RichText.prototype.execCommand.call(this.editor, 'inserthtml', '<br>'); |
---|
347 | } |
---|
348 | } |
---|
349 | return false; |
---|
350 | } |
---|
351 | var _letBrowserHandle = true; |
---|
352 | |
---|
353 | // first remove selection |
---|
354 | selection = rangeapi.getSelection(this.editor.window); |
---|
355 | range = selection.getRangeAt(0); |
---|
356 | if(!range.collapsed){ |
---|
357 | range.deleteContents(); |
---|
358 | selection = rangeapi.getSelection(this.editor.window); |
---|
359 | range = selection.getRangeAt(0); |
---|
360 | } |
---|
361 | |
---|
362 | var block = rangeapi.getBlockAncestor(range.endContainer, null, this.editor.editNode); |
---|
363 | var blockNode = block.blockNode; |
---|
364 | |
---|
365 | // if this is under a LI or the parent of the blockNode is LI, just let browser to handle it |
---|
366 | if((this._checkListLater = (blockNode && (blockNode.nodeName == 'LI' || blockNode.parentNode.nodeName == 'LI')))){ |
---|
367 | if(has("mozilla")){ |
---|
368 | // press enter in middle of P may leave a trailing <br/>, let's remove it later |
---|
369 | this._pressedEnterInBlock = blockNode; |
---|
370 | } |
---|
371 | // if this li only contains spaces, set the content to empty so the browser will outdent this item |
---|
372 | if(/^(\s| | |\xA0|<span\b[^>]*\bclass=['"]Apple-style-span['"][^>]*>(\s| | |\xA0)<\/span>)?(<br>)?$/.test(blockNode.innerHTML)){ |
---|
373 | // empty LI node |
---|
374 | blockNode.innerHTML = ''; |
---|
375 | if(has("webkit")){ // WebKit tosses the range when innerHTML is reset |
---|
376 | newrange = rangeapi.create(this.editor.window); |
---|
377 | newrange.setStart(blockNode, 0); |
---|
378 | selection.removeAllRanges(); |
---|
379 | selection.addRange(newrange); |
---|
380 | } |
---|
381 | this._checkListLater = false; // nothing to check since the browser handles outdent |
---|
382 | } |
---|
383 | return true; |
---|
384 | } |
---|
385 | |
---|
386 | // text node directly under body, let's wrap them in a node |
---|
387 | if(!block.blockNode || block.blockNode === this.editor.editNode){ |
---|
388 | try{ |
---|
389 | RichText.prototype.execCommand.call(this.editor, 'formatblock', this.blockNodeForEnter); |
---|
390 | }catch(e2){ /*squelch FF3 exception bug when editor content is a single BR*/ |
---|
391 | } |
---|
392 | // get the newly created block node |
---|
393 | // FIXME |
---|
394 | block = {blockNode: this.editor.selection.getAncestorElement(this.blockNodeForEnter), |
---|
395 | blockContainer: this.editor.editNode}; |
---|
396 | if(block.blockNode){ |
---|
397 | if(block.blockNode != this.editor.editNode && |
---|
398 | (!(block.blockNode.textContent || block.blockNode.innerHTML).replace(/^\s+|\s+$/g, "").length)){ |
---|
399 | this.removeTrailingBr(block.blockNode); |
---|
400 | return false; |
---|
401 | } |
---|
402 | }else{ // we shouldn't be here if formatblock worked |
---|
403 | block.blockNode = this.editor.editNode; |
---|
404 | } |
---|
405 | selection = rangeapi.getSelection(this.editor.window); |
---|
406 | range = selection.getRangeAt(0); |
---|
407 | } |
---|
408 | |
---|
409 | var newblock = doc.createElement(this.blockNodeForEnter); |
---|
410 | newblock.innerHTML = this.bogusHtmlContent; |
---|
411 | this.removeTrailingBr(block.blockNode); |
---|
412 | var endOffset = range.endOffset; |
---|
413 | var node = range.endContainer; |
---|
414 | if(node.length < endOffset){ |
---|
415 | //We are not checking the right node, try to locate the correct one |
---|
416 | var ret = this._adjustNodeAndOffset(node, endOffset); |
---|
417 | node = ret.node; |
---|
418 | endOffset = ret.offset; |
---|
419 | } |
---|
420 | if(rangeapi.atEndOfContainer(block.blockNode, node, endOffset)){ |
---|
421 | if(block.blockNode === block.blockContainer){ |
---|
422 | block.blockNode.appendChild(newblock); |
---|
423 | }else{ |
---|
424 | domConstruct.place(newblock, block.blockNode, "after"); |
---|
425 | } |
---|
426 | _letBrowserHandle = false; |
---|
427 | // lets move caret to the newly created block |
---|
428 | newrange = rangeapi.create(this.editor.window); |
---|
429 | newrange.setStart(newblock, 0); |
---|
430 | selection.removeAllRanges(); |
---|
431 | selection.addRange(newrange); |
---|
432 | if(this.editor.height){ |
---|
433 | winUtils.scrollIntoView(newblock); |
---|
434 | } |
---|
435 | }else if(rangeapi.atBeginningOfContainer(block.blockNode, |
---|
436 | range.startContainer, range.startOffset)){ |
---|
437 | domConstruct.place(newblock, block.blockNode, block.blockNode === block.blockContainer ? "first" : "before"); |
---|
438 | if(newblock.nextSibling && this.editor.height){ |
---|
439 | // position input caret - mostly WebKit needs this |
---|
440 | newrange = rangeapi.create(this.editor.window); |
---|
441 | newrange.setStart(newblock.nextSibling, 0); |
---|
442 | selection.removeAllRanges(); |
---|
443 | selection.addRange(newrange); |
---|
444 | // browser does not scroll the caret position into view, do it manually |
---|
445 | winUtils.scrollIntoView(newblock.nextSibling); |
---|
446 | } |
---|
447 | _letBrowserHandle = false; |
---|
448 | }else{ //press enter in the middle of P/DIV/Whatever/ |
---|
449 | if(block.blockNode === block.blockContainer){ |
---|
450 | block.blockNode.appendChild(newblock); |
---|
451 | }else{ |
---|
452 | domConstruct.place(newblock, block.blockNode, "after"); |
---|
453 | } |
---|
454 | _letBrowserHandle = false; |
---|
455 | |
---|
456 | // Clone any block level styles. |
---|
457 | if(block.blockNode.style){ |
---|
458 | if(newblock.style){ |
---|
459 | if(block.blockNode.style.cssText){ |
---|
460 | newblock.style.cssText = block.blockNode.style.cssText; |
---|
461 | } |
---|
462 | } |
---|
463 | } |
---|
464 | |
---|
465 | // Okay, we probably have to split. |
---|
466 | rs = range.startContainer; |
---|
467 | var firstNodeMoved; |
---|
468 | if(rs && rs.nodeType == 3){ |
---|
469 | // Text node, we have to split it. |
---|
470 | var nodeToMove, tNode; |
---|
471 | endOffset = range.endOffset; |
---|
472 | if(rs.length < endOffset){ |
---|
473 | //We are not splitting the right node, try to locate the correct one |
---|
474 | ret = this._adjustNodeAndOffset(rs, endOffset); |
---|
475 | rs = ret.node; |
---|
476 | endOffset = ret.offset; |
---|
477 | } |
---|
478 | |
---|
479 | txt = rs.nodeValue; |
---|
480 | startNode = doc.createTextNode(txt.substring(0, endOffset)); |
---|
481 | endNode = doc.createTextNode(txt.substring(endOffset, txt.length)); |
---|
482 | |
---|
483 | // Place the split, then remove original nodes. |
---|
484 | domConstruct.place(startNode, rs, "before"); |
---|
485 | domConstruct.place(endNode, rs, "after"); |
---|
486 | domConstruct.destroy(rs); |
---|
487 | |
---|
488 | // Okay, we split the text. Now we need to see if we're |
---|
489 | // parented to the block element we're splitting and if |
---|
490 | // not, we have to split all the way up. Ugh. |
---|
491 | var parentC = startNode.parentNode; |
---|
492 | while(parentC !== block.blockNode){ |
---|
493 | var tg = parentC.tagName; |
---|
494 | var newTg = doc.createElement(tg); |
---|
495 | // Clone over any 'style' data. |
---|
496 | if(parentC.style){ |
---|
497 | if(newTg.style){ |
---|
498 | if(parentC.style.cssText){ |
---|
499 | newTg.style.cssText = parentC.style.cssText; |
---|
500 | } |
---|
501 | } |
---|
502 | } |
---|
503 | // If font also need to clone over any font data. |
---|
504 | if(parentC.tagName === "FONT"){ |
---|
505 | if(parentC.color){ |
---|
506 | newTg.color = parentC.color; |
---|
507 | } |
---|
508 | if(parentC.face){ |
---|
509 | newTg.face = parentC.face; |
---|
510 | } |
---|
511 | if(parentC.size){ // this check was necessary on IE |
---|
512 | newTg.size = parentC.size; |
---|
513 | } |
---|
514 | } |
---|
515 | |
---|
516 | nodeToMove = endNode; |
---|
517 | while(nodeToMove){ |
---|
518 | tNode = nodeToMove.nextSibling; |
---|
519 | newTg.appendChild(nodeToMove); |
---|
520 | nodeToMove = tNode; |
---|
521 | } |
---|
522 | domConstruct.place(newTg, parentC, "after"); |
---|
523 | startNode = parentC; |
---|
524 | endNode = newTg; |
---|
525 | parentC = parentC.parentNode; |
---|
526 | } |
---|
527 | |
---|
528 | // Lastly, move the split out tags to the new block. |
---|
529 | // as they should now be split properly. |
---|
530 | nodeToMove = endNode; |
---|
531 | if(nodeToMove.nodeType == 1 || (nodeToMove.nodeType == 3 && nodeToMove.nodeValue)){ |
---|
532 | // Non-blank text and non-text nodes need to clear out that blank space |
---|
533 | // before moving the contents. |
---|
534 | newblock.innerHTML = ""; |
---|
535 | } |
---|
536 | firstNodeMoved = nodeToMove; |
---|
537 | while(nodeToMove){ |
---|
538 | tNode = nodeToMove.nextSibling; |
---|
539 | newblock.appendChild(nodeToMove); |
---|
540 | nodeToMove = tNode; |
---|
541 | } |
---|
542 | } |
---|
543 | |
---|
544 | //lets move caret to the newly created block |
---|
545 | newrange = rangeapi.create(this.editor.window); |
---|
546 | var nodeForCursor; |
---|
547 | var innerMostFirstNodeMoved = firstNodeMoved; |
---|
548 | if(this.blockNodeForEnter !== 'BR'){ |
---|
549 | while(innerMostFirstNodeMoved){ |
---|
550 | nodeForCursor = innerMostFirstNodeMoved; |
---|
551 | tNode = innerMostFirstNodeMoved.firstChild; |
---|
552 | innerMostFirstNodeMoved = tNode; |
---|
553 | } |
---|
554 | if(nodeForCursor && nodeForCursor.parentNode){ |
---|
555 | newblock = nodeForCursor.parentNode; |
---|
556 | newrange.setStart(newblock, 0); |
---|
557 | selection.removeAllRanges(); |
---|
558 | selection.addRange(newrange); |
---|
559 | if(this.editor.height){ |
---|
560 | winUtils.scrollIntoView(newblock); |
---|
561 | } |
---|
562 | if(has("mozilla")){ |
---|
563 | // press enter in middle of P may leave a trailing <br/>, let's remove it later |
---|
564 | this._pressedEnterInBlock = block.blockNode; |
---|
565 | } |
---|
566 | }else{ |
---|
567 | _letBrowserHandle = true; |
---|
568 | } |
---|
569 | }else{ |
---|
570 | newrange.setStart(newblock, 0); |
---|
571 | selection.removeAllRanges(); |
---|
572 | selection.addRange(newrange); |
---|
573 | if(this.editor.height){ |
---|
574 | winUtils.scrollIntoView(newblock); |
---|
575 | } |
---|
576 | if(has("mozilla")){ |
---|
577 | // press enter in middle of P may leave a trailing <br/>, let's remove it later |
---|
578 | this._pressedEnterInBlock = block.blockNode; |
---|
579 | } |
---|
580 | } |
---|
581 | } |
---|
582 | return _letBrowserHandle; |
---|
583 | }, |
---|
584 | |
---|
585 | _adjustNodeAndOffset: function(/*DomNode*/node, /*Int*/offset){ |
---|
586 | // summary: |
---|
587 | // In the case there are multiple text nodes in a row the offset may not be within the node. If the offset is larger than the node length, it will attempt to find |
---|
588 | // the next text sibling until it locates the text node in which the offset refers to |
---|
589 | // node: |
---|
590 | // The node to check. |
---|
591 | // offset: |
---|
592 | // The position to find within the text node |
---|
593 | // tags: |
---|
594 | // private. |
---|
595 | while(node.length < offset && node.nextSibling && node.nextSibling.nodeType == 3){ |
---|
596 | //Adjust the offset and node in the case of multiple text nodes in a row |
---|
597 | offset = offset - node.length; |
---|
598 | node = node.nextSibling; |
---|
599 | } |
---|
600 | return {"node": node, "offset": offset}; |
---|
601 | }, |
---|
602 | |
---|
603 | removeTrailingBr: function(container){ |
---|
604 | // summary: |
---|
605 | // If last child of container is a `<br>`, then remove it. |
---|
606 | // tags: |
---|
607 | // private |
---|
608 | var para = /P|DIV|LI/i.test(container.tagName) ? |
---|
609 | container : this.editor.selection.getParentOfType(container, ['P', 'DIV', 'LI']); |
---|
610 | |
---|
611 | if(!para){ |
---|
612 | return; |
---|
613 | } |
---|
614 | if(para.lastChild){ |
---|
615 | if((para.childNodes.length > 1 && para.lastChild.nodeType == 3 && /^[\s\xAD]*$/.test(para.lastChild.nodeValue)) || |
---|
616 | para.lastChild.tagName == 'BR'){ |
---|
617 | |
---|
618 | domConstruct.destroy(para.lastChild); |
---|
619 | } |
---|
620 | } |
---|
621 | if(!para.childNodes.length){ |
---|
622 | para.innerHTML = this.bogusHtmlContent; |
---|
623 | } |
---|
624 | } |
---|
625 | }); |
---|
626 | |
---|
627 | }); |
---|