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