history.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341
  1. /*
  2. 알려진 문제들
  3. -
  4. 덜 중요한 문제들
  5. - layout에 여러 개의 이미지를 한꺼번에 올린 경우에, saveHistory 하지 않음
  6. - table resize 를 한 후에 saveHistory 하지 않음 / modified+마우스클릭 조합일 때, saveHistory를 하는 로직으로 인해 saveHistory가 될 수 있는 경우가 있음, but 완벽하지 않음
  7. - backspace / delete 든 여러 번 눌렀을 때에 한 번만 saveHistory하고 싶다.
  8. */
  9. Trex.I.History = {};
  10. Trex.I.History.Standard = {
  11. getRangeData: function () {
  12. throw Error("Unimplemented abstract method");
  13. },
  14. restoreRange: function (rangeData){
  15. throw Error("Unimplemented abstract method");
  16. }
  17. };
  18. Trex.I.History.Webkit = {
  19. getRangeData: function() {
  20. var p = this.canvas.getProcessor(),
  21. txSel = p.getTxSel(),
  22. rangeCount = txSel.getSel().rangeCount;
  23. var start, end;
  24. if (rangeCount) {
  25. var range = txSel.getSel().getRangeAt(0);
  26. var preSelectionRange = range.cloneRange();
  27. preSelectionRange.selectNodeContents(this.canvas.getCurrentPanel().getDocument().body);
  28. preSelectionRange.setEnd(range.startContainer, range.startOffset);
  29. start = preSelectionRange.toString().length;
  30. end = start + range.toString().length;
  31. } else {
  32. start = 0;
  33. end = 0;
  34. }
  35. return {
  36. start: start,
  37. end: end
  38. };
  39. },
  40. restoreRange: function(savedSel){
  41. var win = this.canvas.getCurrentPanel().getWindow();
  42. var doc = win.document;
  43. var containerEl = doc.body;
  44. var charIndex = 0, range = doc.createRange();
  45. range.setStart(containerEl, 0);
  46. range.collapse(true);
  47. var nodeStack = [containerEl], node, foundStart = false, stop = false;
  48. while (!stop && (node = nodeStack.pop())) {
  49. if (node.nodeType == 3) {
  50. var nextCharIndex = charIndex + node.length;
  51. if (!foundStart && savedSel.start >= charIndex && savedSel.start <= nextCharIndex) {
  52. range.setStart(node, savedSel.start - charIndex);
  53. foundStart = true;
  54. }
  55. if (foundStart && savedSel.end >= charIndex && savedSel.end <= nextCharIndex) {
  56. range.setEnd(node, savedSel.end - charIndex);
  57. stop = true;
  58. }
  59. charIndex = nextCharIndex;
  60. } else {
  61. var i = node.childNodes.length;
  62. while (i--) {
  63. nodeStack.push(node.childNodes[i]);
  64. }
  65. }
  66. }
  67. var sel = win.getSelection();
  68. sel.removeAllRanges();
  69. sel.addRange(range);
  70. }
  71. };
  72. Trex.I.History.Trident = {
  73. getRangeData: function() {
  74. var doc = this.canvas.getCurrentPanel().getDocument();
  75. var containerEl = doc.body;
  76. //refactory 필요.
  77. try{
  78. var selectedTextRange = doc.selection.createRange();
  79. }catch(e){
  80. return {
  81. start:0,
  82. end:0
  83. }
  84. }
  85. var preSelectionTextRange = doc.body.createTextRange();
  86. preSelectionTextRange.moveToElementText(containerEl);
  87. try {
  88. preSelectionTextRange.setEndPoint("EndToStart", selectedTextRange);
  89. var start = preSelectionTextRange.text.length;
  90. return {
  91. start: start,
  92. end: start + selectedTextRange.text.length
  93. }
  94. }catch(e){
  95. }
  96. var onepoint = preSelectionTextRange.text.length;
  97. return {
  98. start: onepoint,
  99. end: onepoint
  100. }
  101. },
  102. restoreRange: function(savedSel) {
  103. var doc = this.canvas.getCurrentPanel().getDocument();
  104. var containerEl = doc.body;
  105. var textRange = doc.body.createTextRange();
  106. textRange.moveToElementText(containerEl);
  107. textRange.collapse(true);
  108. textRange.moveEnd("character", savedSel.end);
  109. textRange.moveStart("character", savedSel.start);
  110. textRange.select();
  111. }
  112. };
  113. /**
  114. * @fileoverview default history class for redo/undo
  115. *
  116. * @author iamdanielkim
  117. */
  118. /**
  119. * @namespace
  120. */
  121. (function(){
  122. function keepMaxLength(list, maxLength) {
  123. while (list.length >= maxLength) {
  124. list.shift();
  125. }
  126. }
  127. var MAX_UNDO_COUNT = 20;
  128. /**
  129. * @class
  130. */
  131. Trex.History = Trex.Class.create({
  132. $mixins: [
  133. Trex.I.History.Standard,
  134. (($tx.msie_nonstd)? Trex.I.History.Trident: Trex.I.History.Webkit)
  135. ],
  136. maxUndoCount: MAX_UNDO_COUNT,
  137. canvas: _NULL,
  138. undoMementoList: _NULL,
  139. redoMementoList: _NULL,
  140. currentMemento: _NULL,
  141. contentModified: _FALSE,
  142. initialize: function(canvas){
  143. this.canvas = canvas;
  144. this.setupHistory();
  145. this.bindKeyEvent(canvas);
  146. },
  147. bindKeyEvent: function(canvas) {
  148. var self = this;
  149. canvas.observeJob('canvas.panel.undo', function() {
  150. self.undoHandler();
  151. });
  152. canvas.observeJob('canvas.panel.redo', function() {
  153. self.redoHandler();
  154. });
  155. },
  156. setupHistory: function() {
  157. this.initHistory({ content: $tom.EMPTY_PARAGRAPH_HTML, scrollTop: 0 });
  158. },
  159. canUndo: function() {
  160. return this.undoMementoList.length > 0;
  161. },
  162. canRedo: function() {
  163. return this.redoMementoList.length > 0;
  164. },
  165. setCurrentMemento: function(memento) {
  166. this.currentMemento = memento;
  167. },
  168. undoHandler: function() {
  169. var self = this;
  170. self.saveHistoryIfEdited();
  171. if (!self.canUndo()) {
  172. return;
  173. }
  174. var undoMemento = self.undoMementoList.pop();
  175. undoMemento.undo();
  176. self.redoMementoList.push(undoMemento);
  177. self.setCurrentMemento(undoMemento);
  178. },
  179. redoHandler: function() {
  180. var self = this;
  181. self.saveHistoryIfEdited();
  182. if (!self.canRedo()) {
  183. return;
  184. }
  185. var redoMemento = self.redoMementoList.pop();
  186. redoMemento.redo();
  187. self.undoMementoList.push(redoMemento);
  188. self.setCurrentMemento(redoMemento);
  189. },
  190. initHistory: function(data) {
  191. var self = this;
  192. self.undoMementoList = [];
  193. self.redoMementoList = [];
  194. var newMemento = new Memento();
  195. var initialData = Object.extend({ content: $tom.EMPTY_PARAGRAPH_HTML, scrollTop: 0 }, data);
  196. newMemento.addUndoData(initialData);
  197. newMemento.addHandler(self.getTextHandler());
  198. self.setCurrentMemento(newMemento);
  199. },
  200. saveHistory: function(before, after, handler) {
  201. var self = this;
  202. var undoMementoList = self.undoMementoList;
  203. var currentMemento = self.currentMemento;
  204. self.redoMementoList = [];
  205. if (arguments.length == 3) {
  206. currentMemento.addUndoRedData(before, after, handler);
  207. }
  208. var textData = self.getTextData();
  209. currentMemento.addRedoData(textData);
  210. keepMaxLength(undoMementoList, self.maxUndoCount);
  211. undoMementoList.push(currentMemento);
  212. var newMemento = new Memento();
  213. newMemento.addHandler(self.getTextHandler());
  214. newMemento.addUndoData(textData);
  215. self.setCurrentMemento(newMemento);
  216. self.contentModified = _FALSE;
  217. },
  218. injectHistory: function(before, after, handler) {
  219. if (!this.canUndo()) {
  220. return;
  221. }
  222. var undoMementoList = this.undoMementoList;
  223. var lastMemento = undoMementoList[undoMementoList.length - 1];
  224. lastMemento.addUndoRedData(before, after, handler);
  225. },
  226. saveHistoryIfEdited: function() {
  227. if (this.contentModified) {
  228. this.saveHistory();
  229. }
  230. },
  231. saveHistoryByKeyEvent: function(event) {
  232. var key = {
  233. code: event.keyCode,
  234. ctrl: event.ctrlKey || (event.keyCode === 17),
  235. alt: event.altKey || (event.keyCode === 18),
  236. shift: event.shiftKey || (event.keyCode === 16)
  237. };
  238. if (key.code == 229) { // ignore mouse click in ff.
  239. return;
  240. }
  241. var self = this;
  242. if (key.code == Trex.__KEY.ENTER || key.code == Trex.__KEY.SPACE || key.code == Trex.__KEY.TAB) {
  243. self.saveHistoryIfEdited();
  244. } else if (key.code == Trex.__KEY.DELETE || key.code == Trex.__KEY.BACKSPACE) {
  245. self.saveHistory();
  246. } else if ((key.code == Trex.__KEY.PASTE || key.code == Trex.__KEY.CUT) && key.ctrl) {
  247. self.saveHistory();
  248. } else if (((key.code > 32 && key.code < 41) && key.shift) || (key.code == 65 && key.ctrl)) { // shift + arrow, home, end, etc.. / select all
  249. self.saveHistoryIfEdited();
  250. } else if (key.ctrl || key.alt || (key.shift && key.code == 16)) {
  251. // content isn't modified
  252. } else {
  253. self.contentModified = _TRUE;
  254. }
  255. },
  256. getTextHandler: function() {
  257. var canvas = this.canvas;
  258. var self = this;
  259. return function(data) {
  260. canvas.setContent(data.content);
  261. var DEFAULT_RESRORE_RANGE = {start: 0, end: 0};
  262. var range = data.range || DEFAULT_RESRORE_RANGE;
  263. self.restoreRange(range);
  264. if ($tx.msie_nonstd) {
  265. // #FTDUEDTR-1122
  266. setTimeout(function() {
  267. canvas.setScrollTop(data.scrollTop);
  268. }, 0);
  269. }
  270. }
  271. },
  272. getTextData:function() {
  273. return {
  274. content: this.canvas.getContent(),
  275. scrollTop: this.canvas.getScrollTop(),
  276. range: this.getRangeData()
  277. }
  278. }
  279. });
  280. var Memento = Trex.Class.create({
  281. initialize: function() {
  282. this.before = {};
  283. this.after = {};
  284. this.handlers = [];
  285. },
  286. addUndoRedData: function(before, after, handler) {
  287. Object.extend(this.before, before);
  288. Object.extend(this.after, after);
  289. this.handlers.push(handler);
  290. },
  291. addHandler: function(handler) {
  292. this.handlers.push(handler);
  293. },
  294. addUndoData: function(data) {
  295. Object.extend(this.before, data);
  296. },
  297. addRedoData: function(data) {
  298. Object.extend(this.after, data);
  299. },
  300. undo: function() {
  301. var self = this;
  302. self.handlers.each(function(handler) {
  303. handler(self.before);
  304. });
  305. },
  306. redo: function() {
  307. var self = this;
  308. self.handlers.each(function(handler) {
  309. handler(self.after);
  310. });
  311. }
  312. });
  313. })();