]> BookStack Code Mirror - bookstack/blob - resources/assets/js/pages/page-show.js
Finished migrated from icon-font to SVG
[bookstack] / resources / assets / js / pages / page-show.js
1 const Clipboard = require("clipboard");
2 const Code = require('../libs/code');
3
4 let setupPageShow = window.setupPageShow = function (pageId) {
5
6     Code.highlight();
7
8     if (!pageId) return;
9
10     // Set up pointer
11     let $pointer = $('#pointer').detach();
12     let pointerShowing = false;
13     let $pointerInner = $pointer.children('div.pointer').first();
14     let isSelection = false;
15     let pointerModeLink = true;
16     let pointerSectionId = '';
17
18     // Select all contents on input click
19     $pointer.on('click', 'input', event => {
20         $(this).select();
21         event.stopPropagation();
22     });
23
24     $pointer.on('click focus', event => {
25         event.stopPropagation();
26     });
27
28     // Pointer mode toggle
29     $pointer.on('click', 'span.icon', event => {
30         event.stopPropagation();
31         let $icon = $(event.currentTarget);
32         pointerModeLink = !pointerModeLink;
33         $icon.find('[data-icon="include"]').toggle(!pointerModeLink);
34         $icon.find('[data-icon="link"]').toggle(pointerModeLink);
35         updatePointerContent();
36     });
37
38     // Set up clipboard
39     let clipboard = new Clipboard($pointer[0].querySelector('button'));
40
41     // Hide pointer when clicking away
42     $(document.body).find('*').on('click focus', event => {
43         if (!pointerShowing || isSelection) return;
44         $pointer.detach();
45         pointerShowing = false;
46     });
47
48     function updatePointerContent() {
49         let inputText = pointerModeLink ? window.baseUrl(`/link/${pageId}#${pointerSectionId}`) : `{{@${pageId}#${pointerSectionId}}}`;
50         if (pointerModeLink && inputText.indexOf('http') !== 0) inputText = window.location.protocol + "//" + window.location.host + inputText;
51
52         $pointer.find('input').val(inputText);
53     }
54
55     // Show pointer when selecting a single block of tagged content
56     $('.page-content [id^="bkmrk"]').on('mouseup keyup', function (e) {
57         e.stopPropagation();
58         let selection = window.getSelection();
59         if (selection.toString().length === 0) return;
60
61         // Show pointer and set link
62         let $elem = $(this);
63         pointerSectionId = $elem.attr('id');
64         updatePointerContent();
65
66         $elem.before($pointer);
67         $pointer.show();
68         pointerShowing = true;
69
70         // Set pointer to sit near mouse-up position
71         let pointerLeftOffset = (e.pageX - $elem.offset().left - ($pointerInner.width() / 2));
72         if (pointerLeftOffset < 0) pointerLeftOffset = 0;
73         let pointerLeftOffsetPercent = (pointerLeftOffset / $elem.width()) * 100;
74         $pointerInner.css('left', pointerLeftOffsetPercent + '%');
75
76         isSelection = true;
77         setTimeout(() => {
78             isSelection = false;
79         }, 100);
80     });
81
82     // Go to, and highlight if necessary, the specified text.
83     function goToText(text) {
84         let idElem = document.getElementById(text);
85         $('.page-content [data-highlighted]').attr('data-highlighted', '').css('background-color', '');
86         if (idElem !== null) {
87             window.scrollAndHighlight(idElem);
88         } else {
89             $('.page-content').find(':contains("' + text + '")').smoothScrollTo();
90         }
91     }
92
93     // Check the hash on load
94     if (window.location.hash) {
95         let text = window.location.hash.replace(/\%20/g, ' ').substr(1);
96         goToText(text);
97     }
98
99     // Sidebar page nav click event
100     $('.sidebar-page-nav').on('click', 'a', event => {
101         goToText(event.target.getAttribute('href').substr(1));
102     });
103
104     // Make the sidebar stick in view on scroll
105     let $window = $(window);
106     let $sidebar = $("#sidebar .scroll-body");
107     let $bookTreeParent = $sidebar.parent();
108
109     // Check the page is scrollable and the content is taller than the tree
110     let pageScrollable = ($(document).height() > $window.height()) && ($sidebar.height() < $('.page-content').height());
111
112     // Get current tree's width and header height
113     let headerHeight = $("#header").height() + $(".toolbar").height();
114     let isFixed = $window.scrollTop() > headerHeight;
115
116     // Fix the tree as a sidebar
117     function stickTree() {
118         $sidebar.width($bookTreeParent.width() + 15);
119         $sidebar.addClass("fixed");
120         isFixed = true;
121     }
122
123     // Un-fix the tree back into position
124     function unstickTree() {
125         $sidebar.css('width', 'auto');
126         $sidebar.removeClass("fixed");
127         isFixed = false;
128     }
129
130     // Checks if the tree stickiness state should change
131     function checkTreeStickiness(skipCheck) {
132         let shouldBeFixed = $window.scrollTop() > headerHeight;
133         if (shouldBeFixed && (!isFixed || skipCheck)) {
134             stickTree();
135         } else if (!shouldBeFixed && (isFixed || skipCheck)) {
136             unstickTree();
137         }
138     }
139     // The event ran when the window scrolls
140     function windowScrollEvent() {
141         checkTreeStickiness(false);
142     }
143
144     // If the page is scrollable and the window is wide enough listen to scroll events
145     // and evaluate tree stickiness.
146     if (pageScrollable && $window.width() > 1000) {
147         $window.on('scroll', windowScrollEvent);
148         checkTreeStickiness(true);
149     }
150
151     // Handle window resizing and switch between desktop/mobile views
152     $window.on('resize', event => {
153         if (pageScrollable && $window.width() > 1000) {
154             $window.on('scroll', windowScrollEvent);
155             checkTreeStickiness(true);
156         } else {
157             $window.off('scroll', windowScrollEvent);
158             unstickTree();
159         }
160     });
161
162
163     // Check if support is present for IntersectionObserver
164     if ('IntersectionObserver' in window &&
165         'IntersectionObserverEntry' in window &&
166         'intersectionRatio' in window.IntersectionObserverEntry.prototype) {
167         addPageHighlighting();
168     }
169
170     function addPageHighlighting() {
171       let pageNav = document.querySelector('.sidebar-page-nav');
172
173       // fetch all the headings.
174       let headings = document.querySelector('.page-content').querySelectorAll('h1, h2, h3, h4, h5, h6');
175       // if headings are present, add observers.
176       if (headings.length > 0 && pageNav !== null) {
177           addNavObserver(headings);
178       }
179
180       function addNavObserver(headings) {
181           // Setup the intersection observer.
182           let intersectOpts = {
183               rootMargin: '0px 0px 0px 0px',
184               threshold: 1.0
185           };
186           let pageNavObserver = new IntersectionObserver(headingVisibilityChange, intersectOpts);
187
188           // observe each heading
189           for (let i = 0; i !== headings.length; ++i) {
190               pageNavObserver.observe(headings[i]);
191           }
192       }
193
194       function headingVisibilityChange(entries, observer) {
195           for (let i = 0; i < entries.length; i++) {
196               let currentEntry = entries[i];
197               let isVisible = (currentEntry.intersectionRatio === 1);
198               toggleAnchorHighlighting(currentEntry.target.id, isVisible);
199           }
200       }
201
202         function toggleAnchorHighlighting(elementId, shouldHighlight) {
203             let anchorsToHighlight = pageNav.querySelectorAll('a[href="#' + elementId + '"]');
204             for (let i = 0; i < anchorsToHighlight.length; i++) {
205                 // Change below to use classList.toggle when IE support is dropped.
206                 if (shouldHighlight) {
207                     anchorsToHighlight[i].classList.add('current-heading');
208                 } else {
209                     anchorsToHighlight[i].classList.remove('current-heading');
210                 }
211             }
212         }
213     }
214 };
215
216 module.exports = setupPageShow;