Ticket #14346: plugin_enterkey_plugin.js

File plugin_enterkey_plugin.js, 19.2 KB (added by Andreas Soroko, 8 years ago)

Modified enterkey plugin which works

Line 
1/**
2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5
6( function() {
7        CKEDITOR.plugins.add( 'enterkey', {
8                init: function( editor ) {
9                        editor.addCommand( 'enter', {
10                                modes: { wysiwyg: 1 },
11                                editorFocus: false,
12                                exec: function( editor ) {
13                                        enter( editor );
14                                }
15                        } );
16
17                        editor.addCommand( 'shiftEnter', {
18                                modes: { wysiwyg: 1 },
19                                editorFocus: false,
20                                exec: function( editor ) {
21                                        shiftEnter( editor );
22                                }
23                        } );
24
25                        editor.setKeystroke( [
26                                [ 13, 'enter' ],
27                                [ CKEDITOR.SHIFT + 13, 'shiftEnter' ]
28                        ] );
29                }
30        } );
31
32        var whitespaces = CKEDITOR.dom.walker.whitespaces(),
33                bookmark = CKEDITOR.dom.walker.bookmark();
34
35        CKEDITOR.plugins.enterkey = {
36                enterBlock: function( editor, mode, range, forceMode ) {
37                        // Get the range for the current selection.
38                        range = range || getRange( editor );
39
40                        // We may not have valid ranges to work on, like when inside a
41                        // contenteditable=false element.
42                        if ( !range )
43                                return;
44
45                        // When range is in nested editable, we have to replace range with this one,
46                        // which have root property set to closest editable, to make auto paragraphing work. (#12162)
47                        range = replaceRangeWithClosestEditableRoot( range );
48
49                        var doc = range.document;
50
51                        var atBlockStart = range.checkStartOfBlock(),
52                                atBlockEnd = range.checkEndOfBlock(),
53                                path = editor.elementPath( range.startContainer ),
54                                block = path.block,
55
56                                // Determine the block element to be used.
57                                blockTag = ( mode == CKEDITOR.ENTER_DIV ? 'div' : 'p' ),
58
59                                newBlock;
60                               
61                        if ( CKEDITOR.env.gecko) {
62                                var originalText = block.getText();
63                                block.setText(originalText);
64                        }
65
66                        // Exit the list when we're inside an empty list item block. (#5376)
67                        if ( atBlockStart && atBlockEnd ) {
68                                // Exit the list when we're inside an empty list item block. (#5376)
69                                if ( block && ( block.is( 'li' ) || block.getParent().is( 'li' ) ) ) {
70                                        // Make sure to point to the li when dealing with empty list item.
71                                        if ( !block.is( 'li' ) )
72                                                block = block.getParent();
73
74                                        var blockParent = block.getParent(),
75                                                blockGrandParent = blockParent.getParent(),
76
77                                                firstChild = !block.hasPrevious(),
78                                                lastChild = !block.hasNext(),
79
80                                                selection = editor.getSelection(),
81                                                bookmarks = selection.createBookmarks(),
82
83                                                orgDir = block.getDirection( 1 ),
84                                                className = block.getAttribute( 'class' ),
85                                                style = block.getAttribute( 'style' ),
86                                                dirLoose = blockGrandParent.getDirection( 1 ) != orgDir,
87
88                                                enterMode = editor.enterMode,
89                                                needsBlock = enterMode != CKEDITOR.ENTER_BR || dirLoose || style || className,
90
91                                                child;
92
93                                        if ( blockGrandParent.is( 'li' ) ) {
94
95                                                // If block is the first or the last child of the parent
96                                                // list, degrade it and move to the outer list:
97                                                // before the parent list if block is first child and after
98                                                // the parent list if block is the last child, respectively.
99                                                //
100                                                //  <ul>                         =>      <ul>
101                                                //      <li>                     =>          <li>
102                                                //          <ul>                 =>              <ul>
103                                                //              <li>x</li>       =>                  <li>x</li>
104                                                //              <li>^</li>       =>              </ul>
105                                                //          </ul>                =>          </li>
106                                                //      </li>                    =>          <li>^</li>
107                                                //  </ul>                        =>      </ul>
108                                                //
109                                                //                              AND
110                                                //
111                                                //  <ul>                         =>      <ul>
112                                                //      <li>                     =>          <li>^</li>
113                                                //          <ul>                 =>          <li>
114                                                //              <li>^</li>       =>              <ul>
115                                                //              <li>x</li>       =>                  <li>x</li>
116                                                //          </ul>                =>              </ul>
117                                                //      </li>                    =>          </li>
118                                                //  </ul>                        =>      </ul>
119
120                                                if ( firstChild || lastChild ) {
121
122                                                        // If it's only child, we don't want to keep perent ul anymore.
123                                                        if ( firstChild && lastChild ) {
124                                                                blockParent.remove();
125                                                        }
126
127                                                        block[lastChild ? 'insertAfter' : 'insertBefore']( blockGrandParent );
128
129                                                        // If the empty block is neither first nor last child
130                                                        // then split the list and the block as an element
131                                                        // of outer list.
132                                                        //
133                                                        //                              =>      <ul>
134                                                        //                              =>          <li>
135                                                        //  <ul>                        =>              <ul>
136                                                        //      <li>                    =>                  <li>x</li>
137                                                        //          <ul>                =>              </ul>
138                                                        //              <li>x</li>      =>          </li>
139                                                        //              <li>^</li>      =>          <li>^</li>
140                                                        //              <li>y</li>      =>          <li>
141                                                        //          </ul>               =>              <ul>
142                                                        //      </li>                   =>                  <li>y</li>
143                                                        //  </ul>                       =>              </ul>
144                                                        //                              =>          </li>
145                                                        //                              =>      </ul>
146
147                                                } else {
148                                                        block.breakParent( blockGrandParent );
149                                                }
150                                        }
151
152                                        else if ( !needsBlock ) {
153                                                block.appendBogus( true );
154
155                                                // If block is the first or last child of the parent
156                                                // list, move all block's children out of the list:
157                                                // before the list if block is first child and after the list
158                                                // if block is the last child, respectively.
159                                                //
160                                                //  <ul>                       =>      <ul>
161                                                //      <li>x</li>             =>          <li>x</li>
162                                                //      <li>^</li>             =>      </ul>
163                                                //  </ul>                      =>      ^
164                                                //
165                                                //                            AND
166                                                //
167                                                //  <ul>                       =>      ^
168                                                //      <li>^</li>             =>      <ul>
169                                                //      <li>x</li>             =>          <li>x</li>
170                                                //  </ul>                      =>      </ul>
171
172                                                if ( firstChild || lastChild ) {
173                                                        while ( ( child = block[ firstChild ? 'getFirst' : 'getLast' ]() ) )
174                                                                child[ firstChild ? 'insertBefore' : 'insertAfter' ]( blockParent );
175                                                }
176
177                                                // If the empty block is neither first nor last child
178                                                // then split the list and put all the block contents
179                                                // between two lists.
180                                                //
181                                                //  <ul>                       =>      <ul>
182                                                //      <li>x</li>             =>          <li>x</li>
183                                                //      <li>^</li>             =>      </ul>
184                                                //      <li>y</li>             =>      ^
185                                                //  </ul>                      =>      <ul>
186                                                //                             =>          <li>y</li>
187                                                //                             =>      </ul>
188
189                                                else {
190                                                        block.breakParent( blockParent );
191
192                                                        while ( ( child = block.getLast() ) )
193                                                                child.insertAfter( blockParent );
194                                                }
195
196                                                block.remove();
197                                        } else {
198                                                // Original path block is the list item, create new block for the list item content.
199                                                if ( path.block.is( 'li' ) ) {
200                                                        // Use <div> block for ENTER_BR and ENTER_DIV.
201                                                        newBlock = doc.createElement( mode == CKEDITOR.ENTER_P ? 'p' : 'div' );
202
203                                                        if ( dirLoose )
204                                                                newBlock.setAttribute( 'dir', orgDir );
205
206                                                        style && newBlock.setAttribute( 'style', style );
207                                                        className && newBlock.setAttribute( 'class', className );
208
209                                                        // Move all the child nodes to the new block.
210                                                        block.moveChildren( newBlock );
211                                                }
212                                                // The original path block is not a list item, just copy the block to out side of the list.
213                                                else {
214                                                        newBlock = path.block;
215                                                }
216
217                                                // If block is the first or last child of the parent
218                                                // list, move it out of the list:
219                                                // before the list if block is first child and after the list
220                                                // if block is the last child, respectively.
221                                                //
222                                                //  <ul>                       =>      <ul>
223                                                //      <li>x</li>             =>          <li>x</li>
224                                                //      <li>^</li>             =>      </ul>
225                                                //  </ul>                      =>      <p>^</p>
226                                                //
227                                                //                            AND
228                                                //
229                                                //  <ul>                       =>      <p>^</p>
230                                                //      <li>^</li>             =>      <ul>
231                                                //      <li>x</li>             =>          <li>x</li>
232                                                //  </ul>                      =>      </ul>
233
234                                                if ( firstChild || lastChild )
235                                                        newBlock[ firstChild ? 'insertBefore' : 'insertAfter' ]( blockParent );
236
237                                                // If the empty block is neither first nor last child
238                                                // then split the list and put the new block between
239                                                // two lists.
240                                                //
241                                                //                             =>       <ul>
242                                                //     <ul>                    =>           <li>x</li>
243                                                //         <li>x</li>          =>       </ul>
244                                                //         <li>^</li>          =>       <p>^</p>
245                                                //         <li>y</li>          =>       <ul>
246                                                //     </ul>                   =>           <li>y</li>
247                                                //                             =>       </ul>
248
249                                                else {
250                                                        block.breakParent( blockParent );
251                                                        newBlock.insertAfter( blockParent );
252                                                }
253
254                                                block.remove();
255                                        }
256
257                                        selection.selectBookmarks( bookmarks );
258
259                                        return;
260                                }
261
262                                if ( block && block.getParent().is( 'blockquote' ) ) {
263                                        block.breakParent( block.getParent() );
264
265                                        // If we were at the start of <blockquote>, there will be an empty element before it now.
266                                        if ( !block.getPrevious().getFirst( CKEDITOR.dom.walker.invisible( 1 ) ) )
267                                                block.getPrevious().remove();
268
269                                        // If we were at the end of <blockquote>, there will be an empty element after it now.
270                                        if ( !block.getNext().getFirst( CKEDITOR.dom.walker.invisible( 1 ) ) )
271                                                block.getNext().remove();
272
273                                        range.moveToElementEditStart( block );
274                                        range.select();
275                                        return;
276                                }
277                        }
278                        // Don't split <pre> if we're in the middle of it, act as shift enter key.
279                        else if ( block && block.is( 'pre' ) ) {
280                                if ( !atBlockEnd ) {
281                                        enterBr( editor, mode, range, forceMode );
282                                        return;
283                                }
284                        }
285
286                        // Split the range.
287                        var splitInfo = range.splitBlock( blockTag );
288
289                        if ( !splitInfo )
290                                return;
291
292                        // Get the current blocks.
293                        var previousBlock = splitInfo.previousBlock,
294                                nextBlock = splitInfo.nextBlock;
295
296                        var isStartOfBlock = splitInfo.wasStartOfBlock,
297                                isEndOfBlock = splitInfo.wasEndOfBlock;
298
299                        var node;
300
301                        // If this is a block under a list item, split it as well. (#1647)
302                        if ( nextBlock ) {
303                                node = nextBlock.getParent();
304                                if ( node.is( 'li' ) ) {
305                                        nextBlock.breakParent( node );
306                                        nextBlock.move( nextBlock.getNext(), 1 );
307                                }
308                        } else if ( previousBlock && ( node = previousBlock.getParent() ) && node.is( 'li' ) ) {
309                                previousBlock.breakParent( node );
310                                node = previousBlock.getNext();
311                                range.moveToElementEditStart( node );
312                                previousBlock.move( previousBlock.getPrevious() );
313                        }
314
315                        // If we have both the previous and next blocks, it means that the
316                        // boundaries were on separated blocks, or none of them where on the
317                        // block limits (start/end).
318                        if ( !isStartOfBlock && !isEndOfBlock ) {
319                                // If the next block is an <li> with another list tree as the first
320                                // child, we'll need to append a filler (<br>/NBSP) or the list item
321                                // wouldn't be editable. (#1420)
322                                if ( nextBlock.is( 'li' ) ) {
323                                        var walkerRange = range.clone();
324                                        walkerRange.selectNodeContents( nextBlock );
325                                        var walker = new CKEDITOR.dom.walker( walkerRange );
326                                        walker.evaluator = function( node ) {
327                                                return !( bookmark( node ) || whitespaces( node ) || node.type == CKEDITOR.NODE_ELEMENT && node.getName() in CKEDITOR.dtd.$inline && !( node.getName() in CKEDITOR.dtd.$empty ) );
328                                        };
329
330                                        node = walker.next();
331                                        if ( node && node.type == CKEDITOR.NODE_ELEMENT && node.is( 'ul', 'ol' ) )
332                                                ( CKEDITOR.env.needsBrFiller ? doc.createElement( 'br' ) : doc.createText( '\xa0' ) ).insertBefore( node );
333                                }
334
335                                // Move the selection to the end block.
336                                if ( nextBlock )
337                                        range.moveToElementEditStart( nextBlock );
338                        } else {
339                                var newBlockDir;
340
341                                if ( previousBlock ) {
342                                        // Do not enter this block if it's a header tag, or we are in
343                                        // a Shift+Enter (#77). Create a new block element instead
344                                        // (later in the code).
345                                        if ( previousBlock.is( 'li' ) || !( headerTagRegex.test( previousBlock.getName() ) || previousBlock.is( 'pre' ) ) ) {
346                                                // Otherwise, duplicate the previous block.
347                                                newBlock = previousBlock.clone();
348                                        }
349                                } else if ( nextBlock ) {
350                                        newBlock = nextBlock.clone();
351                                }
352
353                                if ( !newBlock ) {
354                                        // We have already created a new list item. (#6849)
355                                        if ( node && node.is( 'li' ) )
356                                                newBlock = node;
357                                        else {
358                                                newBlock = doc.createElement( blockTag );
359                                                if ( previousBlock && ( newBlockDir = previousBlock.getDirection() ) )
360                                                        newBlock.setAttribute( 'dir', newBlockDir );
361                                        }
362                                }
363                                // Force the enter block unless we're talking of a list item.
364                                else if ( forceMode && !newBlock.is( 'li' ) ) {
365                                        newBlock.renameNode( blockTag );
366                                }
367
368                                // Recreate the inline elements tree, which was available
369                                // before hitting enter, so the same styles will be available in
370                                // the new block.
371                                var elementPath = splitInfo.elementPath;
372                                if ( elementPath ) {
373                                        for ( var i = 0, len = elementPath.elements.length; i < len; i++ ) {
374                                                var element = elementPath.elements[ i ];
375
376                                                if ( element.equals( elementPath.block ) || element.equals( elementPath.blockLimit ) )
377                                                        break;
378
379                                                if ( CKEDITOR.dtd.$removeEmpty[ element.getName() ] ) {
380                                                        element = element.clone();
381                                                        newBlock.moveChildren( element );
382                                                        newBlock.append( element );
383                                                }
384                                        }
385                                }
386
387                                newBlock.appendBogus();
388
389                                if ( !newBlock.getParent() )
390                                        range.insertNode( newBlock );
391
392                                // list item start number should not be duplicated (#7330), but we need
393                                // to remove the attribute after it's onto the DOM tree because of old IEs (#7581).
394                                newBlock.is( 'li' ) && newBlock.removeAttribute( 'value' );
395
396                                // This is tricky, but to make the new block visible correctly
397                                // we must select it.
398                                // The previousBlock check has been included because it may be
399                                // empty if we have fixed a block-less space (like ENTER into an
400                                // empty table cell).
401                                if ( CKEDITOR.env.ie && isStartOfBlock && ( !isEndOfBlock || !previousBlock.getChildCount() ) ) {
402                                        // Move the selection to the new block.
403                                        range.moveToElementEditStart( isEndOfBlock ? previousBlock : newBlock );
404                                        range.select();
405                                }
406
407                                // Move the selection to the new block.
408                                range.moveToElementEditStart( isStartOfBlock && !isEndOfBlock ? nextBlock : newBlock );
409                        }
410
411                        range.select();
412                        range.scrollIntoView();
413                },
414
415                enterBr: function( editor, mode, range, forceMode ) {
416                        // Get the range for the current selection.
417                        range = range || getRange( editor );
418
419                        // We may not have valid ranges to work on, like when inside a
420                        // contenteditable=false element.
421                        if ( !range )
422                                return;
423
424                        var doc = range.document;
425
426                        var isEndOfBlock = range.checkEndOfBlock();
427
428                        var elementPath = new CKEDITOR.dom.elementPath( editor.getSelection().getStartElement() );
429
430                        var startBlock = elementPath.block,
431                                startBlockTag = startBlock && elementPath.block.getName();
432
433                        if ( !forceMode && startBlockTag == 'li' ) {
434                                enterBlock( editor, mode, range, forceMode );
435                                return;
436                        }
437
438                        // If we are at the end of a header block.
439                        if ( !forceMode && isEndOfBlock && headerTagRegex.test( startBlockTag ) ) {
440                                var newBlock, newBlockDir;
441
442                                if ( ( newBlockDir = startBlock.getDirection() ) ) {
443                                        newBlock = doc.createElement( 'div' );
444                                        newBlock.setAttribute( 'dir', newBlockDir );
445                                        newBlock.insertAfter( startBlock );
446                                        range.setStart( newBlock, 0 );
447                                } else {
448                                        // Insert a <br> after the current paragraph.
449                                        doc.createElement( 'br' ).insertAfter( startBlock );
450
451                                        // A text node is required by Gecko only to make the cursor blink.
452                                        if ( CKEDITOR.env.gecko )
453                                                doc.createText( '' ).insertAfter( startBlock );
454
455                                        // IE has different behaviors regarding position.
456                                        range.setStartAt( startBlock.getNext(), CKEDITOR.env.ie ? CKEDITOR.POSITION_BEFORE_START : CKEDITOR.POSITION_AFTER_START );
457                                }
458                        } else {
459                                var lineBreak;
460
461                                // IE<8 prefers text node as line-break inside of <pre> (#4711).
462                                if ( startBlockTag == 'pre' && CKEDITOR.env.ie && CKEDITOR.env.version < 8 )
463                                        lineBreak = doc.createText( '\r' );
464                                else
465                                        lineBreak = doc.createElement( 'br' );
466
467                                range.deleteContents();
468                                range.insertNode( lineBreak );
469
470                                // Old IEs have different behavior regarding position.
471                                if ( !CKEDITOR.env.needsBrFiller )
472                                        range.setStartAt( lineBreak, CKEDITOR.POSITION_AFTER_END );
473                                else {
474                                        // A text node is required by Gecko only to make the cursor blink.
475                                        // We need some text inside of it, so the bogus <br> is properly
476                                        // created.
477                                        doc.createText( '\ufeff' ).insertAfter( lineBreak );
478
479                                        // If we are at the end of a block, we must be sure the bogus node is available in that block.
480                                        if ( isEndOfBlock ) {
481                                                // In most situations we've got an elementPath.block (e.g. <p>), but in a
482                                                // blockless editor or when autoP is false that needs to be a block limit.
483                                                ( startBlock || elementPath.blockLimit ).appendBogus();
484                                        }
485
486                                        // Now we can remove the text node contents, so the caret doesn't
487                                        // stop on it.
488                                        lineBreak.getNext().$.nodeValue = '';
489
490                                        range.setStartAt( lineBreak.getNext(), CKEDITOR.POSITION_AFTER_START );
491
492                                }
493                        }
494
495                        // This collapse guarantees the cursor will be blinking.
496                        range.collapse( true );
497
498                        range.select();
499                        range.scrollIntoView();
500                }
501        };
502
503        var plugin = CKEDITOR.plugins.enterkey,
504                enterBr = plugin.enterBr,
505                enterBlock = plugin.enterBlock,
506                headerTagRegex = /^h[1-6]$/;
507
508        function shiftEnter( editor ) {
509                // On SHIFT+ENTER:
510                // 1. We want to enforce the mode to be respected, instead
511                // of cloning the current block. (#77)
512                return enter( editor, editor.activeShiftEnterMode, 1 );
513        }
514
515        function enter( editor, mode, forceMode ) {
516                forceMode = editor.config.forceEnterMode || forceMode;
517
518                // Only effective within document.
519                if ( editor.mode != 'wysiwyg' )
520                        return;
521
522                if ( !mode )
523                        mode = editor.activeEnterMode;
524
525                // TODO this should be handled by setting editor.activeEnterMode on selection change.
526                // Check path block specialities:
527                // 1. Cannot be a un-splittable element, e.g. table caption;
528                var path = editor.elementPath();
529                if ( !path.isContextFor( 'p' ) ) {
530                        mode = CKEDITOR.ENTER_BR;
531                        forceMode = 1;
532                }
533
534                editor.fire( 'saveSnapshot' ); // Save undo step.
535
536                if ( mode == CKEDITOR.ENTER_BR )
537                        enterBr( editor, mode, null, forceMode );
538                else
539                        enterBlock( editor, mode, null, forceMode );
540
541                editor.fire( 'saveSnapshot' );
542        }
543
544        function getRange( editor ) {
545                // Get the selection ranges.
546                var ranges = editor.getSelection().getRanges( true );
547
548                // Delete the contents of all ranges except the first one.
549                for ( var i = ranges.length - 1; i > 0; i-- ) {
550                        ranges[ i ].deleteContents();
551                }
552
553                // Return the first range.
554                return ranges[ 0 ];
555        }
556
557        function replaceRangeWithClosestEditableRoot( range ) {
558                var closestEditable = range.startContainer.getAscendant( function( node ) {
559                        return node.type == CKEDITOR.NODE_ELEMENT && node.getAttribute( 'contenteditable' ) == 'true';
560                }, true );
561
562                if ( range.root.equals( closestEditable ) ) {
563                        return range;
564                } else {
565                        var newRange = new CKEDITOR.dom.range( closestEditable );
566
567                        newRange.moveToRange( range );
568                        return newRange;
569                }
570        }
571} )();
© 2003 – 2022, CKSource sp. z o.o. sp.k. All rights reserved. | Terms of use | Privacy policy