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