Fix button multiplication issue.
[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">tytuł dzieła</button><button class="akap-edit-button">wyróżnienie</button><button class="akap-edit-button">słowo obce</button><button class="akap-edit-button">br</button><button class="akap-edit-button">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 buttonName = this.innerHTML;
576
577                     if(buttonName == "słowo obce") {
578                         startTag = "<slowo_obce>";
579                         endTag = "</slowo_obce>";
580                     } else if (buttonName == "wyróżnienie") {
581                         startTag = "<wyroznienie>";
582                         endTag = "</wyroznienie>";
583                     } else if (buttonName == "tytuł dzieła") {
584                         startTag = "<tytul_dziela>";
585                         endTag = "</tytul_dziela>";
586                     } else if (buttonName == "znak spec."){
587                         addSymbol();
588                         return false;
589                     } else if (buttonName == "br") {
590                         startTag = "<br/>";
591                         endTag = "";
592                     }
593
594                     var myField = textAreaOpened;
595
596                     //IE support
597                     if (document.selection) {
598                         textAreaOpened.focus();
599                         sel = document.selection.createRange();
600                         sel.text = startTag + sel.text + endTag;
601                     }
602                     //MOZILLA/NETSCAPE support
603                     else if (textAreaOpened.selectionStart || textAreaOpened.selectionStart == '0') {
604                         var startPos = textAreaOpened.selectionStart;
605                         var endPos = textAreaOpened.selectionEnd;
606                         textAreaOpened.value = textAreaOpened.value.substring(0, startPos)
607                             + startTag + textAreaOpened.value.substring(startPos, endPos) + endTag + textAreaOpened.value.substring(endPos, textAreaOpened.value.length);
608                     }
609                 });
610
611                 $('.accept-button', $overlay).click(function(){
612                     save();
613                     $(document).unbind('click.blur-overlay');
614                 });
615
616                 $(document).bind('click.blur-overlay', function(event){
617                     if ($(event.target).closest('.html-editarea, #specialCharsContainer').length > 0) {
618                         return;
619                     }
620                     save();
621                     $(document).unbind('click.blur-overlay');
622                 });
623
624             },
625             error: function(text){
626                 alert('Błąd! ' + text);
627             }
628         });
629     }
630
631     function createUwagaBefore(before) {
632         xml2html({
633             xml: '<uwaga/>',
634             success: function(element){
635                 let $element = $(element);
636                 $element.insertBefore(before);
637                 openForEdit($element);
638             }
639         });
640     }
641
642     class VisualPerspective extends $.wiki.Perspective {
643         constructor(options) {
644             super(options);
645             let self = this;
646             var element = $("#html-view");
647             var button = $('<button class="edit-button active-block-button">Edytuj</button>');
648             var uwagaButton = $('<button class="uwaga-button active-block-button">Uwaga</button>');
649
650             if (!CurrentDocument.readonly) {
651
652                 $('#html-view').bind('mousemove', function(event){
653                     var editable = $(event.target).closest('*[x-editable]');
654                     $('.active', element).not(editable).removeClass('active').children('.html-editarea-toolbar').remove();
655
656                     if (!editable.hasClass('active')) {
657                         var toolbar = $("<div class='html-editarea-toolbar'><div class='html-editarea-toolbar-left'></div></div>")
658                         editable.append(toolbar);
659                         var buttonSpace = $('.html-editarea-toolbar-left', toolbar);
660                         editable.addClass('active');
661                         buttonSpace.append(button);
662                         if (!editable.is('[x-edit-attribute]') &&
663                             !editable.is('.annotation-inline-box') &&
664                             !editable.is('[x-edit-no-format]')
665                            ) {
666                             buttonSpace.append(uwagaButton);
667                         }
668                     }
669                     if (editable.is('.annotation-inline-box')) {
670                         $('*[x-annotation-box]', editable).css({
671                         }).show();
672                     }
673                     if (editable.is('.reference-inline-box')) {
674                         let preview = $('*[x-preview]', editable);
675                         preview.show();
676                         let link = $("a", preview);
677                         let href = link.attr('href');
678                         if (link.attr('title') == '?' && href.startsWith('https://www.wikidata.org/wiki/')) {
679                             link.attr('title', '…');
680                             let qid = href.split('/').reverse()[0];
681                             $.ajax({
682                                 url: 'https://www.wikidata.org/w/rest.php/wikibase/v1/entities/items/' + qid + '?_fields=labels',
683                                 dataType: "json",
684                                 success: function(data) {
685                                     link.attr(
686                                         'title',
687                                         data['labels']['pl'] || data['labels']['en']
688                                     );
689                                 },
690                             });
691                         }
692                     }
693                 });
694
695                 self.caret = new Caret(element);
696
697                 $('#insert-reference-button').click(function(){
698                     self.flush();
699                     self.addReference();
700                     return false;
701                 });
702
703                 $('#insert-annotation-button').click(function(){
704                     self.flush();
705                     addAnnotation();
706                     return false;
707                 });
708
709                 $('#insert-theme-button').click(function(){
710                     self.flush();
711                     addTheme();
712                     return false;
713                 });
714
715                 $(".insert-inline-tag").click(function() {
716                     self.flush();
717                     self.insertInlineTag($(this).attr('data-tag'));
718                     return false;
719                 });
720
721                 $(".insert-char").click(function() {
722                     self.flush();
723                     addSymbol(caret=self.caret);
724                     return false;
725                 });
726
727                 $(document).on('click', '.edit-button', function(event){
728                     self.flush();
729                     event.preventDefault();
730                     openForEdit($(this).closest('.html-editarea-toolbar').parent());
731                 });
732
733                 $(document).on('click', '.uwaga-button', function(event){
734                     self.flush();
735                     event.preventDefault();
736                     createUwagaBefore($(this).closest('.html-editarea-toolbar').parent());
737                 });
738             }
739
740             $(document).on('click', '[x-node="motyw"]', function(){
741                 selectTheme($(this).attr('theme-class'));
742             });
743
744             element.on('click', '.annotation', function(event) {
745                 self.flush();
746                 event.preventDefault();
747                 event.redakcja_caret_ignore = true;
748                 $('[x-annotation-box]', $(this).parent()).toggleClass('editing');
749                 self.caret.detach();
750             });
751         }
752
753         onEnter(success, failure) {
754             super.onEnter();
755
756             $.blockUI({
757                 message: 'Uaktualnianie widoku...'
758             });
759
760             function _finalize(callback){
761                 $.unblockUI();
762                 if (callback)
763                     callback();
764             }
765
766             let self = this;
767             xml2html({
768                 xml: this.doc.text,
769                 base: this.doc.getBase(),
770                 success: function(element){
771
772                     var htmlView = $('#html-view');
773                     htmlView.html(element);
774                     self.renumber();
775                     if ('PropertiesPerspective' in $.wiki.perspectives)
776                         $.wiki.perspectives.PropertiesPerspective.enable();
777
778                     _finalize(success);
779                 },
780                 error: function(text, source){
781                     let err = '<p class="error">Wystąpił błąd:</p><p>'+text+'</p>';
782                     if (source)
783                         err += '<pre>'+source.replace(/&/g, '&amp;').replace(/</g, '&lt;')+'</pre>'
784                     $('#html-view').html(err);
785                     _finalize(failure);
786                 }
787             });
788         }
789
790         flush() {
791             let self = this;
792             return new Promise((resolve, reject) => {
793                 if ($('#html-view .error').length > 0) {
794                     reject()
795                 } else {
796                     //return _finalize(failure);
797                     html2text({
798                         element: $('#html-view').get(0),
799                         stripOuter: true,
800                         success: (text) => {
801                             self.doc.setText(text);
802                             resolve();
803                         },
804                         error: (text) => {
805                             reject(text);
806                             //$('#source-editor').html('<p>Wystąpił błąd:</p><pre>' + text + '</pre>');
807                         }
808                     });
809                 }
810             });
811         }
812
813         onExit(success, failure) {
814             var self = this;
815
816             self.caret.detach();
817
818             if ('PropertiesPerspective' in $.wiki.perspectives)
819                 $.wiki.perspectives.PropertiesPerspective.disable();
820
821             self.flush().then(() => {
822                 success && success();
823             }).catch((e) => {
824                 // TODO report
825                 console.log('REJECTED!', e);
826                 failure && failure();
827             });
828         };
829
830         insertInlineTag(tag) {
831             this.caret.detach();
832             let self = this;
833
834             let selection = window.getSelection();
835             var n = selection.rangeCount;
836             if (n != 1 || selection.isCollapsed) {
837                 window.alert("Nie zaznaczono obszaru");
838                 return false
839             }
840             let range = selection.getRangeAt(0);
841
842             // Make sure that:
843             // Both ends are in the same x-node container.
844             // Both ends are set to text nodes.
845             // TODO: That the container is a inline-text container.
846             let commonNode = range.endContainer;
847
848             if (commonNode.nodeType == Node.TEXT_NODE) {
849                 commonNode = commonNode.parentNode;
850             }
851             let node = range.startContainer;
852             if (node.nodeType == Node.TEXT_NODE) {
853                 node = node.parentNode;
854             }
855             if (node != commonNode) {
856                 window.alert("Zły obszar.");
857                 return false;
858             }
859
860             let end;
861             if (range.endContainer.nodeType == Node.TEXT_NODE) {
862                 end = range.endContainer.splitText(range.endOffset);
863             } else {
864                 end = document.createTextNode('');
865                 let cont = $(range.endContainer).contents();
866                 if (range.endOffset < cont.length) {
867                     range.endContainer.insertBefore(end, cont[range.endOffset])
868                 } else {
869                     range.endContainer.append(end);
870                 }
871             }
872
873             let current;
874             if (range.startContainer.nodeType == Node.TEXT_NODE) {
875                 current = range.startContainer.splitText(range.startOffset);
876             } else {
877                 current = document.createTextNode('');
878                 let cont = $(range.startContainer).contents();
879                 if (range.startOffset < cont.length) {
880                     range.startContainer.insertBefore(current, cont[range.startOffset])
881                 } else {
882                     startNode.append(current);
883                 }
884             }
885
886             // We will construct a HTML element with the range selected.
887             let div = $("<span x-pass-thru='true'>");
888             while (current != end) {
889                 n = current.nextSibling;
890                 $(current).appendTo(div);
891                 current = n;
892             }
893
894             html2text({
895                 element: div[0],
896                 success: function(d) {
897                     xml2html({
898                         xml: d = '<' + tag + '>' + d + '</' + tag + '>',
899                         success: function(html) {
900                             // What if no end?
901                             node.insertBefore($(html)[0], end);
902                             self.flush();
903                         }
904                     });
905                 },
906                 error: function(a, b) {
907                     console.log(a, b);
908                 }
909             });
910         }
911
912         insertAtRange(range, elem) {
913             let self = this;
914             let $end = $(range.endContainer);
915             if ($end.attr('id') == 'caret') {
916                 self.caret.insert(elem);
917             } else {
918                 range.insertNode(elem[0]);
919             }
920         }
921
922         addReference() {
923             let self = this;
924             var selection = window.getSelection();
925             var n = selection.rangeCount;
926
927             // TODO: if no selection, take caret position..
928             if (n == 0) {
929                 window.alert("Nie zaznaczono żadnego obszaru");
930                 return false;
931             }
932
933             var range = selection.getRangeAt(n - 1);
934             if (!verifyTagInsertPoint(range.endContainer)) {
935                 window.alert("Nie można wstawić w to miejsce referencji.");
936                 return false;
937             }
938
939             var tag = $('<span></span>');
940
941             range.collapse(false);
942             self.insertAtRange(range, tag);
943
944             xml2html({
945                 xml: '<ref href=""/>',
946                 success: function(text){
947                     var t = $(text);
948                     tag.replaceWith(t);
949                     openForEdit(t);
950                 },
951                 error: function(){
952                     tag.remove();
953                     alert('Błąd przy dodawaniu referncji:' + errors);
954                 }
955             })
956         }
957
958         renumber() {
959             let number = 0;
960             $('#html-view *').each((i, e) => {
961                 let $e = $(e);
962                 if ($e.closest('[x-node="abstrakt"]').length) return;
963                 if ($e.closest('[x-node="nota_red"]').length) return;
964                 if ($e.closest('[x-annotation-box="true"]').length) return;
965                 let node = $e.attr('x-node');
966                 if (node == 'numeracja') {
967                     number = 0;
968                 } else if (['werset', 'akap', 'wers'].includes(node)) {
969                     number ++;
970                     $e.attr('x-number', number);
971                 }
972             })
973         }
974     }
975
976     $.wiki.VisualPerspective = VisualPerspective;
977
978 })(jQuery);