initial commit
[namibia] / public / scripts / ckeditor / _source / plugins / enterkey / plugin.js
1 /*
2 Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved.
3 For licensing, see LICENSE.html or http://ckeditor.com/license
4 */
5
6 (function()
7 {
8         CKEDITOR.plugins.add( 'enterkey',
9         {
10                 requires : [ 'keystrokes', 'indent' ],
11
12                 init : function( editor )
13                 {
14                         editor.addCommand( 'enter', {
15                                 modes : { wysiwyg:1 },
16                                 editorFocus : false,
17                                 exec : function( editor ){ enter( editor ); }
18                         });
19
20                         editor.addCommand( 'shiftEnter', {
21                                 modes : { wysiwyg:1 },
22                                 editorFocus : false,
23                                 exec : function( editor ){ shiftEnter( editor ); }
24                         });
25
26                         var keystrokes = editor.keystrokeHandler.keystrokes;
27                         keystrokes[ 13 ] = 'enter';
28                         keystrokes[ CKEDITOR.SHIFT + 13 ] = 'shiftEnter';
29                 }
30         });
31
32         CKEDITOR.plugins.enterkey =
33         {
34                 enterBlock : function( editor, mode, range, forceMode )
35                 {
36                         // Get the range for the current selection.
37                         range = range || getRange( editor );
38
39                         // We may not have valid ranges to work on, like when inside a
40                         // contenteditable=false element.
41                         if ( !range )
42                                 return;
43
44                         var doc = range.document;
45
46                         var atBlockStart = range.checkStartOfBlock(),
47                                 atBlockEnd = range.checkEndOfBlock(),
48                                 path = new CKEDITOR.dom.elementPath( range.startContainer ),
49                                 block = path.block;
50
51                         if ( atBlockStart && atBlockEnd )
52                         {
53                                 // Exit the list when we're inside an empty list item block. (#5376)
54                                 if ( block && ( block.is( 'li' ) || block.getParent().is( 'li' ) ) )
55                                 {
56                                         editor.execCommand( 'outdent' );
57                                         return;
58                                 }
59
60                                 if ( block && block.getParent().is( 'blockquote' ) )
61                                 {
62                                         block.breakParent( block.getParent() );
63
64                                         // If we were at the start of <blockquote>, there will be an empty element before it now.
65                                         if ( !block.getPrevious().getFirst( CKEDITOR.dom.walker.invisible(1) ) )
66                                                 block.getPrevious().remove();
67
68                                         // If we were at the end of <blockquote>, there will be an empty element after it now.
69                                         if ( !block.getNext().getFirst( CKEDITOR.dom.walker.invisible(1) ) )
70                                                 block.getNext().remove();
71
72                                         range.moveToElementEditStart( block );
73                                         range.select();
74                                         return;
75                                 }
76                         }
77                         // Don't split <pre> if we're in the middle of it, act as shift enter key.
78                         else if ( block && block.is( 'pre' ) )
79                         {
80                                 if ( !atBlockEnd )
81                                 {
82                                         enterBr( editor, mode, range, forceMode );
83                                         return;
84                                 }
85                         }
86                         // Don't split caption blocks. (#7944)
87                         else if ( block && CKEDITOR.dtd.$captionBlock[ block.getName() ] )
88                         {
89                                 enterBr( editor, mode, range, forceMode );
90                                 return;
91                         }
92
93                         // Determine the block element to be used.
94                         var blockTag = ( mode == CKEDITOR.ENTER_DIV ? 'div' : 'p' );
95
96                         // Split the range.
97                         var splitInfo = range.splitBlock( blockTag );
98
99                         if ( !splitInfo )
100                                 return;
101
102                         // Get the current blocks.
103                         var previousBlock       = splitInfo.previousBlock,
104                                 nextBlock               = splitInfo.nextBlock;
105
106                         var isStartOfBlock      = splitInfo.wasStartOfBlock,
107                                 isEndOfBlock    = splitInfo.wasEndOfBlock;
108
109                         var node;
110
111                         // If this is a block under a list item, split it as well. (#1647)
112                         if ( nextBlock )
113                         {
114                                 node = nextBlock.getParent();
115                                 if ( node.is( 'li' ) )
116                                 {
117                                         nextBlock.breakParent( node );
118                                         nextBlock.move( nextBlock.getNext(), 1 );
119                                 }
120                         }
121                         else if ( previousBlock && ( node = previousBlock.getParent() ) && node.is( 'li' ) )
122                         {
123                                 previousBlock.breakParent( node );
124                                 node = previousBlock.getNext();
125                                 range.moveToElementEditStart( node );
126                                 previousBlock.move( previousBlock.getPrevious() );
127                         }
128
129                         // If we have both the previous and next blocks, it means that the
130                         // boundaries were on separated blocks, or none of them where on the
131                         // block limits (start/end).
132                         if ( !isStartOfBlock && !isEndOfBlock )
133                         {
134                                 // If the next block is an <li> with another list tree as the first
135                                 // child, we'll need to append a filler (<br>/NBSP) or the list item
136                                 // wouldn't be editable. (#1420)
137                                 if ( nextBlock.is( 'li' )
138                                          && ( node = nextBlock.getFirst( CKEDITOR.dom.walker.invisible( true ) ) )
139                                          && node.is && node.is( 'ul', 'ol' ) )
140                                         ( CKEDITOR.env.ie ? doc.createText( '\xa0' ) : doc.createElement( 'br' ) ).insertBefore( node );
141
142                                 // Move the selection to the end block.
143                                 if ( nextBlock )
144                                         range.moveToElementEditStart( nextBlock );
145                         }
146                         else
147                         {
148                                 var newBlock,
149                                         newBlockDir;
150
151                                 if ( previousBlock )
152                                 {
153                                         // Do not enter this block if it's a header tag, or we are in
154                                         // a Shift+Enter (#77). Create a new block element instead
155                                         // (later in the code).
156                                         if ( previousBlock.is( 'li' ) ||
157                                                         ! ( headerTagRegex.test( previousBlock.getName() ) || previousBlock.is( 'pre' ) ) )
158                                         {
159                                                 // Otherwise, duplicate the previous block.
160                                                 newBlock = previousBlock.clone();
161                                         }
162                                 }
163                                 else if ( nextBlock )
164                                         newBlock = nextBlock.clone();
165
166                                 if ( !newBlock )
167                                 {
168                                         // We have already created a new list item. (#6849)
169                                         if ( node && node.is( 'li' ) )
170                                                 newBlock = node;
171                                         else
172                                         {
173                                                 newBlock = doc.createElement( blockTag );
174                                                 if ( previousBlock && ( newBlockDir = previousBlock.getDirection() ) )
175                                                         newBlock.setAttribute( 'dir', newBlockDir );
176                                         }
177                                 }
178                                 // Force the enter block unless we're talking of a list item.
179                                 else if ( forceMode && !newBlock.is( 'li' ) )
180                                         newBlock.renameNode( blockTag );
181
182                                 // Recreate the inline elements tree, which was available
183                                 // before hitting enter, so the same styles will be available in
184                                 // the new block.
185                                 var elementPath = splitInfo.elementPath;
186                                 if ( elementPath )
187                                 {
188                                         for ( var i = 0, len = elementPath.elements.length ; i < len ; i++ )
189                                         {
190                                                 var element = elementPath.elements[ i ];
191
192                                                 if ( element.equals( elementPath.block ) || element.equals( elementPath.blockLimit ) )
193                                                         break;
194
195                                                 if ( CKEDITOR.dtd.$removeEmpty[ element.getName() ] )
196                                                 {
197                                                         element = element.clone();
198                                                         newBlock.moveChildren( element );
199                                                         newBlock.append( element );
200                                                 }
201                                         }
202                                 }
203
204                                 if ( !CKEDITOR.env.ie )
205                                         newBlock.appendBogus();
206
207                                 if ( !newBlock.getParent() )
208                                         range.insertNode( newBlock );
209
210                                 // list item start number should not be duplicated (#7330), but we need
211                                 // to remove the attribute after it's onto the DOM tree because of old IEs (#7581).
212                                 newBlock.is( 'li' ) && newBlock.removeAttribute( 'value' );
213
214                                 // This is tricky, but to make the new block visible correctly
215                                 // we must select it.
216                                 // The previousBlock check has been included because it may be
217                                 // empty if we have fixed a block-less space (like ENTER into an
218                                 // empty table cell).
219                                 if ( CKEDITOR.env.ie && isStartOfBlock && ( !isEndOfBlock || !previousBlock.getChildCount() ) )
220                                 {
221                                         // Move the selection to the new block.
222                                         range.moveToElementEditStart( isEndOfBlock ? previousBlock : newBlock );
223                                         range.select();
224                                 }
225
226                                 // Move the selection to the new block.
227                                 range.moveToElementEditStart( isStartOfBlock && !isEndOfBlock ? nextBlock : newBlock );
228                 }
229
230                         if ( !CKEDITOR.env.ie )
231                         {
232                                 if ( nextBlock )
233                                 {
234                                         // If we have split the block, adds a temporary span at the
235                                         // range position and scroll relatively to it.
236                                         var tmpNode = doc.createElement( 'span' );
237
238                                         // We need some content for Safari.
239                                         tmpNode.setHtml( '&nbsp;' );
240
241                                         range.insertNode( tmpNode );
242                                         tmpNode.scrollIntoView();
243                                         range.deleteContents();
244                                 }
245                                 else
246                                 {
247                                         // We may use the above scroll logic for the new block case
248                                         // too, but it gives some weird result with Opera.
249                                         newBlock.scrollIntoView();
250                                 }
251                         }
252
253                         range.select();
254                 },
255
256                 enterBr : function( editor, mode, range, forceMode )
257                 {
258                         // Get the range for the current selection.
259                         range = range || getRange( editor );
260
261                         // We may not have valid ranges to work on, like when inside a
262                         // contenteditable=false element.
263                         if ( !range )
264                                 return;
265
266                         var doc = range.document;
267
268                         // Determine the block element to be used.
269                         var blockTag = ( mode == CKEDITOR.ENTER_DIV ? 'div' : 'p' );
270
271                         var isEndOfBlock = range.checkEndOfBlock();
272
273                         var elementPath = new CKEDITOR.dom.elementPath( editor.getSelection().getStartElement() );
274
275                         var startBlock = elementPath.block,
276                                 startBlockTag = startBlock && elementPath.block.getName();
277
278                         var isPre = false;
279
280                         if ( !forceMode && startBlockTag == 'li' )
281                         {
282                                 enterBlock( editor, mode, range, forceMode );
283                                 return;
284                         }
285
286                         // If we are at the end of a header block.
287                         if ( !forceMode && isEndOfBlock && headerTagRegex.test( startBlockTag ) )
288                         {
289                                 var newBlock,
290                                         newBlockDir;
291
292                                 if ( ( newBlockDir = startBlock.getDirection() ) )
293                                 {
294                                         newBlock = doc.createElement( 'div' );
295                                         newBlock.setAttribute( 'dir', newBlockDir );
296                                         newBlock.insertAfter( startBlock );
297                                         range.setStart( newBlock, 0 );
298                                 }
299                                 else
300                                 {
301                                         // Insert a <br> after the current paragraph.
302                                         doc.createElement( 'br' ).insertAfter( startBlock );
303
304                                         // A text node is required by Gecko only to make the cursor blink.
305                                         if ( CKEDITOR.env.gecko )
306                                                 doc.createText( '' ).insertAfter( startBlock );
307
308                                         // IE has different behaviors regarding position.
309                                         range.setStartAt( startBlock.getNext(), CKEDITOR.env.ie ? CKEDITOR.POSITION_BEFORE_START : CKEDITOR.POSITION_AFTER_START );
310                                 }
311                         }
312                         else
313                         {
314                                 var lineBreak;
315
316                                 isPre = ( startBlockTag == 'pre' );
317
318                                 // Gecko prefers <br> as line-break inside <pre> (#4711).
319                                 if ( isPre && !CKEDITOR.env.gecko )
320                                         lineBreak = doc.createText( CKEDITOR.env.ie ? '\r' : '\n' );
321                                 else
322                                         lineBreak = doc.createElement( 'br' );
323
324                                 range.deleteContents();
325                                 range.insertNode( lineBreak );
326
327                                 // IE has different behavior regarding position.
328                                 if ( CKEDITOR.env.ie )
329                                         range.setStartAt( lineBreak, CKEDITOR.POSITION_AFTER_END );
330                                 else
331                                 {
332                                         // A text node is required by Gecko only to make the cursor blink.
333                                         // We need some text inside of it, so the bogus <br> is properly
334                                         // created.
335                                         doc.createText( '\ufeff' ).insertAfter( lineBreak );
336
337                                         // If we are at the end of a block, we must be sure the bogus node is available in that block.
338                                         if ( isEndOfBlock )
339                                                 lineBreak.getParent().appendBogus();
340
341                                         // Now we can remove the text node contents, so the caret doesn't
342                                         // stop on it.
343                                         lineBreak.getNext().$.nodeValue = '';
344
345                                         range.setStartAt( lineBreak.getNext(), CKEDITOR.POSITION_AFTER_START );
346
347                                         // Scroll into view, for non IE.
348                                         var dummy = null;
349
350                                         // BR is not positioned in Opera and Webkit.
351                                         if ( !CKEDITOR.env.gecko )
352                                         {
353                                                 dummy = doc.createElement( 'span' );
354                                                 // We need have some contents for Webkit to position it
355                                                 // under parent node. ( #3681)
356                                                 dummy.setHtml('&nbsp;');
357                                         }
358                                         else
359                                                 dummy = doc.createElement( 'br' );
360
361                                         dummy.insertBefore( lineBreak.getNext() );
362                                         dummy.scrollIntoView();
363                                         dummy.remove();
364                                 }
365                         }
366
367                         // This collapse guarantees the cursor will be blinking.
368                         range.collapse( true );
369
370                         range.select( isPre );
371                 }
372         };
373
374         var plugin = CKEDITOR.plugins.enterkey,
375                 enterBr = plugin.enterBr,
376                 enterBlock = plugin.enterBlock,
377                 headerTagRegex = /^h[1-6]$/;
378
379         function shiftEnter( editor )
380         {
381                 // Only effective within document.
382                 if ( editor.mode != 'wysiwyg' )
383                         return false;
384
385                 // On SHIFT+ENTER:
386                 // 1. We want to enforce the mode to be respected, instead
387                 // of cloning the current block. (#77)
388                 return enter( editor, editor.config.shiftEnterMode, 1 );
389         }
390
391         function enter( editor, mode, forceMode )
392         {
393                 forceMode = editor.config.forceEnterMode || forceMode;
394
395                 // Only effective within document.
396                 if ( editor.mode != 'wysiwyg' )
397                         return false;
398
399                 if ( !mode )
400                         mode = editor.config.enterMode;
401
402                 // Use setTimout so the keys get cancelled immediatelly.
403                 setTimeout( function()
404                         {
405                                 editor.fire( 'saveSnapshot' );  // Save undo step.
406
407                                 if ( mode == CKEDITOR.ENTER_BR )
408                                         enterBr( editor, mode, null, forceMode );
409                                 else
410                                         enterBlock( editor, mode, null, forceMode );
411
412                                 editor.fire( 'saveSnapshot' );
413
414                         }, 0 );
415
416                 return true;
417         }
418
419         function getRange( editor )
420         {
421                 // Get the selection ranges.
422                 var ranges = editor.getSelection().getRanges( true );
423
424                 // Delete the contents of all ranges except the first one.
425                 for ( var i = ranges.length - 1 ; i > 0 ; i-- )
426                 {
427                         ranges[ i ].deleteContents();
428                 }
429
430                 // Return the first range.
431                 return ranges[ 0 ];
432         }
433 })();