~budgester/irm/trunk

« back to all changes in this revision

Viewing changes to FCKeditor/editor/_source/classes/fckenterkey.js

  • Committer: budgester at budgester
  • Date: 2008-03-05 23:14:13 UTC
  • Revision ID: budgester@budgester.com-20080305231413-k5vqfuckfo09ju42
Initial import of IRM codebase

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * FCKeditor - The text editor for Internet - http://www.fckeditor.net
 
3
 * Copyright (C) 2003-2007 Frederico Caldeira Knabben
 
4
 *
 
5
 * == BEGIN LICENSE ==
 
6
 *
 
7
 * Licensed under the terms of any of the following licenses at your
 
8
 * choice:
 
9
 *
 
10
 *  - GNU General Public License Version 2 or later (the "GPL")
 
11
 *    http://www.gnu.org/licenses/gpl.html
 
12
 *
 
13
 *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
 
14
 *    http://www.gnu.org/licenses/lgpl.html
 
15
 *
 
16
 *  - Mozilla Public License Version 1.1 or later (the "MPL")
 
17
 *    http://www.mozilla.org/MPL/MPL-1.1.html
 
18
 *
 
19
 * == END LICENSE ==
 
20
 *
 
21
 * Controls the [Enter] keystroke behavior in a document.
 
22
 */
 
23
 
 
24
/*
 
25
 *      Constructor.
 
26
 *              @targetDocument : the target document.
 
27
 *              @enterMode : the behavior for the <Enter> keystroke.
 
28
 *                      May be "p", "div", "br". Default is "p".
 
29
 *              @shiftEnterMode : the behavior for the <Shift>+<Enter> keystroke.
 
30
 *                      May be "p", "div", "br". Defaults to "br".
 
31
 */
 
32
var FCKEnterKey = function( targetWindow, enterMode, shiftEnterMode )
 
33
{
 
34
        this.Window                     = targetWindow ;
 
35
        this.EnterMode          = enterMode || 'p' ;
 
36
        this.ShiftEnterMode     = shiftEnterMode || 'br' ;
 
37
 
 
38
        // Setup the Keystroke Handler.
 
39
        var oKeystrokeHandler = new FCKKeystrokeHandler( false ) ;
 
40
        oKeystrokeHandler._EnterKey = this ;
 
41
        oKeystrokeHandler.OnKeystroke = FCKEnterKey_OnKeystroke ;
 
42
 
 
43
        oKeystrokeHandler.SetKeystrokes( [
 
44
                [ 13            , 'Enter' ],
 
45
                [ SHIFT + 13, 'ShiftEnter' ],
 
46
                [ 8                     , 'Backspace' ],
 
47
                [ 46            , 'Delete' ]
 
48
        ] ) ;
 
49
 
 
50
        oKeystrokeHandler.AttachToElement( targetWindow.document ) ;
 
51
}
 
52
 
 
53
 
 
54
function FCKEnterKey_OnKeystroke(  keyCombination, keystrokeValue )
 
55
{
 
56
        var oEnterKey = this._EnterKey ;
 
57
 
 
58
        try
 
59
        {
 
60
                switch ( keystrokeValue )
 
61
                {
 
62
                        case 'Enter' :
 
63
                                return oEnterKey.DoEnter() ;
 
64
                                break ;
 
65
 
 
66
                        case 'ShiftEnter' :
 
67
                                return oEnterKey.DoShiftEnter() ;
 
68
                                break ;
 
69
 
 
70
                        case 'Backspace' :
 
71
                                return oEnterKey.DoBackspace() ;
 
72
                                break ;
 
73
 
 
74
                        case 'Delete' :
 
75
                                return oEnterKey.DoDelete() ;
 
76
                }
 
77
        }
 
78
        catch (e)
 
79
        {
 
80
                // If for any reason we are not able to handle it, go
 
81
                // ahead with the browser default behavior.
 
82
        }
 
83
 
 
84
        return false ;
 
85
}
 
86
 
 
87
/*
 
88
 * Executes the <Enter> key behavior.
 
89
 */
 
90
FCKEnterKey.prototype.DoEnter = function( mode, hasShift )
 
91
{
 
92
        this._HasShift = ( hasShift === true ) ;
 
93
 
 
94
        var sMode = mode || this.EnterMode ;
 
95
 
 
96
        if ( sMode == 'br' )
 
97
                return this._ExecuteEnterBr() ;
 
98
        else
 
99
                return this._ExecuteEnterBlock( sMode ) ;
 
100
}
 
