Merge branch 'master' of stigma.nowoczesnapolska.org.pl:platforma
[redakcja.git] / platforma / static / js / models.js
1 /*globals Editor fileId SplitView PanelContainerView EditorView FlashView messageCenter*/
2 Editor.Model = Editor.Object.extend({
3     synced: false,
4     data: null
5 });
6
7 Editor.ToolbarButtonsModel = Editor.Model.extend({
8     className: 'Editor.ToolbarButtonsModel',
9     buttons: {},
10   
11     init: function() {
12         this._super();
13     },
14   
15     load: function() {
16         if (!this.get('buttons').length) {
17             $.ajax({
18                 url: documentInfo.toolbarURL,
19                 dataType: 'json',
20                 success: this.loadSucceeded.bind(this)
21             });
22         }
23     },
24   
25     loadSucceeded: function(data)
26     {
27         // do some escaping
28         $.each(data, function() {
29             $.each(this.buttons, function() {
30                 //do some lame escapes
31                 this.tooltip = this.tooltip.replace(/"/g, """);
32             });
33         });
34         this.set('buttons', data);
35     }
36 });
37
38
39 // Stany modelu:
40 //
41 //                  -> error -> loading
42 //                 /
43 // empty -> loading -> synced -> unsynced -> loading
44 //                           \
45 //                            -> dirty -> updating -> updated -> synced
46 //
47 Editor.XMLModel = Editor.Model.extend({
48     _className: 'Editor.XMLModel',
49     serverURL: null,
50     data: '',
51     state: 'empty',
52   
53     init: function(document, serverURL) {
54         this._super();
55         this.set('state', 'empty');
56         this.set('revision', document.get('revision'));
57         this.document = document;
58         this.serverURL = serverURL;
59         this.toolbarButtonsModel = new Editor.ToolbarButtonsModel();
60         this.addObserver(this, 'data', this.dataChanged.bind(this));
61     },
62   
63     load: function(force) {
64         if (force || this.get('state') == 'empty') {
65             this.set('state', 'loading');
66             messageCenter.addMessage('info', 'xmlload', 'Wczytuję XML...');
67             $.ajax({
68                 url: this.serverURL,
69                 dataType: 'text',
70                 data: {
71                     revision: this.get('revision'),
72                     user: this.document.get('user')
73                     },
74                 success: this.loadingSucceeded.bind(this),
75                 error: this.loadingFailed.bind(this)
76             });
77             return true;
78         }
79         return false;
80     },
81   
82     loadingSucceeded: function(data) {
83         if (this.get('state') != 'loading') {
84             alert('erroneous state:', this.get('state'));
85         }
86         this.set('data', data);
87         this.set('state', 'synced');
88         messageCenter.addMessage('success', 'xmlload', 'Wczytałem XML :-)');
89     },
90   
91     loadingFailed: function() {
92         if (this.get('state') != 'loading') {
93             alert('erroneous state:', this.get('state'));
94         }
95         var message = parseXHRError(response);
96         
97         this.set('error', '<h2>Błąd przy ładowaniu XML</h2><p>'+message+'</p>');
98         this.set('state', 'error');
99         messageCenter.addMessage('error', 'xmlload', 'Nie udało mi się wczytać XML. Spróbuj ponownie :-(');
100     },
101   
102     save: function(message) {
103         if (this.get('state') == 'dirty') {
104             this.set('state', 'updating');
105             messageCenter.addMessage('info', 'xmlsave', 'Zapisuję XML...');
106       
107             var payload = {
108                 contents: this.get('data'),
109                 revision: this.get('revision'),
110                 user: this.document.get('user')
111             };
112             if (message) {
113                 payload.message = message;
114             }
115       
116             $.ajax({
117                 url: this.serverURL,
118                 type: 'post',
119                 dataType: 'json',
120                 data: payload,
121                 success: this.saveSucceeded.bind(this),
122                 error: this.saveFailed.bind(this)
123             });
124             return true;
125         }
126         return false;
127     },
128   
129     saveSucceeded: function(data) {
130         if (this.get('state') != 'updating') {
131             alert('erroneous state:', this.get('state'));
132         }
133         this.set('revision', data.revision);
134         this.set('state', 'updated');
135         messageCenter.addMessage('success', 'xmlsave', 'Zapisałem XML :-)');
136     },
137   
138     saveFailed: function() {
139         if (this.get('state') != 'updating') {
140             alert('erroneous state:', this.get('state'));
141         }
142         messageCenter.addMessage('error', 'xmlsave', 'Nie udało mi się zapisać XML. Spróbuj ponownie :-(');
143         this.set('state', 'dirty');
144     },
145   
146     // For debbuging
147     set: function(property, value) {
148         if (property == 'state') {
149             console.log(this.description(), ':', property, '=', value);
150         }
151         return this._super(property, value);
152     },
153   
154     dataChanged: function(property, value) {
155         if (this.get('state') == 'synced') {
156             this.set('state', 'dirty');
157         }
158     },
159   
160     dispose: function() {
161         this.removeObserver(this);
162         this._super();
163     }
164 });
165
166
167 Editor.HTMLModel = Editor.Model.extend({
168     _className: 'Editor.HTMLModel',
169     dataURL: null,
170     htmlURL: null,
171     renderURL: null,
172     displaData: '',
173     xmlParts: {},
174     state: 'empty',
175   
176     init: function(document, dataURL, htmlURL) {
177         this._super();
178         this.set('state', 'empty');
179         this.set('revision', document.get('revision'));        
180         
181         this.document = document;
182         this.htmlURL = htmlURL;
183         this.dataURL = dataURL;
184         this.renderURL = documentInfo.renderURL;
185         this.xmlParts = {};
186     },
187   
188     load: function(force) {
189         if (force || this.get('state') == 'empty') {
190             this.set('state', 'loading');
191
192             // load the transformed data
193             // messageCenter.addMessage('info', 'Wczytuję HTML...');
194
195             $.ajax({
196                 url: this.htmlURL,
197                 dataType: 'text',
198                 data: {
199                     revision: this.get('revision'),
200                     user: this.document.get('user')
201                     },
202                 success: this.loadingSucceeded.bind(this),
203                 error: this.loadingFailed.bind(this)
204             });
205         }
206     },    
207   
208     loadingSucceeded: function(data) {
209         if (this.get('state') != 'loading') {
210             alert('erroneous state:', this.get('state'));
211         }
212         this.set('data', data);
213         this.set('state', 'synced');
214     },
215   
216     loadingFailed: function(response) {
217         if (this.get('state') != 'loading') {
218             alert('erroneous state:', this.get('state'));
219         }
220         
221         var message = parseXHRError(response);
222         
223         this.set('error', '<p>Nie udało się wczytać widoku HTML: </p>' + message);
224         this.set('state', 'error');        
225     },
226
227     getXMLPart: function(elem, callback)
228     {
229         var path = elem.attr('wl2o:path');
230         if(!this.xmlParts[path])
231             this.loadXMLPart(elem, callback);
232         else
233             callback(path, this.xmlParts[path]);
234     },
235
236     loadXMLPart: function(elem, callback)
237     {
238         var path = elem.attr('wl2o:path');
239         var self = this;
240
241         $.ajax({
242             url: this.dataURL,
243             dataType: 'text',
244             data: {
245                 revision: this.get('revision'),
246                 user: this.document.get('user'),
247                 part: path
248             },
249             success: function(data) {
250                 self.xmlParts[path] = data;
251                 callback(path, data);
252             },
253             // TODO: error handling
254             error: function(data) {
255                 console.log('Failed to load fragment');
256                 callback(undefined, undefined);
257             }
258         });
259     },
260
261     putXMLPart: function(elem, data) {
262         var self = this;
263       
264         var path = elem.attr('wl2o:path');
265         this.xmlParts[path] = data;
266
267         this.set('state', 'unsynced');
268
269         /* re-render the changed fragment */
270         $.ajax({
271             url: this.renderURL,
272             type: "POST",
273             dataType: 'text; charset=utf-8',
274             data: {
275                 fragment: data,
276                 part: path
277             },
278             success: function(htmldata) {
279                 elem.replaceWith(htmldata);
280                 self.set('state', 'dirty');
281             }
282         });
283     },
284
285     save: function(message) {
286         if (this.get('state') == 'dirty') {
287             this.set('state', 'updating');
288
289             var payload = {
290                 chunks: $.toJSON(this.xmlParts),
291                 revision: this.get('revision'),
292                 user: this.document.get('user')
293             };
294
295             if (message) {
296                 payload.message = message;
297             }
298
299             console.log(payload)
300
301             $.ajax({
302                 url: this.dataURL,
303                 type: 'post',
304                 dataType: 'json',
305                 data: payload,
306                 success: this.saveSucceeded.bind(this),
307                 error: this.saveFailed.bind(this)
308             });
309             return true;
310         }
311         return false;
312       
313     },
314
315     saveSucceeded: function(data) {
316         if (this.get('state') != 'updating') {
317             alert('erroneous state:', this.get('state'));
318         }
319
320         // flush the cache
321         this.xmlParts = {};
322     
323         this.set('revision', data.revision);
324         this.set('state', 'updated');
325     },
326
327     saveFailed: function() {
328         if (this.get('state') != 'updating') {
329             alert('erroneous state:', this.get('state'));
330         }        
331         this.set('state', 'dirty');
332     },
333
334     // For debbuging
335     set: function(property, value) {
336         if (property == 'state') {
337             console.log(this.description(), ':', property, '=', value);
338         }
339         return this._super(property, value);
340     }
341 });
342
343
344 Editor.ImageGalleryModel = Editor.Model.extend({
345     _className: 'Editor.ImageGalleryModel',
346     serverURL: null,
347     data: [],
348     state: 'empty',
349
350     init: function(serverURL) {
351         this._super();
352         this.set('state', 'empty');
353         this.serverURL = serverURL;
354         // olewać data
355         this.pages = [];
356     },
357
358     load: function(force) {
359         if (force || this.get('state') == 'empty') {
360             this.set('state', 'loading');
361             $.ajax({
362                 url: this.serverURL,
363                 dataType: 'json',
364                 success: this.loadingSucceeded.bind(this)
365             });
366         }
367     },
368
369     loadingSucceeded: function(data) {
370         if (this.get('state') != 'loading') {
371             alert('erroneous state:', this.get('state'));
372         }
373
374         console.log('galleries:', data);
375
376         if (data.length === 0) {
377             this.set('data', []);
378         } else {            
379             this.set('data', data[0].pages);
380         }
381
382         this.set('state', 'synced');
383     },
384
385     set: function(property, value) {
386         if (property == 'state') {
387             console.log(this.description(), ':', property, '=', value);
388         }
389         return this._super(property, value);
390     }
391 });
392
393
394 Editor.DocumentModel = Editor.Model.extend({
395     _className: 'Editor.DocumentModel',
396     data: null, // name, text_url, user_revision, latest_shared_rev, parts_url, dc_url, size, merge_url
397     contentModels: {},
398     state: 'empty',
399     errors: '',
400     revision: '',
401     user: '',
402   
403     init: function() {
404         this._super();
405         this.set('state', 'empty');        
406     },
407   
408     load: function() {
409         if (this.get('state') == 'empty') {
410             this.set('state', 'loading');
411             messageCenter.addMessage('info', 'docload', 'Ładuję dane dokumentu...');
412             $.ajax({
413                 cache: false,
414                 url: documentInfo.docURL,
415                 dataType: 'json',
416                 success: this.successfulLoad.bind(this),
417                 error: this.failedLoad.bind(this)
418             });
419         }
420     },
421   
422     successfulLoad: function(data) {
423         this.set('data', data);
424         this.set('state', 'synced');
425
426         this.set('revision', data.user_revision);
427         this.set('user', data.user);
428
429         this.contentModels = {
430             'xml': new Editor.XMLModel(this, data.text_url),
431             'html': new Editor.HTMLModel(this, data.text_url, data.html_url),
432             'gallery': new Editor.ImageGalleryModel(this, data.gallery_url)
433         };        
434
435         for (var key in this.contentModels) {
436             this.contentModels[key].addObserver(this, 'state', this.contentModelStateChanged.bind(this));
437         }
438
439         this.error = '';
440
441         messageCenter.addMessage('success', 'docload', 'Dokument załadowany poprawnie :-)');
442     },
443
444     failedLoad: function(response) {
445         if (this.get('state') != 'loading') {
446             alert('erroneous state:', this.get('state'));
447         }
448         
449         var message = parseXHRError(response);        
450         this.set('error', '<h2>Nie udało się wczytać dokumentu</h2><p>'+message+"</p>");
451         this.set('state', 'error');
452     },
453   
454     contentModelStateChanged: function(property, value, contentModel) {
455         if (value == 'dirty') {
456             this.set('state', 'dirty');
457             for (var key in this.contentModels) {
458                 if (this.contentModels[key].guid() != contentModel.guid()) {
459                     this.contentModels[key].set('state', 'unsynced');
460                 }
461             }
462         } else if (value == 'updated') {
463             this.set('state', 'synced');
464             for (key in this.contentModels) {
465                 if (this.contentModels[key].guid() == contentModel.guid()) {
466                     this.contentModels[key].set('state', 'synced');
467                     this.data.user_revision = this.contentModels[key].get('revision');
468                 }
469             }
470             for (key in this.contentModels) {
471                 if (this.contentModels[key].guid() != contentModel.guid()) {
472                     this.contentModels[key].set('revision', this.data.user_revision);
473                     this.contentModels[key].set('state', 'empty');
474                 }
475             }
476         }
477     },
478   
479     saveDirtyContentModel: function(message) {
480         for (var key in this.contentModels) {
481             if (this.contentModels[key].get('state') == 'dirty') {
482                 this.contentModels[key].save(message);
483                 break;
484             }
485         }
486     },
487   
488     update: function() {
489         this.set('state', 'loading');
490         messageCenter.addMessage('info', 'Uaktualniam dokument...');
491         $.ajax({
492             url: this.data.merge_url,
493             dataType: 'json',
494             type: 'post',
495             data: {
496                 type: 'update',
497                 revision: this.revision,
498                 user: this.user
499             },
500             complete: this.updateCompleted.bind(this),
501             success: function(data) {
502                 this.set('updateData', data);
503             }.bind(this)
504         });
505     },
506   
507     updateCompleted: function(xhr, textStatus) {
508         console.log(xhr.status, textStatus);
509         if (xhr.status == 200) { // Sukces
510             this.data = this.get('updateData');
511             this.revision = this.data.user_revision;
512             this.user = this.data.user;
513             
514             messageCenter.addMessage('info', null, 'Uaktualnienie dokumentu do wersji ' + this.get('updateData').revision,
515                 'Uaktualnienie dokumentu do wersji ' + this.get('updateData').revision);
516             for (var key in this.contentModels) {
517                 this.contentModels[key].set('revision', this.data.user_revision);
518                 this.contentModels[key].set('state', 'empty');
519             }
520             messageCenter.addMessage('success', null, 'Uaktualniłem dokument do najnowszej wersji :-)');
521         } else if (xhr.status == 202) { // Wygenerowano PullRequest (tutaj?)
522         } else if (xhr.status == 204) { // Nic nie zmieniono
523             messageCenter.addMessage('info', null, 'Nic się nie zmieniło od ostatniej aktualizacji. Po co mam uaktualniać?');
524         } else if (xhr.status == 409) { // Konflikt podczas operacji
525             messageCenter.addMessage('error', null, 'Wystąpił konflikt podczas aktualizacji. Pędź po programistów! :-(');
526         } else if (xhr.status == 500) {
527             messageCenter.addMessage('critical', null, 'Błąd serwera. Pędź po programistów! :-(');
528         }
529         this.set('state', 'synced');
530         this.set('updateData', null);
531     },
532   
533     merge: function(message) {
534         this.set('state', 'loading');
535         messageCenter.addMessage('info', null, 'Scalam dokument z głównym repozytorium...');
536         $.ajax({
537             url: this.data.merge_url,
538             type: 'post',
539             dataType: 'json',
540             data: {
541                 type: 'share',
542                 revision: this.revision,
543                 user: this.user,
544                 message: message
545             },
546             complete: this.mergeCompleted.bind(this),
547             success: function(data) {
548                 this.set('mergeData', data);
549             }.bind(this)
550         });
551     },
552   
553     mergeCompleted: function(xhr, textStatus) {
554         console.log(xhr.status, textStatus);
555         if (xhr.status == 200) { // Sukces
556             this.data = this.get('updateData');
557             this.revision = this.data.user_revision;
558             this.user = this.data.user;
559             
560             for (var key in this.contentModels) {
561                 this.contentModels[key].set('revision', this.revision);
562                 this.contentModels[key].set('state', 'empty');
563             }
564
565             messageCenter.addMessage('success', null, 'Scaliłem dokument z głównym repozytorium :-)');
566         } else if (xhr.status == 202) { // Wygenerowano PullRequest
567             messageCenter.addMessage('success', null, 'Wysłałem prośbę o scalenie dokumentu z głównym repozytorium.');
568         } else if (xhr.status == 204) { // Nic nie zmieniono
569             messageCenter.addMessage('info', null, 'Nic się nie zmieniło od ostatniego scalenia. Po co mam scalać?');
570         } else if (xhr.status == 409) { // Konflikt podczas operacji
571             messageCenter.addMessage('error', null, 'Wystąpił konflikt podczas scalania. Pędź po programistów! :-(');
572         } else if (xhr.status == 500) {
573             messageCenter.addMessage('critical', null, 'Błąd serwera. Pędź po programistów! :-(');
574         }
575         this.set('state', 'synced');
576         this.set('mergeData', null);
577     },
578   
579     // For debbuging
580     set: function(property, value) {
581         if (property == 'state') {
582             console.log(this.description(), ':', property, '=', value);
583         }
584         return this._super(property, value);
585     }
586 });
587
588
589 var leftPanelView, rightPanelContainer, doc;
590
591 $(function()
592 {
593     var flashView = new FlashView('#flashview', messageCenter);
594     
595     doc = new Editor.DocumentModel();
596
597     EditorView = new EditorView('#body-wrap', doc);
598     EditorView.freeze("<h1>Wczytuję dokument...</h1>");
599
600     leftPanelView = new PanelContainerView('#left-panel-container', doc);
601     rightPanelContainer = new PanelContainerView('#right-panel-container', doc);
602
603     
604 });