smartxml: testing three available levels of transformation undo-awareness
[fnpeditor.git] / src / smartxml / transformation_api_exp.js
1 //use case: edytor robi split, jesli split był na koncu (czyli druga czesc jest pusta)
2 // chce tam dodac wezel tekstowy
3
4 // flow: plugin_key_handler(enter, main-canvas-area) -> plugin_document_action('break-content')
5 // --w srodku--> refactoring tego co teraz w keyboard:
6
7 //1. jedna transformacja z warunkiem (Zarejestrowana przez plugin)
8
9
10
11 var breakContentTransformation = {
12     impl: function(args) {
13         var node = this.context;
14             emptyText = false,
15             newNodes,
16             emptyNode;
17
18         newNodes = node.transform('core.split', {offset: args.offset});
19
20         if(args.offset === 0)
21             emptyNode = newNodes.first;
22         else if(args.offset === node.getText().length); //@ nie ma atEnd :(
23             emptyNode = newNodes.second;
24         
25         if(emptyNode) {
26             emptyText = emptyNode.transform('core.append', {text: ''});
27         }
28
29         return _.extend(newNodes, {emptyText: emptyText});
30     }
31 };
32
33
34 var breakContentAction = function(document, context) {
35     var textNode = context.currentTextNode;
36     if(textNode) {
37         var result, goto;
38
39         result = textNode.transform('core.break-content', {offset: context.offset});
40
41         if(result.emptyText) {
42             goto = result.createdEmpty;
43             gotoOptions = {};
44         } else {
45             goto = result.second;
46             gotoOptions = {caretTo: 'start'};   
47         }
48
49         context.setCurrentElement(goto, gotoOptions);
50     }
51 }
52
53 var toret = {
54     keyHandlers: [
55         {key: 'ENTER', target: 'main-document-area', handler: function(editor) {
56             editor.getAction('core.break-document-content').execute();
57         }},
58     ],
59     
60     actions: [
61         {name: 'core.break-document-content', context: 'main-document-area', action: breakContentAction}
62     ],
63
64     // zapisywanie dokumentu:
65
66     contextTransformations: [
67         {name: 'core.break-content', textNode: true, t: breakContentTransformation},
68
69         // list plugin:
70         {name: 'list.remove-list', elementNode: 'list', t: null}
71         // hipotetyczna akcja na itemie listy
72         {name: 'list.extract', elementNode: 'item', requiresParent: 'list', requiresInParents: '?'}
73     ],
74
75 }
76
77
78 /// STARE
79
80 // 1. detach via totalny fallback
81 var DetachNodeTransformation = function(args) {
82     this.node = args.node;
83     this.document = this.node.document;
84 };
85 $.extend(DetachNodeTransformation.prototype, {
86     run: function() {
87         this.oldRoot = this.node.document.root.clone();
88         this.path = this.node.getPath();
89         this.node.detach(); // @TS
90         
91     },
92     undo: function() {
93         this.document.root.replaceWith(this.oldRoot); // this.getDocument?
94         this.node = this.document.getNodeByPath(this.path);
95     }
96 });
97 transformations['detach'] = DetachNodeTransformation;
98
99 //2. detach via wskazanie changeroot
100
101 var Detach2NodeTransformation = function(args) {
102     this.nodePath = args.node.getPath();
103     this.document = args.node.document;
104 };
105 $.extend(Detach2NodeTransformation.prototype, {
106     run: function() {
107         var node = this.document.getNodeByPath(this.nodePath),
108             root = node.parent() ? node.parent() : this.document.root;
109         
110         this.rootPath = root.getPath();
111         this.oldRoot = (root).clone();
112         node.detach();
113     },
114     undo: function() {
115         this.document.getNodeByPath(this.rootPath).replaceWith(this.oldRoot);
116     }
117 });
118 //transformations['detach2'] = Detach2NodeTransformation;
119
120 //2a. generyczna transformacja
121
122 var createTransformation = function(desc) {
123
124     var NodeTransformation = function(args) {
125         this.nodePath = args.node.getPath();
126         this.document = args.node.document;
127         this.args = args;
128     };
129     $.extend(NodeTransformation.prototype, {
130         run: function() {
131             var node = this.document.getNodeByPath(this.nodePath),
132                 root;
133
134             if(desc.getRoot) {
135                 root = desc.getRoot(node);
136             } else {
137                 root = this.document.root;
138             }
139             
140             this.rootPath = root.getPath();
141             this.oldRoot = (root).clone();
142             desc.impl.call(node, this.args);
143         },
144         undo: function() {
145             this.document.getNodeByPath(this.rootPath).replaceWith(this.oldRoot);
146         }
147     });
148
149     return NodeTransformation;
150 }
151
152
153
154 var contextTransformations = {};
155 contextTransformations['setText'] = createContextTransformation({
156     impl: function(args) {
157         this.setText(args.text);
158     },
159     getChangeRoot: function() {
160         return this.context;
161     }
162 });
163
164 contextTransformations['setAttr'] = createContextTransformation({
165     impl: function(args) {
166         this.setAttr(args.name, args.value);
167     },
168     getChangeRoot: function() {
169         return this.context;
170     }
171 });
172
173 contextTransformations['split'] = createContextTransformation({
174     impl: function(args) {
175         return this.split({offset: args.offset});
176     }//,
177     // getChangeRoot: function() {
178     //     return this.context.parent().parent();
179     // }
180 });
181
182
183 contextTransformations['before'] = createContextTransformation({
184     getChangeRoot: function() {
185         return this.context.parent();
186     },
187     impl: function(args) {
188         this.before(args.node)
189     },
190
191 });
192
193 contextTransformations['before'] = createContextTransformation({
194     impl: function(args) {
195         this.before(args.node)
196     },
197     undo: function() {
198         this.context.detach();
199     }
200 });
201
202
203
204 transformations['detach2'] = createTransformation({
205     // impl: function() {
206     //     //this.setAttr('class', 'cite'); //  
207     // },
208     impl: ElementNode.prototype.detach,
209     getRoot: function(node) {
210         return node.parent();
211     }
212
213 });
214
215 transformations['setText-old'] = createTransformation({
216     impl: function(args) {
217         this.setText(args.text)
218     },
219     getRoot: function(node) {
220         return node;
221     }
222
223 });
224
225 transformations['setClass-old'] = createTransformation({
226     impl: function(args) {
227         this.setClass(args.klass);
228     },
229     getRoot: function(node) {
230         return node;
231     }
232 })
233
234 //3. detach z pełnym własnym redo
235
236 var Detach3NodeTransformation = function(args) {
237     this.node = args.node;
238     this.document = this.node.document;
239 };
240 $.extend(Detach3NodeTransformation.prototype, {
241     run: function() {
242         //this.index = this.node.getIndex();
243         //this.parent = this.node.parent();
244         
245         this.path = this.node.getPath();
246         if(this.node.isSurroundedByTextElements()) {
247             this.prevText = this.node.prev().getText();
248             this.nextText = this.node.next().getText();
249             this.merge = true;
250         } else {
251             this.prevText = this.nextText = null;
252             this.merge = false;
253         }
254
255         this.node.detach();
256     },
257     undo: function() {
258         var parent = this.document.getNodeByPath(this.path.slice(0,-1)),
259             idx = _.last(this.path);
260         var inserted = parent.insertAtIndex(this.node, idx);
261         if(this.merge) {
262             if(inserted.next()) {
263                 inserted.before({text: this.prevText});
264                 inserted.next().setText(this.nextText);
265             } else {
266                 inserted.prev().setText(this.prevText);
267                 inserted.after({text: this.nextText});
268             }
269         }
270     }
271 });
272 transformations['detach3'] = Detach3NodeTransformation;
273
274
275 var registerTransformationsFromObject = function(object) {
276     _.pairs(object).filter(function(pair) {
277         var property = pair[1];
278         return typeof property === 'function' && property._isTransformation;
279     })
280     .forEach(function(pair) {
281         var name = pair[0],
282             method = pair[1];
283         object.registerTransformation(name, createContextTransformation(method));
284     });
285 };
286 registerTransformationsFromObject(ElementNode.prototype);
287 registerTransformationsFromObject(TextNode.prototype);
288 registerTransformationsFromObject(Document.prototype);