Some modernizations.
[redakcja.git] / src / redakcja / static / js / wiki / base.js
1 (function($)
2 {
3     var noop = function() { };
4
5     $.wiki = {
6         perspectives: {},
7         cls: {},
8         state: {
9             "version": 1,
10             "perspectives": {
11                 "ScanGalleryPerspective": {
12                     "show": true,
13                     "page": undefined
14                 },
15                 "CodeMirrorPerspective": {}
16                 /*
17                   "VisualPerspective": {},
18                   "HistoryPerspective": {},
19                   "SummaryPerspective": {}
20                 */
21             }
22         }
23     };
24
25     $.wiki.loadConfig = function() {
26         if(!window.localStorage)
27             return;
28
29         try {
30             var value = window.localStorage.getItem(CurrentDocument.id) || "{}";
31             var config = JSON.parse(value);
32
33             if (config.version == $.wiki.state.version) {
34                 $.wiki.state.perspectives = $.extend($.wiki.state.perspectives, config.perspectives);
35             }
36         } catch(e) {
37             console.log("Failed to load config, using default.");
38         }
39
40         console.log("Loaded:", $.wiki.state, $.wiki.state.version);
41     };
42
43     $(window).bind('unload', function() {
44         if(window.localStorage)
45             window.localStorage.setItem(CurrentDocument.id, JSON.stringify($.wiki.state));
46     })
47
48
49     $.wiki.activePerspective = function() {
50         return this.perspectives[$("#tabs li a.active").parent().attr('id')];
51     };
52
53     $.wiki.exitContext = function() {
54         var ap = this.activePerspective();
55         if(ap) ap.onExit();
56         return ap;
57     };
58
59     $.wiki.enterContext = function(ap) {
60         if(ap) ap.onEnter();
61     };
62
63     $.wiki.isDirty = function() {
64         var ap = this.activePerspective();
65         return (!!CurrentDocument && CurrentDocument.has_local_changes) || ap.dirty();
66     };
67
68     $.wiki.newTab = function(doc, title, klass) {
69         var base_id = 'id' + Math.floor(Math.random()* 5000000000);
70         var id = (''+klass)+'_' + base_id;
71         var $tab = $('<li class="nav-item" id="'+id+'" data-ui-related="'+base_id+'" data-ui-jsclass="'+klass+'" ><a href="#" class="nav-link">'
72                      + title + ' <span class="badge badge-danger tabclose">x</span></a></li>');
73         var $view = $('<div class="editor '+klass+'" id="'+base_id+'"> </div>');
74
75         this.perspectives[id] = new $.wiki[klass]({
76             doc: doc,
77             id: id,
78             base_id: base_id,
79         });
80
81         $('#tabs').append($tab);
82         $view.hide().appendTo('#editor');
83         return {
84             tab: $tab[0],
85             view: $view[0],
86         };
87     };
88
89     $.wiki.initTab = function(options) {
90         var klass = $(options.tab).attr('data-ui-jsclass');
91
92         return new $.wiki[klass]({
93             doc: options.doc,
94             id: $(options.tab).attr('id'),
95             callback: function() {
96                 $.wiki.perspectives[this.perspective_id] = this;
97                 if(options.callback)
98                     options.callback.call(this);
99             }
100         });
101     };
102
103     $.wiki.perspectiveForTab = function(tab) { // element or id
104         return this.perspectives[ $(tab).attr('id')];
105     }
106
107     $.wiki.exitTab = function(tab){
108         var self = this;
109         var $tab = $(tab);
110         if (!('.active', $tab)) return;
111         $('.active', $tab).removeClass('active');
112         self.perspectives[$tab.attr('id')].onExit();
113         $('#' + $tab.attr('data-ui-related')).hide();
114     }
115
116     $.wiki.switchToTab = function(tab){
117         var self = this;
118         var $tab = $(tab);
119
120         if($tab.length != 1)
121             $tab = $(DEFAULT_PERSPECTIVE);
122
123         var $old_a = $tab.closest('.tabs').find('.active');
124
125         $old_a.each(function(){
126             var tab = $(this).parent()
127             $(this).removeClass('active');
128             self.perspectives[tab.attr('id')].onExit();
129             $('#' + tab.attr('data-ui-related')).hide();
130         });
131
132         /* show new */
133         $('a', tab).addClass('active');
134         $('#' + $tab.attr('data-ui-related')).show();
135
136         console.log($tab);
137         console.log($.wiki.perspectives);
138
139         $.wiki.perspectives[$tab.attr('id')].onEnter();
140     };
141
142     /*
143      * Basic perspective.
144      */
145     $.wiki.Perspective = class Perspective {
146         constructor(options) {
147             if(!options) return;
148
149             this.doc = options.doc;
150             if (options.id) {
151                 this.perspective_id = options.id;
152             }
153             else {
154                 this.perspective_id = '';
155             }
156
157             if(options.callback)
158                 options.callback.call(this);
159         };
160
161         config() {
162             return $.wiki.state.perspectives[this.perspective_id];
163         }
164
165         toString() {
166             return this.perspective_id;
167         }
168
169         dirty() {
170             return true;
171         }
172
173         onEnter() {
174             // called when perspective in initialized
175             if (!this.noupdate_hash_onenter) {
176                 document.location.hash = '#' + this.perspective_id;
177             }
178         }
179
180         onExit () {
181             // called when user switches to another perspective
182             if (!this.noupdate_hash_onenter) {
183                 document.location.hash = '';
184             }
185         }
186
187         destroy() {
188             // pass
189         }
190
191         freezeState() {
192             // free UI state (don't store data here)
193         }
194
195         unfreezeState(frozenState) {
196                 // restore UI state
197         }
198     }
199
200     /*
201      * Stub rendering (used in generating history)
202      */
203     $.wiki.renderStub = function(params)
204     {
205         params = $.extend({ 'filters': {} }, params);
206         var $elem = params.stub.clone();
207         $elem.removeClass('row-stub');
208         params.container.append($elem);
209
210         $('*[data-stub-value]', $elem).each(function() {
211             var $this = $(this);
212             var field = $this.attr('data-stub-value');
213
214             var value = params.data[field];
215
216             if(params.filters[field])
217                 value = params.filters[field](value);
218
219             if(value === null || value === undefined) return;
220
221             if(!$this.attr('data-stub-target')) {
222                 $this.text(value);
223             }
224             else {
225                 $this.attr($this.attr('data-stub-target'), value);
226                 $this.removeAttr('data-stub-target');
227                 $this.removeAttr('data-stub-value');
228             }
229         });
230
231         $elem.show();
232         return $elem;
233     };
234
235     /*
236      * Dialogs
237      */
238     class GenericDialog {
239         constructor(element) {
240             if(!element) return;
241
242             var self = this;
243
244             self.$elem = $(element);
245
246             if(!self.$elem.attr('data-ui-initialized')) {
247                 console.log("Initializing dialog", this);
248                 self.initialize();
249                 self.$elem.attr('data-ui-initialized', true);
250             }
251
252             self.show();
253         }
254
255         /*
256          * Steps to follow when the dialog in first loaded on page.
257          */
258         initialize(){
259             var self = this;
260
261             /* bind buttons */
262             $('button[data-ui-action]', self.$elem).click(function(event) {
263                 event.preventDefault();
264
265                 var action = $(this).attr('data-ui-action');
266                 console.log("Button pressed, action: ", action);
267
268                 try {
269                     self[action + "Action"].call(self);
270                 } catch(e) {
271                     console.log("Action failed:", e);
272                     // always hide on cancel
273                     if(action == 'cancel')
274                         self.hide();
275                 }
276             });
277         }
278
279         /*
280          * Prepare dialog for user. Clear any unnessary data.
281          */
282         show() {
283             $.blockUI({
284                 message: this.$elem,
285                 css: {
286                     'top': '25%',
287                     'left': '25%',
288                     'width': '50%',
289                     'max-height': '75%',
290                     'overflow-y': 'scroll'
291                 }
292             });
293         }
294
295         hide() {
296             $.unblockUI();
297         }
298
299         cancelAction() {
300             this.hide();
301         }
302
303         doneAction() {
304             this.hide();
305         }
306
307         clearForm() {
308             $("*[data-ui-error-for]", this.$elem).text('');
309         }
310
311         reportErrors(errors) {
312             var global = $("*[data-ui-error-for='__all__']", this.$elem);
313             var unassigned = [];
314
315             $("*[data-ui-error-for]", this.$elem).text('');
316             for (var field_name in errors)
317             {
318                 var span = $("*[data-ui-error-for='"+field_name+"']", this.$elem);
319
320                 if(!span.length) {
321                     unassigned.push(field_name);
322                     continue;
323                 }
324
325                 span.text(errors[field_name].join(' '));
326             }
327
328             if(unassigned.length > 0)
329                 global.text( global.text() + 'W formularzu wystąpiły błędy');
330         }
331     }
332
333     $.wiki.cls.GenericDialog = GenericDialog;
334
335     $.wiki.showDialog = function(selector, options) {
336         var elem = $(selector);
337
338         if(elem.length != 1) {
339             console.log("Failed to show dialog:", selector, elem);
340             return false;
341         }
342
343         try {
344             var klass = elem.attr('data-ui-jsclass');
345             return new $.wiki.cls[klass](elem, options);
346         } catch(e) {
347             console.log("Failed to show dialog", selector, klass, e);
348             return false;
349         }
350     };
351
352     window.addEventListener("message", (event) => {
353         event.source.close()
354
355         $.ajax("/editor/editor-user-area/", {
356             success: function(d) {
357                 $("#user-area")[0].innerHTML = d;
358             }
359         });
360     }, false);
361
362     $("#login").click(function (e) {
363         e.preventDefault();
364         let h = 600;
365         let w = 500;
366         let x = window.screenX + (window.innerWidth - w) / 2;
367         let y = window.screenY + (window.innerHeight - h) / 2;
368         window.open(
369             "/accounts/login/?next=/editor/back",
370             "login-window",
371             "width=" + w + " height=" + h + " top=" + y + " left=" + x
372         );
373     });
374
375 })(jQuery);