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