fixes #953: Leave users' whitespace alone. I'm serious!
[redakcja.git] / redakcja / static / js / button_scripts.js
1 (function() {
2   var slice = Array.prototype.slice;
3
4   function update(array, args) {
5     var arrayLength = array.length, length = args.length;
6     while (length--) array[arrayLength + length] = args[length];
7     return array;
8   };
9
10   function merge(array, args) {
11     array = slice.call(array, 0);
12     return update(array, args);
13   };
14
15   Function.prototype.bind = function(context) {
16     if (arguments.length < 2 && typeof arguments[0] === 'undefined') {
17       return this;
18     }
19     var __method = this;
20     var args = slice.call(arguments, 1);
21     return function() {
22       var a = merge(args, arguments);
23       return __method.apply(context, a);
24     }
25   }
26
27 })();
28
29 function nblck_each(array, body, after) {
30         $.each(array, function(i) {
31                 body(this, i);
32         });
33
34         after();
35 };
36
37 function nblck_map(array, func, after) {
38         var acc = [];
39
40         nblck_each(array, function(elem, index) {
41                 acc.push(func(elem, index));
42         }, function(){
43                 after(acc);
44         });
45 };
46
47 function ScriptletCenter()
48 {
49     this.scriptlets = {};
50
51     this.scriptlets['insert_tag'] = function(context, params, text, move_forward, move_up, done)
52     {
53         var padding_top = '';
54         for (var i=params.padding_top; i; i--)
55             padding_top += '\n';
56
57         var start_tag = '<'+params.tag;
58         var cursor_inside = false;
59
60         for (var attr in params.attrs) {
61             start_tag += ' '+attr+'="' + params.attrs[attr] + '"';
62         }
63
64         start_tag += '>';
65         var end_tag = '</'+params.tag+'>';
66
67         var padding_bottom = '';
68         for (var i=params.padding_bottom; i; i--)
69             padding_bottom += '\n';
70
71         if(text.length > 0) {
72             // tokenize
73             var output = '';
74             var token = '';
75             for(var index=0; index < text.length; index++)
76             {
77                 if (text[index].match(/\s/)) { // whitespace
78                     token += text[index];
79                 }
80                 else { // character
81                     output += token;
82                     if(output == token) output += padding_top + start_tag;
83                     token = '';
84                     output += text[index];
85                 }
86             }
87
88             if( output[output.length-1] == '\\' ) {
89                 output = output.substr(0, output.length-1) + end_tag + padding_bottom + '\\';
90             } else {
91                 output += end_tag + padding_bottom;
92             }
93             output += token;
94
95             // keep cursor inside tag if some previous scriptlet has already moved it
96             cursor_inside = move_forward != 0 || move_up != 0;
97         }
98         else {
99             if(params.nocontent) {
100                 output = padding_top + "<"+params.tag +" />" + padding_bottom;
101             }
102             else {
103                 output = padding_top + start_tag + end_tag + padding_bottom;
104                 cursor_inside = true;
105             }
106         }
107
108         if (cursor_inside) {
109             move_forward -= params.tag.length + 3;
110             move_up += params.padding_bottom || 0;
111             alert(move_forward + ' ' + move_up)
112         }
113
114         done(output, move_forward, move_up);
115     }.bind(this);
116
117     this.scriptlets['lineregexp'] = function(context, params, text, move_forward, move_up, done) {
118                 var self = this;
119
120         var exprs = $.map(params.exprs, function(expr) {
121             var opts = "g";
122             if(expr.length > 2) {
123                 opts = expr[2];
124             } return {
125                 rx: new RegExp(expr[0], opts),
126                 repl: expr[1]
127                 };
128         });
129
130         if(!text) done(text, move_forward, move_up);
131
132         var changed = 0;
133         var lines = text.split('\n');
134
135                 nblck_map(lines, function(line, index) {
136             var old_line = line;
137             $(exprs).each(function() {
138                 var expr = this;
139                 line = line.replace(expr.rx, expr.repl);
140             });
141
142                         $progress.html(index);
143
144             if(old_line != line) changed += 1;
145             return line;
146         }, function(newlines) {
147             if(changed > 0) {
148                 text = newlines.join('\n');
149             };
150
151             done(text, move_forward, move_up);
152                 });
153     }.bind(this);
154
155     this.scriptlets['fulltextregexp'] = function(context, params, text, move_forward, move_up, done) {
156                 var self = this;
157
158         var exprs = $.map(params.exprs, function(expr) {
159             var opts = "mg";
160             if(expr.length > 2) {
161                 opts = expr[2];
162             }
163             return {
164                 rx: new RegExp(expr[0], opts),
165                 repl: expr[1]
166                 };
167         });
168
169         if(!text) done(text, move_forward, move_up);
170
171                 nblck_each(exprs, function(expr, index) {
172                         $progress.html(600 + index);
173             text = text.replace(expr.rx, expr.repl);
174         }, function() {
175                         done(text, move_forward, move_up);
176                 });
177     }.bind(this);
178
179     this.scriptlets['macro'] = function(context, params, text, move_forward, move_up, done) {
180         var self = this;
181                 var i = 0;
182
183                 function next(text, move_forward, move_up) {
184                 if (i < params.length) {
185                                 var e = params[i];
186                                 i = i + 1;
187                                 self.scriptlets[e[0]](context, e[1], text, move_forward, move_up, next);
188                         }
189                         else {
190                                 done(text, move_forward, move_up);
191                         }
192         };
193
194                 next(text, move_forward, move_up);
195     }.bind(this);
196
197     this.scriptlets['lowercase'] = function(context, params, text, move_forward, move_up, done)
198     {
199         if(!text) done(text, move_forward, move_up);
200         done(text.toLowerCase(), move_forward, move_up);
201     }.bind(this);
202
203
204     this.scriptlets["insert_stanza"] = function(context, params, text, move_forward, move_up, done) {
205         var padding_top = '';
206         for (var i=params.padding_top; i; i--)
207             padding_top += '\n';
208
209         var padding_bottom = '';
210         for (var i=params.padding_bottom; i; i--)
211             padding_bottom += '\n';
212
213         if(text) {
214             var verses = text.split('\n');
215             text = ''; var buf = ''; var ebuf = '';
216             var first = true;
217
218             for(var i=0;  i < verses.length; i++) {
219                 var verse = verses[i].replace(/^\s+/, "").replace(/\s+$/, "");
220                 if(verse) {
221                     text += (buf ? buf + '/\n' : '') + ebuf;
222                     buf = (first ? '<strofa>\n' : '') + verses[i];
223                     ebuf = '';
224                     first = false;
225                 } else {
226                     ebuf += '\n' + verses[i];
227                 }
228             }
229             text = padding_top + text + buf + '\n</strofa>' + padding_bottom + ebuf;
230         }
231         else {
232             text = padding_top + "<strofa></strofa>" + padding_bottom;
233             move_forward -= "</strofa>".length;
234             move_up += params.padding_bottom || 0;
235         }
236
237         done(text, move_forward, move_up);
238     }.bind(this);
239
240
241     this.scriptlets['autotag'] = function(context, params, text, move_forward, move_up, done)
242     {
243         if(!text.match(/^\n+$/)) done(text, move_forward, move_up);
244
245         function insert_done(output, mf) {
246             text += output;
247         }
248
249         if (!params.split) params.split = 2;
250         if (!params.padding) params.padding = 3;
251
252         chunks = text.replace(/^\n+|\n+$/, '').split(new RegExp("\\n{"+params.split+",}"));
253         text = text.match(/^\n+/);
254         if (!text)
255             text = '';
256         padding = '';
257         for(; params.padding; params.padding--) {
258             padding += "\n";
259         }
260
261         if (params.tag == 'strofa')
262             tagger = this.scriptlets['insert_stanza'];
263         else
264             tagger = this.scriptlets['insert_tag'];
265
266         for (i in chunks) {
267             if (chunks[i]) {
268                 if (params.tag == 'akap' && chunks[i].match(/^---/))
269                     tag = 'akap_dialog';
270                 else tag = params.tag;
271                 tagger(context, {tag: tag}, chunks[i], 0, insert_done);
272                 text += padding;
273             }
274         }
275
276         done(text, move_forward, move_up);
277     }.bind(this);
278
279 }
280
281 ScriptletCenter.prototype.callInteractive = function(opts) {
282         $progress = $('<span>Executing script</span>');
283         var self = this;
284
285         /* This won't work, 'cause the JS below might be synchronous :( */
286         /* var timer = setTimeout(function() {
287             $.blockUI({message: $progress});
288             timer = null;
289         }, 1000); */
290
291         $.blockUI({message: $progress, showOverlay: false});
292
293     var input = self.XMLEditorSelectedText(opts.context);
294         self.scriptlets[opts.action](opts.context, opts.extra, input, 0, 0, function(output, move_forward, move_up){
295             /*if(timer)
296                 clearTimeout(timer);
297             else */
298         if (input != output) {
299             self.XMLEditorReplaceSelectedText(opts.context, output)
300         }
301         if (move_forward || move_up) {
302             try {
303                 self.XMLEditorMoveCursorForward(opts.context, move_forward, move_up)
304             }
305             catch(e) {}
306         }
307             $.unblockUI(); // done
308         });
309 }
310
311 ScriptletCenter.prototype.XMLEditorSelectedText = function(editor) {
312
313     return editor.selection();
314 };
315
316 ScriptletCenter.prototype.XMLEditorReplaceSelectedText = function(editor, replacement)
317 {
318         $progress.html("Replacing text");
319         editor.replaceSelection(replacement);
320 };
321
322 ScriptletCenter.prototype.XMLEditorMoveCursorForward = function(panel, right, up) {
323     var pos = panel.cursorPosition();
324     if (up) {
325         line = pos.line;
326         while (up < 0) {
327             line = panel.nextLine(line);
328             ++up;
329         }
330         while (up > 0) {
331             line = panel.prevLine(line);
332             --up;
333         }
334         len = panel.lineContent(line).length;
335         panel.selectLines(line, len + right);
336     }
337     else {
338         panel.selectLines(pos.line, pos.character + right);
339     }
340 };
341
342 var scriptletCenter;
343
344 $(function() {
345     scriptletCenter = new ScriptletCenter();
346 });