Fix dialog actions on Enter
[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, base_id) {
69         var id = (''+klass)+'_' + base_id;
70         var $tab = $('<li class="nav-item" id="'+id+'" data-ui-related="'+base_id+'" data-ui-jsclass="'+klass+'" ><a href="#" class="nav-link">'
71                      + title + ' <span class="badge badge-danger tabclose">x</span></a></li>');
72         var $view = $('<div class="editor '+klass+'" id="'+base_id+'"> </div>');
73
74         this.perspectives[id] = new $.wiki[klass]({
75             doc: doc,
76             id: id,
77             base_id: base_id,
78         });
79
80         $('#tabs').append($tab);
81         $view.hide().appendTo('#editor');
82         return {
83             tab: $tab[0],
84             view: $view[0],
85         };
86     };
87
88     $.wiki.initTab = function(options) {
89         var klass = $(options.tab).attr('data-ui-jsclass');
90
91         let perspective = new $.wiki[klass]({
92             doc: options.doc,
93             id: $(options.tab).attr('id'),
94         });
95         $.wiki.perspectives[perspective.perspective_id] = perspective;
96         return perspective;
97     };
98
99     $.wiki.perspectiveForTab = function(tab) { // element or id
100         return this.perspectives[ $(tab).attr('id')];
101     }
102
103     $.wiki.exitTab = function(tab){
104         var self = this;
105         var $tab = $(tab);
106         if (!('.active', $tab).length) return;
107         $('.active', $tab).removeClass('active');
108         self.perspectives[$tab.attr('id')].onExit();
109         $('#' + $tab.attr('data-ui-related')).hide();
110     }
111
112     $.wiki.switchToTab = function(tab){
113         var self = this;
114         var $tab = $(tab);
115
116         // Create dynamic tabs (for diffs).
117         if ($tab.length != 1) {
118             let parts = tab.split('_');
119             if (parts.length > 1) {
120                 // TODO: register perspectives for it.
121                 if (parts[0] == '#DiffPerspective') {
122                     $tab = $($.wiki.DiffPerspective.openId(parts[1]));
123                 }
124             }
125         }
126
127         if($tab.length != 1)
128             $tab = $(DEFAULT_PERSPECTIVE);
129
130         var $old_a = $tab.closest('.tabs').find('.active');
131
132         $old_a.each(function(){
133             var tab = $(this).parent()
134             $(this).removeClass('active');
135             self.perspectives[tab.attr('id')].onExit();
136             $('#' + tab.attr('data-ui-related')).hide();
137         });
138
139         /* show new */
140         $('a', tab).addClass('active');
141         $('#' + $tab.attr('data-ui-related')).show();
142
143         console.log($tab);
144         console.log($.wiki.perspectives);
145
146         $.wiki.perspectives[$tab.attr('id')].onEnter();
147     };
148
149     /*
150      * Basic perspective.
151      */
152     $.wiki.Perspective = class Perspective {
153         constructor(options) {
154             this.doc = options.doc;
155             this.perspective_id = options.id || ''
156         };
157
158         config() {
159             return $.wiki.state.perspectives[this.perspective_id];
160         }
161
162         toString() {
163             return this.perspective_id;
164         }
165
166         dirty() {
167             return true;
168         }
169
170         onEnter() {
171             // called when perspective in initialized
172             if (!this.noupdate_hash_onenter) {
173                 document.location.hash = '#' + this.perspective_id;
174             }
175         }
176
177         onExit () {
178             // called when user switches to another perspective
179             if (!this.noupdate_hash_onenter) {
180                 document.location.hash = '';
181             }
182         }
183
184         destroy() {
185             // pass
186         }
187     }
188
189     /*
190      * Stub rendering (used in generating history)
191      */
192     $.wiki.renderStub = function(params)
193     {
194         params = $.extend({ 'filters': {} }, params);
195         var $elem = params.stub.clone();
196         $elem.removeClass('row-stub');
197         params.container.append($elem);
198
199         var populate = function($this) {
200             var field = $this.attr('data-stub-value');
201
202             var value = params.data[field];
203
204             if(params.filters[field])
205                 value = params.filters[field](value);
206
207             if(value === null || value === undefined) return;
208
209             if(!$this.attr('data-stub-target')) {
210                 $this.text(value);
211             }
212             else {
213                 $this.attr($this.attr('data-stub-target'), value);
214                 $this.removeAttr('data-stub-target');
215                 $this.removeAttr('data-stub-value');
216             }
217         }
218         if ($elem.attr('data-stub-value')) populate($elem);
219         $('*[data-stub-value]', $elem).each(function() {populate($(this))});
220
221         $elem.show();
222         return $elem;
223     };
224
225     /*
226      * Dialogs
227      */
228     class GenericDialog {
229         constructor(element) {
230             if(!element) return;
231
232             var self = this;
233
234             self.$elem = $(element);
235
236             if(!self.$elem.attr('data-ui-initialized')) {
237                 console.log("Initializing dialog", this);
238                 self.initialize();
239                 self.$elem.attr('data-ui-initialized', true);
240             }
241
242             self.show();
243         }
244
245         /*
246          * Steps to follow when the dialog in first loaded on page.
247          */
248         initialize(){
249             var self = this;
250
251             /* bind buttons */
252             function dataUiAction(elem) {
253                 var action = $(this).attr('data-ui-action');
254                 console.log("Button pressed, action: ", action);
255
256                 try {
257                     self[action + "Action"].call(self);
258                 } catch(e) {
259                     console.log("Action failed:", e);
260                     // always hide on cancel
261                     if(action == 'cancel')
262                         self.hide();
263                 }
264             }
265             $('button[data-ui-action]', self.$elem).click(function(event) {
266                 event.preventDefault();
267                 dataUiAction(this);
268             }).on('keydown'), function(event) {
269                 if (event.key == 'Enter') {
270                     event.preventDefault();
271                     dataUiAction(this);
272                 }
273             });
274         }
275
276         /*
277          * Prepare dialog for user. Clear any unnessary data.
278          */
279         show() {
280             $.blockUI({
281                 message: this.$elem,
282                 css: {
283                     'top': '25%',
284                     'left': '25%',
285                     'width': '50%',
286                     'max-height': '75%',
287                     'overflow-y': 'scroll'
288                 }
289             });
290         }
291
292         hide() {
293             $.unblockUI();
294         }
295
296         cancelAction() {
297             this.hide();
298         }
299
300         doneAction() {
301             this.hide();
302         }
303
304         clearForm() {
305             $("*[data-ui-error-for]", this.$elem).text('');
306         }
307
308         reportErrors(errors) {
309             var global = $("*[data-ui-error-for='__all__']", this.$elem);
310             var unassigned = [];
311
312             $("*[data-ui-error-for]", this.$elem).text('');
313             for (var field_name in errors)
314             {
315                 var span = $("*[data-ui-error-for='"+field_name+"']", this.$elem);
316
317                 if(!span.length) {
318                     unassigned.push(errors[field_name]);
319                     continue;
320                 }
321
322                 span.text(errors[field_name].join(' '));
323             }
324
325             if(unassigned.length > 0)
326                 global.text(
327                     global.text() + 'Wystąpił błąd: ' + unassigned.join(', '));
328         }
329     }
330
331     $.wiki.cls.GenericDialog = GenericDialog;
332
333     $.wiki.showDialog = function(selector, options) {
334         var elem = $(selector);
335
336         if(elem.length != 1) {
337             console.log("Failed to show dialog:", selector, elem);
338             return false;
339         }
340
341         try {
342             var klass = elem.attr('data-ui-jsclass');
343             return new $.wiki.cls[klass](elem, options);
344         } catch(e) {
345             console.log("Failed to show dialog", selector, klass, e);
346             return false;
347         }
348     };
349
350     window.addEventListener("message", (event) => {
351         event.source.close()
352
353         $.ajax("/editor/editor-user-area/", {
354             success: function(d) {
355                 $("#user-area")[0].innerHTML = d;
356                 $('#history-view-editor').toggleClass('can-approve', $('#user-area #pubmark_dialog').length > 0);
357             }
358         });
359     }, false);
360
361     $("#login").click(function (e) {
362         e.preventDefault();
363         let h = 600;
364         let w = 500;
365         let x = window.screenX + (window.innerWidth - w) / 2;
366         let y = window.screenY + (window.innerHeight - h) / 2;
367         window.open(
368             "/accounts/login/?next=/editor/back",
369             "login-window",
370             "width=" + w + " height=" + h + " top=" + y + " left=" + x
371         );
372     });
373
374 })(jQuery);