fix edit buttons wrapping
[redakcja.git] / src / redakcja / static / js / wiki / view_editor_wysiwyg.js
1 (function($) {
2
3     /* Show theme to the user */
4     function selectTheme(themeId){
5         var selection = window.getSelection();
6         selection.removeAllRanges();
7
8         var range = document.createRange();
9         var s = $("[x-node='motyw'][theme-class='" + themeId + "']")[0];
10         var e = $("[x-node='end'][theme-class='" + themeId + "']")[0];
11
12         if (s && e) {
13             range.setStartAfter(s);
14             range.setEndBefore(e);
15             selection.addRange(range);
16         }
17     };
18
19     /* Verify insertion port for annotation or theme */
20     function verifyTagInsertPoint(node){
21         if (node.nodeType == Node.TEXT_NODE) {
22             node = node.parentNode;
23         }
24
25         if (node.nodeType != Node.ELEMENT_NODE) {
26             return false;
27         }
28
29         node = $(node);
30         if (node.attr('id') == 'caret') {
31             node = node.parent();
32         }
33         while (node.attr('x-pass-thru')) {
34             node = node.parent();
35         }
36         var xtype = node.attr('x-node');
37
38         if (!xtype || (xtype.search(':') >= 0) ||
39         xtype == 'motyw' ||
40         xtype == 'begin' ||
41         xtype == 'end') {
42             return false;
43         }
44
45         return true;
46     }
47
48     function verifyThemeBoundaryPoint(node) {
49         if (!verifyTagInsertPoint(node)) return false;
50         node = $(node);
51         // don't allow themes inside annotations
52         if (node.closest('[x-node="pe"]').length > 0)
53             return false;
54
55         return true;
56     }
57
58     /* Convert HTML fragment to plaintext */
59     var ANNOT_FORBIDDEN = ['pt', 'pa', 'pr', 'pe', 'ptrad', 'begin', 'end', 'motyw'];
60
61     function html2plainText(fragment){
62         var text = "";
63
64         $(fragment.childNodes).each(function(){
65             if (this.nodeType == Node.TEXT_NODE)
66                 text += this.nodeValue;
67             else {
68                 if (this.nodeType == Node.ELEMENT_NODE &&
69                         $.inArray($(this).attr('x-node'), ANNOT_FORBIDDEN) == -1) {
70                     text += html2plainText(this);
71                 }
72             };
73         });
74
75         return text;
76     }
77
78
79     /* Insert annotation using current selection */
80     function addAnnotation(){
81         var selection = window.getSelection();
82         var n = selection.rangeCount;
83
84         if (selection.isCollapsed) {
85             window.alert("Nie zaznaczono żadnego obszaru");
86             return false;
87         }
88
89         var range = selection.getRangeAt(n - 1);
90
91         if (!verifyTagInsertPoint(range.endContainer)) {
92             window.alert("Nie można wstawić w to miejsce przypisu.");
93             return false;
94         }
95
96         text = '';
97         for (let i = 0; i < n; ++ i) {
98             let rangeI = selection.getRangeAt(i);
99             if (verifyTagInsertPoint(rangeI.startContainer) &&
100                 verifyTagInsertPoint(rangeI.endContainer)) {
101                 text += html2plainText(rangeI.cloneContents());
102             }
103         }
104         var tag = $('<span></span>');
105         range.collapse(false);
106         range.insertNode(tag[0]);
107
108         xml2html({
109             xml: '<pe><slowo_obce>' + text + '</slowo_obce> --- </pe>',
110             success: function(text){
111                 var t = $(text);
112                 tag.replaceWith(t);
113                 openForEdit(t, trim=false);
114             },
115             error: function(){
116                 tag.remove();
117                 alert('Błąd przy dodawaniu przypisu:' + errors);
118             }
119         })
120     }
121
122
123
124     /* Insert theme using current selection */
125
126     function addTheme(){
127         var selection = window.getSelection();
128         var n = selection.rangeCount;
129
130         if (n == 0) {
131             window.alert("Nie zaznaczono żadnego obszaru");
132             return false;
133         }
134
135         // for now allow only 1 range
136         if (n > 1) {
137             window.alert("Zaznacz jeden obszar.");
138             return false;
139         }
140
141         // remember the selected range
142         var range = selection.getRangeAt(0);
143
144         if ($(range.startContainer).is('.html-editarea') ||
145         $(range.endContainer).is('.html-editarea')) {
146             window.alert("Motywy można oznaczać tylko na tekście nie otwartym do edycji. \n Zamknij edytowany fragment i spróbuj ponownie.");
147             return false;
148         }
149
150         // verify if the start/end points make even sense -
151         // they must be inside a x-node (otherwise they will be discarded)
152         // and the x-node must be a main text
153         if (!verifyThemeBoundaryPoint(range.startContainer)) {
154             window.alert("Motyw nie może się zaczynać w tym miejscu.");
155             return false;
156         }
157
158         if (!verifyThemeBoundaryPoint(range.endContainer)) {
159             window.alert("Motyw nie może się kończyć w tym miejscu.");
160             return false;
161         }
162
163         var date = (new Date()).getTime();
164         var random = Math.floor(4000000000 * Math.random());
165         var id = ('' + date) + '-' + ('' + random);
166
167         var createPoint = function(container, offset) {
168             var offsetBetweenCommas = function(text, offset) {
169                 if(text.length < 2 || offset < 1 || offset > text.length)
170                     return false;
171                 return text[offset-1] === ',' && text[offset] === ',';
172             }
173             var point = document.createRange();
174             offset = offsetBetweenCommas(container.textContent, offset) ? offset - 1 : offset;
175             point.setStart(container, offset);
176             return point;
177         }
178
179         var spoint = createPoint(range.startContainer, range.startOffset);
180         var epoint = createPoint(range.endContainer, range.endOffset);
181
182         var mtag, btag, etag, errors;
183
184         // insert theme-ref
185
186         xml2html({
187             xml: '<end id="e' + id + '" />',
188             success: function(text){
189                 etag = $('<span></span>');
190                 epoint.insertNode(etag[0]);
191                 etag.replaceWith(text);
192                 xml2html({
193                     xml: '<motyw id="m' + id + '"></motyw>',
194                     success: function(text){
195                         mtag = $('<span></span>');
196                         spoint.insertNode(mtag[0]);
197                         mtag.replaceWith(text);
198                         xml2html({
199                             xml: '<begin id="b' + id + '" />',
200                             success: function(text){
201                                 btag = $('<span></span>');
202                                 spoint.insertNode(btag[0])
203                                 btag.replaceWith(text);
204                                 selection.removeAllRanges();
205                                 openForEdit($('[x-node="motyw"][theme-class="' + id + '"]'));
206                             }
207                         });
208                     }
209                 });
210             }
211         });
212     }
213
214     function addSymbol(caret) {
215         let editArea;
216
217         if (caret) {
218             editArea = $("textarea", caret.element)[0];
219         } else {
220             editArea = $('div.html-editarea textarea')[0];
221         }
222
223         if(editArea) {
224             var specialCharsContainer = $("<div id='specialCharsContainer'><a href='#' id='specialCharsClose'>Zamknij</a><table id='tableSpecialChars' style='width: 600px;'></table></div>");
225
226             var specialChars = [
227                 ' ', 'Ą','ą','Ć','ć','Ę','ę','Ł','ł','Ń','ń','Ó','ó','Ś','ś','Ż','ż','Ź','ź','Á','á','À','à',
228                 'Â','â','Ä','ä','Å','å','Ā','ā','Ă','ă','Ã','ã',
229                 'Æ','æ','Ç','ç','Č','č','Ċ','ċ','Ď','ď','É','é','È','è',
230                 'Ê','ê','Ë','ë','Ē','ē','Ě','ě','Ġ','ġ','Ħ','ħ','Í','í','Î','î',
231                 'Ī','ī','Ĭ','ĭ','Ľ','ľ','Ñ','ñ','Ň','ň','Ó','ó','Ö','ö',
232                 'Ô','ô','Ō','ō','Ǒ','ǒ','Œ','œ','Ø','ø','Ř','ř','Š',
233                 'š','Ş','ş','Ť','ť','Ţ','ţ','Ű','ű','Ú','ú','Ù','ù',
234                 'Ü','ü','Ů','ů','Ū','ū','Û','û','Ŭ','ŭ',
235                 'Ý','ý','Ž','ž','ß','Ð','ð','Þ','þ','А','а','Б',
236                 'б','В','в','Г','г','Д','д','Е','е','Ё','ё','Ж',
237                 'ж','З','з','И','и','Й','й','К','к','Л','л','М',
238                 'м','Н','н','О','о','П','п','Р','р','С','с',
239                 'Т','т','У','у','Ф','ф','Х','х','Ц','ц','Ч',
240                 'ч','Ш','ш','Щ','щ','Ъ','ъ','Ы','ы','Ь','ь','Э',
241                 'э','Ю','ю','Я','я','ѓ','є','і','ї','ј','љ','њ',
242                 'Ґ','ґ','Α','α','Β','β','Γ','γ','Δ','δ','Ε','ε',
243                 'Ζ','ζ','Η','η','Θ','θ','Ι','ι','Κ','κ','Λ','λ','Μ',
244                 'μ','Ν','ν','Ξ','ξ','Ο','ο','Π','π','Ρ','ρ','Σ','ς','σ',
245                 'Τ','τ','Υ','υ','Φ','φ','Χ','χ','Ψ','ψ','Ω','ω','–',
246                 '—','¡','¿','$','¢','£','€','©','®','°','¹','²','³',
247                 '¼','½','¾','†','§','‰','•','←','↑','→','↓',
248                 '„','”','„”','«','»','«»','»«','’','[',']','~','|','−','·',
249                 '×','÷','≈','≠','±','≤','≥','∈',
250
251                 // Hebrew
252                 '\u05d0', '\u05d1', '\u05d2', '\u05d3', '\u05d4', '\u05d5', '\u05d6', '\u05d7',
253                 '\u05d8', '\u05d9', '\u05da', '\u05db', '\u05dc', '\u05dd', '\u05de', '\u05df', 
254                 '\u05e0', '\u05e1', '\u05e2', '\u05e3', '\u05e4', '\u05e5', '\u05e6', '\u05e7', 
255                 '\u05e8', '\u05e9', '\u05e0', '\u05ea',
256
257                 '\u0591', '\u0592', '\u0593', '\u0594', '\u0595', '\u0596', '\u0597',
258                 '\u0598', '\u0599', '\u059a', '\u059b', '\u059c', '\u059d', '\u059e', '\u059f', 
259                 '\u05a0', '\u05a1', '\u05a2', '\u05a3', '\u05a4', '\u05a5', '\u05a6', '\u05a7',
260                 '\u05a8', '\u05a9', '\u05aa', '\u05ab', '\u05ac', '\u05ad', '\u05ae', '\u05af', 
261                 '\u05b0', '\u05b1', '\u05b2', '\u05b3', '\u05b4', '\u05b5', '\u05b6', '\u05b7',
262                 '\u05b8', '\u05b9', '\u05ba', '\u05bb', '\u05bc', '\u05bd', '\u05be', '\u05bf', 
263                 '\u05c0', '\u05c1', '\u05c2', '\u05c3', '\u05c4', '\u05c5', '\u05c6', '\u05c7',
264
265                 '\ufb1d', '\ufb1e', '\ufb1f',
266                 '\ufb20', '\ufb21', '\ufb22', '\ufb23', '\ufb24', '\ufb25', '\ufb26', '\ufb27',
267                 '\ufb28', '\ufb29', '\ufb2a', '\ufb2b', '\ufb2c', '\ufb2d', '\ufb2e', '\ufb2f',
268                 '\ufb30', '\ufb31', '\ufb32', '\ufb33', '\ufb34', '\ufb35', '\ufb36',
269                 '\ufb38', '\ufb39', '\ufb3a', '\ufb3b', '\ufb3c',           '\ufb3e',
270                 '\ufb40', '\ufb41',           '\ufb43', '\ufb44',           '\ufb46', '\ufb47',
271                 '\ufb48', '\ufb49', '\ufb4a', '\ufb4b', '\ufb4c', '\ufb4d', '\ufb4e', '\ufb4f', 
272             ]
273             var tableContent = "<tr>";
274
275             for(var i in specialChars) {
276                 if(i % 14 == 0 && i > 0) {
277                     tableContent += "</tr><tr>";
278                 }
279                 tableContent += "<td><input type='button' class='specialBtn' value='"+specialChars[i]+"'/></td>";
280             }
281
282             tableContent += "</tr>";
283             $("body").append(specialCharsContainer);
284
285
286              // localStorage for recently used characters - reading
287              if (typeof(localStorage) != 'undefined') {
288                  if (localStorage.getItem("recentSymbols")) {
289                      var recent = localStorage.getItem("recentSymbols");
290                      var recentArray = recent.split(";");
291                      var recentRow = "";
292                      for(var i in recentArray.reverse()) {
293                         recentRow += "<td><input type='button' class='specialBtn recentSymbol' value='"+recentArray[i]+"'/></td>";
294                      }
295                      recentRow = "<tr>" + recentRow + "</tr>";
296                  }
297              }
298             $("#tableSpecialChars").append(recentRow);
299             $("#tableSpecialChars").append(tableContent);
300
301             /* events */
302
303             $('.specialBtn').click(function(){
304                 var insertVal = $(this).val();
305
306                 // if we want to surround text with quotes
307                 // not sure if just check if value has length == 2
308
309                 if (caret) {
310                     caret.insertChar(insertVal);
311                     caret.focus();
312                 } else {
313                     if (insertVal.length == 2) {
314                         var startTag = insertVal[0];
315                         var endTag = insertVal[1];
316                         var textAreaOpened = editArea;
317                         //IE support
318                         if (document.selection) {
319                             textAreaOpened.focus();
320                             sel = document.selection.createRange();
321                             sel.text = startTag + sel.text + endTag;
322                         }
323                         //MOZILLA/NETSCAPE support
324                         else if (textAreaOpened.selectionStart || textAreaOpened.selectionStart == '0') {
325                             var startPos = textAreaOpened.selectionStart;
326                             var endPos = textAreaOpened.selectionEnd;
327                             textAreaOpened.value = textAreaOpened.value.substring(0, startPos)
328                                 + startTag + textAreaOpened.value.substring(startPos, endPos) + endTag + textAreaOpened.value.substring(endPos, textAreaOpened.value.length);
329                         }
330                     } else {
331                         insertAtCaret(editArea, insertVal);
332                     }
333                 }
334
335                 // localStorage for recently used characters - saving
336                 if (typeof(localStorage) != 'undefined') {
337                     if (localStorage.getItem("recentSymbols")) {
338                         var recent = localStorage.getItem("recentSymbols");
339                         var recentArray = recent.split(";");
340                         var valIndex = $.inArray(insertVal, recentArray);
341                         //alert(valIndex);
342                         if(valIndex == -1) {
343                             // value not present in array yet
344                             if(recentArray.length > 13){
345                                 recentArray.shift();
346                                 recentArray.push(insertVal);
347                             } else {
348                                 recentArray.push(insertVal);
349                             }
350                         } else  {
351                             // value already in the array
352                             for(var i = valIndex; i < recentArray.length; i++){
353                                 recentArray[i] = recentArray[i+1];
354                             }
355                             recentArray[recentArray.length-1] = insertVal;
356                         }
357                         localStorage.setItem("recentSymbols", recentArray.join(";"));
358                     } else {
359                         localStorage.setItem("recentSymbols", insertVal);
360                     }
361                 }
362                 $(specialCharsContainer).remove();
363             });
364             $('#specialCharsClose').click(function(){
365                 $(specialCharsContainer).remove();
366             });
367
368         } else {
369             window.alert('Najedź na fragment tekstu, wybierz "Edytuj" i ustaw kursor na miejscu gdzie chcesz wstawić symbol.');
370         }
371     }
372
373     function insertAtCaret(txtarea,text) {
374         /* http://www.scottklarr.com/topic/425/how-to-insert-text-into-a-textarea-where-the-cursor-is/ */
375         var scrollPos = txtarea.scrollTop;
376         var strPos = 0;
377         var backStart = 0;
378         var br = ((txtarea.selectionStart || txtarea.selectionStart == '0') ? "ff" : (document.selection ? "ie" : false ) );
379         if (br == "ie") {
380             txtarea.focus();
381             var range = document.selection.createRange();
382             range.moveStart ('character', -txtarea.value.length);
383             strPos = backStart = range.text.length;
384         } else if (br == "ff") {
385             strPos = txtarea.selectionStart;
386             backStart = txtarea.selectionEnd;
387         }
388         var front = (txtarea.value).substring(0,strPos);
389         var back = (txtarea.value).substring(backStart,txtarea.value.length);
390         txtarea.value=front+text+back;
391         strPos = strPos + text.length;
392         if (br == "ie") {
393             txtarea.focus();
394             var range = document.selection.createRange();
395             range.moveStart ('character', -txtarea.value.length);
396             range.moveStart ('character', strPos);
397             range.moveEnd ('character', 0);
398             range.select();
399         } else if (br == "ff") {
400             txtarea.selectionStart = strPos;
401             txtarea.selectionEnd = strPos;
402             txtarea.focus();
403         }
404         txtarea.scrollTop = scrollPos;
405     }
406
407     /* open edition window for selected fragment */
408     function openForEdit($origin, trim=true){
409         var $box = null
410
411         // annotations overlay their sub box - not their own box //
412         if ($origin.is(".annotation-inline-box")) {
413             $box = $("*[x-annotation-box]", $origin);
414         }
415         else {
416             $box = $origin;
417         }
418         var x = $box[0].offsetLeft;
419         var y = $box[0].offsetTop;
420
421         var w = $box.outerWidth();
422         var h = $box.innerHeight();
423
424         if ($origin.is(".annotation-inline-box")) {
425             w = Math.max(w, 400);
426             h = Math.max(h, 60);
427             if($('.htmlview div').offset().left + $('.htmlview div').width() > ($('.vsplitbar').offset().left - 480)){
428                 x = -(Math.max($origin.offset().left, $origin.width()));
429             } else {
430                 x = 100;
431             }
432         }
433         if ($origin.is('.reference-inline-box')) {
434             w = 400;
435             h = 32;
436             y -= 32;
437             x = Math.min(
438                 x,
439                 $('.htmlview div').offset().left + $('.htmlview div').width() - 400
440             );
441         }
442
443         // start edition on this node
444         var $overlay = $('<div class="html-editarea"><div class="html-editarea-toolbar"><div class="html-editarea-toolbar-left"><button class="accept-button">Zapisz</button><button class="delete-button">Usuń</button></div><div class="html-editarea-toolbar-right"><button class="akap-edit-button" data-tag="tytul_dziela">tytuł dzieła</button><button class="akap-edit-button" data-tag="wyroznienie">wyróżnienie</button><button class="akap-edit-button" data-tag="slowo_obce">słowo obce</button><button class="akap-edit-button" data-tag-selfclosing="br">br</button><button class="akap-edit-button" data-act="spec">znak spec.</button></div></div><textarea></textarea></div>').css({
445             position: 'absolute',
446             height: h,
447             left: x,
448             top: y,
449             width: w
450         }).appendTo($box[0].offsetParent || $box.parent()).show();
451
452
453         if ($origin.is('*[x-edit-no-format]')) {
454             $('.akap-edit-button').remove();
455         }
456
457         if ($origin.is('[x-node="motyw"]')) {
458             $.themes.autocomplete($('textarea', $overlay));
459         }
460
461         if ($origin.is('[x-node="motyw"]')){
462             $('.delete-button', $overlay).click(function(){
463                 if (window.confirm("Czy jesteś pewien, że chcesz usunąć ten motyw?")) {
464                     $('[theme-class="' + $origin.attr('theme-class') + '"]').remove();
465                     $overlay.remove();
466                     $(document).unbind('click.blur-overlay');
467                     return false;
468                 };
469             });
470         }
471         else if($box.is('*[x-annotation-box]') || $origin.is('*[x-edit-attribute]') || $origin.is('*[x-node="uwaga"]')) {
472             let q;
473             switch ($origin.attr('x-node')) {
474             case 'uwaga':
475                 q = 'tę uwagę';
476                 break;
477             case 'ref':
478                 q = 'tę referencję';
479                 break
480             default:
481                 q = 'ten przypis';
482             }
483             $('.delete-button', $overlay).click(function(){
484                 if (window.confirm("Czy jesteś pewien, że chcesz usunąć " + q + "?")) {
485                     $origin.remove();
486                     $overlay.remove();
487                     $(document).unbind('click.blur-overlay');
488                     return false;
489                 };
490             });
491         }
492         else {
493             $('.delete-button', $overlay).html("Anuluj");
494             $('.delete-button', $overlay).click(function(){
495                 if (window.confirm("Czy jesteś pewien, że chcesz anulować zmiany?")) {
496                     $overlay.remove();
497                     $(document).unbind('click.blur-overlay');
498                     return false;
499                 };
500             });
501         }
502
503
504         var serializer = new XMLSerializer();
505
506         if($box.attr("x-edit-attribute")) {
507             source = $('<span x-pass-thru="true"/>');
508             source.text($box.attr("x-a-wl-" + $box.attr("x-edit-attribute")));
509             source = source[0];
510         } else {
511             source = $box[0];
512         }
513
514         html2text({
515             element: source,
516             stripOuter: true,
517             success: function(text){
518                 let ttext = text;
519                 if (trim) {
520                     ttext = ttext.trim();
521                 }
522                 $('textarea', $overlay).val(ttext);
523
524                 setTimeout(function(){
525                     $('textarea', $overlay).elastic().focus();
526                 }, 50);
527
528                 function save(argument){
529                     var nodeName = $box.attr('x-node') || 'pe';
530                     var insertedText = $('textarea', $overlay).val();
531
532                     if ($origin.is('[x-node="motyw"]')) {
533                         insertedText = insertedText.replace(/,\s*$/, '');
534                     }
535
536                     if($box.attr("x-edit-attribute")) {
537                         xml = '<' + nodeName + ' ' + $box.attr("x-edit-attribute") + '="' + insertedText + '"/>';
538                     } else {
539                         xml = '<' + nodeName + '>' + insertedText + '</' + nodeName + '>';
540                     }
541
542                     xml2html({
543                         xml: xml,
544                         success: function(element){
545                             if (nodeName == 'out-of-flow-text') {
546                                 $(element).children().insertAfter($origin);
547                                 $origin.remove()
548                             }
549                             else if ($box.attr('x-edit-attribute')) {
550                                 $(element).insertAfter($origin);
551                                 $origin.remove();
552                             }
553                             else {
554                                 $origin.html($(element).html());
555                             }
556                             $overlay.remove();
557                             $.wiki.activePerspective().flush();
558                         },
559                         error: function(text){
560                             alert('Błąd! ' + text);
561                         }
562                     })
563
564                     var msg = $("<div class='saveNotify'><p>Pamiętaj, żeby zapisać swoje zmiany.</p></div>");
565                     $("#base").prepend(msg);
566                     $('#base .saveNotify').fadeOut(3000, function(){
567                         $(this).remove();
568                     });
569                 }
570
571                 $('.akap-edit-button', $overlay).click(function(){
572                     var textAreaOpened = $('textarea', $overlay)[0];
573                     var startTag = "";
574                     var endTag = "";
575                     var tag = "";
576                     var $this = $(this);
577
578                     if ($this.data('tag')) {
579                         tag = $this.data('tag');
580                         startTag = "<" + $this.data('tag') + ">";
581                         endTag = "</" + $this.data('tag') + ">";
582                     } else if ($this.data('tag-selfclosing')){
583                         startTag = "<" + $this.data('tag-selfclosing') + "/>";
584                     } else if ($this.data('act')) {
585                         if ($this.data('act') == 'spec') {
586                             addSymbol();
587                         }
588                         return false;
589                     }
590
591                     var myField = textAreaOpened;
592
593                     //IE support
594                     if (document.selection) {
595                         textAreaOpened.focus();
596                         sel = document.selection.createRange();
597                         sel.text = startTag + sel.text + endTag;
598                     }
599                     //MOZILLA/NETSCAPE support
600                     else if (textAreaOpened.selectionStart || textAreaOpened.selectionStart == '0') {
601                         var startPos = textAreaOpened.selectionStart;
602                         var endPos = textAreaOpened.selectionEnd;
603                         textAreaOpened.value = textAreaOpened.value.substring(0, startPos)
604                             + startTag + textAreaOpened.value.substring(startPos, endPos) + endTag + textAreaOpened.value.substring(endPos, textAreaOpened.value.length);
605                     }
606                 });
607
608                 $('.accept-button', $overlay).click(function(){
609                     save();
610                     $(document).unbind('click.blur-overlay');
611                 });
612
613                 $(document).bind('click.blur-overlay', function(event){
614                     if ($(event.target).closest('.html-editarea, #specialCharsContainer').length > 0) {
615                         return;
616                     }
617                     save();
618                     $(document).unbind('click.blur-overlay');
619                 });
620
621             },
622             error: function(text){
623                 alert('Błąd! ' + text);
624             }
625         });
626     }
627
628     function createUwagaBefore(before) {
629         xml2html({
630             xml: '<uwaga/>',
631             success: function(element){
632                 let $element = $(element);
633                 $element.insertBefore(before);
634                 openForEdit($element);
635             }
636         });
637     }
638
639     class VisualPerspective extends $.wiki.Perspective {
640         constructor(options) {
641             super(options);
642             let self = this;
643             var element = $("#html-view");
644             var button = $('<button class="edit-button active-block-button">Edytuj</button>');
645             var uwagaButton = $('<button class="uwaga-button active-block-button">Uwaga</button>');
646
647             if (!CurrentDocument.readonly) {
648
649                 $('#html-view').bind('mousemove', function(event){
650                     var editable = $(event.target).closest('*[x-editable]');
651                     $('.active', element).not(editable).removeClass('active').children('.html-editarea-toolbar').remove();
652
653                     if (!editable.hasClass('active')) {
654                         var toolbar = $("<div class='html-editarea-toolbar'><div class='html-editarea-toolbar-left'></div></div>")
655                         editable.append(toolbar);
656                         var buttonSpace = $('.html-editarea-toolbar-left', toolbar);
657                         editable.addClass('active');
658                         buttonSpace.append(button);
659                         if (!editable.is('[x-edit-attribute]') &&
660                             !editable.is('.annotation-inline-box') &&
661                             !editable.is('[x-edit-no-format]')
662                            ) {
663                             buttonSpace.append(uwagaButton);
664                         }
665                     }
666                     if (editable.is('.annotation-inline-box')) {
667                         $('*[x-annotation-box]', editable).css({
668                         }).show();
669                     }
670                     if (editable.is('.reference-inline-box')) {
671                         let preview = $('*[x-preview]', editable);
672                         preview.show();
673                         let link = $("a", preview);
674                         let href = link.attr('href');
675                         if (link.attr('title') == '?' && href.startsWith('https://www.wikidata.org/wiki/')) {
676                             link.attr('title', '…');
677                             let qid = href.split('/').reverse()[0];
678                             $.ajax({
679                                 url: 'https://www.wikidata.org/w/rest.php/wikibase/v1/entities/items/' + qid + '?_fields=labels',
680                                 dataType: "json",
681                                 success: function(data) {
682                                     link.attr(
683                                         'title',
684                                         data['labels']['pl'] || data['labels']['en']
685                                     );
686                                 },
687                             });
688                         }
689                     }
690                 });
691
692                 self.caret = new Caret(element);
693
694                 $('#insert-reference-button').click(function(){
695                     self.flush();
696                     self.addReference();
697                     return false;
698                 });
699
700                 $('#insert-annotation-button').click(function(){
701                     self.flush();
702                     addAnnotation();
703                     return false;
704                 });
705
706                 $('#insert-theme-button').click(function(){
707                     self.flush();
708                     addTheme();
709                     return false;
710                 });
711
712                 $(".insert-inline-tag").click(function() {
713                     self.flush();
714                     self.insertInlineTag($(this).attr('data-tag'));
715                     return false;
716                 });
717
718                 $(".insert-char").click(function() {
719                     self.flush();
720                     addSymbol(caret=self.caret);
721                     return false;
722                 });
723
724                 $(document).on('click', '.edit-button', function(event){
725                     self.flush();
726                     event.preventDefault();
727                     openForEdit($(this).closest('.html-editarea-toolbar').parent());
728                 });
729
730                 $(document).on('click', '.uwaga-button', function(event){
731                     self.flush();
732                     event.preventDefault();
733                     createUwagaBefore($(this).closest('.html-editarea-toolbar').parent());
734                 });
735             }
736
737             $(document).on('click', '[x-node="motyw"]', function(){
738                 selectTheme($(this).attr('theme-class'));
739             });
740
741             element.on('click', '.annotation', function(event) {
742                 self.flush();
743                 event.preventDefault();
744                 event.redakcja_caret_ignore = true;
745                 $('[x-annotation-box]', $(this).parent()).toggleClass('editing');
746                 self.caret.detach();
747             });
748         }
749
750         onEnter(success, failure) {
751             super.onEnter();
752
753             $.blockUI({
754                 message: 'Uaktualnianie widoku...'
755             });
756
757             function _finalize(callback){
758                 $.unblockUI();
759                 if (callback)
760                     callback();
761             }
762
763             let self = this;
764             xml2html({
765                 xml: this.doc.text,
766                 base: this.doc.getBase(),
767                 success: function(element){
768
769                     var htmlView = $('#html-view');
770                     htmlView.html(element);
771                     self.renumber();
772                     if ('PropertiesPerspective' in $.wiki.perspectives)
773                         $.wiki.perspectives.PropertiesPerspective.enable();
774
775                     _finalize(success);
776                 },
777                 error: function(text, source){
778                     let err = '<p class="error">Wystąpił błąd:</p><p>'+text+'</p>';
779                     if (source)
780                         err += '<pre>'+source.replace(/&/g, '&amp;').replace(/</g, '&lt;')+'</pre>'
781                     $('#html-view').html(err);
782                     _finalize(failure);
783                 }
784             });
785         }
786
787         flush() {
788             let self = this;
789             return new Promise((resolve, reject) => {
790                 if ($('#html-view .error').length > 0) {
791                     reject()
792                 } else {
793                     //return _finalize(failure);
794                     html2text({
795                         element: $('#html-view').get(0),
796                         stripOuter: true,
797                         success: (text) => {
798                             self.doc.setText(text);
799                             resolve();
800                         },
801                         error: (text) => {
802                             reject(text);
803                             //$('#source-editor').html('<p>Wystąpił błąd:</p><pre>' + text + '</pre>');
804                         }
805                     });
806                 }
807             });
808         }
809
810         onExit(success, failure) {
811             var self = this;
812
813             self.caret.detach();
814
815             if ('PropertiesPerspective' in $.wiki.perspectives)
816                 $.wiki.perspectives.PropertiesPerspective.disable();
817
818             self.flush().then(() => {
819                 success && success();
820             }).catch((e) => {
821                 // TODO report
822                 console.log('REJECTED!', e);
823                 failure && failure();
824             });
825         };
826
827         insertInlineTag(tag) {
828             this.caret.detach();
829             let self = this;
830
831             let selection = window.getSelection();
832             var n = selection.rangeCount;
833             if (n != 1 || selection.isCollapsed) {
834                 window.alert("Nie zaznaczono obszaru");
835                 return false
836             }
837             let range = selection.getRangeAt(0);
838
839             // Make sure that:
840             // Both ends are in the same x-node container.
841             // Both ends are set to text nodes.
842             // TODO: That the container is a inline-text container.
843             let commonNode = range.endContainer;
844
845             if (commonNode.nodeType == Node.TEXT_NODE) {
846                 commonNode = commonNode.parentNode;
847             }
848             let node = range.startContainer;
849             if (node.nodeType == Node.TEXT_NODE) {
850                 node = node.parentNode;
851             }
852             if (node != commonNode) {
853                 window.alert("Zły obszar.");
854                 return false;
855             }
856
857             let end;
858             if (range.endContainer.nodeType == Node.TEXT_NODE) {
859                 end = range.endContainer.splitText(range.endOffset);
860             } else {
861                 end = document.createTextNode('');
862                 let cont = $(range.endContainer).contents();
863                 if (range.endOffset < cont.length) {
864                     range.endContainer.insertBefore(end, cont[range.endOffset])
865                 } else {
866                     range.endContainer.append(end);
867                 }
868             }
869
870             let current;
871             if (range.startContainer.nodeType == Node.TEXT_NODE) {
872                 current = range.startContainer.splitText(range.startOffset);
873             } else {
874                 current = document.createTextNode('');
875                 let cont = $(range.startContainer).contents();
876                 if (range.startOffset < cont.length) {
877                     range.startContainer.insertBefore(current, cont[range.startOffset])
878                 } else {
879                     startNode.append(current);
880                 }
881             }
882
883             // We will construct a HTML element with the range selected.
884             let div = $("<span x-pass-thru='true'>");
885             while (current != end) {
886                 n = current.nextSibling;
887                 $(current).appendTo(div);
888                 current = n;
889             }
890
891             html2text({
892                 element: div[0],
893                 success: function(d) {
894                     xml2html({
895                         xml: d = '<' + tag + '>' + d + '</' + tag + '>',
896                         success: function(html) {
897                             // What if no end?
898                             node.insertBefore($(html)[0], end);
899                             self.flush();
900                         }
901                     });
902                 },
903                 error: function(a, b) {
904                     console.log(a, b);
905                 }
906             });
907         }
908
909         insertAtRange(range, elem) {
910             let self = this;
911             let $end = $(range.endContainer);
912             if ($end.attr('id') == 'caret') {
913                 self.caret.insert(elem);
914             } else {
915                 range.insertNode(elem[0]);
916             }
917         }
918
919         addReference() {
920             let self = this;
921             var selection = window.getSelection();
922             var n = selection.rangeCount;
923
924             // TODO: if no selection, take caret position..
925             if (n == 0) {
926                 window.alert("Nie zaznaczono żadnego obszaru");
927                 return false;
928             }
929
930             var range = selection.getRangeAt(n - 1);
931             if (!verifyTagInsertPoint(range.endContainer)) {
932                 window.alert("Nie można wstawić w to miejsce referencji.");
933                 return false;
934             }
935
936             var tag = $('<span></span>');
937
938             range.collapse(false);
939             self.insertAtRange(range, tag);
940
941             xml2html({
942                 xml: '<ref href=""/>',
943                 success: function(text){
944                     var t = $(text);
945                     tag.replaceWith(t);
946                     openForEdit(t);
947                 },
948                 error: function(){
949                     tag.remove();
950                     alert('Błąd przy dodawaniu referncji:' + errors);
951                 }
952             })
953         }
954
955         renumber() {
956             let number = 0;
957             $('#html-view *').each((i, e) => {
958                 let $e = $(e);
959                 if ($e.closest('[x-node="abstrakt"]').length) return;
960                 if ($e.closest('[x-node="nota_red"]').length) return;
961                 if ($e.closest('[x-annotation-box="true"]').length) return;
962                 let node = $e.attr('x-node');
963                 if (node == 'numeracja') {
964                     number = 0;
965                 } else if (['werset', 'akap', 'wers'].includes(node)) {
966                     number ++;
967                     $e.attr('x-number', number);
968                 }
969             })
970         }
971     }
972
973     $.wiki.VisualPerspective = VisualPerspective;
974
975 })(jQuery);