X-Git-Url: http://source.bookstackapp.com/bookstack/blobdiff_plain/4a872012c5568c59aec7dc9825bee28902ce3431..refs/pull/1626/head:/resources/assets/js/components/page-display.js diff --git a/resources/assets/js/components/page-display.js b/resources/assets/js/components/page-display.js index a76160247..2be1c1c48 100644 --- a/resources/assets/js/components/page-display.js +++ b/resources/assets/js/components/page-display.js @@ -1,5 +1,7 @@ import Clipboard from "clipboard/dist/clipboard.min"; import Code from "../services/code"; +import * as DOM from "../services/dom"; +import {scrollAndHighlightElement} from "../services/util"; class PageDisplay { @@ -9,7 +11,6 @@ class PageDisplay { Code.highlight(); this.setupPointer(); - this.setupStickySidebar(); this.setupNavHighlighting(); // Check the hash on load @@ -19,173 +20,143 @@ class PageDisplay { } // Sidebar page nav click event - $('.sidebar-page-nav').on('click', 'a', event => { - this.goToText(event.target.getAttribute('href').substr(1)); - }); + const sidebarPageNav = document.querySelector('.sidebar-page-nav'); + if (sidebarPageNav) { + DOM.onChildEvent(sidebarPageNav, 'a', 'click', (event, child) => { + event.preventDefault(); + window.components['tri-layout'][0].showContent(); + const contentId = child.getAttribute('href').substr(1); + this.goToText(contentId); + window.history.pushState(null, null, '#' + contentId); + }); + } } goToText(text) { - let idElem = document.getElementById(text); - $('.page-content [data-highlighted]').attr('data-highlighted', '').css('background-color', ''); + const idElem = document.getElementById(text); + + DOM.forEach('.page-content [data-highlighted]', elem => { + elem.removeAttribute('data-highlighted'); + elem.style.backgroundColor = null; + }); + if (idElem !== null) { - window.scrollAndHighlight(idElem); + scrollAndHighlightElement(idElem); } else { - $('.page-content').find(':contains("' + text + '")').smoothScrollTo(); + const textElem = DOM.findText('.page-content > div > *', text); + if (textElem) { + scrollAndHighlightElement(textElem); + } } } setupPointer() { - if (document.getElementById('pointer') === null) return; + let pointer = document.getElementById('pointer'); + if (!pointer) { + return; + } + // Set up pointer - let $pointer = $('#pointer').detach(); + pointer = pointer.parentNode.removeChild(pointer); + const pointerInner = pointer.querySelector('div.pointer'); + + // Instance variables let pointerShowing = false; - let $pointerInner = $pointer.children('div.pointer').first(); let isSelection = false; let pointerModeLink = true; let pointerSectionId = ''; // Select all contents on input click - $pointer.on('click', 'input', event => { - $(this).select(); + DOM.onChildEvent(pointer, 'input', 'click', (event, input) => { + input.select(); event.stopPropagation(); }); - $pointer.on('click focus', event => { + // Prevent closing pointer when clicked or focused + DOM.onEvents(pointer, ['click', 'focus'], event => { event.stopPropagation(); }); // Pointer mode toggle - $pointer.on('click', 'span.icon', event => { + DOM.onChildEvent(pointer, 'span.icon', 'click', (event, icon) => { event.stopPropagation(); - let $icon = $(event.currentTarget); pointerModeLink = !pointerModeLink; - $icon.find('[data-icon="include"]').toggle(!pointerModeLink); - $icon.find('[data-icon="link"]').toggle(pointerModeLink); + icon.querySelector('[data-icon="include"]').style.display = (!pointerModeLink) ? 'inline' : 'none'; + icon.querySelector('[data-icon="link"]').style.display = (pointerModeLink) ? 'inline' : 'none'; updatePointerContent(); }); // Set up clipboard - let clipboard = new Clipboard($pointer[0].querySelector('button')); + new Clipboard(pointer.querySelector('button')); // Hide pointer when clicking away - $(document.body).find('*').on('click focus', event => { + DOM.onEvents(document.body, ['click', 'focus'], event => { if (!pointerShowing || isSelection) return; - $pointer.detach(); + pointer = pointer.parentElement.removeChild(pointer); pointerShowing = false; }); - let updatePointerContent = ($elem) => { + let updatePointerContent = (element) => { let inputText = pointerModeLink ? window.baseUrl(`/link/${this.pageId}#${pointerSectionId}`) : `{{@${this.pageId}#${pointerSectionId}}}`; - if (pointerModeLink && inputText.indexOf('http') !== 0) inputText = window.location.protocol + "//" + window.location.host + inputText; + if (pointerModeLink && !inputText.startsWith('http')) { + inputText = window.location.protocol + "//" + window.location.host + inputText; + } - $pointer.find('input').val(inputText); + pointer.querySelector('input').value = inputText; - // update anchor if present - const $editAnchor = $pointer.find('#pointer-edit'); - if ($editAnchor.length !== 0 && $elem) { - const editHref = $editAnchor.data('editHref'); - const element = $elem[0]; + // Update anchor if present + const editAnchor = pointer.querySelector('#pointer-edit'); + if (editAnchor && element) { + const editHref = editAnchor.dataset.editHref; const elementId = element.id; // get the first 50 characters. - let queryContent = element.textContent && element.textContent.substring(0, 50); - $editAnchor[0].href = `${editHref}?content-id=${elementId}&content-text=${encodeURIComponent(queryContent)}`; + const queryContent = element.textContent && element.textContent.substring(0, 50); + editAnchor.href = `${editHref}?content-id=${elementId}&content-text=${encodeURIComponent(queryContent)}`; } }; // Show pointer when selecting a single block of tagged content - $('.page-content [id^="bkmrk"]').on('mouseup keyup', function (e) { - e.stopPropagation(); - let selection = window.getSelection(); - if (selection.toString().length === 0) return; - - // Show pointer and set link - let $elem = $(this); - pointerSectionId = $elem.attr('id'); - updatePointerContent($elem); - - $elem.before($pointer); - $pointer.show(); - pointerShowing = true; - - // Set pointer to sit near mouse-up position - let pointerLeftOffset = (e.pageX - $elem.offset().left - ($pointerInner.width() / 2)); - if (pointerLeftOffset < 0) pointerLeftOffset = 0; - let pointerLeftOffsetPercent = (pointerLeftOffset / $elem.width()) * 100; - $pointerInner.css('left', pointerLeftOffsetPercent + '%'); - - isSelection = true; - setTimeout(() => { - isSelection = false; - }, 100); - }); - } - - setupStickySidebar() { - // Make the sidebar stick in view on scroll - let $window = $(window); - let $sidebar = $("#sidebar .scroll-body"); - let $bookTreeParent = $sidebar.parent(); - - // Check the page is scrollable and the content is taller than the tree - let pageScrollable = ($(document).height() > ($window.height() + 40)) && ($sidebar.height() < $('.page-content').height()); - - // Get current tree's width and header height - let headerHeight = $("#header").height() + $(".toolbar").height(); - let isFixed = $window.scrollTop() > headerHeight; - - // Fix the tree as a sidebar - function stickTree() { - $sidebar.width($bookTreeParent.width() - 32); - $sidebar.addClass("fixed"); - isFixed = true; - } - - // Un-fix the tree back into position - function unstickTree() { - $sidebar.css('width', 'auto'); - $sidebar.removeClass("fixed"); - isFixed = false; - } - - // Checks if the tree stickiness state should change - function checkTreeStickiness(skipCheck) { - let shouldBeFixed = $window.scrollTop() > headerHeight; - if (shouldBeFixed && (!isFixed || skipCheck)) { - stickTree(); - } else if (!shouldBeFixed && (isFixed || skipCheck)) { - unstickTree(); - } - } - // The event ran when the window scrolls - function windowScrollEvent() { - checkTreeStickiness(false); - } - - // If the page is scrollable and the window is wide enough listen to scroll events - // and evaluate tree stickiness. - if (pageScrollable && $window.width() > 1000) { - $window.on('scroll', windowScrollEvent); - checkTreeStickiness(true); - } - - // Handle window resizing and switch between desktop/mobile views - $window.on('resize', event => { - if (pageScrollable && $window.width() > 1000) { - $window.on('scroll', windowScrollEvent); - checkTreeStickiness(true); - } else { - $window.off('scroll', windowScrollEvent); - unstickTree(); - } + DOM.forEach('.page-content [id^="bkmrk"]', bookMarkElem => { + DOM.onEvents(bookMarkElem, ['mouseup', 'keyup'], event => { + event.stopPropagation(); + let selection = window.getSelection(); + if (selection.toString().length === 0) return; + + // Show pointer and set link + pointerSectionId = bookMarkElem.id; + updatePointerContent(bookMarkElem); + + bookMarkElem.parentNode.insertBefore(pointer, bookMarkElem); + pointer.style.display = 'block'; + pointerShowing = true; + isSelection = true; + + // Set pointer to sit near mouse-up position + requestAnimationFrame(() => { + const bookMarkBounds = bookMarkElem.getBoundingClientRect(); + let pointerLeftOffset = (event.pageX - bookMarkBounds.left - 164); + if (pointerLeftOffset < 0) { + pointerLeftOffset = 0 + } + const pointerLeftOffsetPercent = (pointerLeftOffset / bookMarkBounds.width) * 100; + + pointerInner.style.left = pointerLeftOffsetPercent + '%'; + + setTimeout(() => { + isSelection = false; + }, 100); + }); + + }); }); } setupNavHighlighting() { // Check if support is present for IntersectionObserver - if (!'IntersectionObserver' in window || - !'IntersectionObserverEntry' in window || - !'intersectionRatio' in window.IntersectionObserverEntry.prototype) { + if (!('IntersectionObserver' in window) || + !('IntersectionObserverEntry' in window) || + !('intersectionRatio' in window.IntersectionObserverEntry.prototype)) { return; } @@ -207,8 +178,8 @@ class PageDisplay { let pageNavObserver = new IntersectionObserver(headingVisibilityChange, intersectOpts); // observe each heading - for (let i = 0; i !== headings.length; ++i) { - pageNavObserver.observe(headings[i]); + for (let heading of headings) { + pageNavObserver.observe(heading); } } @@ -220,15 +191,9 @@ class PageDisplay { } function toggleAnchorHighlighting(elementId, shouldHighlight) { - let anchorsToHighlight = pageNav.querySelectorAll('a[href="#' + elementId + '"]'); - for (let i = 0; i < anchorsToHighlight.length; i++) { - // Change below to use classList.toggle when IE support is dropped. - if (shouldHighlight) { - anchorsToHighlight[i].classList.add('current-heading'); - } else { - anchorsToHighlight[i].classList.remove('current-heading'); - } - } + DOM.forEach('a[href="#' + elementId + '"]', anchor => { + anchor.closest('li').classList.toggle('current-heading', shouldHighlight); + }); } } }