Edit gallery easily from editor.
[redakcja.git] / src / redakcja / static / js / wiki / wikiapi.js
1 (function($) {
2     $.wikiapi = {};
3     var noop = function() {
4     };
5     var noops = {
6         success: noop,
7         failure: noop
8     };
9     /*
10      * Return absolute reverse path of given named view. (at least he have it
11      * hard-coded in one place)
12      *
13      * TODO: think of a way, not to hard-code it here ;)
14      *
15      */
16     function reverse() {
17         var vname = arguments[0];
18         var base_path = "/editor";
19
20         if (vname == "ajax_document_text") {
21             var path = "/text/" + arguments[1] + '/';
22
23             if (arguments[2] !== undefined)
24                 path += arguments[2] + '/';
25
26             return base_path + path;
27         }
28
29         if (vname == "ajax_document_revert") {
30             return base_path + "/revert/" + arguments[1] + '/';
31         }
32
33
34         if (vname == "ajax_document_history") {
35             return base_path + "/history/" + arguments[1] + '/';
36         }
37
38         if (vname == "ajax_document_gallery") {
39             return base_path + "/gallery/" + arguments[1] + '/';
40         }
41
42         if (vname == "ajax_document_diff")
43             return base_path + "/diff/" + arguments[1] + '/';
44
45         if (vname == "ajax_document_rev")
46             return base_path + "/rev/" + arguments[1] + '/';
47
48         if (vname == "ajax_document_pubmark")
49             return base_path + "/pubmark/" + arguments[1] + '/';
50
51         if (vname == "ajax_cover_preview")
52             return "/cover/preview/";
53
54         console.log("Couldn't reverse match:", vname);
55         return "/404.html";
56     };
57     class Api {
58         static csrf = $('input[name="csrfmiddlewaretoken"]').val();
59
60         // TODO: Add waiting marker, error reporting.
61         static post(url, data) {
62             data['csrfmiddlewaretoken'] = this.csrf;
63             $.ajax({
64                 url: url,
65                 type: "POST",
66                 data: data
67             });
68         }
69
70         static get(url, callback) {
71             $.ajax({
72                 url: url,
73                 type: "GET",
74                 success: function(data) {
75                     callback(data);
76                 },
77             });
78         }
79
80         static setGallery(id, gallery) {
81             this.post(
82                 '/editor/set-gallery/' + id + '/',
83                 {gallery: gallery}
84             )
85         }
86         static setGalleryStart(id, start) {
87             this.post(
88                 '/editor/set-gallery-start/' + id + '/',
89                 {start: start}
90             )
91         }
92
93         static openGalleryEdit(bookSlug) {
94             window.open(
95                 '/documents/book/' + bookSlug + '/gallery/',
96                 '_blank'
97             ).focus();
98         }
99
100         static withGalleryList(callback) {
101             this.get(
102                 '/editor/galleries/',
103                 callback
104             );
105         }
106     }
107
108     /*
109      * Document Abstraction
110      */
111     class WikiDocument {
112         constructor(element_id) {
113             var meta = $('#' + element_id);
114             this.id = meta.attr('data-chunk-id');
115
116             this.revision = $("*[data-key='revision']", meta).text();
117             this.readonly = !!$("*[data-key='readonly']", meta).text();
118
119             this.bookSlug = $("*[data-key='book-slug']", meta).text();
120             this.galleryLink = $("*[data-key='gallery']", meta).text();
121             this.galleryStart = parseInt($("*[data-key='gallery-start']", meta).text());
122             this.fullUri = $("*[data-key='full-uri']", meta).text();
123
124             var diff = $("*[data-key='diff']", meta).text();
125             if (diff) {
126                 diff = diff.split(',');
127                 if (diff.length == 2 && diff[0] < diff[1])
128                     this.diff = diff;
129                 else if (diff.length == 1) {
130                     diff = parseInt(diff);
131                     if (diff != NaN)
132                         this.diff = [diff - 1, diff];
133                 }
134             }
135
136             this.galleryImages = [];
137             this.text = null;
138             this.has_local_changes = false;
139             this.active = true;
140             this._lock = -1;
141             this._context_lock = -1;
142             this._lock_count = 0;
143         };
144
145         triggerDocumentChanged() {
146             $(document).trigger('wlapi_document_changed', this);
147         }
148
149         /*
150          * Fetch text of this document.
151          */
152         fetch(params) {
153             params = $.extend({}, noops, params);
154             var self = this;
155             $.ajax({
156                 method: "GET",
157                 url: reverse("ajax_document_text", self.id),
158                 data: {"revision": self.revision},
159                 dataType: 'json',
160                 success: function(data) {
161                     var changed = false;
162
163                     if (self.text === null || self.revision !== data.revision) {
164                         self.text = data.text;
165                         self.revision = data.revision;
166                         self.gallery = data.gallery;
167                         changed = true;
168                         self.triggerDocumentChanged();
169                     };
170
171                     self.has_local_changes = false;
172                     params['success'](self, changed);
173                 },
174                 error: function() {
175                     params['failure'](self, "Nie udało się wczytać treści dokumentu.");
176                 }
177             });
178         }
179
180         /*
181          * Fetch history of this document.
182          */
183         fetchHistory(params) {
184             /* this doesn't modify anything, so no locks */
185             params = $.extend({}, noops, params);
186             var self = this;
187             $.ajax({
188                 method: "GET",
189                 url: reverse("ajax_document_history", self.id) + "?before=" + params.before,
190                 dataType: 'json',
191                 success: function(data) {
192                     params['success'](self, data);
193                 },
194                 error: function() {
195                     params['failure'](self, "Nie udało się wczytać historii dokumentu.");
196                 }
197             });
198         }
199
200         fetchDiff(params) {
201             /* this doesn't modify anything, so no locks */
202             var self = this;
203             params = $.extend({
204                 'from': self.revision,
205                 'to': self.revision
206             }, noops, params);
207             $.ajax({
208                 method: "GET",
209                 url: reverse("ajax_document_diff", self.id),
210                 dataType: 'html',
211                 data: {
212                     "from": params['from'],
213                     "to": params['to']
214                 },
215                 success: function(data) {
216                     params['success'](self, data);
217                 },
218                 error: function() {
219                     params['failure'](self, "Nie udało się wczytać porównania wersji.");
220                 }
221             });
222         }
223
224         checkRevision(params) {
225             /* this doesn't modify anything, so no locks */
226             var self = this;
227             let active = self.active;
228             self.active = false;
229             $.ajax({
230                 method: "GET",
231                 url: reverse("ajax_document_rev", self.id),
232                 data: {
233                     'a': active,
234                 },
235                 dataType: 'text',
236                 success: function(data) {
237                     if (data == '') {
238                         if (params.error)
239                             params.error();
240                     }
241                     else if (data != self.revision)
242                         params.outdated();
243                 }
244             });
245         }
246
247         /*
248          * Fetch gallery
249          */
250         refreshGallery(params) {
251             params = $.extend({}, noops, params);
252             var self = this;
253             if (!self.galleryLink) {
254                 params['failure'](self, 'Brak galerii.');
255                 return;
256             }
257             $.ajax({
258                 method: "GET",
259                 url: reverse("ajax_document_gallery", self.galleryLink),
260                 dataType: 'json',
261                 // data: {},
262                 success: function(data) {
263                     self.galleryImages = data;
264                     params['success'](self, data);
265                 },
266                 error: function(xhr) {
267                     switch (xhr.status) {
268                     case 403:
269                         var msg = 'Galerie dostępne tylko dla zalogowanych użytkowników.';
270                         break;
271                     case 404:
272                         var msg = "Nie znaleziono galerii o nazwie: '" + self.galleryLink + "'.";
273                     default:
274                         var msg = "Nie udało się wczytać galerii o nazwie: '" + self.galleryLink + "'.";
275                     }
276                     self.galleryImages = [];
277                     params['failure'](self, msg);
278                 }
279             });
280         }
281
282         setGallery(gallery) {
283             this.galleryLink = gallery;
284             Api.setGallery(this.id, gallery);
285         }
286
287         setGalleryStart(start) {
288             this.galleryStart = start;
289             Api.setGalleryStart(this.id, start);
290         }
291
292         openGalleryEdit(start) {
293             Api.openGalleryEdit(this.bookSlug);
294         }
295
296         withGalleryList(callback) {
297             Api.withGalleryList(callback);
298         }
299         
300         /*
301          * Set document's text
302          */
303         setText(text) {
304             if (text == this.text) return;
305             this.text = text;
306             this.has_local_changes = true;
307         }
308
309         /*
310          * Save text back to the server
311          */
312         save(params) {
313             params = $.extend({}, noops, params);
314             var self = this;
315
316             if (!self.has_local_changes) {
317                 console.log("Abort: no changes.");
318                 return params['success'](self, false, "Nie ma zmian do zapisania.");
319             };
320
321             // Serialize form to dictionary
322             var data = {};
323             $.each(params['form'].serializeArray(), function() {
324                 data[this.name] = this.value;
325             });
326
327             data['textsave-text'] = self.text;
328
329             $.ajax({
330                 url: reverse("ajax_document_text", self.id),
331                 type: "POST",
332                 dataType: "json",
333                 data: data,
334                 success: function(data) {
335                     var changed = false;
336
337                     $('#header').removeClass('saving');
338
339                     if (data.text) {
340                         self.text = data.text;
341                         self.revision = data.revision;
342                         self.gallery = data.gallery;
343                         changed = true;
344                         self.triggerDocumentChanged();
345                     };
346
347                     params['success'](self, changed, ((changed && "Udało się zapisać :)") || "Twoja wersja i serwera jest identyczna"));
348                 },
349                 error: function(xhr) {
350                     if ($('#header').hasClass('saving')) {
351                         $('#header').removeClass('saving');
352                         $.blockUI({
353                             message: "<p>Nie udało się zapisać zmian. <br/><button onclick='$.unblockUI()'>OK</button></p>"
354                         })
355                     }
356                     else {
357                         try {
358                             params['failure'](self, $.parseJSON(xhr.responseText));
359                         }
360                         catch (e) {
361                             params['failure'](self, {
362                                 "__message": "<p>Nie udało się zapisać - błąd serwera.</p>"
363                             });
364                         };
365                     }
366                 }
367             });
368
369             $('#save-hide').click(function(){
370                 $('#header').addClass('saving');
371                 $.unblockUI();
372                 $.wiki.blocking.unblock();
373             });
374         } /* end of save() */
375
376         revertToVersion(params) {
377             var self = this;
378             params = $.extend({}, noops, params);
379
380             if (params.revision >= this.revision) {
381                 params.failure(self, 'Proszę wybrać rewizję starszą niż aktualna.');
382                 return;
383             }
384
385             // Serialize form to dictionary
386             var data = {};
387             $.each(params['form'].serializeArray(), function() {
388                 data[this.name] = this.value;
389             });
390
391             $.ajax({
392                 url: reverse("ajax_document_revert", self.id),
393                 type: "POST",
394                 dataType: "json",
395                 data: data,
396                 success: function(data) {
397                     if (data.text) {
398                         self.text = data.text;
399                         self.revision = data.revision;
400                         self.gallery = data.gallery;
401                         self.triggerDocumentChanged();
402
403                         params.success(self, "Udało się przywrócić wersję :)");
404                     }
405                     else {
406                         params.failure(self, "Przywracana wersja identyczna z aktualną. Anulowano przywracanie.");
407                     }
408                 },
409                 error: function(xhr) {
410                     params.failure(self, "Nie udało się przywrócić wersji - błąd serwera.");
411                 }
412             });
413         }
414
415         pubmark(params) {
416             params = $.extend({}, noops, params);
417             var self = this;
418             var data = {
419                 "pubmark-id": self.id,
420             };
421
422             /* unpack form */
423             $.each(params.form.serializeArray(), function() {
424                 data[this.name] = this.value;
425             });
426
427             $.ajax({
428                 url: reverse("ajax_document_pubmark", self.id),
429                 type: "POST",
430                 dataType: "json",
431                 data: data,
432                 success: function(data) {
433                     params.success(self, data.message);
434                 },
435                 error: function(xhr) {
436                     if (xhr.status == 403 || xhr.status == 401) {
437                         params.failure(self, {
438                             "__all__": ["Nie masz uprawnień lub nie jesteś zalogowany."]
439                         });
440                     }
441                     else {
442                         try {
443                             params.failure(self, $.parseJSON(xhr.responseText));
444                         }
445                         catch (e) {
446                             params.failure(self, {
447                                 "__all__": ["Nie udało się - błąd serwera."]
448                             });
449                         };
450                     };
451                 }
452             });
453         }
454
455         refreshCover(params) {
456             var self = this;
457             var data = {
458                 xml: self.text // TODO: send just DC
459             };
460             $.ajax({
461                 url: reverse("ajax_cover_preview"),
462                 type: "POST",
463                 data: data,
464                 success: function(data) {
465                     params.success(data);
466                 },
467                 error: function(xhr) {
468                     // params.failure("Nie udało się odświeżyć okładki - błąd serwera.");
469                 }
470             });
471         }
472
473         getLength(params) {
474             params = $.extend({}, noops, params);
475             var xml = this.text.replace(/\/(\s+)/g, '<br />$1');
476             var parser = new DOMParser();
477             var doc = parser.parseFromString(xml, 'text/xml');
478             var error = $('parsererror', doc);
479
480             if (error.length > 0) {
481                 throw "Not an XML document.";
482             }
483             $.xmlns["rdf"] = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
484             $('rdf|RDF', doc).remove();
485             if (params.noFootnotes) {
486                 $('pa, pe, pr, pt', doc).remove();
487             }
488             if (params.noThemes) {
489                 $('motyw', doc).remove();
490             }
491             var text = $(doc).text();
492             text = $.trim(text.replace(/\s{2,}/g, ' '));
493             return text.length;
494         }
495
496         /* Temporary workaround for relative images. */
497         getBase() {
498             return '/media/dynamic/images/' + this.galleryLink + '/';
499         }
500     }
501
502     $.wikiapi.WikiDocument = WikiDocument;
503 })(jQuery);