0b1234ce553c129d52f90c4f11a2c347529c9d57
[redakcja.git] / platforma / static / js / views / html.js
1 /*global View render_template panels */
2 var HTMLView = View.extend({
3     _className: 'HTMLView',
4     element: null,
5     model: null,
6     template: 'html-view-template',
7   
8     init: function(element, model, parent, template) {
9         var submodel = model.contentModels['html'];
10         this._super(element, submodel, template);
11         this.parent = parent;                
12     
13         this.model
14         .addObserver(this, 'data', this.modelDataChanged.bind(this))
15         .addObserver(this, 'state', this.modelStateChanged.bind(this));
16         
17         this.modelStateChanged('state', this.model.get('state'));
18         this.modelDataChanged('data', this.model.get('data'));
19                 
20         this.model.load();
21
22         this.currentOpen = null;
23         this.currentFocused = null;
24         this.themeBoxes = [];
25     },
26
27     modelDataChanged: function(property, value)
28     {
29         if(!value) return;
30        
31         // the xml model changed
32         var container = $('.htmlview', this.element);
33         container.empty();
34         container.append(value);
35         
36         this.updatePrintLink();
37         
38     /* mark themes */
39     /* $(".theme-ref", this.$docbase).each(function() {
40             var id = $(this).attr('x-theme-class');
41
42             var end = $("span.theme-end[x-theme-class = " + id+"]");
43             var begin = $("span.theme-begin[x-theme-class = " + id+"]");
44
45             var h = $(this).outerHeight();
46
47             h = Math.max(h, end.offset().top - begin.offset().top);
48             $(this).css('height', h);
49         }); */
50     },
51
52     updatePrintLink: function() {
53         var base = this.$printLink.attr('ui:baseref');
54         this.$printLink.attr('href', base + "?user="+this.model.document.get('user')+"&revision=" + this.model.get('revision'));
55     },
56   
57     modelStateChanged: function(property, value) 
58     {
59         var self = $(this);
60
61         if (value == 'synced' || value == 'dirty') {
62             this.unfreeze();
63         } else if (value == 'unsynced') {
64             if(this.currentOpen) this.closeWithoutSave(this.currentOpen);
65             this.freeze('Niezsynchronizowany...');
66         } else if (value == 'loading') {
67             this.freeze('Ładowanie...');
68         } else if (value == 'saving') {
69             this.freeze('Zapisywanie...');
70         } else if (value == 'error') {
71             this.freeze(this.model.get('error'));
72             $('.xml-editor-ref', this.overlay).click(
73                 function(event) {
74                     console.log("Sending scroll rq.", this);
75                     try {
76                         var href = $(this).attr('href').split('-');
77                         var line = parseInt(href[1]);
78                         var column = parseInt(href[2]);
79                     
80                         $(document).trigger('xml-scroll-request', {
81                             line:line,
82                             column:column
83                         });
84                     } catch(e) {
85                         console.log(e);
86                     }
87                 
88                     return false;
89                 });
90         }
91     },
92
93     render: function() {
94         if(this.$docbase)
95             this.$docbase.unbind('click');
96
97         if(this.$printLink) 
98             this.$printLink.unbind();
99
100         if(this.$addThemeButton)
101             this.$addThemeButton.unbind();
102
103         this._super();
104
105         this.$printLink = $('.htmlview-toolbar .html-print-link', this.element);
106         this.$docbase = $('.htmlview', this.element);
107         this.$addThemeButton = $('.htmlview-toolbar .html-add-motive', this.element);
108
109         this.$debugButton = $('.htmlview-toolbar .html-serialize', this.element);
110
111         this.updatePrintLink();
112         this.$docbase.bind('click', this.itemClicked.bind(this));
113         this.$addThemeButton.click( this.addTheme.bind(this) );
114         this.$debugButton.click( this.serialized.bind(this) );
115     },
116
117     serialized: function() {
118         this.model.set('state', 'dirty');
119         console.log( this.model.serializer.serializeToString(this.model.get('data')) );        
120     },
121
122     renderPart: function($e, html) {
123         // exceptions aren't good, but I don't have a better idea right now
124         if($e.attr('x-annotation-box')) {
125             // replace the whole annotation
126             var $p = $e.parent();
127             $p.html(html);
128             var $box = $('*[x-annotation-box]', $p);
129             $box.append( this.$menuTemplate.clone() );
130
131             if(this.currentFocused && $p[0] == this.currentFocused[0])
132             {
133                 this.currentFocused = $p;
134                 $box.css({
135                     'display': 'block'
136                 });
137             }
138
139             return;
140         }
141
142         $e.html(html);
143         $e.append( this.$menuTemplate.clone() );
144     },
145   
146     reload: function() {
147         this.model.load(true);
148     },
149   
150     dispose: function() {
151         this.model.removeObserver(this);
152         this._super();
153     },
154
155     itemClicked: function(event) 
156     {
157         var self = this;
158         
159         console.log('click:', event, event.ctrlKey, event.target);        
160         var $e = $(event.target);
161
162         if($e.hasClass('annotation'))
163         {
164             if(this.currentOpen) return false;
165             
166             var $p = $e.parent();
167             if(this.currentFocused) 
168             {
169                 console.log(this.currentFocused, $p);
170                 if($p[0] == this.currentFocused[0]) {
171                     console.log('unfocus of current');
172                     this.unfocusAnnotation();
173                     return false;
174                 }
175
176                 console.log('switch unfocus');
177                 this.unfocusAnnotation();                
178             }
179
180             this.focusAnnotation($p);
181             return false;
182         }
183
184         /*
185          * Clicking outside of focused area doesn't unfocus by default
186          *  - this greatly simplifies the whole click check
187          */
188
189         if( $e.hasClass('theme-ref') )
190         {
191             console.log($e);
192             this.selectTheme($e.attr('x-theme-class'));
193             return false;
194         }
195
196         /* other buttons */
197         try {
198             if($e.hasClass('edit-button'))
199                 this.openForEdit( this.editableFor($e) );
200
201             if($e.hasClass('accept-button'))
202                 this.closeWithSave( this.editableFor($e) );
203
204             if($e.hasClass('reject-button'))
205                 this.closeWithoutSave( this.editableFor($e) );
206         } catch(e) {
207             messageCenter.addMessage('error', "wlsave", 'Błąd:' + e.text);
208         }
209         
210         return false;
211     },
212
213     unfocusAnnotation: function()
214     {
215         if(!this.currentFocused)
216         {
217             console.log('Redundant unfocus');
218             return false;
219         }
220
221         if(this.currentOpen 
222             && this.currentOpen.is("*[x-annotation-box]")
223             && this.currentOpen.parent()[0] == this.currentFocused[0])
224             {
225             console.log("Can't unfocus open box");
226             return false;
227         }
228
229         var $box = $("*[x-annotation-box]", this.currentFocused);
230         $box.css({
231             'display': 'none'
232         });
233         // this.currentFocused.removeAttr('x-focused');
234         // this.currentFocused.hide();
235         this.currentFocused = null;
236     },
237
238     focusAnnotation: function($e) {
239         this.currentFocused = $e;
240         var $box = $("*[x-annotation-box]", $e);
241         $box.css({
242             'display': 'block'
243         });
244         
245     // $e.attr('x-focused', 'focused');
246     },
247
248     closeWithSave: function($e) {
249         var $edit = $e.data('edit-overlay');
250         var newText = $('textarea', $edit).val();
251
252         this.model.updateWithWLML($e, newText);
253         $edit.remove();
254         this.currentOpen = null;        
255     },
256
257     closeWithoutSave: function($e) {
258         var $edit = $e.data('edit-overlay');
259         $edit.remove();
260         $e.removeAttr('x-open');
261         this.currentOpen = null;
262     },
263
264     editableFor: function($button) 
265     {
266         var $e = $button;
267         var n = 0;
268         
269         while( ($e[0] != this.element[0]) && !($e.attr('x-editable')) && n < 50)
270         {
271             // console.log($e, $e.parent(), this.element);
272             $e = $e.parent();
273             n += 1;
274         }
275
276         if(!$e.attr('x-editable'))
277             throw Exception("Click outside of editable")
278
279         console.log("Trigger", $button, " yields editable: ", $e);
280         return $e;
281     },
282
283     openForEdit: function($origin)
284     {       
285         if(this.currentOpen && this.currentOpen != $origin) {
286             this.closeWithSave(this.currentOpen);    
287         }
288         
289         var $box = null
290
291         // annotations overlay their sub box - not their own box //
292         if($origin.is(".annotation-inline-box"))
293             $box = $("*[x-annotation-box]", $origin);
294         else
295             $box = $origin;
296         
297         var x = $box[0].offsetLeft;
298         var y = $box[0].offsetTop;
299         var w = $box.outerWidth();
300         var h = $box.innerHeight();
301
302         console.log("Edit origin:", $origin, " box:", $box);
303         console.log("offsetParent:", $box[0].offsetParent);
304         console.log("Dimensions: ", x, y, w , h);
305
306         // start edition on this node
307         var $overlay = $('<div class="html-editarea"><textarea></textarea></div>');
308         
309         $overlay.css({
310             position: 'absolute',
311             height: h,
312             left: x,
313             top: y,
314             width: '95%'
315         });
316         
317         try {
318             $('textarea', $overlay).val( this.model.asWLML($origin[0]) );
319
320             if($origin.is(".annotation-inline-box"))
321             {                
322                 if(this.currentFocused) {
323                     // if some other is focused
324                     if($origin[0] != this.currentFocused[0]) {
325                         this.unfocusAnnotation();
326                         this.focusAnnotation($origin);
327                     }
328                 // already focues
329                 }
330                 else { // nothing was focused
331                     this.focusAnnotation($origin);
332                 }
333             }
334             else { // this item is not focusable
335                 if(this.currentFocused) this.unfocusAnnotation();
336             }
337
338             $($box[0].offsetParent).append($overlay);
339             $origin.data('edit-overlay', $overlay);
340         
341             this.currentOpen = $origin;
342             $origin.attr('x-open', 'open');
343         }
344         catch(e) {
345             console.log("Can't open", e);
346         }
347                 
348         return false;
349     },
350
351     addTheme: function() 
352     {
353         var selection = window.getSelection();
354         var n = selection.rangeCount;
355
356         console.log("Range count:", n);
357         
358         if(n == 0)
359             window.alert("Nie zaznaczono żadnego obszaru");
360
361         // for now allow only 1 range
362         if(n > 1)
363             window.alert("Zaznacz jeden obszar");
364
365
366         // from this point, we will assume that the ranges are disjoint
367         for(var i=0; i < n; i++) 
368         {
369             var range = selection.getRangeAt(i);
370             console.log(i, range.startContainer, range.endContainer);
371             var date = (new Date()).getTime();
372             var random = Math.floor(4000000000*Math.random());
373             var id = (''+date) + '-' + (''+random);
374
375             var spoint = document.createRange();
376             var epoint = document.createRange();
377
378             spoint.setStart(range.startContainer, range.startOffset);
379             epoint.setStart(range.endContainer, range.endOffset);
380
381             // insert theme-ref
382             
383             var elem = $('<span x-editable="true" x-node="motyw" class="motyw">Nowy motyw</span>');
384             elem.attr('x-attr-qname-'+id, 'id');
385             elem.attr('x-attr-value-'+id, 'm'+id);
386             spoint.insertNode(elem[0]);
387
388             // insert theme-begin
389             elem = $('<span x-node="begin" class="begin"></span>');
390             elem.attr('x-attr-qname-'+id, 'id');
391             elem.attr('x-attr-value-'+id, 'b'+id);
392             spoint.insertNode(elem[0]);
393             
394             elem = $('<span x-node="end" class="end"></span>');
395             elem.attr('x-attr-qname-'+id, 'id');
396             elem.attr('x-attr-value-'+id, 'e'+id);
397             epoint.insertNode(elem[0]);
398         }
399
400     //selection.removeAllRanges();
401     },
402
403     selectTheme: function(themeId)
404     {
405         var selection = document.getSelection();
406         
407         // remove current selection
408         selection.removeAllRanges();
409
410         var range = document.createRange();
411         var s = $('#m'+themeId)[0];
412         var e = $('#e'+themeId)[0];
413         console.log('Selecting range:', themeId, range, s, e);
414
415         if(s && e) {
416             range.setStartAfter(s);
417             range.setEndBefore(e);
418             selection.addRange(range);
419         }
420     }
421 });
422
423 // Register view
424 panels['html'] = HTMLView;