Komunikat, ze zaczal zapisywac.
[redakcja.git] / project / static / js / editor.js
1 function Panel(panelWrap) {
2     var self = this;
3     self.hotkeys = [];
4     self.wrap = panelWrap;
5     self.contentDiv = $('.panel-content', panelWrap);
6     self.instanceId = Math.ceil(Math.random() * 1000000000);
7     $.log('new panel - wrap: ', self.wrap);
8         
9     $(document).bind('panel:unload.' + self.instanceId,
10         function(event, data) {
11             self.unload(event, data);
12         });
13
14     $(document).bind('panel:contentChanged', function(event, data) {
15         $.log(self, ' got changed event from: ', data);
16         if(self != data)
17             self.otherPanelChanged(event.target);
18         else
19             self.markChanged();
20
21         return false;
22     });
23 }
24
25 Panel.prototype.callHook = function() {
26     var args = $.makeArray(arguments)
27     var hookName = args.splice(0,1)[0]
28     var noHookAction = args.splice(0,1)[0]
29     var result = false;
30
31     $.log('calling hook: ', hookName, 'with args: ', args);
32     if(this.hooks && this.hooks[hookName])
33         result = this.hooks[hookName].apply(this, args);
34     else if (noHookAction instanceof Function)
35         result = noHookAction(args);
36     return result;
37 }
38
39 Panel.prototype.load = function (url) {
40     $.log('preparing xhr load: ', this.wrap);
41     $(document).trigger('panel:unload', this);
42     var self = this;
43     self.current_url = url;
44
45     $.ajax({
46         url: url,
47         dataType: 'html',
48         success: function(data, tstat) {
49             panel_hooks = null;
50             $(self.contentDiv).html(data);
51             self.hooks = panel_hooks;
52             panel_hooks = null;
53             self.connectToolbar();
54             self.callHook('load');
55             self.callHook('toolbarResized');
56         },
57         error: function(request, textStatus, errorThrown) {
58             $.log('ajax', url, this.target, 'error:', textStatus, errorThrown);
59             $(self.contentDiv).html("<p>Wystapił błąd podczas wczytywania panelu.");
60         }
61     });
62 }
63
64 Panel.prototype.unload = function(event, data) {
65     $.log('got unload signal', this, ' target: ', data);
66
67     if( data == this ) {
68         $.log('unloading', this);
69         $(this.contentDiv).html('');
70         this.callHook('unload');
71         this.hooks = null; // flush the hooks
72         return false;
73     };
74 }
75
76 Panel.prototype.refresh = function(event, data) {
77     var self = this;
78     reload = function() {
79         $.log('hard reload for panel ', self.current_url);
80         self.load(self.current_url);
81         return true;
82     }
83
84     if( this.callHook('refresh', reload) )
85         $('.change-notification', this.wrap).fadeOut();
86
87
88 Panel.prototype.otherPanelChanged = function(other) {
89     $.log('panel ', other, ' changed.');
90     if(!this.callHook('dirty'))
91         $('.change-notification', this.wrap).fadeIn();
92 }       
93
94 Panel.prototype.markChanged = function () {
95     this.wrap.addClass('changed');
96 }
97
98 Panel.prototype.changed = function () {
99     return this.wrap.hasClass('changed');
100 }
101
102 Panel.prototype.unmarkChanged = function () {
103     this.wrap.removeClass('changed');
104 }
105
106 Panel.prototype.saveInfo = function() {
107     var saveInfo = {};
108     this.callHook('saveInfo', null, saveInfo);
109     return saveInfo;
110 }
111
112 Panel.prototype.connectToolbar = function()
113 {
114     var self = this;
115     self.hotkeys = [];
116     
117     // check if there is a one
118     var toolbar = $("div.toolbar", this.contentDiv);
119     $.log('Connecting toolbar', toolbar);
120     if(toolbar.length == 0) return;
121
122     // connect group-switch buttons
123     var group_buttons = $('*.toolbar-tabs-container button', toolbar);
124
125     $.log('Found groups:', group_buttons);
126
127     group_buttons.each(function() {
128         var group = $(this);
129         var group_name = group.attr('ui:group');
130         $.log('Connecting group: ' + group_name);
131
132         group.click(function() {
133             // change the active group
134             var active = $("*.toolbar-tabs-container button.active", toolbar);
135             if (active != group) {
136                 active.removeClass('active');                
137                 group.addClass('active');
138                 $(".toolbar-button-groups-container p", toolbar).each(function() {
139                     if ( $(this).attr('ui:group') != group_name) 
140                         $(this).hide();
141                     else
142                         $(this).show();
143                 });
144                 self.callHook('toolbarResized');
145             }
146         });        
147     });
148
149     // connect action buttons
150     var action_buttons = $('*.toolbar-button-groups-container button', toolbar);
151     action_buttons.each(function() {
152         var button = $(this);
153         var hk = button.attr('ui:hotkey');
154         var params = $.evalJSON(button.attr('ui:action-params'));
155
156         var callback = function() {
157             editor.callScriptlet(button.attr('ui:action'), self, params);
158         };
159
160         // connect button
161         button.click(callback);
162         
163         // connect hotkey
164         if(hk) self.hotkeys[parseInt(hk)] = callback;
165
166         // tooltip
167         if (button.attr('ui:tooltip') )
168         {
169             var tooltip = button.attr('ui:tooltip');
170             if(hk) tooltip += ' [Alt+'+hk+']';
171
172             button.wTooltip({
173                 delay: 1000,
174                 style: {
175                     border: "1px solid #7F7D67",
176                     opacity: 0.9,
177                     background: "#FBFBC6",
178                     padding: "1px",
179                     fontSize: "12px"
180                 },
181                 content: tooltip
182             });
183         }
184     });
185 }
186
187 Panel.prototype.hotkeyPressed = function(event)
188 {
189     var callback = this.hotkeys[event.keyCode];
190     if(callback) callback();
191 }
192
193 Panel.prototype.isHotkey = function(event) {
194     if( event.altKey && (this.hotkeys[event.keyCode] != null) )
195         return true;
196     return false;
197 }
198
199 //
200 Panel.prototype.fireEvent = function(name) {
201     $(document).trigger('panel:'+name, this);
202 }
203
204 function Editor()
205 {
206     this.rootDiv = $('#panels');
207     this.popupQueue = [];
208     this.autosaveTimer = null;
209     this.scriplets = {};
210 }
211
212 Editor.prototype.setupUI = function() {
213     // set up the UI visually and attach callbacks
214     var self = this;
215    
216     self.rootDiv.makeHorizPanel({}); // TODO: this probably doesn't belong into jQuery
217     // self.rootDiv.css('top', ($('#header').outerHeight() ) + 'px');
218     
219     $('#panels > *.panel-wrap').each(function() {
220         var panelWrap = $(this);
221         $.log('wrap: ', panelWrap);
222         var panel = new Panel(panelWrap);
223         panelWrap.data('ctrl', panel); // attach controllers to wraps
224         panel.load($('.panel-toolbar select', panelWrap).val());
225         
226         $('.panel-toolbar select', panelWrap).change(function() {
227             var url = $(this).val();
228             panelWrap.data('ctrl').load(url);
229             self.savePanelOptions();
230         });
231
232         $('.panel-toolbar button.refresh-button', panelWrap).click(
233             function() { 
234                 panel.refresh();
235             } );
236     });
237
238     $(document).bind('panel:contentChanged', function() {
239         self.onContentChanged.apply(self, arguments)
240     });
241     
242     $('#toolbar-button-save').click( function (event, data) { 
243         self.saveToBranch();
244     } );
245     $('#toolbar-button-commit').click( function (event, data) { 
246         self.sendPullRequest();
247     } );
248     self.rootDiv.bind('stopResize', function() { 
249         self.savePanelOptions()
250     });
251 }
252
253 Editor.prototype.loadConfig = function() {
254     // Load options from cookie
255     var defaultOptions = {
256         panels: [
257         {
258             name: 'htmleditor',
259             ratio: 0.5
260         },
261
262         {
263             name: 'gallery',
264             ratio: 0.5
265         }
266         ],
267         lastUpdate: 0
268     }
269     
270     try {
271         var cookie = $.cookie('options');
272         this.options = $.secureEvalJSON(cookie);
273         if (!this.options) {
274             this.options = defaultOptions;
275         }
276     } catch (e) {    
277         this.options = defaultOptions;
278     }
279     $.log(this.options);
280     
281     this.loadPanelOptions();
282 }
283
284 Editor.prototype.loadPanelOptions = function() {
285     var self = this;
286     var totalWidth = 0;
287     
288     $('.panel-wrap', self.rootDiv).each(function(index) {
289         var panelWidth = self.options.panels[index].ratio * self.rootDiv.width();
290         if ($(this).hasClass('last-panel')) {
291             $(this).css({
292                 left: totalWidth,
293                 right: 0
294             });
295         } else {
296             $(this).css({
297                 left: totalWidth,
298                 width: panelWidth
299             });
300             totalWidth += panelWidth;               
301         }
302         $.log('panel:', this, $(this).css('left'));
303         $('.panel-toolbar select', this).val(
304             $('.panel-toolbar option[name=' + self.options.panels[index].name + ']', this).attr('value')
305             )
306     });   
307 }
308
309 Editor.prototype.savePanelOptions = function() {
310     var self = this;
311     var panels = [];
312     $('.panel-wrap', self.rootDiv).not('.panel-content-overlay').each(function() {
313         panels.push({
314             name: $('.panel-toolbar option:selected', this).attr('name'),
315             ratio: $(this).width() / self.rootDiv.width()
316         })
317     });
318     self.options.panels = panels;
319     self.options.lastUpdate = (new Date()).getTime() / 1000;
320     $.log($.toJSON(self.options));
321     $.cookie('options', $.toJSON(self.options), {
322         expires: 7,
323         path: '/'
324     });
325 }
326
327 Editor.prototype.saveToBranch = function(msg) 
328 {
329     var changed_panel = $('.panel-wrap.changed');
330     var self = this;
331     $.log('Saving to local branch - panel:', changed_panel);
332
333     if(!msg) msg = "Zapis z edytora platformy.";
334
335     if( changed_panel.length == 0) {
336         $.log('Nothing to save.');
337         return true; /* no changes */
338     }
339
340     if( changed_panel.length > 1) {
341         alert('Błąd: więcej niż jeden panel został zmodyfikowany. Nie można zapisać.');
342         return false;
343     }
344
345     saveInfo = changed_panel.data('ctrl').saveInfo();
346     var postData = ''
347     
348     if(saveInfo.postData instanceof Object)
349         postData = $.param(saveInfo.postData);
350     else
351         postData = saveInfo.postData;
352
353     postData += '&' + $.param({
354         'commit_message': msg
355     })
356
357     self.showPopup('save-waiting', '', -1);
358
359     $.ajax({
360         url: saveInfo.url,
361         dataType: 'json',
362         success: function(data, textStatus) {
363             if (data.result != 'ok') {
364                 self.showPopup('save-error', (data.errors && data.errors[0]) || 'Nieznany błąd X_X.');
365             }
366             else {
367                 self.refreshPanels(changed_panel);
368                 $('#toolbar-button-save').attr('disabled', 'disabled');
369                 $('#toolbar-button-commit').removeAttr('disabled');
370                 if(self.autosaveTimer)
371                     clearTimeout(self.autosaveTimer);
372
373                 if (data.warnings == null)
374                     self.showPopup('save-successful');
375                 else
376                     self.showPopup('save-warn', data.warnings[0]);
377             }
378             
379             self.advancePopupQueue();
380         },
381         error: function(rq, tstat, err) {
382             self.showPopup('save-error', '- bład wewnętrzny serwera.');
383             self.advancePopupQueue();
384         },
385         type: 'POST',
386         data: postData
387     });
388
389     return true;
390 };
391
392 Editor.prototype.autoSave = function() 
393 {
394     this.autosaveTimer = null;
395     // first check if there is anything to save
396     $.log('Autosave');
397     this.saveToBranch("Automatyczny zapis z edytora platformy.");
398 }
399
400 Editor.prototype.onContentChanged = function(event, data) {
401     var self = this;
402
403     $('#toolbar-button-save').removeAttr('disabled');
404     $('#toolbar-button-commit').attr('disabled', 'disabled');
405     
406     if(this.autosaveTimer) return;
407     this.autosaveTimer = setTimeout( function() {
408         self.autoSave();
409     }, 300000 );
410 };
411
412 Editor.prototype.refreshPanels = function(goodPanel) {
413     var self = this;
414     var panels = $('#' + self.rootDiv.attr('id') +' > *.panel-wrap', self.rootDiv.parent());
415
416     panels.each(function() {
417         var panel = $(this).data('ctrl');
418         $.log('Refreshing: ', this, panel);
419         if ( panel.changed() )
420             panel.unmarkChanged();
421         else
422             panel.refresh();
423     });
424 };              
425
426
427 Editor.prototype.sendPullRequest = function () {
428     if( $('.panel-wrap.changed').length != 0)        
429         alert("There are unsaved changes - can't make a pull request.");
430
431     this.showPopup('not-implemented');
432 /*
433         $.ajax({
434                 url: '/pull-request',
435                 dataType: 'json',
436                 success: function(data, textStatus) {
437             $.log('data: ' + data);
438                 },
439                 error: function(rq, tstat, err) {
440                         $.log('commit error', rq, tstat, err);
441                 },
442                 type: 'POST',
443                 data: {}
444         }); */
445 }
446
447 Editor.prototype.showPopup = function(name, text, timeout)
448 {
449     timeout = timeout || 4000;
450     var self = this;
451     self.popupQueue.push( [name, text, timeout] )
452
453     if( self.popupQueue.length > 1) 
454         return;
455
456     var box = $('#message-box > #' + name);
457     $('*.data', box).html(text);
458     box.fadeIn();
459  
460     if(timeout > 0)
461         setTimeout( $.fbind(self, self.advancePopupQueue), timeout);
462 };
463
464 Editor.prototype.advancePopupQueue = function() {
465     var self = this;
466     var elem = this.popupQueue.shift();
467     if(elem) {
468         var box = $('#message-box > #' + elem[0]);
469
470         box.fadeOut(200, function()
471         {
472             $('*.data', box).html();
473
474             if( self.popupQueue.length > 0) {
475                 var ibox = $('#message-box > #' + self.popupQueue[0][0]);
476                 $('*.data', ibox).html(self.popupQueue[0][1]);
477                 ibox.fadeIn();
478                 if(self.popupQueue[0][2] > 0)
479                     setTimeout( $.fbind(self, self.advancePopupQueue), self.popupQueue[0][2]);
480             }
481         });
482     }
483 };
484
485
486 Editor.prototype.registerScriptlet = function(scriptlet_id, scriptlet_func)
487 {
488     // I briefly assume, that it's verified not to break the world on SS
489     if (!this[scriptlet_id])
490         this[scriptlet_id] = scriptlet_func;
491 }
492
493 Editor.prototype.callScriptlet = function(scriptlet_id, panel, params) {
494     var func = this[scriptlet_id]
495     if(!func)
496         throw 'No scriptlet named "' + scriptlet_id + '" found.';
497
498     return func(this, panel, params);
499 }
500   
501 $(function() {
502     $.fbind = function (self, func) {
503         return function() { 
504             return func.apply(self, arguments);
505         };
506     };
507     
508     editor = new Editor();
509
510     // do the layout
511     editor.loadConfig();
512     editor.setupUI();
513 });