]> BookStack Code Mirror - bookstack/blob - resources/js/components/page-display.js
Started refactor and alignment of component system
[bookstack] / resources / js / components / page-display.js
1 import * as DOM from "../services/dom";
2 import {scrollAndHighlightElement} from "../services/util";
3
4 class PageDisplay {
5
6     constructor(elem) {
7         this.elem = elem;
8         this.pageId = elem.getAttribute('page-display');
9
10         window.importVersioned('code').then(Code => Code.highlight());
11         this.setupNavHighlighting();
12         this.setupDetailsCodeBlockRefresh();
13
14         // Check the hash on load
15         if (window.location.hash) {
16             let text = window.location.hash.replace(/\%20/g, ' ').substr(1);
17             this.goToText(text);
18         }
19
20         // Sidebar page nav click event
21         const sidebarPageNav = document.querySelector('.sidebar-page-nav');
22         if (sidebarPageNav) {
23             DOM.onChildEvent(sidebarPageNav, 'a', 'click', (event, child) => {
24                 event.preventDefault();
25                 window.$components.first('tri-layout').showContent();
26                 const contentId = child.getAttribute('href').substr(1);
27                 this.goToText(contentId);
28                 window.history.pushState(null, null, '#' + contentId);
29             });
30         }
31     }
32
33     goToText(text) {
34         const idElem = document.getElementById(text);
35
36         DOM.forEach('.page-content [data-highlighted]', elem => {
37             elem.removeAttribute('data-highlighted');
38             elem.style.backgroundColor = null;
39         });
40
41         if (idElem !== null) {
42             scrollAndHighlightElement(idElem);
43         } else {
44             const textElem = DOM.findText('.page-content > div > *', text);
45             if (textElem) {
46                 scrollAndHighlightElement(textElem);
47             }
48         }
49     }
50
51     setupNavHighlighting() {
52         // Check if support is present for IntersectionObserver
53         if (!('IntersectionObserver' in window) ||
54             !('IntersectionObserverEntry' in window) ||
55             !('intersectionRatio' in window.IntersectionObserverEntry.prototype)) {
56             return;
57         }
58
59         let pageNav = document.querySelector('.sidebar-page-nav');
60
61         // fetch all the headings.
62         let headings = document.querySelector('.page-content').querySelectorAll('h1, h2, h3, h4, h5, h6');
63         // if headings are present, add observers.
64         if (headings.length > 0 && pageNav !== null) {
65             addNavObserver(headings);
66         }
67
68         function addNavObserver(headings) {
69             // Setup the intersection observer.
70             let intersectOpts = {
71                 rootMargin: '0px 0px 0px 0px',
72                 threshold: 1.0
73             };
74             let pageNavObserver = new IntersectionObserver(headingVisibilityChange, intersectOpts);
75
76             // observe each heading
77             for (let heading of headings) {
78                 pageNavObserver.observe(heading);
79             }
80         }
81
82         function headingVisibilityChange(entries, observer) {
83             for (let entry of entries) {
84                 let isVisible = (entry.intersectionRatio === 1);
85                 toggleAnchorHighlighting(entry.target.id, isVisible);
86             }
87         }
88
89         function toggleAnchorHighlighting(elementId, shouldHighlight) {
90             DOM.forEach('a[href="#' + elementId + '"]', anchor => {
91                 anchor.closest('li').classList.toggle('current-heading', shouldHighlight);
92             });
93         }
94     }
95
96     setupDetailsCodeBlockRefresh() {
97         const onToggle = event => {
98             const codeMirrors = [...event.target.querySelectorAll('.CodeMirror')];
99             codeMirrors.forEach(cm => cm.CodeMirror && cm.CodeMirror.refresh());
100         };
101
102         const details = [...this.elem.querySelectorAll('details')];
103         details.forEach(detail => detail.addEventListener('toggle', onToggle));
104     }
105 }
106
107 export default PageDisplay;