#977: slugify tool
[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         }
112
113         done(output, move_forward, move_up);
114     }.bind(this);
115
116     this.scriptlets['lineregexp'] = function(context, params, text, move_forward, move_up, done) {
117                 var self = this;
118
119         var exprs = $.map(params.exprs, function(expr) {
120             var opts = "g";
121             if(expr.length > 2) {
122                 opts = expr[2];
123             } return {
124                 rx: new RegExp(expr[0], opts),
125                 repl: expr[1]
126                 };
127         });
128
129         if(!text) done(text, move_forward, move_up);
130
131         var changed = 0;
132         var lines = text.split('\n');
133
134                 nblck_map(lines, function(line, index) {
135             var old_line = line;
136             $(exprs).each(function() {
137                 var expr = this;
138                 line = line.replace(expr.rx, expr.repl);
139             });
140
141                         $progress.html(index);
142
143             if(old_line != line) changed += 1;
144             return line;
145         }, function(newlines) {
146             if(changed > 0) {
147                 text = newlines.join('\n');
148             };
149
150             done(text, move_forward, move_up);
151                 });
152     }.bind(this);
153
154     this.scriptlets['fulltextregexp'] = function(context, params, text, move_forward, move_up, done) {
155                 var self = this;
156
157         var exprs = $.map(params.exprs, function(expr) {
158             var opts = "mg";
159             if(expr.length > 2) {
160                 opts = expr[2];
161             }
162             return {
163                 rx: new RegExp(expr[0], opts),
164                 repl: expr[1]
165                 };
166         });
167
168         if(!text) done(text, move_forward, move_up);
169
170                 nblck_each(exprs, function(expr, index) {
171                         $progress.html(600 + index);
172             text = text.replace(expr.rx, expr.repl);
173         }, function() {
174                         done(text, move_forward, move_up);
175                 });
176     }.bind(this);
177
178     this.scriptlets['macro'] = function(context, params, text, move_forward, move_up, done) {
179         var self = this;
180                 var i = 0;
181
182                 function next(text, move_forward, move_up) {
183                 if (i < params.length) {
184                                 var e = params[i];
185                                 i = i + 1;
186                                 self.scriptlets[e[0]](context, e[1], text, move_forward, move_up, next);
187                         }
188                         else {
189                                 done(text, move_forward, move_up);
190                         }
191         };
192
193                 next(text, move_forward, move_up);
194     }.bind(this);
195
196     this.scriptlets['lowercase'] = function(context, params, text, move_forward, move_up, done)
197     {
198         if(!text) done(text, move_forward, move_up);
199         done(text.toLowerCase(), move_forward, move_up);
200     }.bind(this);
201
202
203     this.scriptlets["insert_stanza"] = function(context, params, text, move_forward, move_up, done) {
204         var padding_top = '';
205         for (var i=params.padding_top; i; i--)
206             padding_top += '\n';
207
208         var padding_bottom = '';
209         for (var i=params.padding_bottom; i; i--)
210             padding_bottom += '\n';
211
212         if(text) {
213             var verses = text.split('\n');
214             text = ''; var buf = ''; var ebuf = '';
215             var first = true;
216
217             for(var i=0;  i < verses.length; i++) {
218                 var verse = verses[i].replace(/^\s+/, "").replace(/\s+$/, "");
219                 if(verse) {
220                     text += (buf ? buf + '/\n' : '') + ebuf;
221                     buf = (first ? '<strofa>\n' : '') + verses[i];
222                     ebuf = '';
223                     first = false;
224                 } else {
225                     ebuf += '\n' + verses[i];
226                 }
227             }
228             text = padding_top + text + buf + '\n</strofa>' + padding_bottom + ebuf;
229         }
230         else {
231             text = padding_top + "<strofa></strofa>" + padding_bottom;
232             move_forward -= "</strofa>".length;
233             move_up += params.padding_bottom || 0;
234         }
235
236         done(text, move_forward, move_up);
237     }.bind(this);
238
239
240     this.scriptlets['autotag'] = function(context, params, text, move_forward, move_up, done)
241     {
242         if(!text.match(/^\n+$/)) done(text, move_forward, move_up);
243
244         var output = '';
245
246         function insert_done(text, mf, mu) {
247             output += text;
248         }
249
250         if (!params.split) params.split = 2;
251         if (!params.padding) params.padding = 3;
252
253         if (params.tag == 'strofa')
254             tagger = this.scriptlets['insert_stanza'];
255         else
256             tagger = this.scriptlets['insert_tag'];
257
258         var padding_top = text.match(/^\n+/)
259         output = padding_top ? padding_top[0] : '';
260
261         padding = '';
262         for(var i=params.padding; i; --i) {
263             padding += "\n";
264         }
265
266         text = text.substr(output.length);
267         var chunk_reg = new RegExp("^([\\s\\S]+?)(\\n{"+params.split+",}|$)");
268         while (match = text.match(chunk_reg)) {
269             if (params.tag == 'akap' && match[1].match(/^---/))
270                 tag = 'akap_dialog';
271             else tag = params.tag;
272             tagger(context, {tag: tag}, match[1], 0, 0, insert_done);
273             if (match[2].length > params.padding)
274                 output += match[2];
275             else
276                 output += padding;
277             text = text.substr(match[0].length)
278         }
279
280         output += text;
281
282         done(output, move_forward, move_up);
283     }.bind(this);
284
285
286     this.scriptlets['slugify'] = function(context, params, text, move_forward, move_up, done)
287     {
288         done(slugify(text.replace(/_/g, '-')), move_forward, move_up);
289     }.bind(this);
290
291 }
292
293 ScriptletCenter.prototype.callInteractive = function(opts) {
294         $progress = $('<span>Executing script</span>');
295         var self = this;
296
297         /* This won't work, 'cause the JS below might be synchronous :( */
298         /* var timer = setTimeout(function() {
299             $.blockUI({message: $progress});
300             timer = null;
301         }, 1000); */
302
303         $.blockUI({message: $progress, showOverlay: false});
304
305     var input = self.XMLEditorSelectedText(opts.context);
306         self.scriptlets[opts.action](opts.context, opts.extra, input, 0, 0, function(output, move_forward, move_up){
307             /*if(timer)
308                 clearTimeout(timer);
309             else */
310         if (input != output) {
311             self.XMLEditorReplaceSelectedText(opts.context, output)
312         }
313         if (move_forward || move_up) {
314             try {
315                 self.XMLEditorMoveCursorForward(opts.context, move_forward, move_up)
316             }
317             catch(e) {}
318         }
319             $.unblockUI(); // done
320         });
321 }
322
323 ScriptletCenter.prototype.XMLEditorSelectedText = function(editor) {
324
325     return editor.selection();
326 };
327
328 ScriptletCenter.prototype.XMLEditorReplaceSelectedText = function(editor, replacement)
329 {
330         $progress.html("Replacing text");
331         editor.replaceSelection(replacement);
332 };
333
334 ScriptletCenter.prototype.XMLEditorMoveCursorForward = function(panel, right, up) {
335     var pos = panel.cursorPosition();
336     if (up) {
337         line = pos.line;
338         while (up < 0) {
339             line = panel.nextLine(line);
340             ++up;
341         }
342         while (up > 0) {
343             line = panel.prevLine(line);
344             --up;
345         }
346         len = panel.lineContent(line).length;
347         panel.selectLines(line, len + right);
348     }
349     else {
350         panel.selectLines(pos.line, pos.character + right);
351     }
352 };
353
354 var scriptletCenter;
355
356 $(function() {
357     scriptletCenter = new ScriptletCenter();
358 });