Ticket #4508: 4508.patch

File 4508.patch, 20.0 KB (added by Garry Yao, 14 years ago)
  • _source/plugins/enterkey/plugin.js

     
    55
    66(function()
    77{
    8         CKEDITOR.plugins.add( 'enterkey',
     8        CKEDITOR.plugins.enterkey =
    99        {
    10                 requires : [ 'keystrokes', 'indent' ],
    11 
    12                 init : function( editor )
     10                enterBlock : function( editor, mode, range )
    1311                {
    14                         var specialKeys = editor.specialKeys;
    15                         specialKeys[ 13 ] = enter;
    16                         specialKeys[ CKEDITOR.SHIFT + 13 ] = shiftEnter;
    17                 }
    18         });
    19 
    20         var forceMode,
    21                 headerTagRegex = /^h[1-6]$/;
    22 
    23         function shiftEnter( editor )
    24         {
    25                 // On SHIFT+ENTER we want to enforce the mode to be respected, instead
    26                 // of cloning the current block. (#77)
    27                 forceMode = 1;
    28 
    29                 return enter( editor, editor.config.shiftEnterMode );
    30         }
    31 
    32         function enter( editor, mode )
    33         {
    34                 // Only effective within document.
    35                 if ( editor.mode != 'wysiwyg' )
    36                         return false;
    37 
    38                 if ( !mode )
    39                         mode = editor.config.enterMode;
    40 
    41                 // Use setTimout so the keys get cancelled immediatelly.
    42                 setTimeout( function()
    43                         {
    44                                 editor.fire( 'saveSnapshot' );  // Save undo step.
    45                                 if ( mode == CKEDITOR.ENTER_BR || editor.getSelection().getStartElement().hasAscendant( 'pre', true ) )
    46                                         enterBr( editor, mode );
    47                                 else
    48                                         enterBlock( editor, mode );
    49 
    50                                 forceMode = 0;
    51                         }, 0 );
    52 
    53                 return true;
    54         }
    55 
    56         function enterBlock( editor, mode, range )
    57         {
    58                 // Get the range for the current selection.
    59                 range = range || getRange( editor );
     12                        // Get the range for the current selection.
     13                        range = range || getRange( editor );
    6014
    61                 var doc = range.document;
     15                        var doc = range.document;
    6216
    63                 // Determine the block element to be used.
    64                 var blockTag = ( mode == CKEDITOR.ENTER_DIV ? 'div' : 'p' );
     17                        // Determine the block element to be used.
     18                        var blockTag = ( mode == CKEDITOR.ENTER_DIV ? 'div' : 'p' );
    6519
    66                 // Split the range.
    67                 var splitInfo = range.splitBlock( blockTag );
     20                        // Split the range.
     21                        var splitInfo = range.splitBlock( blockTag );
    6822
    69                 if ( !splitInfo )
    70                         return;
     23                        if ( !splitInfo )
     24                                return;
    7125
    72                 // Get the current blocks.
    73                 var previousBlock       = splitInfo.previousBlock,
    74                         nextBlock               = splitInfo.nextBlock;
     26                        // Get the current blocks.
     27                        var previousBlock       = splitInfo.previousBlock,
     28                                nextBlock               = splitInfo.nextBlock;
    7529
    76                 var isStartOfBlock      = splitInfo.wasStartOfBlock,
    77                         isEndOfBlock    = splitInfo.wasEndOfBlock;
     30                        var isStartOfBlock      = splitInfo.wasStartOfBlock,
     31                                isEndOfBlock    = splitInfo.wasEndOfBlock;
    7832
    79                 var node;
     33                        var node;
    8034
    81                 // If this is a block under a list item, split it as well. (#1647)
    82                 if ( nextBlock )
    83                 {
    84                         node = nextBlock.getParent();
    85                         if ( node.is( 'li' ) )
    86                         {
    87                                 nextBlock.breakParent( node );
    88                                 nextBlock.move( nextBlock.getNext(), true );
    89                         }
    90                 }
    91                 else if ( previousBlock && ( node = previousBlock.getParent() ) && node.is( 'li' ) )
    92                 {
    93                         previousBlock.breakParent( node );
    94                         range.moveToElementEditStart( previousBlock.getNext() );
    95                         previousBlock.move( previousBlock.getPrevious() );
    96                 }
     35                        // If this is a block under a list item, split it as well. (#1647)
     36                        if ( nextBlock )
     37                        {
     38                                node = nextBlock.getParent();
     39                                if ( node.is( 'li' ) )
     40                                {
     41                                        nextBlock.breakParent( node );
     42                                        nextBlock.move( nextBlock.getNext(), true );
     43                                }
     44                        }
     45                        else if ( previousBlock && ( node = previousBlock.getParent() ) && node.is( 'li' ) )
     46                        {
     47                                previousBlock.breakParent( node );
     48                                range.moveToElementEditStart( previousBlock.getNext() );
     49                                previousBlock.move( previousBlock.getPrevious() );
     50                        }
    9751
    98                 // If we have both the previous and next blocks, it means that the
    99                 // boundaries were on separated blocks, or none of them where on the
    100                 // block limits (start/end).
    101                 if ( !isStartOfBlock && !isEndOfBlock )
    102                 {
    103                         // If the next block is an <li> with another list tree as the first
    104                         // child, we'll need to append a placeholder or the list item
    105                         // wouldn't be editable. (#1420)
    106                         if ( nextBlock.is( 'li' ) && ( node = nextBlock.getFirst() )
    107                                         && node.is && node.is( 'ul', 'ol') )
    108                                 nextBlock.insertBefore( doc.createText( '\xa0' ), node );
     52                        // If we have both the previous and next blocks, it means that the
     53                        // boundaries were on separated blocks, or none of them where on the
     54                        // block limits (start/end).
     55                        if ( !isStartOfBlock && !isEndOfBlock )
     56                        {
     57                                // If the next block is an <li> with another list tree as the first
     58                                // child, we'll need to append a placeholder or the list item
     59                                // wouldn't be editable. (#1420)
     60                                if ( nextBlock.is( 'li' ) && ( node = nextBlock.getFirst() )
     61                                                && node.is && node.is( 'ul', 'ol') )
     62                                        nextBlock.insertBefore( doc.createText( '\xa0' ), node );
    10963
    110                         // Move the selection to the end block.
    111                         if ( nextBlock )
    112                                 range.moveToElementEditStart( nextBlock );
    113                 }
    114                 else
    115                 {
     64                                // Move the selection to the end block.
     65                                if ( nextBlock )
     66                                        range.moveToElementEditStart( nextBlock );
     67                        }
     68                        else
     69                        {
    11670
    117                         if ( isStartOfBlock && isEndOfBlock && previousBlock.is( 'li' ) )
    118                         {
    119                                 editor.execCommand( 'outdent' );
    120                                 return;
    121                         }
     71                                if ( isStartOfBlock && isEndOfBlock && previousBlock.is( 'li' ) )
     72                                {
     73                                        editor.execCommand( 'outdent' );
     74                                        return;
     75                                }
    12276
    123                         var newBlock;
     77                                var newBlock;
    12478
    125                         if ( previousBlock )
    126                         {
    127                                 // Do not enter this block if it's a header tag, or we are in
    128                                 // a Shift+Enter (#77). Create a new block element instead
    129                                 // (later in the code).
    130                                 if ( !forceMode && !headerTagRegex.test( previousBlock.getName() ) )
    131                                 {
    132                                         // Otherwise, duplicate the previous block.
    133                                         newBlock = previousBlock.clone();
    134                                 }
    135                         }
    136                         else if ( nextBlock )
    137                                 newBlock = nextBlock.clone();
     79                                if ( previousBlock )
     80                                {
     81                                        // Do not enter this block if it's a header tag, or we are in
     82                                        // a Shift+Enter (#77). Create a new block element instead
     83                                        // (later in the code).
     84                                        if ( !forceMode && !headerTagRegex.test( previousBlock.getName() ) )
     85                                        {
     86                                                // Otherwise, duplicate the previous block.
     87                                                newBlock = previousBlock.clone();
     88                                        }
     89                                }
     90                                else if ( nextBlock )
     91                                        newBlock = nextBlock.clone();
    13892
    139                         if ( !newBlock )
    140                                 newBlock = doc.createElement( blockTag );
     93                                if ( !newBlock )
     94                                        newBlock = doc.createElement( blockTag );
    14195
    142                         // Recreate the inline elements tree, which was available
    143                         // before hitting enter, so the same styles will be available in
    144                         // the new block.
    145                         var elementPath = splitInfo.elementPath;
    146                         if ( elementPath )
    147                         {
    148                                 for ( var i = 0, len = elementPath.elements.length ; i < len ; i++ )
    149                                 {
    150                                         var element = elementPath.elements[ i ];
     96                                // Recreate the inline elements tree, which was available
     97                                // before hitting enter, so the same styles will be available in
     98                                // the new block.
     99                                var elementPath = splitInfo.elementPath;
     100                                if ( elementPath )
     101                                {
     102                                        for ( var i = 0, len = elementPath.elements.length ; i < len ; i++ )
     103                                        {
     104                                                var element = elementPath.elements[ i ];
    151105
    152                                         if ( element.equals( elementPath.block ) || element.equals( elementPath.blockLimit ) )
    153                                                 break;
     106                                                if ( element.equals( elementPath.block ) || element.equals( elementPath.blockLimit ) )
     107                                                        break;
    154108
    155                                         if ( CKEDITOR.dtd.$removeEmpty[ element.getName() ] )
    156                                         {
    157                                                 element = element.clone();
    158                                                 newBlock.moveChildren( element );
    159                                                 newBlock.append( element );
    160                                         }
    161                                 }
    162                         }
     109                                                if ( CKEDITOR.dtd.$removeEmpty[ element.getName() ] )
     110                                                {
     111                                                        element = element.clone();
     112                                                        newBlock.moveChildren( element );
     113                                                        newBlock.append( element );
     114                                                }
     115                                        }
     116                                }
    163117
    164                         if ( !CKEDITOR.env.ie )
    165                                 newBlock.appendBogus();
     118                                if ( !CKEDITOR.env.ie )
     119                                        newBlock.appendBogus();
    166120
    167                         range.insertNode( newBlock );
     121                                range.insertNode( newBlock );
    168122
    169                         // This is tricky, but to make the new block visible correctly
    170                         // we must select it.
    171                         // The previousBlock check has been included because it may be
    172                         // empty if we have fixed a block-less space (like ENTER into an
    173                         // empty table cell).
    174                         if ( CKEDITOR.env.ie && isStartOfBlock && ( !isEndOfBlock || !previousBlock.getChildCount() ) )
    175                         {
    176                                 // Move the selection to the new block.
    177                                 range.moveToElementEditStart( isEndOfBlock ? previousBlock : newBlock );
    178                                 range.select();
    179                         }
     123                                // This is tricky, but to make the new block visible correctly
     124                                // we must select it.
     125                                // The previousBlock check has been included because it may be
     126                                // empty if we have fixed a block-less space (like ENTER into an
     127                                // empty table cell).
     128                                if ( CKEDITOR.env.ie && isStartOfBlock && ( !isEndOfBlock || !previousBlock.getChildCount() ) )
     129                                {
     130                                        // Move the selection to the new block.
     131                                        range.moveToElementEditStart( isEndOfBlock ? previousBlock : newBlock );
     132                                        range.select();
     133                                }
    180134
    181                         // Move the selection to the new block.
    182                         range.moveToElementEditStart( isStartOfBlock && !isEndOfBlock ? nextBlock : newBlock );
    183                 }
     135                                // Move the selection to the new block.
     136                                range.moveToElementEditStart( isStartOfBlock && !isEndOfBlock ? nextBlock : newBlock );
     137                        }
    184138
    185                 if ( !CKEDITOR.env.ie )
    186                 {
    187                         if ( nextBlock )
    188                         {
    189                                 // If we have split the block, adds a temporary span at the
    190                                 // range position and scroll relatively to it.
    191                                 var tmpNode = doc.createElement( 'span' );
     139                        if ( !CKEDITOR.env.ie )
     140                        {
     141                                if ( nextBlock )
     142                                {
     143                                        // If we have split the block, adds a temporary span at the
     144                                        // range position and scroll relatively to it.
     145                                        var tmpNode = doc.createElement( 'span' );
    192146
    193                                 // We need some content for Safari.
    194                                 tmpNode.setHtml( '&nbsp;' );
     147                                        // We need some content for Safari.
     148                                        tmpNode.setHtml( '&nbsp;' );
    195149
    196                                 range.insertNode( tmpNode );
    197                                 tmpNode.scrollIntoView();
    198                                 range.deleteContents();
    199                         }
    200                         else
    201                         {
    202                                 // We may use the above scroll logic for the new block case
    203                                 // too, but it gives some weird result with Opera.
    204                                 newBlock.scrollIntoView();
    205                         }
    206                 }
     150                                        range.insertNode( tmpNode );
     151                                        tmpNode.scrollIntoView();
     152                                        range.deleteContents();
     153                                }
     154                                else
     155                                {
     156                                        // We may use the above scroll logic for the new block case
     157                                        // too, but it gives some weird result with Opera.
     158                                        newBlock.scrollIntoView();
     159                                }
     160                        }
    207161
    208                 range.select();
    209         }
     162                        range.select();
     163                },
    210164
    211         function enterBr( editor, mode )
    212         {
    213                 // Get the range for the current selection.
    214                 var range = getRange( editor ),
    215                         doc = range.document;
     165                enterBr : function( editor, mode )
     166                {
     167                        // Get the range for the current selection.
     168                        var range = getRange( editor ),
     169                                doc = range.document;
    216170
    217                 // Determine the block element to be used.
    218                 var blockTag = ( mode == CKEDITOR.ENTER_DIV ? 'div' : 'p' );
     171                        // Determine the block element to be used.
     172                        var blockTag = ( mode == CKEDITOR.ENTER_DIV ? 'div' : 'p' );
    219173
    220                 var isEndOfBlock = range.checkEndOfBlock();
     174                        var isEndOfBlock = range.checkEndOfBlock();
    221175
    222                 var elementPath = new CKEDITOR.dom.elementPath( editor.getSelection().getStartElement() );
     176                        var elementPath = new CKEDITOR.dom.elementPath( editor.getSelection().getStartElement() );
    223177
    224                 var startBlock = elementPath.block,
    225                         startBlockTag = startBlock && elementPath.block.getName();
     178                        var startBlock = elementPath.block,
     179                                startBlockTag = startBlock && elementPath.block.getName();
    226180
    227                 var isPre = false;
     181                        var isPre = false;
    228182
    229                 if ( !forceMode && startBlockTag == 'li' )
    230                 {
    231                         enterBlock( editor, mode, range );
    232                         return;
    233                 }
     183                        if ( !forceMode && startBlockTag == 'li' )
     184                        {
     185                                enterBlock( editor, mode, range );
     186                                return;
     187                        }
    234188
    235                 // If we are at the end of a header block.
    236                 if ( !forceMode && isEndOfBlock && headerTagRegex.test( startBlockTag ) )
    237                 {
    238                         // Insert a <br> after the current paragraph.
    239                         doc.createElement( 'br' ).insertAfter( startBlock );
     189                        // If we are at the end of a header block.
     190                        if ( !forceMode && isEndOfBlock && headerTagRegex.test( startBlockTag ) )
     191                        {
     192                                // Insert a <br> after the current paragraph.
     193                                doc.createElement( 'br' ).insertAfter( startBlock );
    240194
    241                         // A text node is required by Gecko only to make the cursor blink.
    242                         if ( CKEDITOR.env.gecko )
    243                                 doc.createText( '' ).insertAfter( startBlock );
     195                                // A text node is required by Gecko only to make the cursor blink.
     196                                if ( CKEDITOR.env.gecko )
     197                                        doc.createText( '' ).insertAfter( startBlock );
    244198
    245                         // IE has different behaviors regarding position.
    246                         range.setStartAt( startBlock.getNext(), CKEDITOR.env.ie ? CKEDITOR.POSITION_BEFORE_START : CKEDITOR.POSITION_AFTER_START );
    247                 }
    248                 else
    249                 {
    250                         var lineBreak;
     199                                // IE has different behaviors regarding position.
     200                                range.setStartAt( startBlock.getNext(), CKEDITOR.env.ie ? CKEDITOR.POSITION_BEFORE_START : CKEDITOR.POSITION_AFTER_START );
     201                        }
     202                        else
     203                        {
     204                                var lineBreak;
    251205
    252                         isPre = ( startBlockTag == 'pre' );
     206                                isPre = ( startBlockTag == 'pre' );
    253207
    254                         if ( isPre )
    255                                 lineBreak = doc.createText( CKEDITOR.env.ie ? '\r' : '\n' );
    256                         else
    257                                 lineBreak = doc.createElement( 'br' );
     208                                if ( isPre )
     209                                        lineBreak = doc.createText( CKEDITOR.env.ie ? '\r' : '\n' );
     210                                else
     211                                        lineBreak = doc.createElement( 'br' );
    258212
    259                         range.deleteContents();
    260                         range.insertNode( lineBreak );
     213                                range.deleteContents();
     214                                range.insertNode( lineBreak );
    261215
    262                         // A text node is required by Gecko only to make the cursor blink.
    263                         // We need some text inside of it, so the bogus <br> is properly
    264                         // created.
    265                         if ( !CKEDITOR.env.ie )
    266                                 doc.createText( '\ufeff' ).insertAfter( lineBreak );
     216                                // A text node is required by Gecko only to make the cursor blink.
     217                                // We need some text inside of it, so the bogus <br> is properly
     218                                // created.
     219                                if ( !CKEDITOR.env.ie )
     220                                        doc.createText( '\ufeff' ).insertAfter( lineBreak );
    267221
    268                         // If we are at the end of a block, we must be sure the bogus node is available in that block.
    269                         if ( isEndOfBlock && !CKEDITOR.env.ie )
    270                                 lineBreak.getParent().appendBogus();
     222                                // If we are at the end of a block, we must be sure the bogus node is available in that block.
     223                                if ( isEndOfBlock && !CKEDITOR.env.ie )
     224                                        lineBreak.getParent().appendBogus();
    271225
    272                         // Now we can remove the text node contents, so the caret doesn't
    273                         // stop on it.
    274                         if ( !CKEDITOR.env.ie )
    275                                 lineBreak.getNext().$.nodeValue = '';
    276                         // IE has different behavior regarding position.
    277                         if ( CKEDITOR.env.ie )
    278                                 range.setStartAt( lineBreak, CKEDITOR.POSITION_AFTER_END );
    279                         else
    280                                 range.setStartAt( lineBreak.getNext(), CKEDITOR.POSITION_AFTER_START );
     226                                // Now we can remove the text node contents, so the caret doesn't
     227                                // stop on it.
     228                                if ( !CKEDITOR.env.ie )
     229                                        lineBreak.getNext().$.nodeValue = '';
     230                                // IE has different behavior regarding position.
     231                                if ( CKEDITOR.env.ie )
     232                                        range.setStartAt( lineBreak, CKEDITOR.POSITION_AFTER_END );
     233                                else
     234                                        range.setStartAt( lineBreak.getNext(), CKEDITOR.POSITION_AFTER_START );
    281235
    282                         // Scroll into view, for non IE.
    283                         if ( !CKEDITOR.env.ie )
    284                         {
    285                                 var dummy = null;
     236                                // Scroll into view, for non IE.
     237                                if ( !CKEDITOR.env.ie )
     238                                {
     239                                        var dummy = null;
    286240
    287                                 // BR is not positioned in Opera and Webkit.
    288                                 if ( !CKEDITOR.env.gecko )
    289                                 {
    290                                         dummy = doc.createElement( 'span' );
    291                                         // We need have some contents for Webkit to position it
    292                                         // under parent node. ( #3681)
    293                                         dummy.setHtml('&nbsp;');
    294                                 }
    295                                 else
    296                                         dummy = doc.createElement( 'br' );
     241                                        // BR is not positioned in Opera and Webkit.
     242                                        if ( !CKEDITOR.env.gecko )
     243                                        {
     244                                                dummy = doc.createElement( 'span' );
     245                                                // We need have some contents for Webkit to position it
     246                                                // under parent node. ( #3681)
     247                                                dummy.setHtml('&nbsp;');
     248                                        }
     249                                        else
     250                                                dummy = doc.createElement( 'br' );
    297251
    298                                 dummy.insertBefore( lineBreak.getNext() );
    299                                 dummy.scrollIntoView();
    300                                 dummy.remove();
    301                         }
    302                 }
     252                                        dummy.insertBefore( lineBreak.getNext() );
     253                                        dummy.scrollIntoView();
     254                                        dummy.remove();
     255                                }
     256                        }
    303257
    304                 // This collapse guarantees the cursor will be blinking.
    305                 range.collapse( true );
     258                        // This collapse guarantees the cursor will be blinking.
     259                        range.collapse( true );
    306260
    307                 range.select( isPre );
    308         }
     261                        range.select( isPre );
     262                }
     263        };
    309264
     265        var plugin = CKEDITOR.plugins.enterkey,
     266                enterBr = plugin.enterBr,
     267                enterBlock = plugin.enterBlock;
     268
     269        CKEDITOR.plugins.add( 'enterkey',
     270        {
     271                requires : [ 'keystrokes', 'indent' ],
     272
     273                init : function( editor )
     274                {
     275                        var specialKeys = editor.specialKeys;
     276                        specialKeys[ 13 ] = enter;
     277                        specialKeys[ CKEDITOR.SHIFT + 13 ] = shiftEnter;
     278                }
     279        });
     280
     281        var forceMode,
     282                headerTagRegex = /^h[1-6]$/;
     283
     284        function shiftEnter( editor )
     285        {
     286                // On SHIFT+ENTER we want to enforce the mode to be respected, instead
     287                // of cloning the current block. (#77)
     288                forceMode = 1;
     289
     290                return enter( editor, editor.config.shiftEnterMode );
     291        }
     292
     293        function enter( editor, mode )
     294        {
     295                // Only effective within document.
     296                if ( editor.mode != 'wysiwyg' )
     297                        return false;
     298
     299                if ( !mode )
     300                        mode = editor.config.enterMode;
     301
     302                // Use setTimout so the keys get cancelled immediatelly.
     303                setTimeout( function()
     304                        {
     305                                editor.fire( 'saveSnapshot' );  // Save undo step.
     306                                if ( mode == CKEDITOR.ENTER_BR || editor.getSelection().getStartElement().hasAscendant( 'pre', true ) )
     307                                        enterBr( editor, mode );
     308                                else
     309                                        enterBlock( editor, mode );
     310
     311                                forceMode = 0;
     312                        }, 0 );
     313
     314                return true;
     315        }
     316
    310317        function getRange( editor )
    311318        {
    312319                // Get the selection ranges.
  • _source/plugins/pastetext/plugin.js

     
    8383                },
    8484                requires : [ 'clipboard' ]
    8585        });
    86 })();
    8786
    88 CKEDITOR.editor.prototype.insertText = function( text )
    89 {
    90         text = CKEDITOR.tools.htmlEncode( text );
     87        var enterKeyPlugin = CKEDITOR.plugins.enterkey;
     88
     89        function doInsertText( doc, text )
     90        {
     91                // Native text insertion.
     92                if( CKEDITOR.env.ie )
     93                {
     94                        var selection = doc.selection;
     95                        if ( selection.type == 'Control' )
     96                                selection.clear();
     97                        selection.createRange().pasteHTML( text );
     98                }
     99                else
     100                        doc.execCommand( 'inserthtml', false, text );
     101        }
     102
     103        function doEnter( editor, mode )
     104        {
     105                enterKeyPlugin[ mode == CKEDITOR.ENTER_BR ? 'enterBr' : 'enterBlock' ]( editor, mode );
     106        }
     107
     108        CKEDITOR.editor.prototype.insertText = function( text )
     109        {
     110                var mode = this.getSelection().getStartElement().hasAscendant( 'pre', true ) ? CKEDITOR.ENTER_BR : this.config.enterMode,
     111                        doc = this.document.$,
     112                        self = this,
     113                        line;
     114
     115                text = CKEDITOR.tools.htmlEncode( text );
    91116
    92         // TODO: Replace the following with fill line break processing (see V2).
    93         text = text.replace( /(?:\r\n)|\n|\r/g, '<br>' );
     117                var startIndex = 0;
     118                text.replace( /(?:\r\n)|\n|\r/g, function( match, lastIndex )
     119                {
     120                        line = text.substring( startIndex, lastIndex );
     121                        startIndex = lastIndex + match.length;
     122                        line.length && doInsertText( doc, line );
     123                        // Line-break as an editor enter key result.
     124                        doEnter( self, mode );
     125                } );
    94126
    95         this.insertHtml( text );
    96 };
     127                line = text.substring( startIndex, text.length );
     128                line.length && doInsertText( doc, line );
     129        };
    97130
     131})();
     132
     133
    98134/**
    99135 * Whether to force all pasting operations to insert on plain text into the
    100136 * editor, loosing any formatting information possibly available in the source
© 2003 – 2022, CKSource sp. z o.o. sp.k. All rights reserved. | Terms of use | Privacy policy