UndoManager.js 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. /**
  2. * UndoManager.js
  3. *
  4. * Copyright 2009, Moxiecode Systems AB
  5. * Released under LGPL License.
  6. *
  7. * License: http://tinymce.moxiecode.com/license
  8. * Contributing: http://tinymce.moxiecode.com/contributing
  9. */
  10. (function(tinymce) {
  11. var Dispatcher = tinymce.util.Dispatcher;
  12. /**
  13. * This class handles the undo/redo history levels for the editor. Since the build in undo/redo has major drawbacks a custom one was needed.
  14. *
  15. * @class tinymce.UndoManager
  16. */
  17. tinymce.UndoManager = function(editor) {
  18. var self, index = 0, data = [], beforeBookmark;
  19. function getContent() {
  20. return tinymce.trim(editor.getContent({format : 'raw', no_events : 1}));
  21. };
  22. return self = {
  23. /**
  24. * State if the user is currently typing or not. This will add a typing operation into one undo
  25. * level instead of one new level for each keystroke.
  26. *
  27. * @field {Boolean} typing
  28. */
  29. typing : false,
  30. /**
  31. * This event will fire each time a new undo level is added to the undo manager.
  32. *
  33. * @event onAdd
  34. * @param {tinymce.UndoManager} sender UndoManager instance that got the new level.
  35. * @param {Object} level The new level object containing a bookmark and contents.
  36. */
  37. onAdd : new Dispatcher(self),
  38. /**
  39. * This event will fire when the user make an undo of a change.
  40. *
  41. * @event onUndo
  42. * @param {tinymce.UndoManager} sender UndoManager instance that got the new level.
  43. * @param {Object} level The old level object containing a bookmark and contents.
  44. */
  45. onUndo : new Dispatcher(self),
  46. /**
  47. * This event will fire when the user make an redo of a change.
  48. *
  49. * @event onRedo
  50. * @param {tinymce.UndoManager} sender UndoManager instance that got the new level.
  51. * @param {Object} level The old level object containing a bookmark and contents.
  52. */
  53. onRedo : new Dispatcher(self),
  54. /**
  55. * Stores away a bookmark to be used when performing an undo action so that the selection is before
  56. * the change has been made.
  57. *
  58. * @method beforeChange
  59. */
  60. beforeChange : function() {
  61. beforeBookmark = editor.selection.getBookmark(2, true);
  62. },
  63. /**
  64. * Adds a new undo level/snapshot to the undo list.
  65. *
  66. * @method add
  67. * @param {Object} l Optional undo level object to add.
  68. * @return {Object} Undo level that got added or null it a level wasn't needed.
  69. */
  70. add : function(level) {
  71. var i, settings = editor.settings, lastLevel;
  72. level = level || {};
  73. level.content = getContent();
  74. // Add undo level if needed
  75. lastLevel = data[index];
  76. if (lastLevel && lastLevel.content == level.content)
  77. return null;
  78. // Set before bookmark on previous level
  79. if (data[index])
  80. data[index].beforeBookmark = beforeBookmark;
  81. // Time to compress
  82. if (settings.custom_undo_redo_levels) {
  83. if (data.length > settings.custom_undo_redo_levels) {
  84. for (i = 0; i < data.length - 1; i++)
  85. data[i] = data[i + 1];
  86. data.length--;
  87. index = data.length;
  88. }
  89. }
  90. // Get a non intrusive normalized bookmark
  91. level.bookmark = editor.selection.getBookmark(2, true);
  92. // Crop array if needed
  93. if (index < data.length - 1)
  94. data.length = index + 1;
  95. data.push(level);
  96. index = data.length - 1;
  97. self.onAdd.dispatch(self, level);
  98. editor.isNotDirty = 0;
  99. return level;
  100. },
  101. /**
  102. * Undoes the last action.
  103. *
  104. * @method undo
  105. * @return {Object} Undo level or null if no undo was performed.
  106. */
  107. undo : function() {
  108. var level, i;
  109. if (self.typing) {
  110. self.add();
  111. self.typing = false;
  112. }
  113. if (index > 0) {
  114. level = data[--index];
  115. editor.setContent(level.content, {format : 'raw'});
  116. editor.selection.moveToBookmark(level.beforeBookmark);
  117. self.onUndo.dispatch(self, level);
  118. }
  119. return level;
  120. },
  121. /**
  122. * Redoes the last action.
  123. *
  124. * @method redo
  125. * @return {Object} Redo level or null if no redo was performed.
  126. */
  127. redo : function() {
  128. var level;
  129. if (index < data.length - 1) {
  130. level = data[++index];
  131. editor.setContent(level.content, {format : 'raw'});
  132. editor.selection.moveToBookmark(level.bookmark);
  133. self.onRedo.dispatch(self, level);
  134. }
  135. return level;
  136. },
  137. /**
  138. * Removes all undo levels.
  139. *
  140. * @method clear
  141. */
  142. clear : function() {
  143. data = [];
  144. index = 0;
  145. self.typing = false;
  146. },
  147. /**
  148. * Returns true/false if the undo manager has any undo levels.
  149. *
  150. * @method hasUndo
  151. * @return {Boolean} true/false if the undo manager has any undo levels.
  152. */
  153. hasUndo : function() {
  154. return index > 0 || this.typing;
  155. },
  156. /**
  157. * Returns true/false if the undo manager has any redo levels.
  158. *
  159. * @method hasRedo
  160. * @return {Boolean} true/false if the undo manager has any redo levels.
  161. */
  162. hasRedo : function() {
  163. return index < data.length - 1 && !this.typing;
  164. }
  165. };
  166. };
  167. })(tinymce);