101
 
 
102
/*
 
103
 * Executes the <Shift>+<Enter> key behavior.
 
104
 */
 
105
FCKEnterKey.prototype.DoShiftEnter = function()
 
106
{
 
107
        return this.DoEnter( this.ShiftEnterMode, true ) ;
 
108
}
 
109
 
 
110
/*
 
111
 * Executes the <Backspace> key behavior.
 
112
 */
 
113
FCKEnterKey.prototype.DoBackspace = function()
 
114
{
 
115
        var bCustom = false ;
 
116
 
 
117
        // Get the current selection.
 
118
        var oRange = new FCKDomRange( this.Window ) ;
 
119
        oRange.MoveToSelection() ;
 
120
 
 
121
        if ( !oRange.CheckIsCollapsed() )
 
122
                return false ;
 
123
 
 
124
        var oStartBlock = oRange.StartBlock ;
 
125
        var oEndBlock = oRange.EndBlock ;
 
126
 
 
127
        // The selection boundaries must be in the same "block limit" element
 
128
        if ( oRange.StartBlockLimit == oRange.EndBlockLimit && oStartBlock && oEndBlock )
 
129
        {
 
130
                if ( !oRange.CheckIsCollapsed() )
 
131
                {
 
132
                        var bEndOfBlock = oRange.CheckEndOfBlock() ;
 
133
 
 
134
                        oRange.DeleteContents() ;
 
135
 
 
136
                        if ( oStartBlock != oEndBlock )
 
137
                        {
 
138
                                oRange.SetStart(oEndBlock,1) ;
 
139
                                oRange.SetEnd(oEndBlock,1) ;
 
140
 
 
141
//                              if ( bEndOfBlock )
 
142
//                                      oEndBlock.parentNode.removeChild( oEndBlock ) ;
 
143
                        }
 
144
 
 
145
                        oRange.Select() ;
 
146
 
 
147
                        bCustom = ( oStartBlock == oEndBlock ) ;
 
148
                }
 
149
 
 
150
                if ( oRange.CheckStartOfBlock() )
 
151
                {
 
152
                        var oCurrentBlock = oRange.StartBlock ;
 
153
 
 
154
                        var ePrevious = FCKDomTools.GetPreviousSourceElement( oCurrentBlock, true, [ 'BODY', oRange.StartBlockLimit.nodeName ], ['UL','OL'] ) ;
 
155
 
 
156
                        bCustom = this._ExecuteBackspace( oRange, ePrevious, oCurrentBlock ) ;
 
157
                }
 
158
                else if ( FCKBrowserInfo.IsGecko )
 
159
                {
 
160
                        // Firefox looses the selection when executing CheckStartOfBlock, so we must reselect.
 
161
                        oRange.Select() ;
 
162
                }
 
163
        }
 
164
 
 
165
        oRange.Release() ;
 
166
        return bCustom ;
 
167
}
 
168
 
 
169
FCKEnterKey.prototype._ExecuteBackspace = function( range, previous, currentBlock )
 
