当前位置: 代码迷 >> Web前端 >> 编辑器re/undo设计与兑现
  详细解决方案

编辑器re/undo设计与兑现

热度:343   发布时间:2012-11-10 10:48:50.0
编辑器re/undo设计与实现

编辑器撤销(undo)与重做(redo)插件的设计与实现 ,涉及三个方面:

?

1. 编辑器状态定义

?

不仅要包含当前编辑器的内容(body.innerHTML),还要考虑编辑区域用户的选中状态,用户选中每个区域进行高亮等操作,则操作前要把当前选中区域以及内容html都要保存下来:

?

?

?

/**
     * 当前编辑区域状态,包括html与选择区域
     * @param editor
     */
    function Snapshot(editor) {
        var contents = editor.getData(),selection = contents && editor.getSelection();
        //内容html
        this.contents = contents;
        //选择区域书签标志
        this.bookmarks = selection && selection.createBookmarks2(true);
    }
?


2.编辑状态历史的管理


历史管理器维护着编辑状态的栈数据, 其他插件通过触发编辑器的 save 与 restore 事件来通知历史管理器更新内部的状态。


当 save 操作时,历史管理器将当前状态添加到编辑状态栈即可,并更新内部游标。

?

?

restore 操作时,需要利用管理器内部的游标来在状态栈间前(undo)后(redo)游走,用游标处的状态替换当前状态。


?

?

注意点在于,当进行了re/undo操作后,再进行save状态保存,需要对状态栈进行清理,将当前游标以后的历史状态删掉,防止出现历史分支:

?

?

?

?

另一点在于:对于键盘的连续输入,可buffer处理,只有当空闲一定时间后才进行保存,避免细微冗余信息。

?

/**
 * 通过编辑器的save与restore事件,编辑器实例的历史栈管理,与键盘监控
 * @param editor
 */
function UndoManager(editor) {
    //redo undo history stack
    /**
     * 编辑器状态历史保存
     */
    this.history = [];
    this.index = 0;
    this.editor = editor;
    this.bufferTimer = new BufferTimer(500, this.save, this);
    this._init();
}
S.augment(UndoManager, {
    /**
     * 监控键盘输入,buffer处理
     * @param ev
     */
    _keyMonitor: function (ev) {
        //ctrl+z,撤销
        //ctrl+y,重做
        //其他可见字符buffer处理
    },
    _init: function () {
        var self = this,
            editor = self.editor;
        //外部通过editor触发save|restore,管理器捕获事件处理
        editor.on("save", function (ev) {
            if (ev.buffer)
            //键盘操作需要缓存
            self.bufferTimer.run();
            else {
                //其他立即save
                self.save();
            }
        });
        //un/re do  
        editor.on("restore", this.restore, this);
        self._keyMonitor();
        //先save一下
        self.save();
    },
    /**
     * 保存历史
     */
    save: function () {
        //游标后面的历史抛弃
        //超过占最大容量,shift队列出列 
        //当前状态和栈顶状态不同,入栈
        //更新游标  
        //触发afterSave事件  
    },
    /**
     *
     * @param ev
     * ev.d :1.向前撤销 ,-1.向后重做
     */
    restore: function (ev) {
        //更新游标,用游标所在状态替换当前状态
    }
});

?

3.工具栏 ui 与功能调用


RestoreUI 封装工具栏撤销与重做的功能与表现,使用 attribute 抽象出三状态按钮(可用并选中,可用,禁用),监听通过2触发的编辑器 afterSave,afterRestore 事件,通过游标位置和历史栈大小的比较,来决定redo ,undo按钮的禁用与可用状态.

?

/**
             * save,restore完,更新工具栏状态
             */
            editor.on("afterSave", this._respond, this);
            editor.on("afterRestore", this._respond, this);
?


undo:当游标不在状态栈底部时可用(index>0&&history.length>0)

redo:当游标不在状态栈顶部时可用(index < history.length-1)


当点击工具栏按钮时,触发editor的restore事件,注意参数,redo为向后restore,undo为向前restore。

?

/**
             * 触发重做或撤销动作,都是restore,方向不同
             */
            self.el.on("click", function() {
                editor.fire("restore", {
                    d:RedoMap[self.text]
                });
            });
?

?

4.整合


当编辑器实例生成后,全局空间事件通知undo/redo插件,插件对每个编辑器生成对应的历史管理器与工具栏按钮:

?

KE.on("instanceCreated", function(ev) {
        var editor = ev.editor;

        /**
         * 编辑器历史中央管理
         */
        new UndoManager(editor);

        /**
         * 撤销工具栏按钮
         */
        new RestoreUI(editor, "undo");
        /**
         * 重做工具栏按钮
         */
        new RestoreUI(editor, "redo");
    });

?

?

?

插件源码



demo

?

?

1 楼 xtyong 2010-09-25  
这个编辑与撤消的插件,能够整合到xheditor编辑器中吗?
2 楼 yiminghe 2010-09-25  
不能,属于kissy editor一部分