1 /**
  2  * draft for kissy editor
  3  * @author yiminghe@gmail.com
  4  */
  5 KISSY.add("editor/plugin/draft/index", function (S, Editor, localStorage, Overlay, MenuButton) {
  6     var Node = S.Node,
  7         LIMIT = 5,
  8         Event = S.Event,
  9         INTERVAL = 5,
 10         JSON = S['JSON'],
 11         DRAFT_SAVE = "ks-editor-draft-save20110503";
 12 
 13     function padding(n, l, p) {
 14         n += "";
 15         while (n.length < l) {
 16             n = p + n;
 17         }
 18         return n;
 19     }
 20 
 21     function date(d) {
 22         if (S.isNumber(d)) {
 23             d = new Date(d);
 24         }
 25         if (d instanceof Date)
 26             return [
 27                 d.getFullYear(),
 28                 "-",
 29                 padding(d.getMonth() + 1, 2, "0"),
 30                 "-",
 31                 padding(d.getDate(), 2, "0"),
 32                 " ",
 33                 //" ",
 34                 padding(d.getHours(), 2, "0"),
 35                 ":",
 36                 padding(d.getMinutes(), 2, "0"),
 37                 ":",
 38                 padding(d.getSeconds(), 2, "0")
 39                 //" ",
 40                 //" "
 41             ].join("");
 42         else
 43             return d;
 44     }
 45 
 46     function Draft(editor, config) {
 47         this.editor = editor;
 48         this.config = config;
 49         this._init();
 50     }
 51 
 52     var addRes = Editor.Utils.addRes, destroyRes = Editor.Utils.destroyRes;
 53     S.augment(Draft, {
 54 
 55         _getSaveKey:function () {
 56             var self = this,
 57                 cfg = this.config;
 58             return cfg.draft && cfg.draft['saveKey'] || DRAFT_SAVE;
 59         },
 60 
 61         /**
 62          * parse 历史记录延后,点击 select 时才开始 parse
 63          */
 64         _getDrafts:function () {
 65             var self = this;
 66             if (!self.drafts) {
 67                 var str = localStorage.getItem(self._getSaveKey()),
 68                     drafts = [];
 69                 if (str) {
 70                     /**
 71                      * 原生 localStorage 必须串行化
 72                      */
 73                     drafts = (localStorage == window.localStorage) ?
 74                         JSON.parse(decodeURIComponent(str)) : str;
 75                 }
 76                 self.drafts = drafts;
 77             }
 78             return self.drafts;
 79         },
 80         _init:function () {
 81 
 82             var self = this,
 83                 editor = self.editor,
 84                 statusbar = editor.get("statusBarEl"),
 85                 cfg = this.config;
 86             cfg.draft = cfg.draft || {};
 87             self.draftInterval = cfg.draft.interval
 88                 = cfg.draft.interval || INTERVAL;
 89             self.draftLimit = cfg.draft.limit
 90                 = cfg.draft.limit || LIMIT;
 91             var holder = new Node(
 92                 "<div class='ks-editor-draft'>" +
 93                     "<span class='ks-editor-draft-title'>" +
 94                     "内容正文每" +
 95                     cfg.draft.interval
 96                     + "分钟自动保存一次。" +
 97                     "</span>" +
 98                     "</div>").appendTo(statusbar);
 99             self.timeTip = new Node("<span class='ks-editor-draft-time'/>")
100                 .appendTo(holder);
101 
102             var save = new Node(
103                     "<a href='#' " +
104                         "onclick='return false;' " +
105                         "class='ks-editor-button ks-editor-draft-save-btn ks-inline-block' " +
106                         "style='" +
107                         "vertical-align:middle;" +
108                         "padding:1px 9px;" +
109                         "'>" +
110                         "<span class='ks-editor-draft-save'>" +
111                         "</span>" +
112                         "<span>立即保存</span>" +
113                         "</a>"
114                 ).unselectable().appendTo(holder),
115                 versions = new MenuButton({
116                     render:holder,
117                     width:"100px",
118                     prefixCls:"ks-editor-",
119                     menuCfg:{
120                         width:"225px",
121                         align:{
122                             points:['tr', 'br']
123                         }
124                     },
125                     matchElWidth:false,
126                     autoRender:true,
127                     content:"恢复编辑历史"
128                 });
129             self.versions = versions;
130             // 点击才开始 parse
131             versions.on("beforeCollapsedChange", function (e) {
132                 if (!e.newValue) {
133                     versions.detach("beforeCollapsedChange", arguments.callee);
134                     self.sync();
135                 }
136             });
137             save.on("click", function (ev) {
138                 self.save(false);
139                 //如果不阻止,部分页面在ie6下会莫名奇妙把其他input的值丢掉!
140                 ev.halt();
141             });
142 
143             addRes.call(self, save);
144 
145             /*
146              监控form提交,每次提交前保存一次,防止出错
147              */
148             if (editor.get("textarea")[0].form) {
149                 (function () {
150                     var textarea = editor.get("textarea"),
151                         form = textarea[0].form;
152 
153                     function saveF() {
154                         self.save(true);
155                     }
156 
157                     Event.on(form, "submit", saveF);
158                     addRes.call(self, function () {
159                         Event.remove(form, "submit", saveF);
160                     });
161 
162                 })();
163             }
164 
165             var timer = setInterval(function () {
166                 self.save(true);
167             }, self.draftInterval * 60 * 1000);
168 
169             addRes.call(self, function () {
170                 clearInterval(timer);
171             });
172 
173             versions.on("click", self.recover, self);
174             addRes.call(self, versions);
175             self.holder = holder;
176             if (cfg.draft['helpHtml']) {
177 
178                 var help = new Node('<a ' +
179                     'tabindex="0" ' +
180                     'hidefocus="hidefocus" ' +
181                     'class="ks-editor-draft-help" ' +
182                     'title="点击查看帮助" ' +
183                     'href="javascript:void(\'点击查看帮助 \')">点击查看帮助</a>')
184                     .unselectable()
185                     .appendTo(holder);
186 
187                 help.on("click", function () {
188                     help[0].focus();
189                     if (self.helpPopup && self.helpPopup.get("visible")) {
190                         self.helpPopup.hide();
191                     } else {
192                         self._prepareHelp();
193                     }
194                 });
195                 help.on("blur", function () {
196                     self.helpPopup && self.helpPopup.hide();
197                 });
198                 self.helpBtn = help;
199                 addRes.call(self, help);
200                 Editor.Utils.lazyRun(self, "_prepareHelp", "_realHelp");
201             }
202             addRes.call(self, holder);
203         },
204         _prepareHelp:function () {
205             var self = this,
206                 editor = self.editor,
207                 cfg = self.config,
208                 draftCfg = cfg.draft,
209                 help = new Node(draftCfg['helpHtml'] || "");
210             var arrowCss = "height:0;" +
211                 "position:absolute;" +
212                 "fontSize:0;" +
213                 "width:0;" +
214                 "border:8px #000 solid;" +
215                 "border-color:#000 transparent transparent transparent;" +
216                 "border-style:solid dashed dashed dashed;";
217             var arrow = new Node("<div style='" +
218                 arrowCss +
219                 "border-top-color:#CED5E0;" +
220                 "'>" +
221                 "<div style='" +
222                 arrowCss +
223                 "left:-8px;" +
224                 "top:-10px;" +
225                 "border-top-color:white;" +
226                 "'>" +
227                 "</div>" +
228                 "</div>");
229             help.append(arrow);
230             help.css({
231                 border:"1px solid #ACB4BE",
232                 text-align:"left"
233             });
234             self.helpPopup = new Overlay({
235                 content:help,
236                 prefixCls:'ks-editor-',
237                 autoRender:true,
238                 width:help.width() + "px",
239                 zIndex:Editor.baseZIndex(Editor.zIndexManager.OVERLAY),
240                 mask:false
241             });
242             self.helpPopup.get("el")
243                 .css("border", "none");
244             self.helpPopup.arrow = arrow;
245         },
246         _realHelp:function () {
247             var win = this.helpPopup,
248                 helpBtn = this.helpBtn,
249                 arrow = win.arrow;
250             win.show();
251             var off = helpBtn.offset();
252             win.get("el").offset({
253                 left:(off.left - win.get("el").width()) + 17,
254                 top:(off.top - win.get("el").height()) - 7
255             });
256             arrow.offset({
257                 left:off.left - 2,
258                 top:off.top - 8
259             });
260         },
261         disable:function () {
262             this.holder.css("visibility", "hidden");
263         },
264         enable:function () {
265             this.holder.css("visibility", "");
266         },
267         sync:function () {
268             var self = this,
269                 i,
270                 draftLimit = self.draftLimit,
271                 timeTip = self.timeTip,
272                 versions = self.versions,
273                 drafts = self._getDrafts(),
274                 draft, tip
275 
276             if (drafts.length > draftLimit) {
277                 drafts.splice(0, drafts.length - draftLimit);
278             }
279 
280             for (i = 0; i < drafts.length; i++) {
281                 draft = drafts[i];
282                 tip = (draft.auto ? "自动" : "手动") + "保存于 : "
283                     + date(draft.date);
284                 versions.addItem({
285                     xclass:'menuitem',
286                     content:tip,
287                     value:i
288                 });
289             }
290 
291             timeTip.html(tip);
292             localStorage.setItem(self._getSaveKey(),
293                 (localStorage == window.localStorage) ?
294                     encodeURIComponent(JSON.stringify(drafts))
295                     : drafts);
296         },
297 
298         save:function (auto) {
299             var self = this,
300                 drafts = self._getDrafts(),
301                 editor = self.editor,
302             //不使用rawdata
303             //undo 只需获得可视区域内代码
304             //可视区域内代码!= 最终代码
305             //代码模式也要支持草稿功能
306             //统一获得最终代码
307                 data = editor.get("formatData");
308 
309             //如果当前内容为空,不保存版本
310             if (!data) {
311                 return;
312             }
313 
314             if (drafts[drafts.length - 1] &&
315                 data == drafts[drafts.length - 1].content) {
316                 drafts.length -= 1;
317             }
318             self.drafts = drafts.concat({
319                 content:data,
320                 date:new Date().getTime(),
321                 auto:auto
322             });
323             self.sync();
324         },
325 
326         recover:function (ev) {
327             var self = this,
328                 editor = self.editor,
329                 drafts = self._getDrafts(),
330                 v = ev.target.get("value");
331             if (confirm("确认恢复 " + date(drafts[v].date) + " 的编辑历史?")) {
332                 editor.execCommand("save");
333                 editor.set("data", drafts[v].content);
334                 editor.execCommand("save");
335             }
336             self.versions.set("collapsed", true);
337             ev.halt();
338         },
339 
340         destroy:function () {
341             destroyRes.call(this);
342         }
343     });
344 
345     function init(editor, config) {
346         var d = new Draft(editor, config);
347         editor.on("destroy", function () {
348             d.destroy();
349         });
350     }
351 
352     function DraftPlugin(config) {
353         this.config = config || {};
354     }
355 
356     S.augment(DraftPlugin, {
357         renderUI:function (editor) {
358             var config = this.config;
359             if (localStorage.ready) {
360                 localStorage.ready(function () {
361                     init(editor, config);
362                 });
363             } else {
364                 init(editor, config);
365             }
366         }
367     });
368 
369     return DraftPlugin;
370 
371 }, {
372     requires:["editor", "../localStorage/", "overlay", '../menubutton/']
373 });