]>
git.wh0rd.org - tt-rss.git/blob - lib/dijit/_editor/plugins/EnterKeyHandling.js.uncompressed.js
1 define ( "dijit/_editor/plugins/EnterKeyHandling" , [
2 "dojo/_base/declare" , // declare
3 "dojo/dom-construct" , // domConstruct.destroy domConstruct.place
4 "dojo/_base/event" , // event.stop
5 "dojo/keys" , // keys.ENTER
7 "dojo/_base/sniff" , // has("ie") has("mozilla") has("webkit")
8 "dojo/_base/window" , // win.global win.withGlobal
9 "dojo/window" , // winUtils.scrollIntoView
14 ], function ( declare
, domConstruct
, event
, keys
, lang
, has
, win
, winUtils
, _Plugin
, RichText
, rangeapi
, selectionapi
){
17 var _Plugin = dijit._editor._Plugin;
21 // dijit/_editor/plugins/EnterKeyHandling
23 // This plugin tries to make all browsers behave consistently with regard to
24 // how ENTER behaves in the editor window. It traps the ENTER key and alters
25 // the way DOM is constructed in certain cases to try to commonize the generated
26 // DOM and behaviors across browsers.
29 return declare ( "dijit._editor.plugins.EnterKeyHandling" , _Plugin
, {
31 // This plugin tries to make all browsers behave consistently with regard to
32 // how ENTER behaves in the editor window. It traps the ENTER key and alters
33 // the way DOM is constructed in certain cases to try to commonize the generated
34 // DOM and behaviors across browsers.
37 // This plugin has three modes:
39 // * blockNodeForEnter=BR
40 // * blockNodeForEnter=DIV
41 // * blockNodeForEnter=P
43 // In blockNodeForEnter=P, the ENTER key starts a new
44 // paragraph, and shift-ENTER starts a new line in the current paragraph.
45 // For example, the input:
47 // | first paragraph <shift-ENTER>
48 // | second line of first paragraph <ENTER>
56 // | second line of first paragraph
62 // In BR and DIV mode, the ENTER key conceptually goes to a new line in the
63 // current paragraph, and users conceptually create a new paragraph by pressing ENTER twice.
64 // For example, if the user enters text into an editor like this:
74 // It will appear on the screen as two 'paragraphs' of three lines each. Markupwise, this generates:
89 // | <div> </div>
94 // blockNodeForEnter: String
95 // This property decides the behavior of Enter key. It can be either P,
96 // DIV, BR, or empty (which means disable this feature). Anything else
97 // will trigger errors. The default is 'BR'
99 // See class description for more details.
100 blockNodeForEnter
: 'BR' ,
102 constructor : function ( args
){
104 if ( "blockNodeForEnter" in args
){
105 args
. blockNodeForEnter
= args
. blockNodeForEnter
. toUpperCase ();
107 lang
. mixin ( this , args
);
111 setEditor : function ( editor
){
112 // Overrides _Plugin.setEditor().
113 if ( this . editor
=== editor
){ return ; }
114 this . editor
= editor
;
115 if ( this . blockNodeForEnter
== 'BR' ){
116 // While Moz has a mode tht mostly works, it's still a little different,
117 // So, try to just have a common mode and be consistent. Which means
118 // we need to enable customUndo, if not already enabled.
119 this . editor
. customUndo
= true ;
120 editor
. onLoadDeferred
. then ( lang
. hitch ( this , function ( d
){
121 this . connect ( editor
. document
, "onkeypress" , function ( e
){
122 if ( e
. charOrCode
== keys
. ENTER
){
123 // Just do it manually. The handleEnterKey has a shift mode that
124 // Always acts like <br>, so just use it.
125 var ne
= lang
. mixin ({}, e
);
127 if (! this . handleEnterKey ( ne
)){
133 this . connect ( editor
. document
, "onpaste" , function ( e
){
134 setTimeout ( dojo
. hitch ( this , function (){
135 // Use the old range/selection code to kick IE 9 into updating
136 // its range by moving it back, then forward, one 'character'.
137 var r
= this . editor
. document
. selection
. createRange ();
138 r
. move ( 'character' ,- 1 );
140 r
. move ( 'character' , 1 );
147 } else if ( this . blockNodeForEnter
){
148 // add enter key handler
149 // FIXME: need to port to the new event code!!
150 var h
= lang
. hitch ( this , this . handleEnterKey
);
151 editor
. addKeyHandler ( 13 , 0 , 0 , h
); //enter
152 editor
. addKeyHandler ( 13 , 0 , 1 , h
); //shift+enter
153 this . connect ( this . editor
, 'onKeyPressed' , 'onKeyPressed' );
156 onKeyPressed : function (){
158 // Handler for keypress events.
161 if ( this . _checkListLater
){
162 if ( win
. withGlobal ( this . editor
. window
, 'isCollapsed' , dijit
)){
163 var liparent
= win
. withGlobal ( this . editor
. window
, 'getAncestorElement' , selectionapi
, [ 'LI' ]);
165 // circulate the undo detection code by calling RichText::execCommand directly
166 RichText
. prototype . execCommand
. call ( this . editor
, 'formatblock' , this . blockNodeForEnter
);
167 // set the innerHTML of the new block node
168 var block
= win
. withGlobal ( this . editor
. window
, 'getAncestorElement' , selectionapi
, [ this . blockNodeForEnter
]);
170 block
. innerHTML
= this . bogusHtmlContent
;
172 // move to the start by moving backwards one char
173 var r
= this . editor
. document
. selection
. createRange ();
174 r
. move ( 'character' ,- 1 );
178 console
. error ( 'onKeyPressed: Cannot find the new block node' ); // FIXME
182 if ( liparent
. parentNode
. parentNode
. nodeName
== 'LI' ){
183 liparent
= liparent
. parentNode
. parentNode
;
186 var fc
= liparent
. firstChild
;
187 if ( fc
&& fc
. nodeType
== 1 && ( fc
. nodeName
== 'UL' || fc
. nodeName
== 'OL' )){
188 liparent
. insertBefore ( fc
. ownerDocument
. createTextNode ( ' \xA0 ' ), fc
);
189 var newrange
= rangeapi
. create ( this . editor
. window
);
190 newrange
. setStart ( liparent
. firstChild
, 0 );
191 var selection
= rangeapi
. getSelection ( this . editor
. window
, true );
192 selection
. removeAllRanges ();
193 selection
. addRange ( newrange
);
197 this . _checkListLater
= false ;
199 if ( this . _pressedEnterInBlock
){
200 // the new created is the original current P, so we have previousSibling below
201 if ( this . _pressedEnterInBlock
. previousSibling
){
202 this . removeTrailingBr ( this . _pressedEnterInBlock
. previousSibling
);
204 delete this . _pressedEnterInBlock
;
208 // bogusHtmlContent: [private] String
209 // HTML to stick into a new empty block
210 bogusHtmlContent
: ' ' , //
212 // blockNodes: [private] Regex
213 // Regex for testing if a given tag is a block level (display:block) tag
214 blockNodes
: /^(?:P|H1|H2|H3|H4|H5|H6|LI)$/ ,
216 handleEnterKey : function ( e
){
218 // Handler for enter key events when blockNodeForEnter is DIV or P.
220 // Manually handle enter key event to make the behavior consistent across
221 // all supported browsers. See class description for details.
225 var selection
, range
, newrange
, startNode
, endNode
, brNode
, doc
= this . editor
. document
, br
, rs
, txt
;
226 if ( e
. shiftKey
){ // shift+enter always generates <br>
227 var parent
= win
. withGlobal ( this . editor
. window
, "getParentElement" , selectionapi
);
228 var header
= rangeapi
. getAncestor ( parent
, this . blockNodes
);
230 if ( header
. tagName
== 'LI' ){
231 return true ; // let browser handle
233 selection
= rangeapi
. getSelection ( this . editor
. window
);
234 range
= selection
. getRangeAt ( 0 );
235 if (! range
. collapsed
){
236 range
. deleteContents ();
237 selection
= rangeapi
. getSelection ( this . editor
. window
);
238 range
= selection
. getRangeAt ( 0 );
240 if ( rangeapi
. atBeginningOfContainer ( header
, range
. startContainer
, range
. startOffset
)){
241 br
= doc
. createElement ( 'br' );
242 newrange
= rangeapi
. create ( this . editor
. window
);
243 header
. insertBefore ( br
, header
. firstChild
);
244 newrange
. setStartAfter ( br
);
245 selection
. removeAllRanges ();
246 selection
. addRange ( newrange
);
247 } else if ( rangeapi
. atEndOfContainer ( header
, range
. startContainer
, range
. startOffset
)){
248 newrange
= rangeapi
. create ( this . editor
. window
);
249 br
= doc
. createElement ( 'br' );
250 header
. appendChild ( br
);
251 header
. appendChild ( doc
. createTextNode ( ' \xA0 ' ));
252 newrange
. setStart ( header
. lastChild
, 0 );
253 selection
. removeAllRanges ();
254 selection
. addRange ( newrange
);
256 rs
= range
. startContainer
;
257 if ( rs
&& rs
. nodeType
== 3 ){
258 // Text node, we have to split it.
260 win
. withGlobal ( this . editor
. window
, function (){
261 startNode
= doc
. createTextNode ( txt
. substring ( 0 , range
. startOffset
));
262 endNode
= doc
. createTextNode ( txt
. substring ( range
. startOffset
));
263 brNode
= doc
. createElement ( "br" );
265 if ( endNode
. nodeValue
== "" && has ( "webkit" )){
266 endNode
= doc
. createTextNode ( ' \xA0 ' )
268 domConstruct
. place ( startNode
, rs
, "after" );
269 domConstruct
. place ( brNode
, startNode
, "after" );
270 domConstruct
. place ( endNode
, brNode
, "after" );
271 domConstruct
. destroy ( rs
);
272 newrange
= rangeapi
. create ();
273 newrange
. setStart ( endNode
, 0 );
274 selection
. removeAllRanges ();
275 selection
. addRange ( newrange
);
279 return true ; // let browser handle
282 selection
= rangeapi
. getSelection ( this . editor
. window
);
283 if ( selection
. rangeCount
){
284 range
= selection
. getRangeAt ( 0 );
285 if ( range
&& range
. startContainer
){
286 if (! range
. collapsed
){
287 range
. deleteContents ();
288 selection
= rangeapi
. getSelection ( this . editor
. window
);
289 range
= selection
. getRangeAt ( 0 );
291 rs
= range
. startContainer
;
292 if ( rs
&& rs
. nodeType
== 3 ){
293 // Text node, we have to split it.
294 win
. withGlobal ( this . editor
. window
, lang
. hitch ( this , function (){
295 var endEmpty
= false ;
297 var offset
= range
. startOffset
;
298 if ( rs
. length
< offset
){
299 //We are not splitting the right node, try to locate the correct one
300 ret
= this . _adjustNodeAndOffset ( rs
, offset
);
306 startNode
= doc
. createTextNode ( txt
. substring ( 0 , offset
));
307 endNode
= doc
. createTextNode ( txt
. substring ( offset
));
308 brNode
= doc
. createElement ( "br" );
311 endNode
= doc
. createTextNode ( ' \xA0 ' );
315 if ( startNode
. length
){
316 domConstruct
. place ( startNode
, rs
, "after" );
320 domConstruct
. place ( brNode
, startNode
, "after" );
321 domConstruct
. place ( endNode
, brNode
, "after" );
322 domConstruct
. destroy ( rs
);
323 newrange
= rangeapi
. create ();
324 newrange
. setStart ( endNode
, 0 );
325 newrange
. setEnd ( endNode
, endNode
. length
);
326 selection
. removeAllRanges ();
327 selection
. addRange ( newrange
);
328 if ( endEmpty
&& ! has ( "webkit" )){
329 selectionapi
. remove ();
331 selectionapi
. collapse ( true );
336 if ( range
. startOffset
>= 0 ){
337 targetNode
= rs
. childNodes
[ range
. startOffset
];
339 win
. withGlobal ( this . editor
. window
, lang
. hitch ( this , function (){
340 var brNode
= doc
. createElement ( "br" );
341 var endNode
= doc
. createTextNode ( ' \xA0 ' );
343 rs
. appendChild ( brNode
);
344 rs
. appendChild ( endNode
);
346 domConstruct
. place ( brNode
, targetNode
, "before" );
347 domConstruct
. place ( endNode
, brNode
, "after" );
349 newrange
= rangeapi
. create ( win
. global
);
350 newrange
. setStart ( endNode
, 0 );
351 newrange
. setEnd ( endNode
, endNode
. length
);
352 selection
. removeAllRanges ();
353 selection
. addRange ( newrange
);
354 selectionapi
. collapse ( true );
359 // don't change this: do not call this.execCommand, as that may have other logic in subclass
360 RichText
. prototype . execCommand
. call ( this . editor
, 'inserthtml' , '<br>' );
365 var _letBrowserHandle
= true ;
367 // first remove selection
368 selection
= rangeapi
. getSelection ( this . editor
. window
);
369 range
= selection
. getRangeAt ( 0 );
370 if (! range
. collapsed
){
371 range
. deleteContents ();
372 selection
= rangeapi
. getSelection ( this . editor
. window
);
373 range
= selection
. getRangeAt ( 0 );
376 var block
= rangeapi
. getBlockAncestor ( range
. endContainer
, null , this . editor
. editNode
);
377 var blockNode
= block
. blockNode
;
379 // if this is under a LI or the parent of the blockNode is LI, just let browser to handle it
380 if (( this . _checkListLater
= ( blockNode
&& ( blockNode
. nodeName
== 'LI' || blockNode
. parentNode
. nodeName
== 'LI' )))){
382 // press enter in middle of P may leave a trailing <br/>, let's remove it later
383 this . _pressedEnterInBlock
= blockNode
;
385 // if this li only contains spaces, set the content to empty so the browser will outdent this item
386 if ( /^(\s| | |\xA0|<span\b[^>]*\bclass=['"]Apple-style-span['"][^>]*>(\s| | |\xA0)<\/span>)?(<br>)?$/ . test ( blockNode
. innerHTML
)){
388 blockNode
. innerHTML
= '' ;
389 if ( has ( "webkit" )){ // WebKit tosses the range when innerHTML is reset
390 newrange
= rangeapi
. create ( this . editor
. window
);
391 newrange
. setStart ( blockNode
, 0 );
392 selection
. removeAllRanges ();
393 selection
. addRange ( newrange
);
395 this . _checkListLater
= false ; // nothing to check since the browser handles outdent
400 // text node directly under body, let's wrap them in a node
401 if (! block
. blockNode
|| block
. blockNode
=== this . editor
. editNode
){
403 RichText
. prototype . execCommand
. call ( this . editor
, 'formatblock' , this . blockNodeForEnter
);
404 } catch ( e2
){ /*squelch FF3 exception bug when editor content is a single BR*/ }
405 // get the newly created block node
407 block
= { blockNode
: win
. withGlobal ( this . editor
. window
, "getAncestorElement" , selectionapi
, [ this . blockNodeForEnter
]),
408 blockContainer
: this . editor
. editNode
};
410 if ( block
. blockNode
!= this . editor
. editNode
&&
411 (!( block
. blockNode
. textContent
|| block
. blockNode
. innerHTML
). replace ( /^\s+|\s+$/g , "" ). length
)){
412 this . removeTrailingBr ( block
. blockNode
);
415 } else { // we shouldn't be here if formatblock worked
416 block
. blockNode
= this . editor
. editNode
;
418 selection
= rangeapi
. getSelection ( this . editor
. window
);
419 range
= selection
. getRangeAt ( 0 );
422 var newblock
= doc
. createElement ( this . blockNodeForEnter
);
423 newblock
. innerHTML
= this . bogusHtmlContent
;
424 this . removeTrailingBr ( block
. blockNode
);
425 var endOffset
= range
. endOffset
;
426 var node
= range
. endContainer
;
427 if ( node
. length
< endOffset
){
428 //We are not checking the right node, try to locate the correct one
429 var ret
= this . _adjustNodeAndOffset ( node
, endOffset
);
431 endOffset
= ret
. offset
;
433 if ( rangeapi
. atEndOfContainer ( block
. blockNode
, node
, endOffset
)){
434 if ( block
. blockNode
=== block
. blockContainer
){
435 block
. blockNode
. appendChild ( newblock
);
437 domConstruct
. place ( newblock
, block
. blockNode
, "after" );
439 _letBrowserHandle
= false ;
440 // lets move caret to the newly created block
441 newrange
= rangeapi
. create ( this . editor
. window
);
442 newrange
. setStart ( newblock
, 0 );
443 selection
. removeAllRanges ();
444 selection
. addRange ( newrange
);
445 if ( this . editor
. height
){
446 winUtils
. scrollIntoView ( newblock
);
448 } else if ( rangeapi
. atBeginningOfContainer ( block
. blockNode
,
449 range
. startContainer
, range
. startOffset
)){
450 domConstruct
. place ( newblock
, block
. blockNode
, block
. blockNode
=== block
. blockContainer
? "first" : "before" );
451 if ( newblock
. nextSibling
&& this . editor
. height
){
452 // position input caret - mostly WebKit needs this
453 newrange
= rangeapi
. create ( this . editor
. window
);
454 newrange
. setStart ( newblock
. nextSibling
, 0 );
455 selection
. removeAllRanges ();
456 selection
. addRange ( newrange
);
457 // browser does not scroll the caret position into view, do it manually
458 winUtils
. scrollIntoView ( newblock
. nextSibling
);
460 _letBrowserHandle
= false ;
461 } else { //press enter in the middle of P/DIV/Whatever/
462 if ( block
. blockNode
=== block
. blockContainer
){
463 block
. blockNode
. appendChild ( newblock
);
465 domConstruct
. place ( newblock
, block
. blockNode
, "after" );
467 _letBrowserHandle
= false ;
469 // Clone any block level styles.
470 if ( block
. blockNode
. style
){
472 if ( block
. blockNode
. style
. cssText
){
473 newblock
. style
. cssText
= block
. blockNode
. style
. cssText
;
478 // Okay, we probably have to split.
479 rs
= range
. startContainer
;
481 if ( rs
&& rs
. nodeType
== 3 ){
482 // Text node, we have to split it.
483 var nodeToMove
, tNode
;
484 endOffset
= range
. endOffset
;
485 if ( rs
. length
< endOffset
){
486 //We are not splitting the right node, try to locate the correct one
487 ret
= this . _adjustNodeAndOffset ( rs
, endOffset
);
489 endOffset
= ret
. offset
;
493 startNode
= doc
. createTextNode ( txt
. substring ( 0 , endOffset
));
494 endNode
= doc
. createTextNode ( txt
. substring ( endOffset
, txt
. length
));
496 // Place the split, then remove original nodes.
497 domConstruct
. place ( startNode
, rs
, "before" );
498 domConstruct
. place ( endNode
, rs
, "after" );
499 domConstruct
. destroy ( rs
);
501 // Okay, we split the text. Now we need to see if we're
502 // parented to the block element we're splitting and if
503 // not, we have to split all the way up. Ugh.
504 var parentC
= startNode
. parentNode
;
505 while ( parentC
!== block
. blockNode
){
506 var tg
= parentC
. tagName
;
507 var newTg
= doc
. createElement ( tg
);
508 // Clone over any 'style' data.
511 if ( parentC
. style
. cssText
){
512 newTg
. style
. cssText
= parentC
. style
. cssText
;
516 // If font also need to clone over any font data.
517 if ( parentC
. tagName
=== "FONT" ){
519 newTg
. color
= parentC
. color
;
522 newTg
. face
= parentC
. face
;
524 if ( parentC
. size
){ // this check was necessary on IE
525 newTg
. size
= parentC
. size
;
529 nodeToMove
= endNode
;
531 tNode
= nodeToMove
. nextSibling
;
532 newTg
. appendChild ( nodeToMove
);
535 domConstruct
. place ( newTg
, parentC
, "after" );
538 parentC
= parentC
. parentNode
;
541 // Lastly, move the split out tags to the new block.
542 // as they should now be split properly.
543 nodeToMove
= endNode
;
544 if ( nodeToMove
. nodeType
== 1 || ( nodeToMove
. nodeType
== 3 && nodeToMove
. nodeValue
)){
545 // Non-blank text and non-text nodes need to clear out that blank space
546 // before moving the contents.
547 newblock
. innerHTML
= "" ;
549 firstNodeMoved
= nodeToMove
;
551 tNode
= nodeToMove
. nextSibling
;
552 newblock
. appendChild ( nodeToMove
);
557 //lets move caret to the newly created block
558 newrange
= rangeapi
. create ( this . editor
. window
);
560 var innerMostFirstNodeMoved
= firstNodeMoved
;
561 if ( this . blockNodeForEnter
!== 'BR' ){
562 while ( innerMostFirstNodeMoved
){
563 nodeForCursor
= innerMostFirstNodeMoved
;
564 tNode
= innerMostFirstNodeMoved
. firstChild
;
565 innerMostFirstNodeMoved
= tNode
;
567 if ( nodeForCursor
&& nodeForCursor
. parentNode
){
568 newblock
= nodeForCursor
. parentNode
;
569 newrange
. setStart ( newblock
, 0 );
570 selection
. removeAllRanges ();
571 selection
. addRange ( newrange
);
572 if ( this . editor
. height
){
573 winUtils
. scrollIntoView ( newblock
);
576 // press enter in middle of P may leave a trailing <br/>, let's remove it later
577 this . _pressedEnterInBlock
= block
. blockNode
;
580 _letBrowserHandle
= true ;
583 newrange
. setStart ( newblock
, 0 );
584 selection
. removeAllRanges ();
585 selection
. addRange ( newrange
);
586 if ( this . editor
. height
){
587 winUtils
. scrollIntoView ( newblock
);
590 // press enter in middle of P may leave a trailing <br/>, let's remove it later
591 this . _pressedEnterInBlock
= block
. blockNode
;
595 return _letBrowserHandle
;
598 _adjustNodeAndOffset : function ( /*DomNode*/ node
, /*Int*/ offset
){
600 // 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
601 // the next text sibling until it locates the text node in which the offset refers to
603 // The node to check.
605 // The position to find within the text node
608 while ( node
. length
< offset
&& node
. nextSibling
&& node
. nextSibling
. nodeType
== 3 ){
609 //Adjust the offset and node in the case of multiple text nodes in a row
610 offset
= offset
- node
. length
;
611 node
= node
. nextSibling
;
613 return { "node" : node
, "offset" : offset
};
616 removeTrailingBr : function ( container
){
618 // If last child of container is a <br>, then remove it.
621 var para
= /P|DIV|LI/i . test ( container
. tagName
) ?
622 container
: selectionapi
. getParentOfType ( container
,[ 'P' , 'DIV' , 'LI' ]);
626 if (( para
. childNodes
. length
> 1 && para
. lastChild
. nodeType
== 3 && /^[\s\xAD]*$/ . test ( para
. lastChild
. nodeValue
)) ||
627 para
. lastChild
. tagName
== 'BR' ){
629 domConstruct
. destroy ( para
. lastChild
);
632 if (! para
. childNodes
. length
){
633 para
. innerHTML
= this . bogusHtmlContent
;