Fixes for inserting footnotes, references.
[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', '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
125
126
127     /* Insert theme using current selection */
128
129     function addTheme(){
130         var selection = window.getSelection();
131         var n = selection.rangeCount;
132
133         if (n == 0) {
134             window.alert("Nie zaznaczono żadnego obszaru");
135             return false;
136         }
137
138         // for now allow only 1 range
139         if (n > 1) {
140             window.alert("Zaznacz jeden obszar.");
141             return false;
142         }
143
144
145         // remember the selected range
146         var range = selection.getRangeAt(0);
147
148
149         if ($(range.startContainer).is('.html-editarea') ||
150         $(range.endContainer).is('.html-editarea')) {
151             window.alert("Motywy można oznaczać tylko na tekście nie otwartym do edycji. \n Zamknij edytowany fragment i spróbuj ponownie.");
152             return false;
153         }
154
155         // verify if the start/end points make even sense -
156         // they must be inside a x-node (otherwise they will be discarded)
157         // and the x-node must be a main text
158         if (!verifyThemeBoundaryPoint(range.startContainer)) {
159             window.alert("Motyw nie może się zaczynać w tym miejscu.");
160             return false;
161         }
162
163         if (!verifyThemeBoundaryPoint(range.endContainer)) {
164             window.alert("Motyw nie może się kończyć w tym miejscu.");
165             return false;
166         }
167
168         var date = (new Date()).getTime();
169         var random = Math.floor(4000000000 * Math.random());
170         var id = ('' + date) + '-' + ('' + random);
171
172         var createPoint = function(container, offset) {
173             var offsetBetweenCommas = function(text, offset) {
174                 if(text.length < 2 || offset < 1 || offset > text.length)
175                     return false;
176                 return text[offset-1] === ',' && text[offset] === ',';
177             }
178             var point = document.createRange();
179             offset = offsetBetweenCommas(container.textContent, offset) ? offset - 1 : offset;
180             point.setStart(container, offset);
181             return point;
182         }
183
184         var spoint = createPoint(range.startContainer, range.startOffset);
185         var epoint = createPoint(range.endContainer, range.endOffset);
186
187         var mtag, btag, etag, errors;
188
189         // insert theme-ref
190
191         xml2html({
192             xml: '<end id="e' + id + '" />',
193             success: function(text){
194                 etag = $('<span></span>');
195                 epoint.insertNode(etag[0]);
196                 etag.replaceWith(text);
197                 xml2html({
198                     xml: '<motyw id="m' + id + '"></motyw>',
199                     success: function(text){
200                         mtag = $('<span></span>');
201                         spoint.insertNode(mtag[0]);
202                         mtag.replaceWith(text);
203                         xml2html({
204                             xml: '<begin id="b' + id + '" />',
205                             success: function(text){
206                                 btag = $('<span></span>');
207                                 spoint.insertNode(btag[0])
208                                 btag.replaceWith(text);
209                                 selection.removeAllRanges();
210                                 openForEdit($('[x-node="motyw"][theme-class="' + id + '"]'));
211                             }
212                         });
213                     }
214                 });
215             }
216         });
217     }
218
219     function addSymbol(caret) {
220         let editArea;
221
222         if (caret) {
223             editArea = $("textarea", caret.element)[0];
224         } else {
225             editArea = $('div.html-editarea textarea')[0];
226         }
227
228         if(editArea) {
229             var specialCharsContainer = $("<div id='specialCharsContainer'><a href='#' id='specialCharsClose'>Zamknij</a><table id='tableSpecialChars' style='width: 600px;'></table></div>");
230
231             var specialChars = [' ', 'Ą','ą','Ć','ć','Ę','ę','Ł','ł','Ń','ń','Ó','ó','Ś','ś','Ż','ż','Ź','ź','Á','á','À','à',
232             'Â','â','Ä','ä','Å','å','Ā','ā','Ă','ă','Ã','ã',
233             'Æ','æ','Ç','ç','Č','č','Ċ','ċ','Ď','ď','É','é','È','è',
234             'Ê','ê','Ë','ë','Ē','ē','Ě','ě','Ġ','ġ','Ħ','ħ','Í','í','Î','î',
235             'Ī','ī','Ĭ','ĭ','Ľ','ľ','Ñ','ñ','Ň','ň','Ó','ó','Ö','ö',
236             'Ô','ô','Ō','ō','Ǒ','ǒ','Œ','œ','Ø','ø','Ř','ř','Š',
237             'š','Ş','ş','Ť','ť','Ţ','ţ','Ű','ű','Ú','ú','Ù','ù',
238             'Ü','ü','Ů','ů','Ū','ū','Û','û','Ŭ','ŭ',
239             'Ý','ý','Ž','ž','ß','Ð','ð','Þ','þ','А','а','Б',
240             'б','В','в','Г','г','Д','д','Е','е','Ё','ё','Ж',
241             'ж','З','з','И','и','Й','й','К','к','Л','л','М',
242             'м','Н','н','О','о','П','п','Р','р','С','с',
243             'Т','т','У','у','Ф','ф','Х','х','Ц','ц','Ч',
244             'ч','Ш','ш','Щ','щ','Ъ','ъ','Ы','ы','Ь','ь','Э',
245             'э','Ю','ю','Я','я','ѓ','є','і','ї','ј','љ','њ',
246             'Ґ','ґ','Α','α','Β','β','Γ','γ','Δ','δ','Ε','ε',
247             'Ζ','ζ','Η','η','Θ','θ','Ι','ι','Κ','κ','Λ','λ','Μ',
248             'μ','Ν','ν','Ξ','ξ','Ο','ο','Π','π','Ρ','ρ','Σ','ς','σ',
249             'Τ','τ','Υ','υ','Φ','φ','Χ','χ','Ψ','ψ','Ω','ω','–',
250             '—','¡','¿','$','¢','£','€','©','®','°','¹','²','³',
251             '¼','½','¾','†','§','‰','•','←','↑','→','↓',
252             '„','”','„”','«','»','«»','»«','’','[',']','~','|','−','·',
253             '×','÷','≈','≠','±','≤','≥','∈'];
254             var tableContent = "<tr>";
255
256             for(var i in specialChars) {
257                 if(i % 14 == 0 && i > 0) {
258                     tableContent += "</tr><tr>";
259                 }
260                 tableContent += "<td><input type='button' class='specialBtn' value='"+specialChars[i]+"'/></td>";
261             }
262
263             tableContent += "</tr>";
264             $("body").append(specialCharsContainer);
265
266
267              // localStorage for recently used characters - reading
268              if (typeof(localStorage) != 'undefined') {
269                  if (localStorage.getItem("recentSymbols")) {
270                      var recent = localStorage.getItem("recentSymbols");
271                      var recentArray = recent.split(";");
272                      var recentRow = "";
273                      for(var i in recentArray.reverse()) {
274                         recentRow += "<td><input type='button' class='specialBtn recentSymbol' value='"+recentArray[i]+"'/></td>";
275                      }
276                      recentRow = "<tr>" + recentRow + "</tr>";
277                  }
278              }
279             $("#tableSpecialChars").append(recentRow);
280             $("#tableSpecialChars").append(tableContent);
281
282             /* events */
283
284             $('.specialBtn').click(function(){
285                 var insertVal = $(this).val();
286
287                 // if we want to surround text with quotes
288                 // not sure if just check if value has length == 2
289
290                 if (caret) {
291                     caret.insertChar(insertVal);
292                     caret.focus();
293                 } else {
294                     if (insertVal.length == 2) {
295                         var startTag = insertVal[0];
296                         var endTag = insertVal[1];
297                         var textAreaOpened = editArea;
298                         //IE support
299                         if (document.selection) {
300                             textAreaOpened.focus();
301                             sel = document.selection.createRange();
302                             sel.text = startTag + sel.text + endTag;
303                         }
304                         //MOZILLA/NETSCAPE support
305                         else if (textAreaOpened.selectionStart || textAreaOpened.selectionStart == '0') {
306                             var startPos = textAreaOpened.selectionStart;
307                             var endPos = textAreaOpened.selectionEnd;
308                             textAreaOpened.value = textAreaOpened.value.substring(0, startPos)
309                                 + startTag + textAreaOpened.value.substring(startPos, endPos) + endTag + textAreaOpened.value.substring(endPos, textAreaOpened.value.length);
310                         }
311                     } else {
312                         insertAtCaret(editArea, insertVal);
313                     }
314                 }
315
316                 // localStorage for recently used characters - saving
317                 if (typeof(localStorage) != 'undefined') {
318                     if (localStorage.getItem("recentSymbols")) {
319                         var recent = localStorage.getItem("recentSymbols");
320                         var recentArray = recent.split(";");
321                         var valIndex = $.inArray(insertVal, recentArray);
322                         //alert(valIndex);
323                         if(valIndex == -1) {
324                             // value not present in array yet
325                             if(recentArray.length > 13){
326                                 recentArray.shift();
327                                 recentArray.push(insertVal);
328                             } else {
329                                 recentArray.push(insertVal);
330                             }
331                         } else  {
332                             // value already in the array
333                             for(var i = valIndex; i < recentArray.length; i++){
334                                 recentArray[i] = recentArray[i+1];
335                             }
336                             recentArray[recentArray.length-1] = insertVal;
337                         }
338                         localStorage.setItem("recentSymbols", recentArray.join(";"));
339                     } else {
340                         localStorage.setItem("recentSymbols", insertVal);
341                     }
342                 }
343                 $(specialCharsContainer).remove();
344             });
345             $('#specialCharsClose').click(function(){
346                 $(specialCharsContainer).remove();
347             });
348
349         } else {
350             window.alert('Najedź na fragment tekstu, wybierz "Edytuj" i ustaw kursor na miejscu gdzie chcesz wstawić symbol.');
351         }
352     }
353
354     function insertAtCaret(txtarea,text) {
355         /* http://www.scottklarr.com/topic/425/how-to-insert-text-into-a-textarea-where-the-cursor-is/ */
356         var scrollPos = txtarea.scrollTop;
357         var strPos = 0;
358         var backStart = 0;
359         var br = ((txtarea.selectionStart || txtarea.selectionStart == '0') ? "ff" : (document.selection ? "ie" : false ) );
360         if (br == "ie") {
361             txtarea.focus();
362             var range = document.selection.createRange();
363             range.moveStart ('character', -txtarea.value.length);
364             strPos = backStart = range.text.length;
365         } else if (br == "ff") {
366             strPos = txtarea.selectionStart;
367             backStart = txtarea.selectionEnd;
368         }
369         var front = (txtarea.value).substring(0,strPos);
370         var back = (txtarea.value).substring(backStart,txtarea.value.length);
371         txtarea.value=front+text+back;
372         strPos = strPos + text.length;
373         if (br == "ie") {
374             txtarea.focus();
375             var range = document.selection.createRange();
376             range.moveStart ('character', -txtarea.value.length);
377             range.moveStart ('character', strPos);
378             range.moveEnd ('character', 0);
379             range.select();
380         } else if (br == "ff") {
381             txtarea.selectionStart = strPos;
382             txtarea.selectionEnd = strPos;
383             txtarea.focus();
384         }
385         txtarea.scrollTop = scrollPos;
386     }
387
388     /* open edition window for selected fragment */
389     function openForEdit($origin){
390         var $box = null
391
392         // annotations overlay their sub box - not their own box //
393         if ($origin.is(".annotation-inline-box")) {
394             $box = $("*[x-annotation-box]", $origin);
395         }
396         else {
397             $box = $origin;
398         }
399         var x = $box[0].offsetLeft;
400         var y = $box[0].offsetTop;
401
402         var w = $box.outerWidth();
403         var h = $box.innerHeight();
404
405         if ($origin.is(".annotation-inline-box")) {
406             w = Math.max(w, 400);
407             h = Math.max(h, 60);
408             if($('.htmlview div').offset().left + $('.htmlview div').width() > ($('.vsplitbar').offset().left - 480)){
409                 x = -(Math.max($origin.offset().left, $origin.width()));
410             } else {
411                 x = 100;
412             }
413         }
414         if ($origin.is('.reference-inline-box')) {
415             w = 400;
416             h = 32;
417             y -= 32;
418             x = Math.min(
419                 x,
420                 $('.htmlview div').offset().left + $('.htmlview div').width() - 400
421             );
422         }
423
424         // start edition on this node
425         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({
426             position: 'absolute',
427             height: h,
428             left: x,
429             top: y,
430             width: w
431         }).appendTo($box[0].offsetParent || $box.parent()).show();
432
433
434         if ($origin.is('*[x-edit-no-format]')) {
435             $('.akap-edit-button').remove();
436         }
437
438         if ($origin.is('[x-node="motyw"]')) {
439             $.themes.autocomplete($('textarea', $overlay));
440         }
441
442         if ($origin.is('[x-node="motyw"]')){
443             $('.delete-button', $overlay).click(function(){
444                 if (window.confirm("Czy jesteś pewien, że chcesz usunąć ten motyw?")) {
445                     $('[theme-class="' + $origin.attr('theme-class') + '"]').remove();
446                     $overlay.remove();
447                     $(document).unbind('click.blur-overlay');
448                     return false;
449                 };
450             });
451         }
452         else if($box.is('*[x-annotation-box]') || $origin.is('*[x-edit-attribute]') || $origin.is('*[x-node="uwaga"]')) {
453             let q;
454             switch ($origin.attr('x-node')) {
455             case 'uwaga':
456                 q = 'tę uwagę';
457                 break;
458             case 'ref':
459                 q = 'tę referencję';
460                 break
461             default:
462                 q = 'ten przypis';
463             }
464             $('.delete-button', $overlay).click(function(){
465                 if (window.confirm("Czy jesteś pewien, że chcesz usunąć " + q + "?")) {
466                     $origin.remove();
467                     $overlay.remove();
468                     $(document).unbind('click.blur-overlay');
469                     return false;
470                 };
471             });
472         }
473         else {
474             $('.delete-button', $overlay).html("Anuluj");
475             $('.delete-button', $overlay).click(function(){
476                 if (window.confirm("Czy jesteś pewien, że chcesz anulować zmiany?")) {
477                     $overlay.remove();
478                     $(document).unbind('click.blur-overlay');
479                     return false;
480                 };
481             });
482         }
483
484
485         var serializer = new XMLSerializer();
486
487         if($box.attr("x-edit-attribute")) {
488             source = $('<span x-pass-thru="true"/>');
489             source.text($box.attr("x-a-wl-" + $box.attr("x-edit-attribute")));
490             source = source[0];
491         } else {
492             source = $box[0];
493         }
494
495         html2text({
496             element: source,
497             stripOuter: true,
498             success: function(text){
499                 $('textarea', $overlay).val($.trim(text));
500
501                 setTimeout(function(){
502                     $('textarea', $overlay).elastic().focus();
503                 }, 50);
504
505                 function save(argument){
506                     var nodeName = $box.attr('x-node') || 'pe';
507                     var insertedText = $('textarea', $overlay).val();
508
509                     if ($origin.is('[x-node="motyw"]')) {
510                         insertedText = insertedText.replace(/,\s*$/, '');
511                     }
512
513                     if($box.attr("x-edit-attribute")) {
514                         xml = '<' + nodeName + ' ' + $box.attr("x-edit-attribute") + '="' + insertedText + '"/>';
515                     } else {
516                         xml = '<' + nodeName + '>' + insertedText + '</' + nodeName + '>';
517                     }
518
519
520                     xml2html({
521                         xml: xml,
522                         success: function(element){
523                             if (nodeName == 'out-of-flow-text') {
524                                 $(element).children().insertAfter($origin);
525                                 $origin.remove()
526                             }
527                             else if ($box.attr('x-edit-attribute')) {
528                                 $(element).insertAfter($origin);
529                                 $origin.remove();
530                             }
531                             else {
532                                 $origin.html($(element).html());
533                             }
534                             $overlay.remove();
535                         },
536                         error: function(text){
537                             alert('Błąd! ' + text);
538                         }
539                     })
540
541                     var msg = $("<div class='saveNotify'><p>Pamiętaj, żeby zapisać swoje zmiany.</p></div>");
542                     $("#base").prepend(msg);
543                     $('#base .saveNotify').fadeOut(3000, function(){
544                         $(this).remove();
545                     });
546                 }
547
548                 $('.akap-edit-button', $overlay).click(function(){
549                         var textAreaOpened = $('textarea', $overlay)[0];
550                         var startTag = "";
551                         var endTag = "";
552                         var buttonName = this.innerHTML;
553
554                         if(buttonName == "słowo obce") {
555                                 startTag = "<slowo_obce>";
556                                 endTag = "</slowo_obce>";
557                         } else if (buttonName == "wyróżnienie") {
558                                 startTag = "<wyroznienie>";
559                                 endTag = "</wyroznienie>";
560                         } else if (buttonName == "tytuł dzieła") {
561                                 startTag = "<tytul_dziela>";
562                                 endTag = "</tytul_dziela>";
563                         } else if(buttonName == "znak spec."){
564                             addSymbol();
565                             return false;
566                         }
567
568                         var myField = textAreaOpened;
569
570                         //IE support
571                         if (document.selection) {
572                             textAreaOpened.focus();
573                             sel = document.selection.createRange();
574                             sel.text = startTag + sel.text + endTag;
575                         }
576                         //MOZILLA/NETSCAPE support
577                         else if (textAreaOpened.selectionStart || textAreaOpened.selectionStart == '0') {
578                             var startPos = textAreaOpened.selectionStart;
579                             var endPos = textAreaOpened.selectionEnd;
580                             textAreaOpened.value = textAreaOpened.value.substring(0, startPos)
581                                   + startTag + textAreaOpened.value.substring(startPos, endPos) + endTag + textAreaOpened.value.substring(endPos, textAreaOpened.value.length);
582                         }
583                 });
584
585                 $('.accept-button', $overlay).click(function(){
586                     save();
587                     $(document).unbind('click.blur-overlay');
588                 });
589
590                 $(document).bind('click.blur-overlay', function(event){
591                     if ($(event.target).closest('.html-editarea, #specialCharsContainer').length > 0) {
592                         return;
593                     }
594                     save();
595                     $(document).unbind('click.blur-overlay');
596                 });
597
598             },
599             error: function(text){
600                 alert('Błąd! ' + text);
601             }
602         });
603     }
604
605     function createUwagaBefore(before) {
606         xml2html({
607             xml: '<uwaga/>',
608             success: function(element){
609                 let $element = $(element);
610                 $element.insertBefore(before);
611                 openForEdit($element);
612             }
613         });
614     }
615
616     function VisualPerspective(options){
617         perspective = self = this;
618
619         var old_callback = options.callback;
620
621         options.callback = function(){
622             var element = $("#html-view");
623             var button = $('<button class="edit-button active-block-button">Edytuj</button>');
624             var uwagaButton = $('<button class="uwaga-button active-block-button">Uwaga</button>');
625
626             if (!CurrentDocument.readonly) {
627
628                 $('#html-view').bind('mousemove', function(event){
629                     var editable = $(event.target).closest('*[x-editable]');
630                     $('.active', element).not(editable).removeClass('active').children('.active-block-button').remove();
631
632                     if (!editable.hasClass('active')) {
633                         editable.addClass('active').append(button);
634                         if (!editable.is('[x-edit-attribute]') &&
635                             !editable.is('.annotation-inline-box') &&
636                             !editable.is('[x-edit-no-format]')
637                            ) {
638                             editable.append(uwagaButton);
639                         }
640                     }
641                     if (editable.is('.annotation-inline-box')) {
642                         $('*[x-annotation-box]', editable).css({
643                         }).show();
644                     }
645                 });
646
647                 perspective.caret = new Caret(element);
648                 
649                 $('#insert-reference-button').click(function(){
650                     self.addReference();
651                     return false;
652                 });
653
654                 $('#insert-annotation-button').click(function(){
655                     addAnnotation();
656                     return false;
657                 });
658
659                 $('#insert-theme-button').click(function(){
660                     addTheme();
661                     return false;
662                 });
663
664
665                 $(".insert-inline-tag").click(function() {
666                     perspective.insertInlineTag($(this).attr('data-tag'));
667                     return false;
668                 });
669
670                 $(".insert-char").click(function() {
671                     addSymbol(caret=perspective.caret);
672                     return false;
673                 });
674
675                 $(document).on('click', '.edit-button', function(event){
676                     event.preventDefault();
677                     openForEdit($(this).parent());
678                 });
679
680                 $(document).on('click', '.uwaga-button', function(event){
681                     event.preventDefault();
682                     createUwagaBefore($(this).parent());
683                 });
684             }
685
686             $(document).on('click', '[x-node="motyw"]', function(){
687                 selectTheme($(this).attr('theme-class'));
688             });
689
690             element.on('click', '.annotation', function(event) {
691                 event.preventDefault();
692                 event.redakcja_caret_ignore = true;
693                 $('[x-annotation-box]', $(this).parent()).toggleClass('editing');
694                 perspective.caret.detach();
695             });
696
697             old_callback.call(this);
698         };
699
700         $.wiki.Perspective.call(this, options);
701     };
702
703     VisualPerspective.prototype = new $.wiki.Perspective();
704
705     VisualPerspective.prototype.onEnter = function(success, failure){
706         $.wiki.Perspective.prototype.onEnter.call(this);
707
708         $.blockUI({
709             message: 'Uaktualnianie widoku...'
710         });
711
712         function _finalize(callback){
713             $.unblockUI();
714             if (callback)
715                 callback();
716         }
717
718         perspective = this;
719         xml2html({
720             xml: this.doc.text,
721             base: this.doc.getBase(),
722             success: function(element){
723
724                 var htmlView = $('#html-view');
725                 htmlView.html(element);
726
727                 _finalize(success);
728             },
729             error: function(text, source){
730                 err = '<p class="error">Wystąpił błąd:</p><p>'+text+'</p>';
731                 if (source)
732                     err += '<pre>'+source.replace(/&/g, '&amp;').replace(/</g, '&lt;')+'</pre>'
733                 $('#html-view').html(err);
734                 _finalize(failure);
735             }
736         });
737     };
738
739     VisualPerspective.prototype.onExit = function(success, failure){
740         var self = this;
741
742         self.caret.detach();
743
744         $.wiki.exitTab('#PropertiesPerspective');
745         
746         $.blockUI({
747             message: 'Zapisywanie widoku...'
748         });
749
750         function _finalize(callback){
751             $.unblockUI();
752             if (callback)
753                 callback();
754         }
755
756         if ($('#html-view .error').length > 0)
757             return _finalize(failure);
758
759         html2text({
760             element: $('#html-view').get(0),
761             stripOuter: true,
762             success: function(text){
763                 self.doc.setText(text);
764                 _finalize(success);
765             },
766             error: function(text){
767                 $('#source-editor').html('<p>Wystąpił błąd:</p><pre>' + text + '</pre>');
768                 _finalize(failure);
769             }
770         });
771     };
772
773     VisualPerspective.prototype.insertInlineTag = function(tag) {
774         this.caret.detach();
775
776         let selection = window.getSelection();
777         var n = selection.rangeCount;
778         if (n != 1 || selection.isCollapsed) {
779             window.alert("Nie zaznaczono obszaru");
780             return false
781         }
782         let range = selection.getRangeAt(0);
783
784         // Make sure that:
785         // Both ends are in the same x-node container.
786         // TODO: That the container is a inline-text container.
787         let node = range.startContainer;
788         if (node.nodeType == node.TEXT_NODE) {
789             node = node.parentNode;
790         }
791         let endNode = range.endContainer;
792         if (endNode.nodeType == endNode.TEXT_NODE) {
793             endNode = endNode.parentNode;
794         }
795         if (node != endNode) {
796             window.alert("Zły obszar.");
797             return false;
798         }
799
800         // We will construct a HTML element with the range selected.
801         let div = $("<span x-pass-thru='true'>");
802
803         contents = $(node).contents();
804         let startChildIndex = node == range.startContainer ? 0 : contents.index(range.startContainer);
805         let endChildIndex = contents.index(range.endContainer);
806
807         current = range.startContainer;
808         if (current.nodeType == current.TEXT_NODE) {
809             current = current.splitText(range.startOffset);
810         }
811         while (current != range.endContainer) {
812             n = current.nextSibling;
813             $(current).appendTo(div);
814             current = n;
815         }
816         if (current.nodeType == current.TEXT_NODE) {
817             end = current.splitText(range.endOffset);
818         }
819         $(current).appendTo(div);
820         
821         html2text({
822             element: div[0],
823             success: function(d) {
824                 xml2html({
825                     xml: d = '<' + tag + '>' + d + '</' + tag + '>',
826                     success: function(html) {
827                         // What if no end?
828                         node.insertBefore($(html)[0], end);
829                     }
830                 });
831             },
832             error: function(a, b) {
833                 console.log(a, b);
834             }
835         });
836     };
837
838     VisualPerspective.prototype.insertAtRange = function(range, elem) {
839         let self = this;
840         let $end = $(range.endContainer);
841         if ($end.attr('id') == 'caret') {
842             self.caret.insert(elem);
843         } else {
844             range.insertNode(elem[0]);
845         }
846     }
847
848     VisualPerspective.prototype.addReference = function() {
849         let self = this;
850         var selection = window.getSelection();
851         var n = selection.rangeCount;
852
853         // TODO: if no selection, take caret position..
854         if (n == 0) {
855             window.alert("Nie zaznaczono żadnego obszaru");
856             return false;
857         }
858
859         var range = selection.getRangeAt(n - 1);
860         if (!verifyTagInsertPoint(range.endContainer)) {
861             window.alert("Nie można wstawić w to miejsce referencji.");
862             return false;
863         }
864
865         var tag = $('<span></span>');
866
867         range.collapse(false);
868         self.insertAtRange(range, tag);
869
870         xml2html({
871             xml: '<ref href=""/>',
872             success: function(text){
873                 var t = $(text);
874                 tag.replaceWith(t);
875                 openForEdit(t);
876             },
877             error: function(){
878                 tag.remove();
879                 alert('Błąd przy dodawaniu referncji:' + errors);
880             }
881         })
882     }
883
884     $.wiki.VisualPerspective = VisualPerspective;
885
886 })(jQuery);