undo.js 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  1. /**
  2. * undo redo
  3. * @file
  4. * @since 1.2.6.1
  5. */
  6. /**
  7. * 撤销上一次执行的命令
  8. * @command undo
  9. * @method execCommand
  10. * @param { String } cmd 命令字符串
  11. * @example
  12. * ```javascript
  13. * editor.execCommand( 'undo' );
  14. * ```
  15. */
  16. /**
  17. * 重做上一次执行的命令
  18. * @command redo
  19. * @method execCommand
  20. * @param { String } cmd 命令字符串
  21. * @example
  22. * ```javascript
  23. * editor.execCommand( 'redo' );
  24. * ```
  25. */
  26. UE.plugins["undo"] = function() {
  27. var saveSceneTimer;
  28. var me = this,
  29. maxUndoCount = me.options.maxUndoCount || 20,
  30. maxInputCount = me.options.maxInputCount || 20,
  31. fillchar = new RegExp(domUtils.fillChar + "|</hr>", "gi"); // ie会产生多余的</hr>
  32. var noNeedFillCharTags = {
  33. ol: 1,
  34. ul: 1,
  35. table: 1,
  36. tbody: 1,
  37. tr: 1,
  38. body: 1
  39. };
  40. var orgState = me.options.autoClearEmptyNode;
  41. function compareAddr(indexA, indexB) {
  42. if (indexA.length != indexB.length) return 0;
  43. for (var i = 0, l = indexA.length; i < l; i++) {
  44. if (indexA[i] != indexB[i]) return 0;
  45. }
  46. return 1;
  47. }
  48. function compareRangeAddress(rngAddrA, rngAddrB) {
  49. if (rngAddrA.collapsed != rngAddrB.collapsed) {
  50. return 0;
  51. }
  52. if (
  53. !compareAddr(rngAddrA.startAddress, rngAddrB.startAddress) ||
  54. !compareAddr(rngAddrA.endAddress, rngAddrB.endAddress)
  55. ) {
  56. return 0;
  57. }
  58. return 1;
  59. }
  60. function UndoManager() {
  61. this.list = [];
  62. this.index = 0;
  63. this.hasUndo = false;
  64. this.hasRedo = false;
  65. this.undo = function() {
  66. if (this.hasUndo) {
  67. if (!this.list[this.index - 1] && this.list.length == 1) {
  68. this.reset();
  69. return;
  70. }
  71. while (
  72. this.list[this.index].content == this.list[this.index - 1].content
  73. ) {
  74. this.index--;
  75. if (this.index == 0) {
  76. return this.restore(0);
  77. }
  78. }
  79. this.restore(--this.index);
  80. }
  81. };
  82. this.redo = function() {
  83. if (this.hasRedo) {
  84. while (
  85. this.list[this.index].content == this.list[this.index + 1].content
  86. ) {
  87. this.index++;
  88. if (this.index == this.list.length - 1) {
  89. return this.restore(this.index);
  90. }
  91. }
  92. this.restore(++this.index);
  93. }
  94. };
  95. this.restore = function() {
  96. var me = this.editor;
  97. var scene = this.list[this.index];
  98. var root = UE.htmlparser(scene.content.replace(fillchar, ""));
  99. me.options.autoClearEmptyNode = false;
  100. me.filterInputRule(root);
  101. me.options.autoClearEmptyNode = orgState;
  102. //trace:873
  103. //去掉展位符
  104. me.document.body.innerHTML = root.toHtml();
  105. me.fireEvent("afterscencerestore");
  106. //处理undo后空格不展位的问题
  107. if (browser.ie) {
  108. utils.each(
  109. domUtils.getElementsByTagName(me.document, "td th caption p"),
  110. function(node) {
  111. if (domUtils.isEmptyNode(node)) {
  112. domUtils.fillNode(me.document, node);
  113. }
  114. }
  115. );
  116. }
  117. try {
  118. var rng = new dom.Range(me.document).moveToAddress(scene.address);
  119. rng.select(
  120. noNeedFillCharTags[rng.startContainer.nodeName.toLowerCase()]
  121. );
  122. } catch (e) {}
  123. this.update();
  124. this.clearKey();
  125. //不能把自己reset了
  126. me.fireEvent("reset", true);
  127. };
  128. this.getScene = function() {
  129. var me = this.editor;
  130. var rng = me.selection.getRange(),
  131. rngAddress = rng.createAddress(false, true);
  132. me.fireEvent("beforegetscene");
  133. var root = UE.htmlparser(me.body.innerHTML);
  134. me.options.autoClearEmptyNode = false;
  135. me.filterOutputRule(root);
  136. me.options.autoClearEmptyNode = orgState;
  137. var cont = root.toHtml();
  138. //trace:3461
  139. //这个会引起回退时导致空格丢失的情况
  140. // browser.ie && (cont = cont.replace(/>&nbsp;</g, '><').replace(/\s*</g, '<').replace(/>\s*/g, '>'));
  141. me.fireEvent("aftergetscene");
  142. return {
  143. address: rngAddress,
  144. content: cont
  145. };
  146. };
  147. this.save = function(notCompareRange, notSetCursor) {
  148. clearTimeout(saveSceneTimer);
  149. var currentScene = this.getScene(notSetCursor),
  150. lastScene = this.list[this.index];
  151. if (lastScene && lastScene.content != currentScene.content) {
  152. me.trigger("contentchange");
  153. }
  154. //内容相同位置相同不存
  155. if (
  156. lastScene &&
  157. lastScene.content == currentScene.content &&
  158. (notCompareRange
  159. ? 1
  160. : compareRangeAddress(lastScene.address, currentScene.address))
  161. ) {
  162. return;
  163. }
  164. this.list = this.list.slice(0, this.index + 1);
  165. this.list.push(currentScene);
  166. //如果大于最大数量了,就把最前的剔除
  167. if (this.list.length > maxUndoCount) {
  168. this.list.shift();
  169. }
  170. this.index = this.list.length - 1;
  171. this.clearKey();
  172. //跟新undo/redo状态
  173. this.update();
  174. };
  175. this.update = function() {
  176. this.hasRedo = !!this.list[this.index + 1];
  177. this.hasUndo = !!this.list[this.index - 1];
  178. };
  179. this.reset = function() {
  180. this.list = [];
  181. this.index = 0;
  182. this.hasUndo = false;
  183. this.hasRedo = false;
  184. this.clearKey();
  185. };
  186. this.clearKey = function() {
  187. keycont = 0;
  188. lastKeyCode = null;
  189. };
  190. }
  191. me.undoManger = new UndoManager();
  192. me.undoManger.editor = me;
  193. function saveScene() {
  194. this.undoManger.save();
  195. }
  196. me.addListener("saveScene", function() {
  197. var args = Array.prototype.splice.call(arguments, 1);
  198. this.undoManger.save.apply(this.undoManger, args);
  199. });
  200. // me.addListener('beforeexeccommand', saveScene);
  201. // me.addListener('afterexeccommand', saveScene);
  202. me.addListener("reset", function(type, exclude) {
  203. if (!exclude) {
  204. this.undoManger.reset();
  205. }
  206. });
  207. me.commands["redo"] = me.commands["undo"] = {
  208. execCommand: function(cmdName) {
  209. this.undoManger[cmdName]();
  210. },
  211. queryCommandState: function(cmdName) {
  212. return this.undoManger[
  213. "has" + (cmdName.toLowerCase() == "undo" ? "Undo" : "Redo")
  214. ]
  215. ? 0
  216. : -1;
  217. },
  218. notNeedUndo: 1
  219. };
  220. var keys = {
  221. // /*Backspace*/ 8:1, /*Delete*/ 46:1,
  222. /*Shift*/ 16: 1,
  223. /*Ctrl*/ 17: 1,
  224. /*Alt*/ 18: 1,
  225. 37: 1,
  226. 38: 1,
  227. 39: 1,
  228. 40: 1
  229. },
  230. keycont = 0,
  231. lastKeyCode;
  232. //输入法状态下不计算字符数
  233. var inputType = false;
  234. me.addListener("ready", function() {
  235. domUtils.on(this.body, "compositionstart", function() {
  236. inputType = true;
  237. });
  238. domUtils.on(this.body, "compositionend", function() {
  239. inputType = false;
  240. });
  241. });
  242. //快捷键
  243. me.addshortcutkey({
  244. Undo: "ctrl+90", //undo
  245. Redo: "ctrl+89" //redo
  246. });
  247. var isCollapsed = true;
  248. me.addListener("keydown", function(type, evt) {
  249. var me = this;
  250. var keyCode = evt.keyCode || evt.which;
  251. if (
  252. !keys[keyCode] &&
  253. !evt.ctrlKey &&
  254. !evt.metaKey &&
  255. !evt.shiftKey &&
  256. !evt.altKey
  257. ) {
  258. if (inputType) return;
  259. if (!me.selection.getRange().collapsed) {
  260. me.undoManger.save(false, true);
  261. isCollapsed = false;
  262. return;
  263. }
  264. if (me.undoManger.list.length == 0) {
  265. me.undoManger.save(true);
  266. }
  267. clearTimeout(saveSceneTimer);
  268. function save(cont) {
  269. cont.undoManger.save(false, true);
  270. cont.fireEvent("selectionchange");
  271. }
  272. saveSceneTimer = setTimeout(function() {
  273. if (inputType) {
  274. var interalTimer = setInterval(function() {
  275. if (!inputType) {
  276. save(me);
  277. clearInterval(interalTimer);
  278. }
  279. }, 300);
  280. return;
  281. }
  282. save(me);
  283. }, 200);
  284. lastKeyCode = keyCode;
  285. keycont++;
  286. if (keycont >= maxInputCount) {
  287. save(me);
  288. }
  289. }
  290. });
  291. me.addListener("keyup", function(type, evt) {
  292. var keyCode = evt.keyCode || evt.which;
  293. if (
  294. !keys[keyCode] &&
  295. !evt.ctrlKey &&
  296. !evt.metaKey &&
  297. !evt.shiftKey &&
  298. !evt.altKey
  299. ) {
  300. if (inputType) return;
  301. if (!isCollapsed) {
  302. this.undoManger.save(false, true);
  303. isCollapsed = true;
  304. }
  305. }
  306. });
  307. //扩展实例,添加关闭和开启命令undo
  308. me.stopCmdUndo = function() {
  309. me.__hasEnterExecCommand = true;
  310. };
  311. me.startCmdUndo = function() {
  312. me.__hasEnterExecCommand = false;
  313. };
  314. };