Your IP : 216.73.216.79


Current Path : /var/www/html/bookstack/resources/js/components/
Upload File :
Current File : /var/www/html/bookstack/resources/js/components/page-display.js

import Clipboard from "clipboard/dist/clipboard.min";
import * as DOM from "../services/dom";
import {scrollAndHighlightElement} from "../services/util";

class PageDisplay {

    constructor(elem) {
        this.elem = elem;
        this.pageId = elem.getAttribute('page-display');

        window.importVersioned('code').then(Code => Code.highlight());
        this.setupPointer();
        this.setupNavHighlighting();
        this.setupDetailsCodeBlockRefresh();

        // Check the hash on load
        if (window.location.hash) {
            let text = window.location.hash.replace(/\%20/g, ' ').substr(1);
            this.goToText(text);
        }

        // Sidebar page nav click event
        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) {
        const idElem = document.getElementById(text);

        DOM.forEach('.page-content [data-highlighted]', elem => {
            elem.removeAttribute('data-highlighted');
            elem.style.backgroundColor = null;
        });

        if (idElem !== null) {
            scrollAndHighlightElement(idElem);
        } else {
            const textElem = DOM.findText('.page-content > div > *', text);
            if (textElem) {
                scrollAndHighlightElement(textElem);
            }
        }
    }

    setupPointer() {
        let pointer = document.getElementById('pointer');
        if (!pointer) {
            return;
        }

        // Set up pointer
        pointer = pointer.parentNode.removeChild(pointer);
        const pointerInner = pointer.querySelector('div.pointer');

        // Instance variables
        let pointerShowing = false;
        let isSelection = false;
        let pointerModeLink = true;
        let pointerSectionId = '';

        // Select all contents on input click
        DOM.onChildEvent(pointer, 'input', 'click', (event, input) => {
            input.select();
            event.stopPropagation();
        });

        // Prevent closing pointer when clicked or focused
        DOM.onEvents(pointer, ['click', 'focus'], event => {
            event.stopPropagation();
        });

        // Pointer mode toggle
        DOM.onChildEvent(pointer, 'span.icon', 'click', (event, icon) => {
            event.stopPropagation();
            pointerModeLink = !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
        new Clipboard(pointer.querySelector('button'));

        // Hide pointer when clicking away
        DOM.onEvents(document.body, ['click', 'focus'], event => {
            if (!pointerShowing || isSelection) return;
            pointer = pointer.parentElement.removeChild(pointer);
            pointerShowing = false;
        });

        let updatePointerContent = (element) => {
            let inputText = pointerModeLink ? window.baseUrl(`/link/${this.pageId}#${pointerSectionId}`) : `{{@${this.pageId}#${pointerSectionId}}}`;
            if (pointerModeLink && !inputText.startsWith('http')) {
                inputText = window.location.protocol + "//" + window.location.host + inputText;
            }

            pointer.querySelector('input').value = inputText;

            // 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.
                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
        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)) {
            return;
        }

        let pageNav = document.querySelector('.sidebar-page-nav');

        // fetch all the headings.
        let headings = document.querySelector('.page-content').querySelectorAll('h1, h2, h3, h4, h5, h6');
        // if headings are present, add observers.
        if (headings.length > 0 && pageNav !== null) {
            addNavObserver(headings);
        }

        function addNavObserver(headings) {
            // Setup the intersection observer.
            let intersectOpts = {
                rootMargin: '0px 0px 0px 0px',
                threshold: 1.0
            };
            let pageNavObserver = new IntersectionObserver(headingVisibilityChange, intersectOpts);

            // observe each heading
            for (let heading of headings) {
                pageNavObserver.observe(heading);
            }
        }

        function headingVisibilityChange(entries, observer) {
            for (let entry of entries) {
                let isVisible = (entry.intersectionRatio === 1);
                toggleAnchorHighlighting(entry.target.id, isVisible);
            }
        }

        function toggleAnchorHighlighting(elementId, shouldHighlight) {
            DOM.forEach('a[href="#' + elementId + '"]', anchor => {
                anchor.closest('li').classList.toggle('current-heading', shouldHighlight);
            });
        }
    }

    setupDetailsCodeBlockRefresh() {
        const onToggle = event => {
            const codeMirrors = [...event.target.querySelectorAll('.CodeMirror')];
            codeMirrors.forEach(cm => cm.CodeMirror && cm.CodeMirror.refresh());
        };

        const details = [...this.elem.querySelectorAll('details')];
        details.forEach(detail => detail.addEventListener('toggle', onToggle));
    }
}

export default PageDisplay;