Ticket #2763: 2763.diff

File 2763.diff, 17.0 KB (added by Garry Yao, 15 years ago)
  • _source/plugins/undoredo/plugin.js

    ### Eclipse Workspace Patch 1.0
    #P ckeditor3.0
     
     1/**
     2 * @fileOverview Undo/Redo system for saving shapshot for document modification
     3 *               and other recordable changes.
     4 * @see #2763, #2, #915
     5 * @description
     6 *      <p>
     7 *      <span style="border-collapse: separate; color: rgb(0, 0, 0);
     8 *      font-family: Verdana; font-size: 13px; font-style: normal; font-variant:
     9 *      normal; font-weight: normal; letter-spacing: normal; line-height:
     10 *      normal; orphans: 2; text-indent: 0px; text-transform: none; white-space:
     11 *      normal; widows: 2; word-spacing: 0px;" class="Apple-style-span">
     12 *      <p>
     13 *      Undo/Redo system need to be ported from v2, the features could be summed
     14 *      up as below:
     15 *      </p>
     16 *      <ul>
     17 *      <li>Undo system restore/retrieve document status from both selection
     18 *      and content</li>
     19 *      <li>All commands that modify the document could support undo/redo
     20 *      feature,but each command has chance to optionaly declare whether would
     21 *      hooked with undo systemwhen they're registed.</li>
     22 *      </ul>
     23 *      <blockquote><blockquote>
     24 *      <p>
     25 *      <strong>Note</strong>: Since from v3 all keystroke will pre-bind to
     26 *      command, so keystrokes also hooked with undo system through command
     27 *      interface.
     28 *      </p>
     29 *      </blockquote></blockquote>
     30 *      <ul>
     31 *      <li>Undo feature itself performed as a command, A button plugin is
     32 *      required for interfacing this command to end user and keystroke pair of
     33 *      'Ctrl-Z' and 'Ctrl-Y' too.</li>
     34 *      <li>Support empty/reset all the undo snapshots for clean up, and A
     35 *      button plugin is required for interfacing this<span
     36 *      class="Apple-converted-space">&nbsp;</span><i>reset</i></li>
     37 *      <li>snapshot for undo system could be recorded in two forms:
     38 *      <ol>
     39 *      <li>One snapshot for each record action, this apply for almost every
     40 *      functional commands, but a few special keystroke commands that also
     41 *      being recorded in this way including:
     42 *      <ol style="list-style-type: lower-alpha;" class="loweralpha">
     43 *      <li>'Enter'</li>
     44 *      <li>'ShiftEnter'</li>
     45 *      <li>'CtrlBackspace'</li>
     46 *      <li>'Delete'</li>
     47 *      <li>'Tab'</li>
     48 *      <li>'Ctrl-X'</li>
     49 *      <li>'Ctrl-V'</li>
     50 *      </ol>
     51 *      </li>
     52 *      <li>One snapshot for a serials of record actions, this apply for most
     53 *      keystroke commands.</li>
     54 *      </ol>
     55 *      </li>
     56 *      </ul>
     57 *      </span>
     58 *      </p>
     59 */
     60CKEDITOR.plugins.add('undoredo', {
     61
     62                        requires : ['selection', 'wysiwygarea'],
     63                        beforeInit : function(editor) {
     64
     65                                var urm = new UndoRedoManager(editor);
     66                                function recordCommand(evt) {
     67                                        var cp /* {FCKEDITOR.command.Pair} */= evt.data;
     68                                        if (cp.command.supportUndoRedo !== false
     69                                                        // ingore mode change command
     70                                                        && cp.name !== 'source' && cp.name !== 'wysiwyg'
     71                                                        && urm.enabled) {
     72                                                urm.save(evt.name === 'beforeCommandExec'
     73                                                                ? true
     74                                                                : false);
     75                                        }
     76                                }
     77
     78                                editor.on('beforeCommandExec', recordCommand);
     79                                editor.on('afterCommandExec', recordCommand);
     80
     81                                // sensitive to mode change, only appliable for 'wysiwyg' mode
     82                                editor.on('mode', function(currentMode) {
     83                                                        urm.enabled = currentMode.data === 'wysiwyg';
     84                                                });
     85
     86                                editor.addCommand('undo',
     87                                                /**
     88                                                 * Rollback to last modification of this document
     89                                                 *
     90                                                 * @type CKEDITOR.command status of document
     91                                                 */
     92                                                {
     93                                        exec : function() {
     94                                                if (urm.undo()) {
     95                                                        this.fire('AfterUndo');
     96                                                        // TODO: fire 'selectionchange'
     97                                                }
     98                                        },
     99                                        supportUndoRedo : false
     100                                });
     101
     102                                editor.addCommand('redo',
     103                                                /**
     104                                                 * Retrieve to next modification of this document
     105                                                 *
     106                                                 * @type CKEDITOR.command status of document
     107                                                 */
     108                                                {
     109                                        exec : function() {
     110                                                if (urm.redo()) {
     111                                                        this.fire('AfterRedo');
     112                                                        // TODO: fire 'selectionchange'
     113                                                }
     114                                        },
     115                                        supportUndoRedo : false
     116                                });
     117                        }
     118                });
     119
     120/**
     121 * @constructor Main logic for Redo/Undo feature
     122 * @related FCKUndo in v2
     123 */
     124function UndoRedoManager(editor) {
     125
     126        /**
     127         * @field Whether undo system is usable decided by envoriment
     128         */
     129        this.enabled = false;
     130
     131        var UNDO_NUM_LIMIT = 20;
     132
     133        /**
     134         * Stack for all the undo and redo snapshots, they're always created/removed
     135         * in consistency.
     136         *
     137         * @type {Array<DocumentImage>}
     138         *
     139         */
     140        var undoSnaps = [], redoSnaps = [];
     141
     142        /**
     143         * Current snapshot history index
     144         */
     145        var index = 0; // First capture start from 1
     146
     147        /**
     148         * @private Get the current blockediting mode of this editor
     149         * @param editor
     150         * @param mode
     151         * @return
     152         */
     153        function getMode(mode) {
     154                return editor._.modes && editor._.modes[mode || editor.mode];
     155        };
     156
     157        /**
     158         * @constructor DocumentImage - A snapshot image which represent the current
     159         *              document status.
     160         */
     161        function DocumentImage() {
     162
     163                var bms, ranges, cWithBm, c;
     164
     165                c = getMode('wysiwyg').getSnapshotData();
     166                bms = editor.document.getSelection().createBookmarks(true);
     167                ranges = editor.document.getSelection().getRanges(); // Get ranges
     168                // from cache
     169                cWithBm = getMode('wysiwyg').getSnapshotData() // Get content along
     170                // with bookmark
     171                editor.document.getSelection().selectBookmarks(bms); // restore
     172                // selection by
     173                // the bookmark
     174                // and btw we
     175                // also
     176                // destroy the nodes in document
     177                return {
     178                        'content' : /** {String} original document string */
     179                        c,
     180                        'dirtyContent' :
     181                        /**
     182                         * {String} document content string with bookmarks
     183                         */
     184                        cWithBm,
     185                        'bookmark'/** {Array<CKEDITOR.dom.range.Bookmark>} */
     186                        : bms,
     187                        equals :
     188                        /**
     189                         * Compare if two document snapshot are the same, this is only
     190                         * content comparison with bookmarks striped.
     191                         *
     192                         * @img {DocumentImage} The other image to compare with
     193                         */
     194                        function(img) {
     195                                return this.content == img.content;
     196                        }
     197                }
     198        };
     199
     200        /**
     201         * @method Save a snapshot of document image for later retrieve, content
     202         *         being saved as raw html string, while selection saved as a
     203         *         lightweight bookmark.
     204         */
     205        this.save = function(isFront) {
     206                var sns = isFront ? undoSnaps : redoSnaps;
     207
     208                if (index == UNDO_NUM_LIMIT) {
     209                        sns.unshift(); // Die out earlier ones.
     210                }
     211
     212                sns.splice(index, sns.length - index); // Drop older snaps
     213
     214                var img = new DocumentImage();
     215                if (sns.length && img.equals(sns[sns.length - 1])) // duplicate
     216                        // examination
     217                        return;
     218
     219                sns.push(img);
     220
     221                if (!isFront)
     222                        index++; // increase when command has been fully recorded.
     223        }
     224
     225        function restoreImage(index, isUndo) {
     226                var img;
     227                img = isUndo ? undoSnaps[index] : redoSnaps[index];
     228                if (img.content) {
     229                        getMode('wysiwyg').loadSnapshotData(img.dirtyContent);
     230                        if (img.bookmark) {
     231                                editor.document.getSelection().selectBookmarks(img.bookmark);
     232                        }
     233                }
     234        }
     235
     236        /**
     237         * Check the current state of redo
     238         *
     239         * @return {Boolean} Whether the document has previous state to retrieve
     240         */
     241        this.redoAble = function() {
     242                return index < redoSnaps.length;
     243        }
     244
     245        /**
     246         * Check the current state of undo
     247         *
     248         * @return {Boolean} Whether the document has future state to restore
     249         */
     250        this.undoAble = function() {
     251                return index > 0
     252        }
     253        /**
     254         * Perform undo on current index.
     255         *
     256         * @method
     257         */
     258        this.undo = function() {
     259                if (this.undoAble()) {
     260                        restoreImage(--index, true); // retrieve previous one from undo
     261                        // history
     262                } else
     263                        return false;
     264        }
     265
     266        /**
     267         * Perform redo on current index.
     268         *
     269         * @method
     270         */
     271        this.redo = function() {
     272                if (this.redoAble()) {
     273                        restoreImage(index++, false); // retrieve next one from redo
     274                        // history
     275                } else
     276                        return false;
     277        }
     278
     279}
  • _source/plugins/editingblock/plugin.js

     
    190190                modeEditor.load( holderElement, data || this.getData() );
    191191
    192192                this.mode = mode;
    193                 this.fire( 'mode' );
     193                this.fire( 'mode' , mode);
    194194        };
    195195
    196196        /**
  • _source/core/dom/range.js

     
    424424                        return docFrag;
    425425                },
    426426
    427                 // This is an "intrusive" way to create a bookmark. It includes <span> tags
    428                 // in the range boundaries. The advantage of it is that it is possible to
    429                 // handle DOM mutations when moving back to the bookmark.
    430                 // Attention: the inclusion of nodes in the DOM is a design choice and
    431                 // should not be changed as there are other points in the code that may be
    432                 // using those nodes to perform operations. See GetBookmarkNode.
    433                 createBookmark : function()
     427                /**
     428                 * This is an "intrusive" way to create a bookmark. It includes <span>
     429                 * tags in the range boundaries. The advantage of it is that it is
     430                 * possible to handle DOM mutations when moving back to the bookmark.
     431                 * Attention: the inclusion of nodes in the DOM is a design choice.
     432                 *
     433                 * @param serializeable {Boolean} If set to true will record the bookmark with startNode/endNode IDs for serializing purpose.
     434                 */
     435                createBookmark : function(serializeable)
    434436                {
    435437                        var startNode, endNode;
    436438                        var clone;
     
    451453
    452454                                clone = this.clone();
    453455                                clone.collapse();
    454                                 clone.insertNode( endNode );
     456                                clone.insertNode( endNode );        //CONFUSE: This insert will not influence current selection.
    455457                        }
    456458
    457459                        clone = this.clone();
    458460                        clone.collapse( true );
    459                         clone.insertNode( startNode );
     461                        clone.insertNode( startNode );       //CONFUSE: This insert will fade current selection out.
    460462
    461463                        // Update the range position.
    462464                        if ( endNode )
     
    466468                        }
    467469                        else
    468470                                this.moveToPosition( startNode, CKEDITOR.POSITION_AFTER_END );
    469 
     471           
     472            if(serializeable)
     473            {
     474                startNode.setAttribute('id',(new Date()).valueOf() + Math.floor(Math.random()*1000) + 'S');
     475                if(endNode)
     476                    endNode.setAttribute('id',(new Date()).valueOf() + Math.floor(Math.random()*1000) + 'E');
     477            }
    470478                        return {
    471                                 startNode : startNode,
    472                                 endNode : endNode
     479                                startNode : serializeable? startNode.getAttribute('id') : startNode,
     480                                endNode : endNode? ( serializeable? endNode.getAttribute('id') : endNode) : null 
    473481                        };
    474482                },
    475 
     483       
     484        /**
     485         * Move the range boundaries to where the bookmarks indicated.
     486         * @see CKEDITOR.dom.range::createBookmark
     487         * @param bookmark {Object}
     488         */
    476489                moveToBookmark : function( bookmark )
    477490                {
     491            var st =  bookmark.startNode, ed = bookmark.endNode;
     492           
    478493                        // Set the range start at the bookmark start node position.
    479                         this.setStartBefore( bookmark.startNode );
     494                        this.setStartBefore(typeof st === 'string'? (st = this.document.getById(st)): st);
     495           
    480496
    481497                        // Remove it, because it may interfere in the setEndBefore call.
    482                         bookmark.startNode.remove();
     498                        st.remove();
    483499
    484500                        // Set the range end at the bookmark end node position, or simply
    485501                        // collapse it if it is not available.
    486                         var endNode = bookmark.endNode;
    487                         if ( endNode )
     502                        if ( ed )
    488503                        {
    489                                 this.setEndBefore( endNode );
    490                                 endNode.remove();
     504                                this.setEndBefore( typeof ed  === 'string'? (ed = this.document.getById(ed) ): ed );
     505                                ed.remove();
    491506                        }
    492507                        else
    493508                                this.collapse( true );
  • _source/core/editor.js

     
    361361                execCommand : function( commandName, data )
    362362                {
    363363                        var command = this.getCommand( commandName );
     364            var cp =
     365            /**
     366             * Name-command pair to trace commands behavious
     367             * @type FCKEDITOR.command.Pair
     368             */
     369                {name: commandName, command:command}; 
    364370                        if ( command && command.state != CKEDITOR.TRISTATE_DISABLED )
    365                                 return command.exec( this, data );
     371            {
     372                this.fire('beforeCommandExec', cp);               
     373                                command.exec( this, data );
     374                this.fire('afterCommandExec', cp);
     375            }
    366376
    367377                        // throw 'Unknown command name "' + commandName + '"';
    368378                        return false;
  • _source/plugins/selection/plugin.js

     
    630630                                                sel.addRange( nativeRange );
    631631                                        }
    632632                                },
    633 
    634                 createBookmarks : function()
     633        /**
     634         * This method is a delegate to ceate bookmark for every ranges in this selection.
     635         * @see CKEDITOR.dom.range::createBookmark
     636         */
     637                createBookmarks : function(serializable)
    635638                {
    636639                        var retval = [],
    637640                                ranges = this.getRanges();
    638641                        for ( var i = 0 ; i < ranges.length ; i++ )
    639                                 retval.push( ranges[i].createBookmark() );
     642                                retval.push( ranges[i].createBookmark(serializable) );
    640643                        return retval;
    641644                },
    642645
  • _source/core/commanddefinition.js

     
    4949 *     }
    5050 * });
    5151 */
     52 
     53 /**
     54 * Whether the command need to be hooked into the redo/undo system.
     55 * @name  CKEDITOR.commandDefinition.supportUndoRedo
     56 * @type {Boolean} If not defined or 'true' both hook into undo system, set it to 'false' explicitly  keep it out. 
     57 * @field
     58 * @example
     59 *  editorInstance.addCommand( 'sample',
     60 * {
     61 *     CKEDITOR.commandDefinition.supportUndoRedo : false    //declare this command does not support undo/redo 
     62 * });
     63 */
     64
  • _source/tests/core/dom/range.html

     
    15071507                        assert.areSame( 3, range.endOffset, 'range.endOffset' );
    15081508                        assert.isFalse( range.collapsed, 'range.collapsed' );
    15091509                },
     1510               
     1511                //Test serializable bookmark create and apply
     1512                test_bookmark_single_1: function () {
    15101513
     1514                        var range = new CKEDITOR.dom.range( doc );
     1515                        range.setStartBefore( doc.getById('_Span'));
     1516                    range.setEndAfter(doc.getById('_P'));
     1517                    bm = range.createBookmark(true);
     1518                    range.setStartBefore( doc.getById('_P2'));
     1519                    range.setEndAfter(doc.getById('_P2'));
     1520                    range.moveToBookmark(bm);
     1521
     1522                    assert.areSame( document.getElementById( '_P' ), range.startContainer.$ ,'range.startContainer');
     1523                        assert.areSame( 1, range.startOffset, 'range.startOffset');
     1524                        assert.areSame( document.getElementById( 'playground2' ), range.endContainer.$, 'range.endContainer');
     1525                        assert.areSame( 4, range.endOffset, 'range.endOffset' );
     1526                        assert.isNull(document.getElementById(bm.startNode), 'bookmark.startNode');
     1527                        assert.isNull(document.getElementById(bm.endNode), 'bookmark.endNode');
     1528                       
     1529                   
     1530                   
     1531                },
     1532
    15111533                /////////////
    15121534
    15131535                setUp : function()
  • _source/core/config.js

     
    1 /*
     1/*
    22 * CKEditor - The text editor for Internet - http://ckeditor.com
    33 * Copyright (C) 2003-2008 Frederico Caldeira Knabben
    44 *
     
    161161         * @example
    162162         * config.plugins = 'elementspath,toolbar,wysiwygarea';
    163163         */
    164         plugins : 'basicstyles,button,dialog,elementspath,horizontalrule,htmldataprocessor,keystrokes,removeformat,smiley,link,sourcearea,tab,toolbar,wysiwygarea,forms,image,find,table,specialchar,flash,print,pagebreak,newpage',
     164        plugins : 'basicstyles,button,dialog,elementspath,horizontalrule,htmldataprocessor,keystrokes,removeformat,smiley,link,sourcearea,tab,toolbar,wysiwygarea,forms,image,find,table,specialchar,flash,print,pagebreak,undoredo,newpage',
    165165
    166166        /**
    167167         * The theme to be used to build the UI.
     
    350350                        descriptions : [ ':)', ':(', ';)', ':D', ':/', ':P',
    351351                        '', '', '', '', '', '',
    352352                        '', ';(', '', '', '', '',
    353                         ':kiss', '', ],
     353                        ':kiss', '' ],
    354354
    355355                        //TODO: update path
    356356                        path : CKEDITOR.basePath +  '_source/plugins/smiley/images/',
© 2003 – 2022, CKSource sp. z o.o. sp.k. All rights reserved. | Terms of use | Privacy policy