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