trumbowyg.history.js 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. /*/* ===========================================================
  2. * trumbowyg.history.js v1.0
  3. * history plugin for Trumbowyg
  4. * http://alex-d.github.com/Trumbowyg
  5. * ===========================================================
  6. * Author : Sven Dunemann [dunemann@forelabs.eu]
  7. */
  8. (function ($) {
  9. 'use strict';
  10. $.extend(true, $.trumbowyg, {
  11. langs: {
  12. de: {
  13. history: {
  14. redo: 'Wiederholen',
  15. undo: 'Rückgängig'
  16. }
  17. },
  18. en: {
  19. history: {
  20. redo: 'Redo',
  21. undo: 'Undo'
  22. }
  23. },
  24. da: {
  25. history: {
  26. redo: 'Annuller fortryd',
  27. undo: 'Fortryd'
  28. }
  29. },
  30. fr: {
  31. history: {
  32. redo: 'Annuler',
  33. undo: 'Rétablir'
  34. }
  35. },
  36. zh_tw: {
  37. history: {
  38. redo: '重做',
  39. undo: '復原'
  40. }
  41. }
  42. },
  43. plugins: {
  44. history: {
  45. init: function (t) {
  46. t.o.plugins.history = $.extend(true, {
  47. _stack: [],
  48. _index: -1,
  49. _focusEl: undefined
  50. }, t.o.plugins.history || {});
  51. var btnBuildDefRedo = {
  52. title: t.lang.history.redo,
  53. ico: 'redo',
  54. key: 'Y',
  55. fn: function () {
  56. if (t.o.plugins.history._index < t.o.plugins.history._stack.length - 1) {
  57. t.o.plugins.history._index += 1;
  58. var index = t.o.plugins.history._index;
  59. var newState = t.o.plugins.history._stack[index];
  60. t.execCmd('html', newState);
  61. // because of some semantic optimisations we have to save the state back
  62. // to history
  63. t.o.plugins.history._stack[index] = t.$ed.html();
  64. carretToEnd();
  65. toggleButtonStates();
  66. }
  67. }
  68. };
  69. var btnBuildDefUndo = {
  70. title: t.lang.history.undo,
  71. ico: 'undo',
  72. key: 'Z',
  73. fn: function () {
  74. if (t.o.plugins.history._index > 0) {
  75. t.o.plugins.history._index -= 1;
  76. var index = t.o.plugins.history._index,
  77. newState = t.o.plugins.history._stack[index];
  78. t.execCmd('html', newState);
  79. // because of some semantic optimisations we have to save the state back
  80. // to history
  81. t.o.plugins.history._stack[index] = t.$ed.html();
  82. carretToEnd();
  83. toggleButtonStates();
  84. }
  85. }
  86. };
  87. var pushToHistory = function () {
  88. var index = t.o.plugins.history._index,
  89. stack = t.o.plugins.history._stack,
  90. latestState = stack.slice(-1)[0] || '<p></p>',
  91. prevState = stack[index],
  92. newState = t.$ed.html(),
  93. focusEl = t.doc.getSelection().focusNode,
  94. focusElText = '',
  95. latestStateTagsList,
  96. newStateTagsList,
  97. prevFocusEl = t.o.plugins.history._focusEl;
  98. latestStateTagsList = $('<div>' + latestState + '</div>').find('*').map(function () {
  99. return this.localName;
  100. });
  101. newStateTagsList = $('<div>' + newState + '</div>').find('*').map(function () {
  102. return this.localName;
  103. });
  104. if (focusEl) {
  105. t.o.plugins.history._focusEl = focusEl;
  106. focusElText = focusEl.outerHTML || focusEl.textContent;
  107. }
  108. if (newState !== prevState) {
  109. // a new stack entry is defined when current insert ends on a whitespace character
  110. // or count of node elements has been changed
  111. // or focused element differs from previous one
  112. if (focusElText.slice(-1).match(/\s/) ||
  113. !arraysAreIdentical(latestStateTagsList, newStateTagsList) ||
  114. t.o.plugins.history._index <= 0 || focusEl !== prevFocusEl)
  115. {
  116. t.o.plugins.history._index += 1;
  117. // remove newer entries in history when something new was added
  118. // because timeline was changes with interaction
  119. t.o.plugins.history._stack = stack.slice(
  120. 0, t.o.plugins.history._index
  121. );
  122. // now add new state to modifed history
  123. t.o.plugins.history._stack.push(newState);
  124. } else {
  125. // modify last stack entry
  126. t.o.plugins.history._stack[index] = newState;
  127. }
  128. toggleButtonStates();
  129. }
  130. };
  131. var toggleButtonStates = function () {
  132. var index = t.o.plugins.history._index,
  133. stackSize = t.o.plugins.history._stack.length,
  134. undoState = (index > 0),
  135. redoState = (stackSize !== 0 && index !== stackSize - 1);
  136. toggleButtonState('historyUndo', undoState);
  137. toggleButtonState('historyRedo', redoState);
  138. };
  139. var toggleButtonState = function (btn, enable) {
  140. var button = t.$box.find('.trumbowyg-' + btn + '-button');
  141. if (enable) {
  142. button.removeClass('trumbowyg-disable');
  143. } else if (!button.hasClass('trumbowyg-disable')) {
  144. button.addClass('trumbowyg-disable');
  145. }
  146. };
  147. var arraysAreIdentical = function (a, b) {
  148. if (a === b) {
  149. return true;
  150. }
  151. if (a == null || b == null) {
  152. return false;
  153. }
  154. if (a.length !== b.length) {
  155. return false;
  156. }
  157. for (var i = 0; i < a.length; i += 1) {
  158. if (a[i] !== b[i]) {
  159. return false;
  160. }
  161. }
  162. return true;
  163. };
  164. var carretToEnd = function () {
  165. var node = t.doc.getSelection().focusNode,
  166. range = t.doc.createRange();
  167. if (node.childNodes.length > 0) {
  168. range.setStartAfter(node.childNodes[node.childNodes.length - 1]);
  169. range.setEndAfter(node.childNodes[node.childNodes.length - 1]);
  170. t.doc.getSelection().removeAllRanges();
  171. t.doc.getSelection().addRange(range);
  172. }
  173. };
  174. t.$c.on('tbwinit tbwchange', pushToHistory);
  175. t.addBtnDef('historyRedo', btnBuildDefRedo);
  176. t.addBtnDef('historyUndo', btnBuildDefUndo);
  177. }
  178. }
  179. }
  180. });
  181. })(jQuery);