+
+
+
+ let zakladki = {};
+ $.get({
+ url: '/zakladki/',
+ success: function(data) {
+ zakladki = data;
+ $.each(zakladki, (i, e) => {
+ zakladkaUpdateFor(
+ // TODO: not just paragraphs.
+ $('[href="#' + e.anchor + '"]').nextAll('.paragraph').first()
+ );
+ });
+ }
+ });
+
+ // TODO: create bookmarks on init
+ // We need to do that from anchors.
+
+ function zakladkaUpdateFor($item) {
+
+ let anchor = $item.prevAll('.target').first().attr('name');
+
+ if (anchor in zakladki) {
+ let $booktag = $item.data('booktag');
+ if (!$booktag) {
+
+ // TODO: only copy without the dialog.
+ $booktag = $("<div class='zakladka'>");
+ $booktag.append($('.icon', $zakladka).clone());
+
+ $item.data('booktag', $booktag);
+ $booktag.data('p', $item);
+ $booktag.data('anchor', anchor);
+ $zakladka.after($booktag);
+
+ zakladkaSetPosition($booktag);
+ $booktag.show();
+ }
+
+ $z = $booktag;
+ if (zakladki[anchor].note) {
+ $z.removeClass('zakladka-exists');
+ $z.addClass('zakladka-note');
+ } else {
+ $z.removeClass('zakladka-note');
+ $z.addClass('zakladka-exists');
+ }
+ } else {
+ let $booktag = $item.data('booktag');
+ if ($booktag) {
+ $item.data('booktag', null);
+ $zakladka.append($("#zakladka-box"));
+ $booktag.remove();
+ }
+ }
+ }
+
+ function zakladkaSetPosition($z) {
+ $item = $z.data('p');
+ pos = getPositionInBookText($item);
+ $z.css({
+ display: 'block',
+ top: pos.y,
+ right: ($('#main-text').width() - $('#book-text').width()) / 2,
+ });
+ }
+
+ let $zakladka = $('#zakladka');
+ $('#book-text .paragraph').on('mouseover', function() {showMarker($(this));});
+ $('#book-text .verse').on('mouseover', function() {showMarker($(this));});
+ //$.PMarker.showForP(this);
+
+
+ function showMarker(p) {
+
+ // Close the currently tag box when moving to another one.
+ // TBD: Do we want to keep the box open and prevent moving?
+ $("#zakladka-box").hide();
+
+ let anchor = p.prevAll('.target').first().attr('name');
+ // Don't bother if there's no anchor to use.
+ if (!anchor) return;
+
+ // Only show tag if there is not already a tag for this p.
+ if (p.data('booktag')) {
+ $zakladka.hide();
+ } else {
+ $zakladka.data('p', p);
+ $zakladka.data('anchor', anchor);
+
+ // (not needed) zakladkaUpdateClass();
+ // TODO: UPDATE THIS ON OPEN?
+ //let note = anchor in zakladki ? zakladki[anchor].note : '';
+ //$('textarea', $zakladka).val(note);
+
+ zakladkaSetPosition($zakladka);
+ $zakladka.show();
+ }
+ }
+
+ $(".zakladka-tool_zakladka").on('click', function() {
+ let $z = $("#zakladka-box").data('z');
+ let anchor = $z.data('anchor');
+ let $p = $z.data('p');
+ $.post({
+ url: '/zakladki/',
+ data: {
+ csrfmiddlewaretoken: csrf,
+ anchor: anchor
+ },
+ success: function(data) {
+ zakladki[data.anchor] = data;
+ $("#zakladka-box").hide();
+
+ // Just hide, and create new .zakladka if not already exists?
+ // In general no hiding 'classed' .zakladka.
+ // So the 'cursor' .zakladka doesn't ever need class update.
+ //zakladkaUpdateClass();
+ zakladkaUpdateFor($p);
+
+ }
+ });
+ });
+
+ $(".zakladka-tool_notka_text textarea").on('input', function() {
+ // FIXME: no use const $zakladka here, check which .zakladka are we attached to.
+ let $z = $(this).closest('.zakladka');
+ let anchor = $z.data('anchor');
+
+ $("#notka-saved").hide();
+ //$("#notka-save").show();
+ $.post({
+ url: '/zakladki/' + zakladki[anchor].uuid + '/',
+ data: {
+ csrfmiddlewaretoken: csrf,
+ note: $(this).val()
+ },
+ success: function(data) {
+ zakladki[anchor] = data;
+ zakladkaUpdateFor($z.data('p'));
+ $("#notka-save").hide();
+ $("#notka-saved").fadeIn();
+ }
+ });
+ });
+
+ $(".zakladka-tool_zakladka_delete").on('click', function() {
+ let $z = $(this).closest('.zakladka');
+ let anchor = $z.data('anchor');
+ $.post({
+ url: '/zakladki/' + zakladki[anchor].uuid + '/delete/',
+ data: {
+ csrfmiddlewaretoken: csrf,
+ },
+ success: function(data) {
+ delete zakladki[anchor];
+ $("#zakladka-box").hide();
+ zakladkaUpdateFor($z.data('p'));
+ }
+ });
+ });
+
+ $("#main-text").on("click", ".zakladka .icon", function() {
+ let $z = $(this).closest('.zakladka');
+ let $box = $("#zakladka-box");
+ $z.append($box);
+ $box.data('z', $z);
+
+ let $p = $z.data('p');
+ let anchor = $z.data('anchor');
+ let note = anchor in zakladki ? zakladki[anchor].note : '';
+
+ $('.zakladka-tool_zakladka', $box).toggle(!(anchor in zakladki));
+ $('.zakladka-tool_sluchaj', $box).toggle($p.hasClass('syncable')).data('sync', $p.attr('id'));
+ $('textarea', $box).val(note);
+
+ $box.toggle();
+ });
+
+
+ class QBox {
+ constructor(qbox) {
+ this.qbox = qbox;
+ }
+ showForSelection(sel) {
+ // TODO: only consider ranges inside text.?
+ this.selection = sel;
+
+ // TODO: multiple ranges.
+ let range = sel.getRangeAt(0);
+ let rect = range.getBoundingClientRect();
+
+ putNoteAt(this.qbox, range)
+ }
+ showForBlock(b) {
+ let rect = b.getBoundingClientRect();
+
+ putNoteAt(this.qbox, b, 'left')
+ }
+ hide() {
+ this.qbox.data('anchoredTo', null);
+ this.qbox.fadeOut();
+ }
+ hideCopied() {
+ this.qbox.data('anchoredTo', null);
+ this.qbox.addClass('copied').fadeOut(1500, () => {
+ this.qbox.removeClass('copied');
+ });
+ }
+
+ copyText() {
+ // TODO: only consider ranges inside text.?
+ let range = this.selection.getRangeAt(0);
+ let e = range.startContainer;
+ let anchor = getIdForElem(e);
+ let text = window.location.protocol + '//' +
+ window.location.host +
+ window.location.pathname;
+
+ navigator.clipboard.writeText(
+ this.selection.toString() +
+ '\n\nCałość czytaj na: ' + text
+ );
+ this.hideCopied();
+ }
+ copyLink() {
+ // TODO: only consider ranges inside text.?
+ let range = this.selection.getRangeAt(0);
+ let e = range.startContainer;
+ let anchor = getIdForElem(e);
+ let text = window.location.protocol + '//' +
+ window.location.host +
+ window.location.pathname;
+ if (anchor) text += '#' + anchor;
+ navigator.clipboard.writeText(text);
+
+ this.hideCopied();
+ }
+ quote() {
+ // What aboot non-contiguous selections?
+ let sel = this.selection;
+ let textContent = sel.toString();
+ let anchor = getIdForElem(sel.getRangeAt(0).startContainer);
+ let paths = getSelectionPaths(sel);
+ $.post({
+ url: '/cytaty/',
+ data: {
+ csrfmiddlewaretoken: csrf,
+ text: textContent,
+ startElem: anchor,
+ //endElem: endElem,
+ //startOffset: 0,
+ //endOffset: 0,
+ paths: paths,
+ },
+ success: function (data) {
+ var win = window.open('/cytaty/' + data.uuid + '/', '_blank');
+ }
+ });
+
+ }
+
+ }
+ let qbox = new QBox($("#qbox"));
+
+
+ function getPathToNode(elem) {
+ // Need normalize?
+ let path = [];
+ while (elem.id != 'book-text') {
+ let p = elem.parentElement;
+ path.unshift([...p.childNodes].indexOf(elem))
+ elem = p;
+ }
+ return path;
+ }
+ function getSelectionPaths(selection) {
+ // does it work?
+ let range1 = selection.getRangeAt(0);
+ let range2 = selection.getRangeAt(selection.rangeCount - 1);
+ let paths = [
+ getPathToNode(range1.startContainer) + [range1.startOffset],
+ getPathToNode(range2.endContainer) + [range2.endOffset]
+ ]
+ return paths;
+ }
+
+
+ function getIdForElem(elem) {
+ // is it used?
+ let $elem = $(elem);
+ // check if inside book-text?
+
+ while (true) {
+ if ($elem.hasClass('target')) {
+ return $elem.attr('name');
+ }
+ $p = $elem.prev();
+ if ($p.length) {
+ $elem = $p;
+ } else {
+ // Gdy wychodzimy w górę -- to jest ten moment, w którym znajdujemy element od którego wychodzimy i zliczamy znaki.
+
+
+ $p = $elem.parent()
+ if ($p.length) {
+ // is there text?
+ $elem = $p;
+ } else {
+ return undefined;
+ }
+ }
+ }
+ }
+
+ function getIdForElem(elem) {
+ // is it used?
+ // check if inside book-text?
+ $elem = $(elem);
+ while (true) {
+ if ($elem.hasClass('target')) {
+ return $elem.attr('name');
+ }
+ $p = $elem.prev();
+ if ($p.length) {
+ $elem = $p;
+ } else {
+ $p = $elem.parent()
+ if ($p.length) {
+ // is there text?
+ $elem = $p;
+ } else {
+ return undefined;
+ }
+ }
+ }
+ }
+
+
+ function positionToIIDOffset(container, offset) {
+ // Container and offset follow Range rules.
+ // If container is a text node, offset is text offset.
+ // If container is an element node, offset is number of child nodes from container start.
+ // (containers of type Comment, CDATASection - ignore)z
+ }
+
+
+ function updateQBox() {
+ sel = document.getSelection();
+ let goodS = true;
+ if (sel.isCollapsed || sel.rangeCount < 1) {
+ goodS = false;
+ }
+
+ if (!goodS) {
+ qbox.hide();
+ } else {
+ qbox.showForSelection(sel);
+ }
+ };
+ $(document).on('selectionchange', updateQBox);
+
+ function updateBoxes() {
+ updateNote(qbox.qbox);
+ updateNote($('#annotation-box'));
+
+ }
+ $(window).on('scroll', updateBoxes);
+ $(window).on('resize', updateBoxes);
+
+
+ $(window).on('resize', function() {
+ $('.zakladka').each(function() {
+ zakladkaSetPosition($(this));
+ });
+ });
+
+ $('a.anchor').on('click', function(e) {
+ // Workaround for bad TOC markers.
+ if ($(this).closest('#toc').length) return;
+ if ($(this).closest('#wltoc').length) return;
+ e.preventDefault();
+
+ let sel = window.getSelection();
+ sel.removeAllRanges();
+ let range = document.createRange();
+
+ let $p = $(this).nextAll('.paragraph').first()
+ range.selectNode($p[0]);
+ sel.addRange(range);
+
+ qbox.showForSelection(sel);
+
+ showMarker($p);
+ });
+
+
+
+ $('.qbox-t-copy').on('click', function(e) {
+ e.preventDefault();
+ qbox.copyText();
+ });
+ $('.qbox-t-link').on('click', function(e) {
+ e.preventDefault();
+ qbox.copyLink();
+ });
+ $('.qbox-t-quote').on('click', function(e) {
+ e.preventDefault();
+ qbox.quote();
+ });
+
+
+ /*
+ $(".paragraph").on('click', function(e) {
+ qbox.showForBlock(this);
+ });
+ */
+
+
+ function scrollToAnchor(anchor) {
+ if (anchor) {
+ var anchor_name = anchor.slice(1);
+ var element = $('a[name="' + anchor_name + '"]');
+ if (element.length > 0) {
+ $("html").animate({
+ scrollTop: element.offset().top - 55
+ }, {
+ duration: 500,
+ done: function() {
+ history.pushState({}, '', anchor);
+ },
+ });
+ }
+ }
+ }
+ scrollToAnchor(window.location.hash)
+ $('#toc, #themes, #book-text, #annotation').on('click', 'a', function(event) {
+ event.preventDefault();
+ scrollToAnchor($(this).attr('href'));
+ });
+
+