170
{
 
171
        var bCustom = false ;
 
172
 
 
173
        // We could be in a nested LI.
 
174
        if ( !previous && currentBlock.nodeName.IEquals( 'LI' ) && currentBlock.parentNode.parentNode.nodeName.IEquals( 'LI' ) )
 
175
        {
 
176
                this._OutdentWithSelection( currentBlock, range ) ;
 
177
                return true ;
 
178
        }
 
179
 
 
180
        if ( previous && previous.nodeName.IEquals( 'LI' ) )
 
181
        {
 
182
                var oNestedList = FCKDomTools.GetLastChild( previous, ['UL','OL'] ) ;
 
183
 
 
184
                while ( oNestedList )
 
185
                {
 
186
                        previous = FCKDomTools.GetLastChild( oNestedList, 'LI' ) ;
 
187
                        oNestedList = FCKDomTools.GetLastChild( previous, ['UL','OL'] ) ;
 
188
                }
 
189
        }
 
190
 
 
191
        if ( previous && currentBlock )
 
192
        {
 
193
                // If we are in a LI, and the previous block is not an LI, we must outdent it.
 
194
                if ( currentBlock.nodeName.IEquals( 'LI' ) && !previous.nodeName.IEquals( 'LI' ) )
 
195
                {
 
196
                        this._OutdentWithSelection( currentBlock, range ) ;
 
197
                        return true ;
 
198
                }
 
199
 
 
200
                // Take a reference to the parent for post processing cleanup.
 
201
                var oCurrentParent = currentBlock.parentNode ;
 
202
 
 
203
                var sPreviousName = previous.nodeName.toLowerCase() ;
 
204
                if ( FCKListsLib.EmptyElements[ sPreviousName ] != null || sPreviousName == 'table' )
 
205
                {
 
206
                        FCKDomTools.RemoveNode( previous ) ;
 
207
                        bCustom = true ;
 
208
                }
 
209
                else
 
210
                {
 
211
                        // Remove the current block.
 
212
                        FCKDomTools.RemoveNode( currentBlock ) ;
 
213
 
 
214
                        // Remove any empty tag left by the block removal.
 
215
                        while ( oCurrentParent.innerHTML.Trim().length == 0 )
 
216
                        {
 
217
                                var oParent = oCurrentParent.parentNode ;
 
218
                                oParent.removeChild( oCurrentParent ) ;
 
219
                                oCurrentParent = oParent ;
 
220
                        }
 
221
 
 
222
                        // Cleanup the previous and the current elements.
 
223
                        FCKDomTools.TrimNode( currentBlock ) ;
 
224
                        FCKDomTools.TrimNode( previous ) ;
 
225
 
 
226
                        // Append a space to the previous.
 
227
                        // Maybe it is not always desirable...
 
228
                        // previous.appendChild( this.Window.document.createTextNode( ' ' ) ) ;
 
229
 
 
230
                        // Set the range to the end of the previous element and bookmark it.
 
231
                        range.SetStart( previous, 2 ) ;
 
232
                        range.Collapse( true ) ;
 
233
                        var oBookmark = range.CreateBookmark() ;
 
234
 
 
235
                        // Move the contents of the block to the previous element and delete it.
 
236
                        FCKDomTools.MoveChildren( currentBlock, previous ) ;
 
237
 
 
238
                        // Place the selection at the bookmark.
 
239
                        range.MoveToBookmark( oBookmark ) ;
 
240
                        range.Select() ;
 
241
 
 
242
                        bCustom = true ;
 
243
                }
 
244
        }
 
245
 
 
246
        return bCustom ;
 
247
}
 
248
 
 
249
/*
 
250
 * Executes the <Delete> key behavior.
 
251
 */
 
252
FCKEnterKey.prototype.DoDelete = function()
 
253
{
 
254
        // The <Delete> has the same effect as the <Backspace>, so we have the same
 
255
        // results if we just move to the next block and apply the same <Backspace> logic.
 
256
 
 
257
        var bCustom = false ;
 
258
 
 
259
        // Get the current selection.
 
260
        var oRange = new FCKDomRange( this.Window ) ;
 
261
        oRange.MoveToSelection() ;
 
262
 
 
263
        // There is just one special case for collapsed selections at the end of a block.
 
264
        if ( oRange.CheckIsCollapsed() && oRange.CheckEndOfBlock( FCKBrowserInfo.IsGecko ) )
 
265
        {
 
266
                var oCurrentBlock = oRange.StartBlock ;
 
267
 
 
268
                var eNext = FCKDomTools.GetNextSourceElement( oCurrentBlock, true, [ oRange.StartBlockLimit.nodeName ], ['UL','OL'] ) ;
 
269
 
 
270
                bCustom = this._ExecuteBackspace( oRange, oCurrentBlock, eNext ) ;
 
271
        }
 
272
 
 
273
        oRange.Release() ;
 
274
        return bCustom ;
 
275
}
 
276
 
 
277
FCKEnterKey.prototype._ExecuteEnterBlock = function( blockTag, range )
 
