X-Git-Url: https://git.mdrn.pl/wolnelektury.git/blobdiff_plain/d9c51b94bfe6b1cda1790d57a9108b83c6e1643b..f7d8acded4a58d423035c5759f6dee9a34396959:/src/wolnelektury/static/js/book_text/references.js diff --git a/src/wolnelektury/static/js/book_text/references.js b/src/wolnelektury/static/js/book_text/references.js index cdfc1f06f..146067523 100644 --- a/src/wolnelektury/static/js/book_text/references.js +++ b/src/wolnelektury/static/js/book_text/references.js @@ -1,4 +1,5 @@ (function($){$(function(){ + let csrf = $("[name='csrfmiddlewaretoken']").val(); var interestingReferences = $("#interesting-references").text(); if (interestingReferences) { @@ -104,4 +105,579 @@ _paq.push(['trackEvent', 'html', 'reference']); }); + + + function putNoteAt($elem, anchor, side) { + $elem.data('anchoredTo', anchor); + updateNote($elem, side); + } + + function updateNote($elem, side) { + anchor = $elem.data('anchoredTo') + if (!anchor) return; + let anchorRect = anchor.getBoundingClientRect(); + + let x = anchorRect.x + anchorRect.width / 2; + let y = anchorRect.y; + if ($elem.data('attach-bottom')) { + y += anchorRect.height; + } + minx = $("#book-text").position().left; + maxx = minx + $("#book-text").width(); + + margin = 20; + minx += margin; + maxx -= margin; + maxx += 10000; + + //boxwidth = 470; + boxwidth = $elem.width(); + + if (maxx - minx <= boxwidth) { + nx = margin; + right = margin; + leftoffset = x - margin; + } else { + right = ''; + + // default position. + leftoffset = 40; + leftoffset = $elem.data('default-leftoffset'); + + nx = x - leftoffset; + + $elem.css({right: ''}); + + // Do we need to move away from the left? + if (nx < minx) { + let d = minx - nx; + nx += d; + leftoffset -= d; + } + + // Do we need to move away from the right? + if (nx + boxwidth > maxx) { + // ACTUALLY CALCULATE STUFF + // if maxx - minx < 470 px -- daj z lewej do prawej i już! + + right = ''; + let d = nx + boxwidth - maxx; + //if (leftoffset + d > $elem.width() - 10) d = $elem.width() - leftoffset - 10; + nx -= d; + leftoffset += d; + } + } + $elem.css({ + left: nx, + right: right + }); + if (!$elem.data('attach-bottom')) { + ny = y - $elem.height() - 10; + } else { + ny = y + 10; + } + $elem.css({ + top: ny + }); + $('.pointer', $elem).css({ + left: leftoffset - 6 + }); + + $elem.css({ + display: "block" + }); + } + + function closeNoteBox() { + $('#annotation-box').data('anchoredTo', null).fadeOut(); + } + $(document).on('click', function(event) { + let t = $(event.target); + if (t.parents('#annotation-box').length && !t.is('#footnote-link')) { + return; + } + closeNoteBox(); + }); + $(window).on('resize', closeNoteBox); + + function getPositionInBookText($e) { + let x = 0, y = 0; + + // Ok dla Y, nie ok dla X + + while ($e.attr('id') != 'book-text') { + let p = $e.position(); + x += p.left; + y += p.top; + $e = $e.offsetParent(); + break; + } + return {"x": x, "y": y} + } + + $('#book-text .annotation').on('click', function(event) { + if ($(this).parents('#footnotes').length) return; + event.preventDefault(); + + + + let x = $(this).width() / 2, y = 0; + let elem = $(this); + while (elem.attr('id') != 'book-text') { + let p = $(elem).position(); + x += p.left; + y += p.top; + elem = elem.parent(); + } + href = $(this).attr('href').substr(1); + content = $("[name='" + href + "']").next().next().html(); + if (!content) return; + $("#annotation-content").html(content); + $("#footnote-link").attr('href', '#' + href) + + + putNoteAt($('#annotation-box'), this); + event.stopPropagation(); + }); + + + + 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 = $("
"); + $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) { + 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')); + }); + + })})(jQuery);