Onepager: when no wiki link.
[wolnelektury.git] / src / wolnelektury / static / js / book_text / references.js
1 (function($){$(function(){
2     let csrf = $("[name='csrfmiddlewaretoken']").val();
3
4     var interestingReferences = $("#interesting-references").text();
5     if (interestingReferences) {
6         interestingReferences = $.parseJSON(interestingReferences);
7     }
8     if (Object.keys(interestingReferences).length) {
9         $("#settings-references").css('display', 'block');
10     }
11
12     var map_enabled = false;
13     var marker = null;
14     var map = null;
15
16     function enable_map() {
17         if (!$("#reference-map").length) return;
18
19         $("#reference-map").show('slow');
20
21         if (map_enabled) return;
22
23         map = L.map('reference-map').setView([0, 0], 11);
24         L.tileLayer('https://tile.thunderforest.com/cycle/{z}/{x}/{y}.png?apikey=a8a97f0ae5134403ac38c1a075b03e15', {
25             attribution: 'Maps © <a href="http://www.thunderforest.com">Thunderforest</a>, Data © <a href="http://www.openstreetmap.org/copyright">OpenStreetMap contributors</a>'
26         }).addTo(map);
27         marker = L.circleMarker([0,0]);
28
29         map_enabled = true;
30     }
31     function disable_map() {
32         $("#reference-map").hide('slow');
33     }
34     
35
36     $("#reference-close").on("click", function(event) {
37         event.preventDefault();
38         $("#reference-box").hide();
39     });
40     
41     $('a.reference').each(function() {
42         $this = $(this);
43         uri = $this.attr('data-uri');
44         if (uri == '') {
45             $this.remove();
46             return;
47         }
48         if (interestingReferences.hasOwnProperty(uri)) {
49             $this.addClass('interesting');
50             ref = interestingReferences[uri];
51
52             if (ref.wikipedia_link) {
53                 $this.attr('href', ref.wikipedia_link);
54                 $this.attr('target', '_blank');
55             } else {
56                 $this.removeAttr('href')
57             }
58         }
59     });
60
61
62     $('a.reference.interesting').on('click', function(event) {
63         event.preventDefault();
64
65         $("#reference-box").show();
66
67         $this = $(this);
68         uri = $this.attr('data-uri');
69         ref = interestingReferences[uri];
70
71         if (ref.location) {
72             enable_map();
73
74             let newLoc = [
75                 ref.location[0],
76                 ref.location[1] + Math.round(
77                     (map.getCenter().lng - ref.location[1]) / 360
78                 ) * 360
79             ];
80
81             marker.setLatLng(newLoc);
82             marker.bindTooltip(ref.label).openTooltip();
83             map.addLayer(marker);
84
85             map.panTo(newLoc, {
86                 animate: true,
87                 duration: 1,
88             });
89         } else {
90             disable_map();
91             if (map) {
92                 map.removeLayer(marker);
93             }
94         }
95
96         $("#reference-images a").remove();
97         if (ref.images) {
98             $.each(ref.images, function(i, e) {
99                 $i = $("<a target='_blank'><img></a>");
100                 $i.attr('href', e.page);
101                 $('img', $i).attr('src', e.thumburl || e.url);
102                 if (e.thumbresolution) {
103                     $('img', $i).attr('width', e.thumbresolution[0]).attr('height', e.thumbresolution[1]);
104                 }
105
106                 $("#reference-images").append($i);
107             })
108         }
109
110         $("#reference-link").text(ref.label);
111         if (ref.wikipedia_link) {
112             $("#reference-link").attr('href', ref.wikipedia_link);
113         } else {
114             $('#reference-link').removeAttr('href');
115         }
116
117         $("#reference-print").attr('href', '/mapa/ulotka/' + ref.id);
118         
119         _paq.push(['trackEvent', 'html', 'reference']);
120     });
121
122
123     function putNoteAt($elem, anchor, side) {
124         $elem.data('anchoredTo', anchor);
125         updateNote($elem, side);
126     }
127
128     function updateNote($elem, side) {
129         anchor = $elem.data('anchoredTo')
130         if (!anchor) return;
131         let anchorRect = anchor.getBoundingClientRect();
132
133         let x = anchorRect.x + anchorRect.width / 2;
134         let y = anchorRect.y;
135         if ($elem.data('attach-bottom')) {
136             y += anchorRect.height;
137         }
138         minx = $("#book-text").position().left;
139         maxx = minx + $("#book-text").width();
140
141         margin = 20;
142         minx += margin;
143         maxx -= margin;
144         maxx += 10000;
145
146         //boxwidth = 470;
147         boxwidth = $elem.width();
148         
149         if (maxx - minx <= boxwidth) {
150             nx = margin;
151             right = margin;
152             leftoffset = x - margin;
153         } else {
154             right = '';
155
156             leftoffset = $elem.data('default-leftoffset');
157             if (leftoffset === undefined) {
158                 leftoffset = $elem.width() / 2;
159             }
160             
161             nx = x - leftoffset;
162
163             $elem.css({right: ''});
164
165             // Do we need to move away from the left?
166             if (nx < minx) {
167                 let d = minx - nx;
168                 nx += d;
169                 leftoffset -= d;
170             }
171
172             // Do we need to move away from the right?
173             if (nx + boxwidth > maxx) {
174                 right = '';
175                 let d = nx + boxwidth - maxx;
176                 nx -= d;
177                 leftoffset += d;
178             }
179         }
180         $elem.css({
181             left: nx,
182             right: right
183         });
184         $elem.css({
185             display: "block"
186         });
187         if (!$elem.data('attach-bottom')) {
188             ny = y - $elem.height() - 10;
189         } else {
190             ny = y + 10;
191         }
192         $elem.css({
193             top: ny
194         });
195         $('.pointer', $elem).css({
196             left: leftoffset - 6
197         });
198
199     }
200
201     function closeNoteBox() {
202         $('#annotation-box').data('anchoredTo', null).fadeOut();
203     }
204     $(document).on('click', function(event) {
205         let t = $(event.target);
206         if (t.parents('#annotation-box').length && !t.is('#footnote-link')) {
207             return;
208         }
209         closeNoteBox();
210     });
211     $(window).on('resize', closeNoteBox);
212
213     function getPositionInBookText($e) {
214         let x = 0, y = 0;
215
216         // Ok dla Y, nie ok dla X
217         
218         while ($e.attr('id') != 'book-text') {
219             let p = $e.position();
220             x += p.left;
221             y += p.top;
222             $e = $e.offsetParent();
223             break;
224         }
225         return {"x": x, "y": y}
226     }
227     
228     $('#book-text .annotation').on('click', function(event) {
229         if ($(this).parents('#footnotes').length) return;
230         event.preventDefault();
231
232         href = $(this).attr('href').substr(1);
233         content = $("[name='" + href + "']").next().next().html();
234         if (!content) return;
235         $("#annotation-content").html(content);
236         $("#footnote-link").attr('href', '#' + href)
237
238         putNoteAt($('#annotation-box'), this);
239         event.stopPropagation();
240     });
241
242
243     
244     let zakladki = {};
245     $.get({
246         url: '/zakladki/',
247         success: function(data) {
248             zakladki = data;
249             $.each(zakladki, (i, e) => {
250                 zakladkaUpdateFor(
251                     // TODO: not just paragraphs.
252                     $('[href="#' + e.anchor + '"]').nextAll('.paragraph').first()
253                 );
254             });
255         }
256     });
257
258     // TODO: create bookmarks on init
259     // We need to do that from anchors.
260     
261     function zakladkaUpdateFor($item) {
262
263         let anchor = $item.prevAll('.target').first().attr('name');
264         
265         if (anchor in zakladki) {
266             let $booktag = $item.data('booktag');
267             if (!$booktag) {
268
269                 // TODO: only copy without the dialog.
270                 $booktag = $("<div class='zakladka'>");
271                 $booktag.append($('.icon', $zakladka).clone());
272                 
273                 $item.data('booktag', $booktag);
274                 $booktag.data('p', $item);
275                 $booktag.data('anchor', anchor);
276                 $zakladka.after($booktag);
277
278                 zakladkaSetPosition($booktag);
279                 $booktag.show();
280             }
281
282             $z = $booktag;
283             if (zakladki[anchor].note) {
284                 $z.removeClass('zakladka-exists');
285                 $z.addClass('zakladka-note');
286             } else {
287                 $z.removeClass('zakladka-note');
288                 $z.addClass('zakladka-exists');
289             }
290         } else {
291             let $booktag = $item.data('booktag');
292             if ($booktag) {
293                 $item.data('booktag', null);
294                 $zakladka.append($("#zakladka-box"));
295                 $booktag.remove();
296             }
297         }
298     }
299
300     function zakladkaSetPosition($z) {
301         $item = $z.data('p');
302         pos = getPositionInBookText($item);
303         $z.css({
304             display: 'block',
305             top: pos.y,
306             right: ($('#main-text').width() - $('#book-text').width()) / 2,
307         });
308     }
309
310     let $zakladka = $('#zakladka');
311     $('#book-text .paragraph').on('mouseover', function() {showMarker($(this));});
312     $('#book-text .verse').on('mouseover', function() {showMarker($(this));});
313         //$.PMarker.showForP(this);
314
315
316     function showMarker(p) {
317         
318         // Close the currently tag box when moving to another one.
319         // TBD: Do we want to keep the box open and prevent moving?
320         $("#zakladka-box").hide();
321
322         let anchor = p.prevAll('.target').first().attr('name');
323         // Don't bother if there's no anchor to use.
324         if (!anchor) return;
325
326         // Only show tag if there is not already a tag for this p.
327         if (p.data('booktag')) {
328             $zakladka.hide();
329         } else {
330             $zakladka.data('p', p);
331             $zakladka.data('anchor', anchor);
332
333             // (not needed) zakladkaUpdateClass();
334             // TODO: UPDATE THIS ON OPEN?
335             //let note = anchor in zakladki ? zakladki[anchor].note : ''; 
336             //$('textarea', $zakladka).val(note);
337
338             zakladkaSetPosition($zakladka);
339             $zakladka.show();
340         }
341     }
342
343     $(".zakladka-tool_zakladka").on('click', function() {
344         let $z = $("#zakladka-box").data('z');
345         let anchor = $z.data('anchor');
346         let $p = $z.data('p');
347         $.post({
348             url: '/zakladki/',
349             data: {
350                 csrfmiddlewaretoken: csrf,
351                 anchor: anchor
352             },
353             success: function(data) {
354                 zakladki[data.anchor] = data;
355                 $("#zakladka-box").hide();
356
357                 // Just hide, and create new .zakladka if not already exists?
358                 // In general no hiding 'classed' .zakladka.
359                 // So the 'cursor' .zakladka doesn't ever need class update.
360                 //zakladkaUpdateClass();
361                 zakladkaUpdateFor($p);
362
363             }
364         });
365     });
366
367     $(".zakladka-tool_notka_text textarea").on('input', function() {
368         // FIXME: no use const $zakladka here, check which .zakladka are we attached to.
369         let $z = $(this).closest('.zakladka');
370         let anchor = $z.data('anchor');
371
372         $("#notka-saved").hide();
373         //$("#notka-save").show();
374         $.post({
375             url: '/zakladki/' + zakladki[anchor].uuid + '/',
376             data: {
377                 csrfmiddlewaretoken: csrf,
378                 note: $(this).val()
379             },
380             success: function(data) {
381                 zakladki[anchor] = data;
382                 zakladkaUpdateFor($z.data('p'));
383                 $("#notka-save").hide();
384                 $("#notka-saved").fadeIn();
385             }
386         });
387     });
388
389     $(".zakladka-tool_zakladka_delete").on('click', function() {
390         let $z = $(this).closest('.zakladka');
391         let anchor = $z.data('anchor');
392         $.post({
393             url: '/zakladki/' + zakladki[anchor].uuid + '/delete/',
394             data: {
395                 csrfmiddlewaretoken: csrf,
396             },
397             success: function(data) {
398                 delete zakladki[anchor];
399                 $("#zakladka-box").hide();
400                 zakladkaUpdateFor($z.data('p'));
401             }
402         });
403     });
404
405     $("#main-text").on("click", ".zakladka .icon", function() {
406         let $z = $(this).closest('.zakladka');
407         let $box = $("#zakladka-box");
408         $z.append($box);
409         $box.data('z', $z);
410
411         let $p = $z.data('p');
412         let anchor = $z.data('anchor');
413         let note = anchor in zakladki ? zakladki[anchor].note : ''; 
414
415         $('.zakladka-tool_zakladka', $box).toggle(!(anchor in zakladki));
416         $('.zakladka-tool_sluchaj', $box).toggle($p.hasClass('syncable')).data('sync', $p.attr('id'));
417         $('textarea', $box).val(note);
418
419         $box.toggle();
420     });
421
422
423     class QBox {
424         constructor(qbox) {
425             this.qbox = qbox;
426         }
427         showForSelection(sel) {
428             // TODO: only consider ranges inside text.?
429             this.selection = sel;
430
431             // TODO: multiple ranges.
432             let range = sel.getRangeAt(0);
433             let rect = range.getBoundingClientRect();
434
435             putNoteAt(this.qbox, range)
436         }
437         showForBlock(b) {
438             let rect = b.getBoundingClientRect();
439
440             putNoteAt(this.qbox, b, 'left')
441         }
442         hide() {
443             this.qbox.data('anchoredTo', null);
444             this.qbox.fadeOut();
445         }
446         hideCopied() {
447             this.qbox.data('anchoredTo', null);
448             this.qbox.addClass('copied').fadeOut(1500, () => {
449                 this.qbox.removeClass('copied');
450             });
451         }
452
453         copyText() {
454             // TODO: only consider ranges inside text.?
455             let range = this.selection.getRangeAt(0);
456             let e = range.startContainer;
457             let anchor = getIdForElem(e);
458             let text = window.location.protocol + '//' +
459                 window.location.host +
460                 window.location.pathname;
461
462             navigator.clipboard.writeText(
463                 this.selection.toString() +
464                     '\n\nCałość czytaj na: ' + text
465             );
466             this.hideCopied();
467         }
468         copyLink() {
469             // TODO: only consider ranges inside text.?
470             let range = this.selection.getRangeAt(0);
471             let e = range.startContainer;
472             let anchor = getIdForElem(e);
473             let text = window.location.protocol + '//' +
474                 window.location.host +
475                 window.location.pathname;
476             if (anchor) text += '#' + anchor;
477             navigator.clipboard.writeText(text);
478             
479             this.hideCopied();
480         }
481         quote() {
482             // What aboot non-contiguous selections?
483             let sel = this.selection;
484             let textContent = sel.toString();
485             let anchor = getIdForElem(sel.getRangeAt(0).startContainer);
486             let paths = getSelectionPaths(sel);
487             $.post({
488                 url: '/cytaty/',
489                 data: {
490                     csrfmiddlewaretoken: csrf,
491                     text: textContent,
492                     startElem: anchor,
493                     //endElem: endElem,
494                     //startOffset: 0,
495                     //endOffset: 0,
496                     paths: paths,
497                 },
498                 success: function (data) {
499                     var win = window.open('/cytaty/' + data.uuid + '/', '_blank');
500                 }
501             });
502             
503         }
504         
505     }
506     let qbox = new QBox($("#qbox"));
507
508
509     function getPathToNode(elem) {
510         // Need normalize?
511         let path = [];
512         while (elem.id != 'book-text') {
513             let p = elem.parentElement;
514             path.unshift([...p.childNodes].indexOf(elem))
515             elem = p;
516         }
517         return path;
518     }
519     function getSelectionPaths(selection) {
520         // does it work?
521         let range1 = selection.getRangeAt(0);
522         let range2 = selection.getRangeAt(selection.rangeCount - 1);
523         let paths = [
524             getPathToNode(range1.startContainer) + [range1.startOffset],
525             getPathToNode(range2.endContainer) + [range2.endOffset]
526         ]
527         return paths;
528     }
529     
530
531     function getIdForElem(elem) {
532         // is it used?
533         let $elem = $(elem);
534         // check if inside book-text?
535
536         while (true) {
537             if ($elem.hasClass('target')) {
538                 return $elem.attr('name');
539             }
540             $p = $elem.prev();
541             if ($p.length) {
542                 $elem = $p;
543             } else {
544                 // Gdy wychodzimy w górę -- to jest ten moment, w którym znajdujemy element od którego wychodzimy i zliczamy znaki.
545
546                 
547                 $p = $elem.parent()
548                 if ($p.length) {
549                     // is there text?
550                     $elem = $p;
551                 } else {
552                     return undefined;
553                 }
554             }
555         }
556     }
557
558     function getIdForElem(elem) {
559         // is it used?
560         // check if inside book-text?
561         $elem = $(elem);
562         while (true) {
563             if ($elem.hasClass('target')) {
564                 return $elem.attr('name');
565             }
566             $p = $elem.prev();
567             if ($p.length) {
568                 $elem = $p;
569             } else {
570                 $p = $elem.parent()
571                 if ($p.length) {
572                     // is there text?
573                     $elem = $p;
574                 } else {
575                     return undefined;
576                 }
577             }
578         }
579     }
580
581
582     function positionToIIDOffset(container, offset) {
583         // Container and offset follow Range rules.
584         // If container is a text node, offset is text offset.
585         // If container is an element node, offset is number of child nodes from container start.
586         // (containers of type Comment, CDATASection - ignore)z
587     }
588
589
590     function updateQBox() {
591         sel = document.getSelection();
592         let goodS = true;
593         if (sel.isCollapsed || sel.rangeCount < 1) {
594             goodS = false;
595         }
596         
597         if (!goodS) {
598             qbox.hide();
599         } else {
600             qbox.showForSelection(sel);
601         }
602     };
603     $(document).on('selectionchange', updateQBox);
604
605     function updateBoxes() {
606         updateNote(qbox.qbox);
607         updateNote($('#annotation-box'));
608         
609     }
610     $(window).on('scroll', updateBoxes);
611     $(window).on('resize', updateBoxes);
612
613
614     $(window).on('resize', function() {
615         $('.zakladka').each(function() {
616             zakladkaSetPosition($(this));
617         });
618     });
619
620     $('a.anchor').on('click', function(e) {
621         // Workaround for bad TOC markers.
622         if ($(this).closest('#toc').length) return;
623         if ($(this).closest('.toc').length) return;
624         if ($(this).closest('#wltoc').length) return;
625         e.preventDefault();
626
627         let sel = window.getSelection();
628         sel.removeAllRanges();
629         let range = document.createRange();
630
631         let $p = $(this).nextAll('.paragraph').first()
632         range.selectNodeContents($p[0]);
633         sel.addRange(range);
634         
635         qbox.showForSelection(sel);
636
637         showMarker($p);
638     });
639     
640    
641     
642     $('.qbox-t-copy').on('click', function(e) {
643         e.preventDefault();
644         qbox.copyText();
645     });
646     $('.qbox-t-link').on('click', function(e) {
647         e.preventDefault();
648         qbox.copyLink();
649     });
650     $('.qbox-t-quote').on('click', function(e) {
651         e.preventDefault();
652         qbox.quote();
653     });
654
655
656     /*
657     $(".paragraph").on('click', function(e) {
658         qbox.showForBlock(this);
659     });
660     */
661
662     
663     function scrollToAnchor(anchor) {
664         if (anchor) {
665             var anchor_name = anchor.slice(1);
666             var element = $('a[name="' + anchor_name + '"]');
667             if (element.length > 0) {
668                 $("html").animate({
669                     scrollTop: element.offset().top - 55
670                 }, {
671                     duration: 500,
672                     done: function() {
673                         history.pushState({}, '', anchor);
674                     },
675                 });
676                 return true;
677             }
678         }
679     }
680     scrollToAnchor(window.location.hash)
681     $('#toc, #themes, #book-text, #annotation, .scroll').on('click', 'a', function(event) {
682         if (scrollToAnchor($(this).attr('href'))) {
683             event.preventDefault();
684         }
685     });
686     
687 })})(jQuery);