278
{
 
279
        // Get the current selection.
 
280
        var oRange = range || new FCKDomRange( this.Window ) ;
 
281
 
 
282
        // If we don't have a range, move it to the selection.
 
283
        if ( !range )
 
284
                oRange.MoveToSelection() ;
 
285
 
 
286
        // The selection boundaries must be in the same "block limit" element.
 
287
        if ( oRange.StartBlockLimit == oRange.EndBlockLimit )
 
288
        {
 
289
                // If the StartBlock or EndBlock are not available (for text without a
 
290
                // block tag), we must fix them, by moving the text to a block.
 
291
                if ( !oRange.StartBlock )
 
292
                        this._FixBlock( oRange, true, blockTag ) ;
 
293
 
 
294
                if ( !oRange.EndBlock )
 
295
                        this._FixBlock( oRange, false, blockTag ) ;
 
296
 
 
297
                // Get the current blocks.
 
298
                var eStartBlock = oRange.StartBlock ;
 
299
                var eEndBlock   = oRange.EndBlock ;
 
300
 
 
301
                // Delete the current selection.
 
302
                if ( !oRange.CheckIsEmpty() )
 
303
                        oRange.DeleteContents() ;
 
304
 
 
305
                // If the selection boundaries are in the same block element
 
306
                if ( eStartBlock == eEndBlock )
 
307
                {
 
308
                        var eNewBlock ;
 
309
 
 
310
                        var bIsStartOfBlock     = oRange.CheckStartOfBlock() ;
 
311
                        var bIsEndOfBlock       = oRange.CheckEndOfBlock() ;
 
312
 
 
313
                        if ( bIsStartOfBlock && !bIsEndOfBlock )
 
314
                        {
 
315
                                eNewBlock = eStartBlock.cloneNode(false) ;
 
316
 
 
317
                                if ( FCKBrowserInfo.IsGeckoLike )
 
318
                                        eNewBlock.innerHTML = GECKO_BOGUS ;
 
319
 
 
320
                                // Place the new block before the current block element.
 
321
                                eStartBlock.parentNode.insertBefore( eNewBlock, eStartBlock ) ;
 
322
 
 
323
                                // This is tricky, but to make the new block visible correctly
 
324
                                // we must select it.
 
325
                                if ( FCKBrowserInfo.IsIE )
 
326
                                {
 
327
                                        // Move the selection to the new block.
 
328
                                        oRange.MoveToNodeContents( eNewBlock ) ;
 
329
 
 
330
                                        oRange.Select() ;
 
331
                                }
 
332
 
 
333
                                // Move the selection to the new block.
 
334
                                oRange.MoveToElementEditStart( eStartBlock ) ;
 
335
                        }
 
336
                        else
 
337
                        {
 
338
                                // Check if the selection is at the end of the block.
 
339
                                if ( bIsEndOfBlock )
 
340
                                {
 
341
                                        var sStartBlockTag = eStartBlock.tagName.toUpperCase() ;
 
342
 
 
343
                                        // If the entire block is selected, and we are in a LI, let's decrease its indentation.
 
344
                                        if ( bIsStartOfBlock && sStartBlockTag == 'LI' )
 
345
                                        {
 
346
                                                this._OutdentWithSelection( eStartBlock, oRange ) ;
 
347
                                                oRange.Release() ;
 
348
                                                return true ;
 
349
                                        }
 
350
                                        else
 
351
                                        {
 
352
                                                // If is a header tag, or we are in a Shift+Enter (#77),
 
353
                                                // create a new block element.
 
354
                                                if ( (/^H[1-6]$/).test( sStartBlockTag ) || this._HasShift )
 
355
                                                        eNewBlock = this.Window.document.createElement( blockTag ) ;
 
356
                                                // Otherwise, duplicate the current block.
 
357
                                                else
 
358
                                                {
 
359
                                                        eNewBlock = eStartBlock.cloneNode(false) ;
 
360
                                                        this._RecreateEndingTree( eStartBlock, eNewBlock ) ;
 
361
                                                }
 
362
 
 
363
                                                if ( FCKBrowserInfo.IsGeckoLike )
 
364
                                                {
 
365
                                                        eNewBlock.innerHTML = GECKO_BOGUS ;
 
366
 
 
367
                                                        // If the entire block is selected, let's add a bogus in the start block.
 
368
                                                        if ( bIsStartOfBlock )
 
369
                                                                eStartBlock.innerHTML = GECKO_BOGUS ;
 
370
                                                }
 
371
                                        }
 
372
                                }
 
373
                                else
 
374
                                {
 
375
                                        // Extract the contents of the block from the selection point to the end of its contents.
 
376
                                        oRange.SetEnd( eStartBlock, 2 ) ;
 
377
                                        var eDocFrag = oRange.ExtractContents() ;
 
378
 
 
379
                                        // Duplicate the block element after it.
 
380
                                        eNewBlock = eStartBlock.cloneNode(false) ;
 
381
 
 
382
                                        // It could be that we are in a LI with a child UL/OL. Insert a bogus to give us space to type.
 
383
                                        FCKDomTools.TrimNode( eDocFrag.RootNode ) ;
 
384
                                        if ( eDocFrag.RootNode.firstChild.nodeType == 1 && eDocFrag.RootNode.firstChild.tagName.toUpperCase().Equals( 'UL', 'OL' ) )
 
385
                                                eNewBlock.innerHTML = GECKO_BOGUS ;
 
386
 
 
387
                                        // Place the extracted contents in the duplicated block.
 
388
                                        eDocFrag.AppendTo( eNewBlock ) ;
 
389
 
 
390
                                        if ( FCKBrowserInfo.IsGecko )
 
391
                                        {
 
392
                                                // In Gecko, the last child node must be a bogus <br>.
 
393
                                                this._AppendBogusBr( eStartBlock ) ;
 
394
                                                this._AppendBogusBr( eNewBlock ) ;
 
395
                                        }
 
396
                                }
 
397
 
 
398
                                if ( eNewBlock )
 
399
                                {
 
400
                                        FCKDomTools.InsertAfterNode( eStartBlock, eNewBlock ) ;
 
401
 
 
402
                                        // Move the selection to the new block.
 
403
                                        oRange.MoveToElementEditStart( eNewBlock ) ;
 
404
 
 
405
                                        if ( FCKBrowserInfo.IsGecko )
 
406
                                                eNewBlock.scrollIntoView( false ) ;
 
407
                                }
 
408
                        }
 
409
                }
 
410
                else
 
411
                {
 
412
                        // Move the selection to the end block.
 
413
                        oRange.MoveToElementEditStart( eEndBlock ) ;
 
414
                }
 
415
 
 
416
                oRange.Select() ;
 
417
        }
 
418
 
 
419
        // Release the resources used by the range.
 
420
        oRange.Release() ;
 
421
 
 
422
        return true ;
 
423
}
 
