class Util { forEach(elements, handler) { elements = elements || []; for (let i = 0; i < elements.length; i++) handler(elements[i]); } getScrollTop() { return (document.documentElement && document.documentElement.scrollTop) || document.body.scrollTop; } isMobile() { return window.matchMedia('only screen and (max-width: 680px)').matches; } isTocStatic() { return window.matchMedia('only screen and (max-width: 960px)').matches; } animateCSS(element, animation, reserved, callback) { if (!Array.isArray(animation)) animation = [animation]; element.classList.add('animated', ...animation); const handler = () => { element.classList.remove('animated', ...animation); element.removeEventListener('animationend', handler); if (typeof callback === 'function') callback(); }; if (!reserved) element.addEventListener('animationend', handler, false); } } class Theme { constructor() { this.config = window.config; this.data = this.config.data; this.isDark = document.body.getAttribute('theme') === 'dark'; this.util = new Util(); this.newScrollTop = this.util.getScrollTop(); this.oldScrollTop = this.newScrollTop; this.scrollEventSet = new Set(); this.resizeEventSet = new Set(); this.switchThemeEventSet = new Set(); this.clickMaskEventSet = new Set(); this.tocSelected = -1; if (window.objectFitImages) objectFitImages(); } initSVGIcon() { this.util.forEach(document.querySelectorAll('[data-svg-src]'), $icon => { fetch($icon.getAttribute('data-svg-src')) .then(response => response.text()) .then(svg => { const $temp = document.createElement('div'); $temp.insertAdjacentHTML('afterbegin', svg); const $svg = $temp.firstChild; $svg.setAttribute('data-svg-src', $icon.getAttribute('data-svg-src')); $svg.classList.add('icon'); const $titleElements = $svg.getElementsByTagName('title'); if ($titleElements.length) $svg.removeChild($titleElements[0]); $icon.parentElement.replaceChild($svg, $icon); }) .catch(err => { console.error(err); }); }); } initTwemoji() { if (this.config.twemoji) twemoji.parse(document.body); } initMenuMobile() { const $menuToggleMobile = document.getElementById('menu-toggle-mobile'); const $menuMobile = document.getElementById('menu-mobile'); $menuToggleMobile.addEventListener('click', () => { document.body.classList.toggle('blur'); $menuToggleMobile.classList.toggle('active'); $menuMobile.classList.toggle('active'); }, false); this._menuMobileOnClickMask = this._menuMobileOnClickMask || (() => { $menuToggleMobile.classList.remove('active'); $menuMobile.classList.remove('active'); }); this.clickMaskEventSet.add(this._menuMobileOnClickMask); } initSwitchTheme() { this.util.forEach(document.getElementsByClassName('theme-switch'), $themeSwitch => { $themeSwitch.addEventListener('click', () => { if (document.body.getAttribute('theme') === 'dark') { document.body.setAttribute('theme', 'light'); } else { document.body.setAttribute('theme', 'dark'); } this.isDark = !this.isDark; window.localStorage && localStorage.setItem('theme', this.isDark ? 'dark' : 'light'); window.REMARK42.changeTheme(document.body.getAttribute('theme') === 'dark' ? 'dark' : 'light'); for (let event of this.switchThemeEventSet) event(); }, false); }); } initSearch() { const searchConfig = this.config.search; const isMobile = this.util.isMobile(); if (!searchConfig || isMobile && this._searchMobileOnce || !isMobile && this._searchDesktopOnce) return; const maxResultLength = searchConfig.maxResultLength ? searchConfig.maxResultLength : 10; const snippetLength = searchConfig.snippetLength ? searchConfig.snippetLength : 50; const highlightTag = searchConfig.highlightTag ? searchConfig.highlightTag : 'em'; const suffix = isMobile ? 'mobile' : 'desktop'; const $header = document.getElementById(`header-${suffix}`); const $searchInput = document.getElementById(`search-input-${suffix}`); const $searchToggle = document.getElementById(`search-toggle-${suffix}`); const $searchLoading = document.getElementById(`search-loading-${suffix}`); const $searchClear = document.getElementById(`search-clear-${suffix}`); if (isMobile) { this._searchMobileOnce = true; $searchInput.addEventListener('focus', () => { document.body.classList.add('blur'); $header.classList.add('open'); }, false); document.getElementById('search-cancel-mobile').addEventListener('click', () => { $header.classList.remove('open'); document.body.classList.remove('blur'); document.getElementById('menu-toggle-mobile').classList.remove('active'); document.getElementById('menu-mobile').classList.remove('active'); $searchLoading.style.display = 'none'; $searchClear.style.display = 'none'; this._searchMobile && this._searchMobile.autocomplete.setVal(''); }, false); $searchClear.addEventListener('click', () => { $searchClear.style.display = 'none'; this._searchMobile && this._searchMobile.autocomplete.setVal(''); }, false); this._searchMobileOnClickMask = this._searchMobileOnClickMask || (() => { $header.classList.remove('open'); $searchLoading.style.display = 'none'; $searchClear.style.display = 'none'; this._searchMobile && this._searchMobile.autocomplete.setVal(''); }); this.clickMaskEventSet.add(this._searchMobileOnClickMask); } else { this._searchDesktopOnce = true; $searchToggle.addEventListener('click', () => { document.body.classList.add('blur'); $header.classList.add('open'); $searchInput.focus(); }, false); $searchClear.addEventListener('click', () => { $searchClear.style.display = 'none'; this._searchDesktop && this._searchDesktop.autocomplete.setVal(''); }, false); this._searchDesktopOnClickMask = this._searchDesktopOnClickMask || (() => { $header.classList.remove('open'); $searchLoading.style.display = 'none'; $searchClear.style.display = 'none'; this._searchDesktop && this._searchDesktop.autocomplete.setVal(''); }); this.clickMaskEventSet.add(this._searchDesktopOnClickMask); } $searchInput.addEventListener('input', () => { if ($searchInput.value === '') $searchClear.style.display = 'none'; else $searchClear.style.display = 'inline'; }, false); const initAutosearch = () => { const autosearch = autocomplete(`#search-input-${suffix}`, { hint: false, autoselect: true, dropdownMenuContainer: `#search-dropdown-${suffix}`, clearOnSelected: true, cssClasses: {noPrefix: true}, debug: true, }, { name: 'search', source: (query, callback) => { $searchLoading.style.display = 'inline'; $searchClear.style.display = 'none'; const finish = (results) => { $searchLoading.style.display = 'none'; $searchClear.style.display = 'inline'; callback(results); }; if (searchConfig.type === 'lunr') { const search = () => { if (lunr.queryHandler) query = lunr.queryHandler(query); const results = {}; this._index.search(query).forEach(({ref, matchData: {metadata}}) => { const matchData = this._indexData[ref]; let {uri, title, content: context} = matchData; if (results[uri]) return; let position = 0; Object.values(metadata).forEach(({content}) => { if (content) { const matchPosition = content.position[0][0]; if (matchPosition < position || position === 0) position = matchPosition; } }); position -= snippetLength / 5; if (position > 0) { position += context.substr(position, 20).lastIndexOf(' ') + 1; context = '...' + context.substr(position, snippetLength); } else { context = context.substr(0, snippetLength); } Object.keys(metadata).forEach(key => { title = title.replace(new RegExp(`(${key})`, 'gi'), `<${highlightTag}>$1`); context = context.replace(new RegExp(`(${key})`, 'gi'), `<${highlightTag}>$1`); }); results[uri] = { 'uri': uri, 'title': title, 'date': matchData.date, 'context': context, }; }); return Object.values(results).slice(0, maxResultLength); } if (!this._index) { fetch(searchConfig.lunrIndexURL) .then(response => response.json()) .then(data => { const indexData = {}; this._index = lunr(function () { if (searchConfig.lunrLanguageCode) this.use(lunr[searchConfig.lunrLanguageCode]); this.ref('objectID'); this.field('title', {boost: 50}); this.field('tags', {boost: 20}); this.field('categories', {boost: 20}); this.field('content', {boost: 10}); this.metadataWhitelist = ['position']; data.forEach((record) => { indexData[record.objectID] = record; this.add(record); }); }); this._indexData = indexData; finish(search()); }).catch(err => { console.error(err); finish([]); }); } else finish(search()); } else if (searchConfig.type === 'algolia') { this._algoliaIndex = this._algoliaIndex || algoliasearch(searchConfig.algoliaAppID, searchConfig.algoliaSearchKey).initIndex(searchConfig.algoliaIndex); this._algoliaIndex .search(query, { offset: 0, length: maxResultLength * 8, attributesToHighlight: ['title'], attributesToSnippet: [`content:${snippetLength}`], highlightPreTag: `<${highlightTag}>`, highlightPostTag: ``, }) .then(({hits}) => { const results = {}; hits.forEach(({uri, date, _highlightResult: {title}, _snippetResult: {content}}) => { if (results[uri] && results[uri].context.length > content.value) return; results[uri] = { uri: uri, title: title.value, date: date, context: content.value, }; }); finish(Object.values(results).slice(0, maxResultLength)); }) .catch(err => { console.error(err); finish([]); }); } }, templates: { suggestion: ({title, date, context}) => `
${title}${date}
${context}
`, empty: ({query}) => `
${searchConfig.noResultsFound}: "${query}"
`, footer: ({}) => { const {searchType, icon, href} = searchConfig.type === 'algolia' ? { searchType: 'algolia', icon: '', href: 'https://www.algolia.com/', } : { searchType: 'Lunr.js', icon: '', href: 'https://lunrjs.com/', }; return ``; }, }, }); autosearch.on('autocomplete:selected', (_event, suggestion, _dataset, _context) => { window.location.assign(suggestion.uri); }); if (isMobile) this._searchMobile = autosearch; else this._searchDesktop = autosearch; }; if (searchConfig.lunrSegmentitURL && !document.getElementById('lunr-segmentit')) { const script = document.createElement('script'); script.id = 'lunr-segmentit'; script.type = 'text/javascript'; script.src = searchConfig.lunrSegmentitURL; script.async = true; if (script.readyState) { script.onreadystatechange = () => { if (script.readyState == 'loaded' || script.readyState == 'complete') { script.onreadystatechange = null; initAutosearch(); } }; } else { script.onload = () => { initAutosearch(); }; } document.body.appendChild(script); } else initAutosearch(); } initDetails() { this.util.forEach(document.getElementsByClassName('details'), $details => { const $summary = $details.getElementsByClassName('details-summary')[0]; $summary.addEventListener('click', () => { $details.classList.toggle('open'); }, false); }); } initTable() { this.util.forEach(document.querySelectorAll('.single table'), $table => { const $wrapper = document.createElement('div'); $wrapper.className = 'table-wrapper'; $table.parentElement.replaceChild($wrapper, $table); $wrapper.appendChild($table); }); } initShareHeader() { this.util.forEach(document.querySelectorAll('.content-break h2'), $header => { $header.insertAdjacentHTML('afterend', `
link telegram vk twitter fb
`); }); } initToc() { const $tocCore = document.getElementById('TableOfContents'); if ($tocCore === null) return; if (document.getElementById('toc-static').getAttribute('data-kept') || this.util.isTocStatic()) { const $tocContentStatic = document.getElementById('toc-content-static'); if ($tocCore.parentElement !== $tocContentStatic) { $tocCore.parentElement.removeChild($tocCore); $tocContentStatic.appendChild($tocCore); } if (this._tocOnScroll) this.scrollEventSet.delete(this._tocOnScroll); } else { const $tocContentAuto = document.getElementById('toc-content-auto'); if ($tocCore.parentElement !== $tocContentAuto) { $tocCore.parentElement.removeChild($tocCore); $tocContentAuto.appendChild($tocCore); } const $toc = document.getElementById('toc-auto'); const $page = document.getElementsByClassName('content-block')[0]; const rect = $page.getBoundingClientRect(); $toc.style.left = `${rect.left + rect.width + 20}px`; $toc.style.maxWidth = `${$page.getBoundingClientRect().left - 20 + 314}px`; $toc.style.visibility = 'visible'; const $tocLinkElements = $tocCore.querySelectorAll('a:first-child'); const $tocLiElements = $tocCore.getElementsByTagName('li'); const $headerLinkElements = document.getElementsByClassName('headerLink'); const headerIsFixed = document.body.getAttribute('data-header-desktop') !== 'normal'; const headerHeight = document.getElementById('header-desktop').offsetHeight; const TOP_SPACING = 20 + (headerIsFixed ? headerHeight : 0); const minTocTop = $toc.offsetTop; const minScrollTop = minTocTop - TOP_SPACING + (headerIsFixed ? 0 : headerHeight); this._tocOnScroll = this._tocOnScroll || (() => { const footerTop = document.getElementById('toc-final').offsetTop; const maxTocTop = footerTop - $toc.getBoundingClientRect().height; const maxScrollTop = maxTocTop - TOP_SPACING + (headerIsFixed ? 0 : headerHeight); if (this.newScrollTop < minScrollTop) { $toc.style.position = 'absolute'; $toc.style.top = `${minTocTop}px`; } else if (this.newScrollTop > maxScrollTop) { $toc.style.position = 'absolute'; $toc.style.top = `${maxTocTop}px`; } else { $toc.style.position = 'fixed'; $toc.style.top = `${TOP_SPACING}px`; } this.util.forEach($tocLinkElements, $tocLink => { $tocLink.classList.remove('active'); }); this.util.forEach($tocLiElements, $tocLi => { $tocLi.classList.remove('has-active'); }); const INDEX_SPACING = 20 + (headerIsFixed ? headerHeight : 0); let activeTocIndex = $headerLinkElements.length - 1; for (let i = 0; i < $headerLinkElements.length - 1; i++) { const thisTop = $headerLinkElements[i].getBoundingClientRect().top; const nextTop = $headerLinkElements[i + 1].getBoundingClientRect().top; if ((i === 0 && thisTop > INDEX_SPACING) || (thisTop <= INDEX_SPACING && nextTop > INDEX_SPACING)) { activeTocIndex = i; break; } } if (activeTocIndex !== -1) { const $selHeader = $headerLinkElements[activeTocIndex]; let $selectedToC; for (let i = 0; i < $tocLinkElements.length; i++) { if ($tocLinkElements[i].hash.slice(1) === encodeURI($selHeader.id)) { $selectedToC = $tocLinkElements[i]; if (i !== this.tocSelected) { this.tocSelected = i; } break; } } if (typeof $selectedToC === "undefined" && this.tocSelected !== -1) { $selectedToC = $tocLinkElements[this.tocSelected]; } if (typeof $selectedToC !== "undefined") { $selectedToC.classList.add('active'); let $parent = $selectedToC.parentElement; while ($parent !== $tocCore) { $parent.classList.add('has-active'); $parent = $parent.parentElement.parentElement; } } } }); this._tocOnScroll(); this.scrollEventSet.add(this._tocOnScroll); } } initMath() { if (this.config.math) renderMathInElement(document.body, this.config.math); } initMermaid() { const $mermaidElements = document.getElementsByClassName('mermaid'); if ($mermaidElements.length) { mermaid.initialize({startOnLoad: false, theme: 'null'}); this.util.forEach($mermaidElements, $mermaid => { mermaid.mermaidAPI.render('svg-' + $mermaid.id, this.data[$mermaid.id], svgCode => { $mermaid.insertAdjacentHTML('afterbegin', svgCode); }, $mermaid); }); } } initEcharts() { this._echartsOnSwitchTheme = this._echartsOnSwitchTheme || (() => { this._echartsArr = this._echartsArr || []; for (let i = 0; i < this._echartsArr.length; i++) { this._echartsArr[i].dispose(); } this._echartsArr = []; this.util.forEach(document.getElementsByClassName('echarts'), $echarts => { const chart = echarts.init($echarts, this.isDark ? 'dark' : 'macarons', {renderer: 'svg'}); chart.setOption(JSON.parse(this.data[$echarts.id])); this._echartsArr.push(chart); }); }); this.switchThemeEventSet.add(this._echartsOnSwitchTheme); this._echartsOnSwitchTheme(); this._echartsOnResize = this._echartsOnResize || (() => { for (let i = 0; i < this._echartsArr.length; i++) { this._echartsArr[i].resize(); } }); this.resizeEventSet.add(this._echartsOnResize); } initMapbox() { if (this.config.mapbox) { mapboxgl.accessToken = this.config.mapbox.accessToken; mapboxgl.setRTLTextPlugin(this.config.mapbox.RTLTextPlugin); this._mapboxArr = this._mapboxArr || []; this.util.forEach(document.getElementsByClassName('mapbox'), $mapbox => { const {lng, lat, zoom, lightStyle, darkStyle, marked, navigation, geolocate, scale, fullscreen} = this.data[$mapbox.id]; const mapbox = new mapboxgl.Map({ container: $mapbox, center: [lng, lat], zoom: zoom, minZoom: .2, style: this.isDark ? darkStyle : lightStyle, attributionControl: false, }); if (marked) { new mapboxgl.Marker().setLngLat([lng, lat]).addTo(mapbox); } if (navigation) { mapbox.addControl(new mapboxgl.NavigationControl(), 'bottom-right'); } if (geolocate) { mapbox.addControl(new mapboxgl.GeolocateControl({ positionOptions: { enableHighAccuracy: true, }, showUserLocation: true, trackUserLocation: true, }), 'bottom-right'); } if (scale) { mapbox.addControl(new mapboxgl.ScaleControl()); } if (fullscreen) { mapbox.addControl(new mapboxgl.FullscreenControl()); } mapbox.addControl(new MapboxLanguage()); this._mapboxArr.push(mapbox); }); this._mapboxOnSwitchTheme = this._mapboxOnSwitchTheme || (() => { this.util.forEach(this._mapboxArr, mapbox => { const $mapbox = mapbox.getContainer(); const {lightStyle, darkStyle} = this.data[$mapbox.id]; mapbox.setStyle(this.isDark ? darkStyle : lightStyle); mapbox.addControl(new MapboxLanguage()); }); }); this.switchThemeEventSet.add(this._mapboxOnSwitchTheme); } } initComment() { if (this.config.comment) { if (this.config.comment.gitalk) { this.config.comment.gitalk.body = decodeURI(window.location.href); const gitalk = new Gitalk(this.config.comment.gitalk); gitalk.render('gitalk'); } if (this.config.comment.valine) new Valine(this.config.comment.valine); if (this.config.comment.utterances) { const utterancesConfig = this.config.comment.utterances; const script = document.createElement('script'); script.src = 'https://utteranc.es/client.js'; script.type = 'text/javascript'; script.setAttribute('repo', utterancesConfig.repo); script.setAttribute('issue-term', utterancesConfig.issueTerm); if (utterancesConfig.label) script.setAttribute('label', utterancesConfig.label); script.setAttribute('theme', this.isDark ? utterancesConfig.darkTheme : utterancesConfig.lightTheme); script.crossOrigin = 'anonymous'; script.async = true; document.getElementById('utterances').appendChild(script); this._utterancesOnSwitchTheme = this._utterancesOnSwitchTheme || (() => { const message = { type: 'set-theme', theme: this.isDark ? utterancesConfig.darkTheme : utterancesConfig.lightTheme, }; const iframe = document.querySelector('.utterances-frame'); iframe.contentWindow.postMessage(message, 'https://utteranc.es'); }); this.switchThemeEventSet.add(this._utterancesOnSwitchTheme); } } } initSmoothScroll() { if (SmoothScroll) new SmoothScroll('[href^="#"]', { speed: 300, speedAsDuration: true, header: '#header-desktop' }); } initCookieconsent() { if (this.config.cookieconsent) cookieconsent.initialise(this.config.cookieconsent); } onScroll() { const $headers = []; if (document.body.getAttribute('data-header-desktop') === 'auto') $headers.push(document.getElementById('header-desktop')); if (document.body.getAttribute('data-header-mobile') === 'auto') $headers.push(document.getElementById('header-mobile')); if (document.getElementById('comments')) { const $viewComments = document.getElementById('view-comments'); $viewComments.href = `#comments`; $viewComments.style.display = 'block'; } const $fixedButtons = document.getElementById('fixed-buttons'); const ACCURACY = 20, MINIMUM = 100; window.addEventListener('scroll', () => { this.newScrollTop = this.util.getScrollTop(); const scroll = this.newScrollTop - this.oldScrollTop; const isMobile = this.util.isMobile(); this.util.forEach($headers, $header => { if (scroll > ACCURACY) { $header.classList.remove('fadeInDown'); this.util.animateCSS($header, ['fadeOutUp', 'faster'], true); } else if (scroll < -ACCURACY) { $header.classList.remove('fadeOutUp'); this.util.animateCSS($header, ['fadeInDown', 'faster'], true); } }); if (this.newScrollTop > MINIMUM) { if (isMobile && scroll > ACCURACY) { $fixedButtons.classList.remove('fadeIn'); this.util.animateCSS($fixedButtons, ['fadeOut', 'faster'], true); } else if (!isMobile || scroll < -ACCURACY) { $fixedButtons.style.display = 'block'; $fixedButtons.classList.remove('fadeOut'); this.util.animateCSS($fixedButtons, ['fadeIn', 'faster'], true); } } else { if (!isMobile) { $fixedButtons.classList.remove('fadeIn'); this.util.animateCSS($fixedButtons, ['fadeOut', 'faster'], true); } $fixedButtons.style.display = 'none'; } for (let event of this.scrollEventSet) event(); this.oldScrollTop = this.newScrollTop; }, false); } onResize() { window.addEventListener('resize', () => { if (!this._resizeTimeout) { this._resizeTimeout = window.setTimeout(() => { this._resizeTimeout = null; for (let event of this.resizeEventSet) event(); this.initToc(); this.initMermaid(); this.initSearch(); }, 100); } }, false); } onClickMask() { document.getElementById('mask').addEventListener('click', () => { for (let event of this.clickMaskEventSet) event(); document.body.classList.remove('blur'); }, false); } init() { try { this.initSVGIcon(); this.initTwemoji(); this.initMenuMobile(); this.initSwitchTheme(); this.initSearch(); this.initDetails(); this.initTable(); this.initShareHeader(); this.initSmoothScroll(); this.initMath(); this.initMermaid(); this.initEcharts(); this.initMapbox(); this.initCookieconsent(); } catch (err) { console.error(err); } window.setTimeout(() => { this.initToc(); this.initComment(); this.onScroll(); this.onResize(); this.onClickMask(); }, 100); } } const themeInit = () => { const theme = new Theme(); theme.init(); }; if (document.readyState !== 'loading') { themeInit(); } else { document.addEventListener('DOMContentLoaded', themeInit, false); }