forked from MarkBind/markbind
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.js
190 lines (167 loc) · 6.04 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
/* global pageVueRenderFn:readonly, pageVueStaticRenderFns:readonly */
// pageVueRenderFn and pageVueStaticRenderFns exist in dynamically generated script by Page/index.js
// eslint-disable-next-line import/no-extraneous-dependencies
import vueCommonAppFactory from './VueCommonAppFactory';
import initScrollTopButton from './scrollTopButton';
import './styles/index.css';
const { MarkBindVue, appFactory } = vueCommonAppFactory;
Vue.use(MarkBindVue.plugin);
function scrollToUrlAnchorHeading() {
if (window.location.hash) {
// remove leading hash to get element ID
const headingElement = document.getElementById(window.location.hash.slice(1));
if (headingElement) {
headingElement.scrollIntoView();
window.scrollBy(0, -document.body.style.paddingTop.replace('px', ''));
}
}
}
function detectAndApplyStickyHeaderStyles() {
jQuery(':header').each((index, heading) => {
if (heading.id) {
jQuery(heading).removeAttr('id'); // to avoid duplicated id problem
}
});
const headerSelector = jQuery('header[sticky]');
const isSticky = headerSelector.length !== 0;
if (!isSticky) {
return;
}
const updateHeaderHeight = () => {
const newHeaderHeight = headerSelector.height();
document.documentElement.style.setProperty('--header-height', `${newHeaderHeight}px`);
};
let isHidden = false;
const toggleHeaderOverflow = () => {
// reset overflow when header shows again to allow content
// in the header such as search dropdown etc. to overflow
if (!isHidden) {
headerSelector.css('overflow', '');
updateHeaderHeight();
}
};
let lastOffset = 0;
let lastHash = window.location.hash;
const toggleHeaderOnScroll = () => {
// prevent toggling of header on desktop site
if (window.innerWidth > 767) { return; }
if (lastHash !== window.location.hash) {
lastHash = window.location.hash;
isHidden = true;
headerSelector.removeClass('hide-header');
return;
}
lastHash = window.location.hash;
const currentOffset = window.pageYOffset;
const isEndOfPage = (window.innerHeight + currentOffset) >= document.body.offsetHeight;
// to prevent page from auto scrolling when header is toggled at the end of page
if (isEndOfPage) { return; }
if (currentOffset > lastOffset) {
const headerEnd = headerSelector.height() + headerSelector[0].getBoundingClientRect().top;
const isBeforeHeader = currentOffset < headerEnd;
if (isBeforeHeader) {
return;
}
isHidden = true;
headerSelector.addClass('hide-header');
} else {
isHidden = false;
headerSelector.removeClass('hide-header');
}
lastOffset = currentOffset;
};
const resizeObserver = new ResizeObserver(() => {
// hide header overflow when user scrolls to support transition effect
if (isHidden) {
headerSelector.css('overflow', 'hidden');
return;
}
updateHeaderHeight();
});
resizeObserver.observe(headerSelector[0]);
headerSelector[0].addEventListener('transitionend', toggleHeaderOverflow);
let scrollTimeout;
window.addEventListener('scroll', () => {
if (scrollTimeout) {
clearTimeout(scrollTimeout);
}
scrollTimeout = setTimeout(toggleHeaderOnScroll, 20);
});
}
function updateSearchData(vm) {
jQuery.getJSON(`${baseUrl}/siteData.json`)
.then((siteData) => {
vm.searchData = siteData.pages;
});
}
/*
* Changes every <script src defer type="application/javascript" style-bypass-vue-compilation>
* placeholder tags that was used to bypass Vue compilation back into original intended <style> tags.
*/
function restoreStyleTags() {
const tagsToRestore = document.querySelectorAll('script[style-bypass-vue-compilation]');
tagsToRestore.forEach((oldScriptTag) => {
const restoredStyleTag = document.createElement('style');
restoredStyleTag.innerHTML = oldScriptTag.innerHTML;
oldScriptTag.parentNode.replaceChild(restoredStyleTag, oldScriptTag);
});
}
function executeAfterMountedRoutines() {
restoreStyleTags();
scrollToUrlAnchorHeading();
detectAndApplyStickyHeaderStyles();
}
window.handleSiteNavClick = function (elem, useAnchor = true) {
if (useAnchor) {
const anchorElements = elem.getElementsByTagName('a');
if (anchorElements.length) {
window.location.href = anchorElements[0].href;
return;
}
}
const dropdownContent = elem.nextElementSibling;
const dropdownIcon = elem.lastElementChild;
dropdownContent.classList.toggle('site-nav-dropdown-container-open');
dropdownIcon.classList.toggle('site-nav-rotate-icon');
};
function setup() {
const vm = new Vue({
render(createElement) {
return pageVueRenderFn.call(this, createElement);
},
staticRenderFns: pageVueStaticRenderFns,
...appFactory(),
mounted() {
executeAfterMountedRoutines();
},
});
/*
* For SSR, if we mount onto the wrong element (without data-server-rendered attribute) in our SSR setup,
* hydration will fail silently and turn into client-side rendering, which is not what we want.
* Thus, we will always force hydration so that we always know when hydration has failed, so that we can
* address the hydration issue accordingly.
*/
vm.$mount('#app', true); // second parameter, 'true', enables force hydration
}
function setupWithSearch() {
const vm = new Vue({
render(createElement) {
return pageVueRenderFn.call(this, createElement);
},
staticRenderFns: pageVueStaticRenderFns,
...appFactory(),
mounted() {
executeAfterMountedRoutines();
updateSearchData(this);
},
});
/*
* For SSR, if we mount onto the wrong element (without data-server-rendered attribute) in our SSR setup,
* hydration will fail silently and turn into client-side rendering, which is not what we want.
* Thus, we will always force hydration so that we always know when hydration has failed, so that we can
* address the hydration issue accordingly.
*/
vm.$mount('#app', true); // second parameter, 'true', enables force hydration
}
initScrollTopButton();
export default { setup, setupWithSearch };