424
 
 
425
FCKEnterKey.prototype._ExecuteEnterBr = function( blockTag )
 
426
{
 
427
        // Get the current selection.
 
428
        var oRange = new FCKDomRange( this.Window ) ;
 
429
        oRange.MoveToSelection() ;
 
430
 
 
431
        // The selection boundaries must be in the same "block limit" element.
 
432
        if ( oRange.StartBlockLimit == oRange.EndBlockLimit )
 
433
        {
 
434
                oRange.DeleteContents() ;
 
435
 
 
436
                // Get the new selection (it is collapsed at this point).
 
437
                oRange.MoveToSelection() ;
 
438
 
 
439
                var bIsStartOfBlock     = oRange.CheckStartOfBlock() ;
 
440
                var bIsEndOfBlock       = oRange.CheckEndOfBlock() ;
 
441
 
 
442
                var sStartBlockTag = oRange.StartBlock ? oRange.StartBlock.tagName.toUpperCase() : '' ;
 
443
 
 
444
                var bHasShift = this._HasShift ;
 
445
 
 
446
                if ( !bHasShift && sStartBlockTag == 'LI' )
 
447
                        return this._ExecuteEnterBlock( null, oRange ) ;
 
448
 
 
449
                // If we are at the end of a header block.
 
450
                if ( !bHasShift && bIsEndOfBlock && (/^H[1-6]$/).test( sStartBlockTag ) )
 
451
                {
 
452
                        FCKDebug.Output( 'BR - Header' ) ;
 
453
 
 
454
                        // Insert a BR after the current paragraph.
 
455
                        FCKDomTools.InsertAfterNode( oRange.StartBlock, this.Window.document.createElement( 'br' ) ) ;
 
456
 
 
457
                        // The space is required by Gecko only to make the cursor blink.
 
458
                        if ( FCKBrowserInfo.IsGecko )
 
459
                                FCKDomTools.InsertAfterNode( oRange.StartBlock, this.Window.document.createTextNode( '' ) ) ;
 
460
 
 
461
                        // IE and Gecko have different behaviors regarding the position.
 
462
                        oRange.SetStart( oRange.StartBlock.nextSibling, FCKBrowserInfo.IsIE ? 3 : 1 ) ;
 
463
                }
 
464
                else
 
465
                {
 
466
                        FCKDebug.Output( 'BR - No Header' ) ;
 
467
 
 
468
                        var eBr = this.Window.document.createElement( 'br' ) ;
 
469
 
 
470
                        oRange.InsertNode( eBr ) ;
 
471
 
 
472
                        // The space is required by Gecko only to make the cursor blink.
 
473
                        if ( FCKBrowserInfo.IsGecko )
 
474
                                FCKDomTools.InsertAfterNode( eBr, this.Window.document.createTextNode( '' ) ) ;
 
475
 
 
476
                        // If we are at the end of a block, we must be sure the bogus node is available in that block.
 
477
                        if ( bIsEndOfBlock && FCKBrowserInfo.IsGecko )
 
478
                                this._AppendBogusBr( eBr.parentNode ) ;
 
479
 
 
480
                        if ( FCKBrowserInfo.IsIE )
 
481
                                oRange.SetStart( eBr, 4 ) ;
 
482
                        else
 
483
                                oRange.SetStart( eBr.nextSibling, 1 ) ;
 
484
 
 
485
                }
 
486
 
 
487
                // This collapse guarantees the cursor will be blinking.
 
488
                oRange.Collapse( true ) ;
 
489
 
 
490
                oRange.Select() ;
 
491
        }
 
492
 
 
493
        // Release the resources used by the range.
 
494
        oRange.Release() ;
 
495
 
 
496
        return true ;
 
497
}
 
