Fix caret positioning outside editable.
[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 = function(options) {
146                 if(!options) return;
147
148                 this.doc = options.doc;
149                 if (options.id) {
150                         this.perspective_id = options.id;
151                 }
152                 else {
153                         this.perspective_id = '';
154                 }
155
156                 if(options.callback)
157                         options.callback.call(this);
158         };
159
160         $.wiki.Perspective.prototype.config = function() {
161                 return $.wiki.state.perspectives[this.perspective_id];
162         }
163
164         $.wiki.Perspective.prototype.toString = function() {
165                 return this.perspective_id;
166         };
167
168         $.wiki.Perspective.prototype.dirty = function() {
169                 return true;
170         };
171
172         $.wiki.Perspective.prototype.onEnter = function () {
173                 // called when perspective in initialized
174                 if (!this.noupdate_hash_onenter) {
175                         document.location.hash = '#' + this.perspective_id;
176                 }
177         };
178
179         $.wiki.Perspective.prototype.onExit = function () {
180                 // called when user switches to another perspective
181                 if (!this.noupdate_hash_onenter) {
182                         document.location.hash = '';
183                 }
184         };
185
186         $.wiki.Perspective.prototype.destroy = function() {
187                 // pass
188         };
189
190         $.wiki.Perspective.prototype.freezeState = function () {
191                 // free UI state (don't store data here)
192         };
193
194         $.wiki.Perspective.prototype.unfreezeState = function (frozenState) {
195                 // restore UI state
196         };
197
198         /*
199          * Stub rendering (used in generating history)
200          */
201         $.wiki.renderStub = function(params)
202         {
203                 params = $.extend({ 'filters': {} }, params);
204                 var $elem = params.stub.clone();
205                 $elem.removeClass('row-stub');
206                 params.container.append($elem);
207
208                 $('*[data-stub-value]', $elem).each(function() {
209                         var $this = $(this);
210                         var field = $this.attr('data-stub-value');
211
212                         var value = params.data[field];
213
214                         if(params.filters[field])
215                                 value = params.filters[field](value);
216
217                         if(value === null || value === undefined) return;
218
219                         if(!$this.attr('data-stub-target')) {
220                                 $this.text(value);
221                         }
222                         else {
223                                 $this.attr($this.attr('data-stub-target'), value);
224                                 $this.removeAttr('data-stub-target');
225                                 $this.removeAttr('data-stub-value');
226                         }
227                 });
228
229                 $elem.show();
230                 return $elem;
231         };
232
233         /*
234          * Dialogs
235          */
236         function GenericDialog(element) {
237                 if(!element) return;
238
239                 var self = this;
240
241                 self.$elem = $(element);
242
243                 if(!self.$elem.attr('data-ui-initialized')) {
244                         console.log("Initializing dialog", this);
245                         self.initialize();
246                         self.$elem.attr('data-ui-initialized', true);
247                 }
248
249                 self.show();
250         };
251
252         GenericDialog.prototype = {
253
254                 /*
255                 * Steps to follow when the dialog in first loaded on page.
256                 */
257                 initialize: function(){
258                         var self = this;
259
260                         /* bind buttons */
261                         $('button[data-ui-action]', self.$elem).click(function(event) {
262                                 event.preventDefault();
263
264                                 var action = $(this).attr('data-ui-action');
265                                 console.log("Button pressed, action: ", action);
266
267                                 try {
268                                         self[action + "Action"].call(self);
269                                 } catch(e) {
270                                         console.log("Action failed:", e);
271                                         // always hide on cancel
272                                         if(action == 'cancel')
273                                                 self.hide();
274                                 }
275                         });
276                 },
277
278                 /*
279                  * Prepare dialog for user. Clear any unnessary data.
280                 */
281                 show: function() {
282                         $.blockUI({
283                                 message: this.$elem,
284                                 css: {
285                                     'top': '25%',
286                                     'left': '25%',
287                                     'width': '50%',
288                                     'max-height': '75%',
289                                     'overflow-y': 'scroll'
290                                 }
291                         });
292                 },
293
294                 hide: function(){
295                         $.unblockUI();
296                 },
297
298                 cancelAction: function() {
299                         this.hide();
300                 },
301
302                 doneAction: function() {
303                         this.hide();
304                 },
305
306                 clearForm: function() {
307                         $("*[data-ui-error-for]", this.$elem).text('');
308                 },
309
310                 reportErrors: function(errors) {
311                         var global = $("*[data-ui-error-for='__all__']", this.$elem);
312                         var unassigned = [];
313
314             $("*[data-ui-error-for]", this.$elem).text('');
315                         for (var field_name in errors)
316                         {
317                                 var span = $("*[data-ui-error-for='"+field_name+"']", this.$elem);
318
319                                 if(!span.length) {
320                                         unassigned.push(field_name);
321                                         continue;
322                                 }
323
324                                 span.text(errors[field_name].join(' '));
325                         }
326
327                         if(unassigned.length > 0)
328                                 global.text( global.text() + 'W formularzu wystąpiły błędy');
329                 }
330         };
331
332         $.wiki.cls.GenericDialog = GenericDialog;
333
334         $.wiki.showDialog = function(selector, options) {
335                 var elem = $(selector);
336
337                 if(elem.length != 1) {
338                         console.log("Failed to show dialog:", selector, elem);
339                         return false;
340                 }
341
342                 try {
343                     var klass = elem.attr('data-ui-jsclass');
344                         return new $.wiki.cls[klass](elem, options);
345                 } catch(e) {
346                         console.log("Failed to show dialog", selector, klass, e);
347                         return false;
348                 }
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);