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 });