498
 
 
499
// Transform a block without a block tag in a valid block (orphan text in the body or td, usually).
 
500
FCKEnterKey.prototype._FixBlock = function( range, isStart, blockTag )
 
501
{
 
502
        // Bookmark the range so we can restore it later.
 
503
        var oBookmark = range.CreateBookmark() ;
 
504
 
 
505
        // Collapse the range to the requested ending boundary.
 
506
        range.Collapse( isStart ) ;
 
507
 
 
508
        // Expands it to the block contents.
 
509
        range.Expand( 'block_contents' ) ;
 
510
 
 
511
        // Create the fixed block.
 
512
        var oFixedBlock = this.Window.document.createElement( blockTag ) ;
 
513
 
 
514
        // Move the contents of the temporary range to the fixed block.
 
515
        range.ExtractContents().AppendTo( oFixedBlock ) ;
 
516
        FCKDomTools.TrimNode( oFixedBlock ) ;
 
517
 
 
518
        // Insert the fixed block into the DOM.
 
519
        range.InsertNode( oFixedBlock ) ;
 
520
 
 
521
        // Move the range back to the bookmarked place.
 
522
        range.MoveToBookmark( oBookmark ) ;
 
523
}
 
524
 
 
525
// Appends a bogus <br> at the end of the element, if not yet available.
 
526
FCKEnterKey.prototype._AppendBogusBr = function( element )
 
527
{
 
528
        var eLastChild = element.getElementsByTagName('br') ;
 
529
 
 
530
        if ( eLastChild )
 
531
                eLastChild = eLastChild[ eLastChild.legth - 1 ] ;
 
532
 
 
533
        if ( !eLastChild || eLastChild.getAttribute( 'type', 2 ) != '_moz' )
 
534
                element.appendChild( FCKTools.CreateBogusBR( this.Window.document ) ) ;
 
535
}
 
536
 
 
537
// Recreate the elements tree at the end of the source block, at the beginning
 
538
// of the target block. Eg.:
 
539
//      If source = <p><u>Some</u> sample <b><i>text</i></b></p> then target = <p><b><i></i></b></p>
 
540
//      If source = <p><u>Some</u> sample text</p> then target = <p></p>
 
541
FCKEnterKey.prototype._RecreateEndingTree = function( source, target )
 
542
{
 
543
        while ( ( source = source.lastChild ) && source.nodeType == 1 && FCKListsLib.InlineChildReqElements[ source.nodeName.toLowerCase() ] != null )
 
544
                target = target.insertBefore( source.cloneNode( false ), target.firstChild ) ;
 
545
}
 
546
 
 
547
// Outdents a LI, maintaining the seletion defined on a range.
 
548
FCKEnterKey.prototype._OutdentWithSelection = function( li, range )
 
549
{
 
550
        var oBookmark = range.CreateBookmark() ;
 
551
 
 
552
        FCKListHandler.OutdentListItem( li ) ;
 
553
 
 
554
        range.MoveToBookmark( oBookmark ) ;
 
555
        range.Select() ;
 
556
}
 
 
b'\\ No newline at end of file'