Undo
[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             this.galleryImages = [];
125             this.text = null;
126             this.has_local_changes = false;
127             this.active = true;
128             this._lock = -1;
129             this._context_lock = -1;
130             this._lock_count = 0;
131         };
132
133         triggerDocumentChanged() {
134             $(document).trigger('wlapi_document_changed', this);
135         }
136
137         /*
138          * Fetch text of this document.
139          */
140         fetch(params) {
141             params = $.extend({}, noops, params);
142             var self = this;
143             $.ajax({
144                 method: "GET",
145                 url: reverse("ajax_document_text", self.id),
146                 data: {"revision": self.revision},
147                 dataType: 'json',
148                 success: function(data) {
149                     var changed = false;
150
151                     if (self.text === null || self.revision !== data.revision) {
152                         self.text = data.text;
153                         $.wiki.undo.push(data.text);
154                         self.revision = data.revision;
155                         self.gallery = data.gallery;
156                         changed = true;
157                         self.triggerDocumentChanged();
158                     };
159
160                     self.has_local_changes = false;
161                     params['success'](self, changed);
162                 },
163                 error: function() {
164                     params['failure'](self, "Nie udało się wczytać treści dokumentu.");
165                 }
166             });
167         }
168
169         /*
170          * Fetch history of this document.
171          */
172         fetchHistory(params) {
173             /* this doesn't modify anything, so no locks */
174             params = $.extend({}, noops, params);
175             var self = this;
176             $.ajax({
177                 method: "GET",
178                 url: reverse("ajax_document_history", self.id) + "?before=" + params.before,
179                 dataType: 'json',
180                 success: function(data) {
181                     params['success'](self, data);
182                 },
183                 error: function() {
184                     params['failure'](self, "Nie udało się wczytać historii dokumentu.");
185                 }
186             });
187         }
188
189         fetchDiff(params) {
190             /* this doesn't modify anything, so no locks */
191             var self = this;
192             params = $.extend({
193                 'from': self.revision,
194                 'to': self.revision
195             }, noops, params);
196             $.ajax({
197                 method: "GET",
198                 url: reverse("ajax_document_diff", self.id),
199                 dataType: 'html',
200                 data: {
201                     "from": params['from'],
202                     "to": params['to']
203                 },
204                 success: function(data) {
205                     params['success'](self, data);
206                 },
207                 error: function() {
208                     params['failure'](self, "Nie udało się wczytać porównania wersji.");
209                 }
210             });
211         }
212
213         checkRevision(params) {
214             /* this doesn't modify anything, so no locks */
215             var self = this;
216             let active = self.active;
217             self.active = false;
218             $.ajax({
219                 method: "GET",
220                 url: reverse("ajax_document_rev", self.id),
221                 data: {
222                     'a': active,
223                 },
224                 dataType: 'text',
225                 success: function(data) {
226                     if (data == '') {
227                         if (params.error)
228                             params.error();
229                     }
230                     else if (data != self.revision)
231                         params.outdated();
232                 }
233             });
234         }
235
236         /*
237          * Fetch gallery
238          */
239         refreshGallery(params) {
240             params = $.extend({}, noops, params);
241             var self = this;
242             if (!self.galleryLink) {
243                 params['failure'](self, 'Brak galerii.');
244                 return;
245             }
246             $.ajax({
247                 method: "GET",
248                 url: reverse("ajax_document_gallery", self.galleryLink),
249                 dataType: 'json',
250                 // data: {},
251                 success: function(data) {
252                     self.galleryImages = data;
253                     params['success'](self, data);
254                 },
255                 error: function(xhr) {
256                     switch (xhr.status) {
257                     case 403:
258                         var msg = 'Galerie dostępne tylko dla zalogowanych użytkowników.';
259                         break;
260                     case 404:
261                         var msg = "Nie znaleziono galerii o nazwie: '" + self.galleryLink + "'.";
262                     default:
263                         var msg = "Nie udało się wczytać galerii o nazwie: '" + self.galleryLink + "'.";
264                     }
265                     self.galleryImages = [];
266                     params['failure'](self, msg);
267                 }
268             });
269         }
270
271         setGallery(gallery) {
272             this.galleryLink = gallery;
273             Api.setGallery(this.id, gallery);
274         }
275
276         setGalleryStart(start) {
277             this.galleryStart = start;
278             Api.setGalleryStart(this.id, start);
279         }
280
281         openGalleryEdit(start) {
282             Api.openGalleryEdit(this.bookSlug);
283         }
284
285         withGalleryList(callback) {
286             Api.withGalleryList(callback);
287         }
288         
289         /*
290          * Set document's text
291          */
292         setText(text, silent=false) {
293             if (text == this.text) return;
294             if (!silent) {
295                 $.wiki.undo.push(text);
296             }
297             this.text = text;
298             this.has_local_changes = true;
299         }
300
301         undo() {
302             let ctx = $.wiki.exitContext();
303             this.setText(
304                 $.wiki.undo.undo(),
305                 true
306             );
307             $.wiki.enterContext(ctx);
308         }
309         redo() {
310             let ctx = $.wiki.exitContext();
311             this.setText(
312                 $.wiki.undo.redo(),
313                 true
314             );
315             $.wiki.enterContext(ctx);
316         }
317
318         /*
319          * Save text back to the server
320          */
321         save(params) {
322             params = $.extend({}, noops, params);
323             var self = this;
324
325             if (!self.has_local_changes) {
326                 console.log("Abort: no changes.");
327                 return params['success'](self, false, "Nie ma zmian do zapisania.");
328             };
329
330             // Serialize form to dictionary
331             var data = {};
332             $.each(params['form'].serializeArray(), function() {
333                 data[this.name] = this.value;
334             });
335
336             data['textsave-text'] = self.text;
337
338             $.ajax({
339                 url: reverse("ajax_document_text", self.id),
340                 type: "POST",
341                 dataType: "json",
342                 data: data,
343                 success: function(data) {
344                     var changed = false;
345
346                     $('#header').removeClass('saving');
347
348                     if (data.text) {
349                         self.text = data.text;
350                         self.revision = data.revision;
351                         self.gallery = data.gallery;
352                         changed = true;
353                         self.triggerDocumentChanged();
354                     };
355
356                     params['success'](self, changed, ((changed && "Udało się zapisać :)") || "Twoja wersja i serwera jest identyczna"));
357                 },
358                 error: function(xhr) {
359                     if ($('#header').hasClass('saving')) {
360                         $('#header').removeClass('saving');
361                         $.blockUI({
362                             message: "<p>Nie udało się zapisać zmian. <br/><button onclick='$.unblockUI()'>OK</button></p>"
363                         })
364                     }
365                     else {
366                         try {
367                             params['failure'](self, $.parseJSON(xhr.responseText));
368                         }
369                         catch (e) {
370                             params['failure'](self, {
371                                 "__message": "<p>Nie udało się zapisać - błąd serwera.</p>"
372                             });
373                         };
374                     }
375                 }
376             });
377
378             $('#save-hide').click(function(){
379                 $('#header').addClass('saving');
380                 $.unblockUI();
381                 $.wiki.blocking.unblock();
382             });
383         } /* end of save() */
384
385         revertToVersion(params) {
386             var self = this;
387             params = $.extend({}, noops, params);
388
389             if (params.revision >= this.revision) {
390                 params.failure(self, 'Proszę wybrać rewizję starszą niż aktualna.');
391                 return;
392             }
393
394             // Serialize form to dictionary
395             var data = {};
396             $.each(params['form'].serializeArray(), function() {
397                 data[this.name] = this.value;
398             });
399
400             $.ajax({
401                 url: reverse("ajax_document_revert", self.id),
402                 type: "POST",
403                 dataType: "json",
404                 data: data,
405                 success: function(data) {
406                     if (data.text) {
407                         self.text = data.text;
408                         self.revision = data.revision;
409                         self.gallery = data.gallery;
410                         self.triggerDocumentChanged();
411
412                         params.success(self, "Udało się przywrócić wersję :)");
413                     }
414                     else {
415                         params.failure(self, "Przywracana wersja identyczna z aktualną. Anulowano przywracanie.");
416                     }
417                 },
418                 error: function(xhr) {
419                     params.failure(self, "Nie udało się przywrócić wersji - błąd serwera.");
420                 }
421             });
422         }
423
424         pubmark(params) {
425             params = $.extend({}, noops, params);
426             var self = this;
427             var data = {
428                 "pubmark-id": self.id,
429             };
430
431             /* unpack form */
432             $.each(params.form.serializeArray(), function() {
433                 data[this.name] = this.value;
434             });
435
436             $.ajax({
437                 url: reverse("ajax_document_pubmark", self.id),
438                 type: "POST",
439                 dataType: "json",
440                 data: data,
441                 success: function(data) {
442                     params.success(self, data.message);
443                 },
444                 error: function(xhr) {
445                     if (xhr.status == 403 || xhr.status == 401) {
446                         params.failure(self, {
447                             "__all__": ["Nie masz uprawnień lub nie jesteś zalogowany."]
448                         });
449                     }
450                     else {
451                         try {
452                             params.failure(self, $.parseJSON(xhr.responseText));
453                         }
454                         catch (e) {
455                             params.failure(self, {
456                                 "__all__": ["Nie udało się - błąd serwera."]
457                             });
458                         };
459                     };
460                 }
461             });
462         }
463
464         refreshCover(params) {
465             var self = this;
466             var data = {
467                 xml: self.text // TODO: send just DC
468             };
469             $.ajax({
470                 url: reverse("ajax_cover_preview"),
471                 type: "POST",
472                 data: data,
473                 success: function(data) {
474                     params.success(data);
475                 },
476                 error: function(xhr) {
477                     // params.failure("Nie udało się odświeżyć okładki - błąd serwera.");
478                 }
479             });
480         }
481
482         getLength(params) {
483             params = $.extend({}, noops, params);
484             var xml = this.text.replace(/\/(\s+)/g, '<br />$1');
485             var parser = new DOMParser();
486             var doc = parser.parseFromString(xml, 'text/xml');
487             var error = $('parsererror', doc);
488
489             if (error.length > 0) {
490                 throw "Not an XML document.";
491             }
492             $.xmlns["rdf"] = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
493             $('rdf|RDF', doc).remove();
494             if (params.noFootnotes) {
495                 $('pa, pe, pr, pt', doc).remove();
496             }
497             if (params.noThemes) {
498                 $('motyw', doc).remove();
499             }
500             var text = $(doc).text();
501             text = $.trim(text.replace(/\s{2,}/g, ' '));
502             return text.length;
503         }
504
505         /* Temporary workaround for relative images. */
506         getBase() {
507             return '/media/dynamic/images/' + this.galleryLink + '/';
508         }
509     }
510
511     $.wikiapi.WikiDocument = WikiDocument;
512 })(jQuery);