🏗️ First time sync the css & js files from hexo theme NexT
This commit is contained in:
56
assets/js/bookmark.js
Normal file
56
assets/js/bookmark.js
Normal file
@@ -0,0 +1,56 @@
|
||||
/* global CONFIG */
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
'use strict';
|
||||
|
||||
const doSaveScroll = () => {
|
||||
localStorage.setItem('bookmark' + location.pathname, window.scrollY);
|
||||
};
|
||||
|
||||
const scrollToMark = () => {
|
||||
let top = localStorage.getItem('bookmark' + location.pathname);
|
||||
top = parseInt(top, 10);
|
||||
// If the page opens with a specific hash, just jump out
|
||||
if (!isNaN(top) && location.hash === '') {
|
||||
// Auto scroll to the position
|
||||
window.anime({
|
||||
targets : document.scrollingElement,
|
||||
duration : 200,
|
||||
easing : 'linear',
|
||||
scrollTop: top
|
||||
});
|
||||
}
|
||||
};
|
||||
// Register everything
|
||||
const init = function(trigger) {
|
||||
// Create a link element
|
||||
const link = document.querySelector('.book-mark-link');
|
||||
// Scroll event
|
||||
window.addEventListener('scroll', () => link.classList.toggle('book-mark-link-fixed', window.scrollY === 0), { passive: true });
|
||||
// Register beforeunload event when the trigger is auto
|
||||
if (trigger === 'auto') {
|
||||
// Register beforeunload event
|
||||
window.addEventListener('beforeunload', doSaveScroll);
|
||||
document.addEventListener('pjax:send', doSaveScroll);
|
||||
}
|
||||
// Save the position by clicking the icon
|
||||
link.addEventListener('click', () => {
|
||||
doSaveScroll();
|
||||
window.anime({
|
||||
targets : link,
|
||||
duration: 200,
|
||||
easing : 'linear',
|
||||
top : -30,
|
||||
complete: () => {
|
||||
setTimeout(() => {
|
||||
link.style.top = '';
|
||||
}, 400);
|
||||
}
|
||||
});
|
||||
});
|
||||
scrollToMark();
|
||||
document.addEventListener('pjax:success', scrollToMark);
|
||||
};
|
||||
|
||||
init(CONFIG.bookmark.save);
|
||||
});
|
||||
25
assets/js/comments-buttons.js
Normal file
25
assets/js/comments-buttons.js
Normal file
@@ -0,0 +1,25 @@
|
||||
/* global CONFIG */
|
||||
|
||||
(function() {
|
||||
const commentButton = document.querySelectorAll('.comment-button');
|
||||
commentButton.forEach(element => {
|
||||
const commentClass = element.classList[2];
|
||||
element.addEventListener('click', () => {
|
||||
commentButton.forEach(active => active.classList.toggle('active', active === element));
|
||||
document.querySelectorAll('.comment-position').forEach(active => active.classList.toggle('active', active.classList.contains(commentClass)));
|
||||
if (CONFIG.comments.storage) {
|
||||
localStorage.setItem('comments_active', commentClass);
|
||||
}
|
||||
});
|
||||
});
|
||||
let { activeClass } = CONFIG.comments;
|
||||
if (CONFIG.comments.storage) {
|
||||
activeClass = localStorage.getItem('comments_active') || activeClass;
|
||||
}
|
||||
if (activeClass) {
|
||||
const activeButton = document.querySelector(`.comment-button.${activeClass}`);
|
||||
if (activeButton) {
|
||||
activeButton.click();
|
||||
}
|
||||
}
|
||||
})();
|
||||
21
assets/js/comments.js
Normal file
21
assets/js/comments.js
Normal file
@@ -0,0 +1,21 @@
|
||||
/* global CONFIG */
|
||||
|
||||
window.addEventListener('tabs:register', () => {
|
||||
let { activeClass } = CONFIG.comments;
|
||||
if (CONFIG.comments.storage) {
|
||||
activeClass = localStorage.getItem('comments_active') || activeClass;
|
||||
}
|
||||
if (activeClass) {
|
||||
const activeTab = document.querySelector(`a[href="#comment-${activeClass}"]`);
|
||||
if (activeTab) {
|
||||
activeTab.click();
|
||||
}
|
||||
}
|
||||
});
|
||||
if (CONFIG.comments.storage) {
|
||||
window.addEventListener('tabs:click', event => {
|
||||
if (!event.target.matches('.tabs-comment .tab-content .tab-pane')) return;
|
||||
const commentClass = event.target.classList[1];
|
||||
localStorage.setItem('comments_active', commentClass);
|
||||
});
|
||||
}
|
||||
66
assets/js/config.js
Normal file
66
assets/js/config.js
Normal file
@@ -0,0 +1,66 @@
|
||||
if (!window.NexT) window.NexT = {};
|
||||
|
||||
(function() {
|
||||
const className = 'next-config';
|
||||
|
||||
const staticConfig = {};
|
||||
let variableConfig = {};
|
||||
|
||||
const parse = text => JSON.parse(text || '{}');
|
||||
|
||||
const update = name => {
|
||||
const targetEle = document.querySelector(`.${className}[data-name="${name}"]`);
|
||||
if (!targetEle) return;
|
||||
const parsedConfig = parse(targetEle.text);
|
||||
if (name === 'main') {
|
||||
Object.assign(staticConfig, parsedConfig);
|
||||
} else {
|
||||
variableConfig[name] = parsedConfig;
|
||||
}
|
||||
};
|
||||
|
||||
update('main');
|
||||
|
||||
window.CONFIG = new Proxy({}, {
|
||||
get(overrideConfig, name) {
|
||||
let existing;
|
||||
if (name in staticConfig) {
|
||||
existing = staticConfig[name];
|
||||
} else {
|
||||
if (!(name in variableConfig)) update(name);
|
||||
existing = variableConfig[name];
|
||||
}
|
||||
|
||||
// For unset override and mixable existing
|
||||
if (!(name in overrideConfig) && typeof existing === 'object') {
|
||||
// Get ready to mix.
|
||||
overrideConfig[name] = {};
|
||||
}
|
||||
|
||||
if (name in overrideConfig) {
|
||||
const override = overrideConfig[name];
|
||||
|
||||
// When mixable
|
||||
if (typeof override === 'object' && typeof existing === 'object') {
|
||||
// Mix, proxy changes to the override.
|
||||
return new Proxy({ ...existing, ...override }, {
|
||||
set(target, prop, value) {
|
||||
target[prop] = value;
|
||||
override[prop] = value;
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return override;
|
||||
}
|
||||
|
||||
// Only when not mixable and override hasn't been set.
|
||||
return existing;
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('pjax:success', () => {
|
||||
variableConfig = {};
|
||||
});
|
||||
})();
|
||||
125
assets/js/motion.js
Normal file
125
assets/js/motion.js
Normal file
@@ -0,0 +1,125 @@
|
||||
/* global NexT, CONFIG */
|
||||
|
||||
NexT.motion = {};
|
||||
|
||||
NexT.motion.integrator = {
|
||||
queue: [],
|
||||
init : function() {
|
||||
this.queue = [];
|
||||
return this;
|
||||
},
|
||||
add: function(fn) {
|
||||
const sequence = fn();
|
||||
if (CONFIG.motion.async) this.queue.push(sequence);
|
||||
else this.queue = this.queue.concat(sequence);
|
||||
return this;
|
||||
},
|
||||
bootstrap: function() {
|
||||
if (!CONFIG.motion.async) this.queue = [this.queue];
|
||||
this.queue.forEach(sequence => {
|
||||
const timeline = window.anime.timeline({
|
||||
duration: 200,
|
||||
easing : 'linear'
|
||||
});
|
||||
sequence.forEach(item => {
|
||||
if (item.deltaT) timeline.add(item, item.deltaT);
|
||||
else timeline.add(item);
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
NexT.motion.middleWares = {
|
||||
header: function() {
|
||||
const sequence = [];
|
||||
|
||||
function getMistLineSettings(targets) {
|
||||
sequence.push({
|
||||
targets,
|
||||
scaleX : [0, 1],
|
||||
duration: 500,
|
||||
deltaT : '-=200'
|
||||
});
|
||||
}
|
||||
|
||||
function pushToSequence(targets, sequenceQueue = false) {
|
||||
sequence.push({
|
||||
targets,
|
||||
opacity: 1,
|
||||
top : 0,
|
||||
deltaT : sequenceQueue ? '-=200' : '-=0'
|
||||
});
|
||||
}
|
||||
|
||||
pushToSequence('header.header');
|
||||
CONFIG.scheme === 'Mist' && getMistLineSettings('.logo-line');
|
||||
CONFIG.scheme === 'Muse' && pushToSequence('.custom-logo-image');
|
||||
pushToSequence('.site-title');
|
||||
pushToSequence('.site-brand-container .toggle', true);
|
||||
pushToSequence('.site-subtitle');
|
||||
(CONFIG.scheme === 'Pisces' || CONFIG.scheme === 'Gemini') && pushToSequence('.custom-logo-image');
|
||||
|
||||
document.querySelectorAll('.menu-item').forEach(targets => {
|
||||
sequence.push({
|
||||
targets,
|
||||
complete: () => targets.classList.add('animated', 'fadeInDown'),
|
||||
deltaT : '-=200'
|
||||
});
|
||||
});
|
||||
|
||||
return sequence;
|
||||
},
|
||||
|
||||
subMenu: function() {
|
||||
const subMenuItem = document.querySelectorAll('.sub-menu .menu-item');
|
||||
if (subMenuItem.length > 0) {
|
||||
subMenuItem.forEach(element => {
|
||||
element.classList.add('animated');
|
||||
});
|
||||
}
|
||||
return [];
|
||||
},
|
||||
|
||||
postList: function() {
|
||||
const sequence = [];
|
||||
const { post_block, post_header, post_body, coll_header } = CONFIG.motion.transition;
|
||||
|
||||
function animate(animation, selector) {
|
||||
if (!animation) return;
|
||||
document.querySelectorAll(selector).forEach(targets => {
|
||||
sequence.push({
|
||||
targets,
|
||||
complete: () => targets.classList.add('animated', animation),
|
||||
deltaT : '-=100'
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
animate(post_block, '.post-block, .pagination, .comments');
|
||||
animate(coll_header, '.collection-header');
|
||||
animate(post_header, '.post-header');
|
||||
animate(post_body, '.post-body');
|
||||
|
||||
return sequence;
|
||||
},
|
||||
|
||||
sidebar: function() {
|
||||
const sidebar = document.querySelector('.sidebar');
|
||||
const sidebarTransition = CONFIG.motion.transition.sidebar;
|
||||
// Only for Pisces | Gemini.
|
||||
if (sidebarTransition && (CONFIG.scheme === 'Pisces' || CONFIG.scheme === 'Gemini')) {
|
||||
return [{
|
||||
targets : sidebar,
|
||||
complete: () => sidebar.classList.add('animated', sidebarTransition)
|
||||
}];
|
||||
}
|
||||
return [];
|
||||
},
|
||||
|
||||
footer: function() {
|
||||
return [{
|
||||
targets: document.querySelector('.footer'),
|
||||
opacity: 1
|
||||
}];
|
||||
}
|
||||
};
|
||||
75
assets/js/next-boot.js
Normal file
75
assets/js/next-boot.js
Normal file
@@ -0,0 +1,75 @@
|
||||
/* global NexT, CONFIG */
|
||||
|
||||
NexT.boot = {};
|
||||
|
||||
NexT.boot.registerEvents = function() {
|
||||
|
||||
NexT.utils.registerScrollPercent();
|
||||
NexT.utils.registerCanIUseTag();
|
||||
|
||||
// Mobile top menu bar.
|
||||
document.querySelector('.site-nav-toggle .toggle').addEventListener('click', event => {
|
||||
event.currentTarget.classList.toggle('toggle-close');
|
||||
const siteNav = document.querySelector('.site-nav');
|
||||
if (!siteNav) return;
|
||||
siteNav.style.setProperty('--scroll-height', siteNav.scrollHeight + 'px');
|
||||
document.body.classList.toggle('site-nav-on');
|
||||
});
|
||||
|
||||
document.querySelectorAll('.sidebar-nav li').forEach((element, index) => {
|
||||
element.addEventListener('click', () => {
|
||||
NexT.utils.activateSidebarPanel(index);
|
||||
});
|
||||
});
|
||||
|
||||
window.addEventListener('hashchange', () => {
|
||||
const tHash = location.hash;
|
||||
if (tHash !== '' && !tHash.match(/%\S{2}/)) {
|
||||
const target = document.querySelector(`.tabs ul.nav-tabs li a[href="${tHash}"]`);
|
||||
target && target.click();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
NexT.boot.refresh = function() {
|
||||
|
||||
/**
|
||||
* Register JS handlers by condition option.
|
||||
* Need to add config option in Front-End at 'scripts/helpers/next-config.js' file.
|
||||
*/
|
||||
CONFIG.prism && window.Prism.highlightAll();
|
||||
CONFIG.mediumzoom && window.mediumZoom('.post-body :not(a) > img, .post-body > img', {
|
||||
background: 'var(--content-bg-color)'
|
||||
});
|
||||
CONFIG.lazyload && window.lozad('.post-body img').observe();
|
||||
CONFIG.pangu && window.pangu.spacingPage();
|
||||
|
||||
CONFIG.exturl && NexT.utils.registerExtURL();
|
||||
NexT.utils.registerCopyCode();
|
||||
NexT.utils.registerTabsTag();
|
||||
NexT.utils.registerActiveMenuItem();
|
||||
NexT.utils.registerLangSelect();
|
||||
NexT.utils.registerSidebarTOC();
|
||||
NexT.utils.registerPostReward();
|
||||
NexT.utils.wrapTableWithBox();
|
||||
NexT.utils.registerVideoIframe();
|
||||
};
|
||||
|
||||
NexT.boot.motion = function() {
|
||||
// Define Motion Sequence & Bootstrap Motion.
|
||||
if (CONFIG.motion.enable) {
|
||||
NexT.motion.integrator
|
||||
.add(NexT.motion.middleWares.header)
|
||||
.add(NexT.motion.middleWares.postList)
|
||||
.add(NexT.motion.middleWares.sidebar)
|
||||
.add(NexT.motion.middleWares.footer)
|
||||
.bootstrap();
|
||||
}
|
||||
NexT.utils.updateSidebarPosition();
|
||||
};
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
NexT.boot.registerEvents();
|
||||
NexT.boot.refresh();
|
||||
NexT.boot.motion();
|
||||
});
|
||||
34
assets/js/pjax.js
Normal file
34
assets/js/pjax.js
Normal file
@@ -0,0 +1,34 @@
|
||||
/* global NexT, CONFIG, Pjax */
|
||||
|
||||
const pjax = new Pjax({
|
||||
selectors: [
|
||||
'head title',
|
||||
'script[type="application/json"]',
|
||||
'.main-inner',
|
||||
'.post-toc-wrap',
|
||||
'.languages',
|
||||
'.pjax'
|
||||
],
|
||||
analytics: false,
|
||||
cacheBust: false,
|
||||
scrollTo : !CONFIG.bookmark.enable
|
||||
});
|
||||
|
||||
document.addEventListener('pjax:success', () => {
|
||||
pjax.executeScripts(document.querySelectorAll('script[data-pjax]'));
|
||||
NexT.boot.refresh();
|
||||
// Define Motion Sequence & Bootstrap Motion.
|
||||
if (CONFIG.motion.enable) {
|
||||
NexT.motion.integrator
|
||||
.init()
|
||||
.add(NexT.motion.middleWares.subMenu)
|
||||
.add(NexT.motion.middleWares.postList)
|
||||
.bootstrap();
|
||||
}
|
||||
if (CONFIG.sidebar.display !== 'remove') {
|
||||
const hasTOC = document.querySelector('.post-toc');
|
||||
document.querySelector('.sidebar-inner').classList.toggle('sidebar-nav-active', hasTOC);
|
||||
NexT.utils.activateSidebarPanel(hasTOC ? 0 : 1);
|
||||
NexT.utils.updateSidebarPosition();
|
||||
}
|
||||
});
|
||||
138
assets/js/schedule.js
Normal file
138
assets/js/schedule.js
Normal file
@@ -0,0 +1,138 @@
|
||||
/* global CONFIG */
|
||||
|
||||
// https://developers.google.com/calendar/api/v3/reference/events/list
|
||||
(function() {
|
||||
// Initialization
|
||||
const calendar = {
|
||||
orderBy : 'startTime',
|
||||
showLocation: false,
|
||||
offsetMax : 72,
|
||||
offsetMin : 4,
|
||||
showDeleted : false,
|
||||
singleEvents: true,
|
||||
maxResults : 250
|
||||
};
|
||||
|
||||
// Read config form theme config file
|
||||
Object.assign(calendar, CONFIG.calendar);
|
||||
|
||||
const now = new Date();
|
||||
const timeMax = new Date();
|
||||
const timeMin = new Date();
|
||||
|
||||
timeMax.setHours(now.getHours() + calendar.offsetMax);
|
||||
timeMin.setHours(now.getHours() - calendar.offsetMin);
|
||||
|
||||
// Build URL
|
||||
const params = {
|
||||
key : calendar.api_key,
|
||||
orderBy : calendar.orderBy,
|
||||
timeMax : timeMax.toISOString(),
|
||||
timeMin : timeMin.toISOString(),
|
||||
showDeleted : calendar.showDeleted,
|
||||
singleEvents: calendar.singleEvents,
|
||||
maxResults : calendar.maxResults
|
||||
};
|
||||
|
||||
const request_url = new URL(`https://www.googleapis.com/calendar/v3/calendars/${calendar.calendar_id}/events`);
|
||||
Object.entries(params).forEach(param => request_url.searchParams.append(...param));
|
||||
|
||||
function getRelativeTime(current, previous) {
|
||||
const msPerMinute = 60 * 1000;
|
||||
const msPerHour = msPerMinute * 60;
|
||||
const msPerDay = msPerHour * 24;
|
||||
const msPerMonth = msPerDay * 30;
|
||||
const msPerYear = msPerDay * 365;
|
||||
|
||||
let elapsed = current - previous;
|
||||
const tense = elapsed > 0 ? ' ago' : ' later';
|
||||
|
||||
elapsed = Math.abs(elapsed);
|
||||
|
||||
if (elapsed < msPerHour) {
|
||||
return Math.round(elapsed / msPerMinute) + ' minutes' + tense;
|
||||
} else if (elapsed < msPerDay) {
|
||||
return Math.round(elapsed / msPerHour) + ' hours' + tense;
|
||||
} else if (elapsed < msPerMonth) {
|
||||
return 'about ' + Math.round(elapsed / msPerDay) + ' days' + tense;
|
||||
} else if (elapsed < msPerYear) {
|
||||
return 'about ' + Math.round(elapsed / msPerMonth) + ' months' + tense;
|
||||
}
|
||||
|
||||
return 'about ' + Math.round(elapsed / msPerYear) + ' years' + tense;
|
||||
}
|
||||
|
||||
function buildEventDOM(tense, event, start, end) {
|
||||
const durationFormat = {
|
||||
weekday: 'short',
|
||||
hour : '2-digit',
|
||||
minute : '2-digit'
|
||||
};
|
||||
const relativeTime = tense === 'now' ? 'NOW' : getRelativeTime(now, start);
|
||||
const duration = start.toLocaleTimeString([], durationFormat) + ' - ' + end.toLocaleTimeString([], durationFormat);
|
||||
|
||||
let location = '';
|
||||
if (calendar.showLocation && event.location) {
|
||||
location = `<span class="event-location event-details">${event.location}</span>`;
|
||||
}
|
||||
let description = '';
|
||||
if (event.description) {
|
||||
description = `<span class="event-description event-details">${event.description}</span>`;
|
||||
}
|
||||
|
||||
const eventContent = `<section class="event event-${tense}">
|
||||
<h2 class="event-summary">
|
||||
${event.summary}
|
||||
<span class="event-relative-time">${relativeTime}</span>
|
||||
</h2>
|
||||
${location}
|
||||
<span class="event-duration event-details">${duration}</span>
|
||||
${description}
|
||||
</section>`;
|
||||
return eventContent;
|
||||
}
|
||||
|
||||
function fetchData() {
|
||||
const eventList = document.querySelector('.event-list');
|
||||
if (!eventList) return;
|
||||
|
||||
fetch(request_url.href).then(response => {
|
||||
return response.json();
|
||||
}).then(data => {
|
||||
if (data.items.length === 0) {
|
||||
eventList.innerHTML = '<hr>';
|
||||
return;
|
||||
}
|
||||
// Clean the event list
|
||||
eventList.innerHTML = '';
|
||||
let prevEnd = 0; // used to decide where to insert an <hr>
|
||||
const utc = new Date().getTimezoneOffset() * 60000;
|
||||
|
||||
data.items.forEach(event => {
|
||||
// Parse data
|
||||
const start = new Date(event.start.dateTime || (new Date(event.start.date).getTime() + utc));
|
||||
const end = new Date(event.end.dateTime || (new Date(event.end.date).getTime() + utc));
|
||||
|
||||
let tense = 'now';
|
||||
if (end < now) {
|
||||
tense = 'past';
|
||||
} else if (start > now) {
|
||||
tense = 'future';
|
||||
}
|
||||
|
||||
if (tense === 'future' && prevEnd < now) {
|
||||
eventList.insertAdjacentHTML('beforeend', '<hr>');
|
||||
}
|
||||
|
||||
eventList.insertAdjacentHTML('beforeend', buildEventDOM(tense, event, start, end));
|
||||
prevEnd = end;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fetchData();
|
||||
const fetchDataTimer = setInterval(fetchData, 60000);
|
||||
document.addEventListener('pjax:send', () => {
|
||||
clearInterval(fetchDataTimer);
|
||||
});
|
||||
})();
|
||||
60
assets/js/schemes/muse.js
Normal file
60
assets/js/schemes/muse.js
Normal file
@@ -0,0 +1,60 @@
|
||||
/* global CONFIG */
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
const isRight = CONFIG.sidebar.position === 'right';
|
||||
|
||||
const sidebarToggleMotion = {
|
||||
mouse: {},
|
||||
init : function() {
|
||||
window.addEventListener('mousedown', this.mousedownHandler.bind(this));
|
||||
window.addEventListener('mouseup', this.mouseupHandler.bind(this));
|
||||
document.querySelector('.sidebar-dimmer').addEventListener('click', this.clickHandler.bind(this));
|
||||
document.querySelector('.sidebar-toggle').addEventListener('click', this.clickHandler.bind(this));
|
||||
window.addEventListener('sidebar:show', this.showSidebar);
|
||||
window.addEventListener('sidebar:hide', this.hideSidebar);
|
||||
},
|
||||
mousedownHandler: function(event) {
|
||||
this.mouse.X = event.pageX;
|
||||
this.mouse.Y = event.pageY;
|
||||
},
|
||||
mouseupHandler: function(event) {
|
||||
const deltaX = event.pageX - this.mouse.X;
|
||||
const deltaY = event.pageY - this.mouse.Y;
|
||||
const clickingBlankPart = Math.hypot(deltaX, deltaY) < 20 && event.target.matches('.main');
|
||||
// Fancybox has z-index property, but medium-zoom does not, so the sidebar will overlay the zoomed image.
|
||||
if (clickingBlankPart || event.target.matches('img.medium-zoom-image')) {
|
||||
this.hideSidebar();
|
||||
}
|
||||
},
|
||||
clickHandler: function() {
|
||||
document.body.classList.contains('sidebar-active') ? this.hideSidebar() : this.showSidebar();
|
||||
},
|
||||
showSidebar: function() {
|
||||
document.body.classList.add('sidebar-active');
|
||||
const animateAction = isRight ? 'fadeInRight' : 'fadeInLeft';
|
||||
document.querySelectorAll('.sidebar .animated').forEach((element, index) => {
|
||||
element.style.animationDelay = (100 * index) + 'ms';
|
||||
element.classList.remove(animateAction);
|
||||
setTimeout(() => {
|
||||
// Trigger a DOM reflow
|
||||
element.classList.add(animateAction);
|
||||
});
|
||||
});
|
||||
},
|
||||
hideSidebar: function() {
|
||||
document.body.classList.remove('sidebar-active');
|
||||
}
|
||||
};
|
||||
if (CONFIG.sidebar.display !== 'remove') sidebarToggleMotion.init();
|
||||
|
||||
function updateFooterPosition() {
|
||||
const footer = document.querySelector('.footer');
|
||||
const containerHeight = document.querySelector('header.header').offsetHeight + document.querySelector('.main').offsetHeight + footer.offsetHeight;
|
||||
footer.classList.toggle('footer-fixed', containerHeight <= window.innerHeight);
|
||||
}
|
||||
|
||||
updateFooterPosition();
|
||||
window.addEventListener('resize', updateFooterPosition);
|
||||
window.addEventListener('scroll', updateFooterPosition, { passive: true });
|
||||
});
|
||||
7
assets/js/third-party/analytics/baidu-analytics.js
vendored
Normal file
7
assets/js/third-party/analytics/baidu-analytics.js
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
/* global _hmt */
|
||||
|
||||
if (!window._hmt) window._hmt = [];
|
||||
|
||||
document.addEventListener('pjax:success', () => {
|
||||
_hmt.push(['_trackPageview', location.pathname]);
|
||||
});
|
||||
35
assets/js/third-party/analytics/google-analytics.js
vendored
Normal file
35
assets/js/third-party/analytics/google-analytics.js
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
/* global CONFIG, dataLayer, gtag */
|
||||
|
||||
if (!CONFIG.google_analytics.only_pageview) {
|
||||
if (CONFIG.hostname === location.hostname) {
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
window.gtag = function() {
|
||||
dataLayer.push(arguments);
|
||||
};
|
||||
gtag('js', new Date());
|
||||
gtag('config', CONFIG.google_analytics.tracking_id);
|
||||
|
||||
document.addEventListener('pjax:success', () => {
|
||||
gtag('event', 'page_view', {
|
||||
page_location: location.href,
|
||||
page_path : location.pathname,
|
||||
page_title : document.title
|
||||
});
|
||||
});
|
||||
}
|
||||
} else {
|
||||
const sendPageView = () => {
|
||||
if (CONFIG.hostname !== location.hostname) return;
|
||||
const uid = localStorage.getItem('uid') || (Math.random() + '.' + Math.random());
|
||||
localStorage.setItem('uid', uid);
|
||||
navigator.sendBeacon('https://www.google-analytics.com/collect', new URLSearchParams({
|
||||
v : 1,
|
||||
tid: CONFIG.google_analytics.tracking_id,
|
||||
cid: uid,
|
||||
t : 'pageview',
|
||||
dp : encodeURIComponent(location.pathname)
|
||||
}));
|
||||
};
|
||||
document.addEventListener('pjax:complete', sendPageView);
|
||||
sendPageView();
|
||||
}
|
||||
10
assets/js/third-party/analytics/growingio.js
vendored
Normal file
10
assets/js/third-party/analytics/growingio.js
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
/* global CONFIG, gio */
|
||||
|
||||
if (!window.gio) {
|
||||
window.gio = function() {
|
||||
(window.gio.q = window.gio.q || []).push(arguments);
|
||||
};
|
||||
}
|
||||
|
||||
gio('init', `${CONFIG.growingio_analytics}`, {});
|
||||
gio('send');
|
||||
19
assets/js/third-party/chat/chatra.js
vendored
Normal file
19
assets/js/third-party/chat/chatra.js
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
/* global CONFIG, Chatra */
|
||||
|
||||
(function() {
|
||||
if (CONFIG.chatra.embed) {
|
||||
window.ChatraSetup = {
|
||||
mode : 'frame',
|
||||
injectTo: CONFIG.chatra.embed
|
||||
};
|
||||
}
|
||||
|
||||
window.ChatraID = CONFIG.chatra.id;
|
||||
|
||||
const chatButton = document.querySelector('.sidebar-button button');
|
||||
if (chatButton) {
|
||||
chatButton.addEventListener('click', () => {
|
||||
Chatra('openChat', true);
|
||||
});
|
||||
}
|
||||
})();
|
||||
5
assets/js/third-party/chat/gitter.js
vendored
Normal file
5
assets/js/third-party/chat/gitter.js
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
/* global CONFIG */
|
||||
|
||||
((window.gitter = {}).chat = {}).options = {
|
||||
room: CONFIG.gitter.room
|
||||
};
|
||||
10
assets/js/third-party/chat/tidio.js
vendored
Normal file
10
assets/js/third-party/chat/tidio.js
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
/* global tidioChatApi */
|
||||
|
||||
(function() {
|
||||
const chatButton = document.querySelector('.sidebar-button button');
|
||||
if (chatButton) {
|
||||
chatButton.addEventListener('click', () => {
|
||||
tidioChatApi.open();
|
||||
});
|
||||
}
|
||||
})();
|
||||
39
assets/js/third-party/comments/changyan.js
vendored
Normal file
39
assets/js/third-party/comments/changyan.js
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
/* global NexT, CONFIG */
|
||||
|
||||
document.addEventListener('page:loaded', () => {
|
||||
const { appid, appkey } = CONFIG.changyan;
|
||||
const mainJs = 'https://cy-cdn.kuaizhan.com/upload/changyan.js';
|
||||
const countJs = `https://cy-cdn.kuaizhan.com/upload/plugins/plugins.list.count.js?clientId=${appid}`;
|
||||
|
||||
// Get the number of comments
|
||||
setTimeout(() => {
|
||||
return NexT.utils.getScript(countJs, {
|
||||
attributes: {
|
||||
async: true,
|
||||
id : 'cy_cmt_num'
|
||||
}
|
||||
});
|
||||
}, 0);
|
||||
|
||||
// When scroll to comment section
|
||||
if (CONFIG.page.comments && !CONFIG.page.isHome) {
|
||||
NexT.utils.loadComments('#SOHUCS')
|
||||
.then(() => {
|
||||
return NexT.utils.getScript(mainJs, {
|
||||
attributes: {
|
||||
async: true
|
||||
}
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
window.changyan.api.config({
|
||||
appid,
|
||||
conf: appkey
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('Failed to load Changyan', error);
|
||||
});
|
||||
}
|
||||
});
|
||||
41
assets/js/third-party/comments/disqus.js
vendored
Normal file
41
assets/js/third-party/comments/disqus.js
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
/* global NexT, CONFIG, DISQUS */
|
||||
|
||||
document.addEventListener('page:loaded', () => {
|
||||
|
||||
if (CONFIG.disqus.count) {
|
||||
const loadCount = () => {
|
||||
NexT.utils.getScript(`https://${CONFIG.disqus.shortname}.disqus.com/count.js`, {
|
||||
attributes: { id: 'dsq-count-scr' }
|
||||
});
|
||||
};
|
||||
|
||||
// defer loading until the whole page loading is completed
|
||||
window.addEventListener('load', loadCount, false);
|
||||
}
|
||||
|
||||
if (CONFIG.page.comments) {
|
||||
// `disqus_config` should be a global variable
|
||||
// See https://help.disqus.com/en/articles/1717084-javascript-configuration-variables
|
||||
window.disqus_config = function() {
|
||||
this.page.url = CONFIG.page.permalink;
|
||||
this.page.identifier = CONFIG.page.path;
|
||||
this.page.title = CONFIG.page.title;
|
||||
if (CONFIG.disqus.i18n.disqus !== 'disqus') {
|
||||
this.language = CONFIG.disqus.i18n.disqus;
|
||||
}
|
||||
};
|
||||
NexT.utils.loadComments('#disqus_thread').then(() => {
|
||||
if (window.DISQUS) {
|
||||
DISQUS.reset({
|
||||
reload: true,
|
||||
config: window.disqus_config
|
||||
});
|
||||
} else {
|
||||
NexT.utils.getScript(`https://${CONFIG.disqus.shortname}.disqus.com/embed.js`, {
|
||||
attributes: { dataset: { timestamp: '' + +new Date() } }
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
18
assets/js/third-party/comments/disqusjs.js
vendored
Normal file
18
assets/js/third-party/comments/disqusjs.js
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
/* global NexT, CONFIG, DisqusJS */
|
||||
|
||||
document.addEventListener('page:loaded', () => {
|
||||
if (!CONFIG.page.comments) return;
|
||||
|
||||
NexT.utils.loadComments('#disqus_thread')
|
||||
.then(() => NexT.utils.getScript(CONFIG.disqusjs.js, { condition: window.DisqusJS }))
|
||||
.then(() => {
|
||||
window.dsqjs = new DisqusJS({
|
||||
api : CONFIG.disqusjs.api || 'https://disqus.com/api/',
|
||||
apikey : CONFIG.disqusjs.apikey,
|
||||
shortname : CONFIG.disqusjs.shortname,
|
||||
url : CONFIG.page.permalink,
|
||||
identifier: CONFIG.page.path,
|
||||
title : CONFIG.page.title
|
||||
});
|
||||
});
|
||||
});
|
||||
24
assets/js/third-party/comments/gitalk.js
vendored
Normal file
24
assets/js/third-party/comments/gitalk.js
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
/* global NexT, CONFIG, Gitalk */
|
||||
|
||||
document.addEventListener('page:loaded', () => {
|
||||
if (!CONFIG.page.comments) return;
|
||||
|
||||
NexT.utils.loadComments('.gitalk-container')
|
||||
.then(() => NexT.utils.getScript(CONFIG.gitalk.js, {
|
||||
condition: window.Gitalk
|
||||
}))
|
||||
.then(() => {
|
||||
const gitalk = new Gitalk({
|
||||
clientID : CONFIG.gitalk.client_id,
|
||||
clientSecret : CONFIG.gitalk.client_secret,
|
||||
repo : CONFIG.gitalk.repo,
|
||||
owner : CONFIG.gitalk.github_id,
|
||||
admin : [CONFIG.gitalk.admin_user],
|
||||
id : CONFIG.gitalk.path_md5,
|
||||
proxy : CONFIG.gitalk.proxy,
|
||||
language : CONFIG.gitalk.language || window.navigator.language,
|
||||
distractionFreeMode: CONFIG.gitalk.distraction_free_mode
|
||||
});
|
||||
gitalk.render(document.querySelector('.gitalk-container'));
|
||||
});
|
||||
});
|
||||
15
assets/js/third-party/comments/isso.js
vendored
Normal file
15
assets/js/third-party/comments/isso.js
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
/* global NexT, CONFIG */
|
||||
|
||||
document.addEventListener('page:loaded', () => {
|
||||
if (!CONFIG.page.comments) return;
|
||||
|
||||
NexT.utils.loadComments('#isso-thread')
|
||||
.then(() => NexT.utils.getScript(`${CONFIG.isso}js/embed.min.js`, {
|
||||
attributes: {
|
||||
dataset: {
|
||||
isso: `${CONFIG.isso}`
|
||||
}
|
||||
},
|
||||
parentNode: document.querySelector('#isso-thread')
|
||||
}));
|
||||
});
|
||||
19
assets/js/third-party/comments/livere.js
vendored
Normal file
19
assets/js/third-party/comments/livere.js
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
/* global NexT, CONFIG, LivereTower */
|
||||
|
||||
document.addEventListener('page:loaded', () => {
|
||||
if (!CONFIG.page.comments) return;
|
||||
|
||||
NexT.utils.loadComments('#lv-container').then(() => {
|
||||
window.livereOptions = {
|
||||
refer: CONFIG.page.path.replace(/index\.html$/, '')
|
||||
};
|
||||
|
||||
if (typeof LivereTower === 'function') return;
|
||||
|
||||
NexT.utils.getScript('https://cdn-city.livere.com/js/embed.dist.js', {
|
||||
attributes: {
|
||||
async: true
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
17
assets/js/third-party/comments/utterances.js
vendored
Normal file
17
assets/js/third-party/comments/utterances.js
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
/* global NexT, CONFIG */
|
||||
|
||||
document.addEventListener('page:loaded', () => {
|
||||
if (!CONFIG.page.comments) return;
|
||||
|
||||
NexT.utils.loadComments('.utterances-container')
|
||||
.then(() => NexT.utils.getScript('https://utteranc.es/client.js', {
|
||||
attributes: {
|
||||
async : true,
|
||||
crossOrigin : 'anonymous',
|
||||
'repo' : CONFIG.utterances.repo,
|
||||
'issue-term': CONFIG.utterances.issue_term,
|
||||
'theme' : CONFIG.utterances.theme
|
||||
},
|
||||
parentNode: document.querySelector('.utterances-container')
|
||||
}));
|
||||
});
|
||||
38
assets/js/third-party/fancybox.js
vendored
Normal file
38
assets/js/third-party/fancybox.js
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
document.addEventListener('page:loaded', () => {
|
||||
|
||||
/**
|
||||
* Wrap images with fancybox.
|
||||
*/
|
||||
document.querySelectorAll('.post-body :not(a) > img, .post-body > img').forEach(element => {
|
||||
const $image = $(element);
|
||||
const imageLink = $image.attr('data-src') || $image.attr('src');
|
||||
const $imageWrapLink = $image.wrap(`<a class="fancybox fancybox.image" href="${imageLink}" itemscope itemtype="http://schema.org/ImageObject" itemprop="url"></a>`).parent('a');
|
||||
if ($image.is('.post-gallery img')) {
|
||||
$imageWrapLink.attr('data-fancybox', 'gallery').attr('rel', 'gallery');
|
||||
} else if ($image.is('.group-picture img')) {
|
||||
$imageWrapLink.attr('data-fancybox', 'group').attr('rel', 'group');
|
||||
} else {
|
||||
$imageWrapLink.attr('data-fancybox', 'default').attr('rel', 'default');
|
||||
}
|
||||
|
||||
const imageTitle = $image.attr('title') || $image.attr('alt');
|
||||
if (imageTitle) {
|
||||
// Do not append image-caption if pandoc has already created a figcaption
|
||||
if (!$imageWrapLink.next('figcaption').length) {
|
||||
$imageWrapLink.append(`<p class="image-caption">${imageTitle}</p>`);
|
||||
}
|
||||
// Make sure img title tag will show correctly in fancybox
|
||||
$imageWrapLink.attr('title', imageTitle).attr('data-caption', imageTitle);
|
||||
}
|
||||
});
|
||||
|
||||
$.fancybox.defaults.hash = false;
|
||||
$('.fancybox').fancybox({
|
||||
loop : true,
|
||||
helpers: {
|
||||
overlay: {
|
||||
locked: false
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
7
assets/js/third-party/math/katex.js
vendored
Normal file
7
assets/js/third-party/math/katex.js
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
/* global NexT, CONFIG */
|
||||
|
||||
document.addEventListener('page:loaded', () => {
|
||||
if (!CONFIG.enableMath) return;
|
||||
|
||||
NexT.utils.getScript(CONFIG.katex.copy_tex_js).catch(() => {});
|
||||
});
|
||||
36
assets/js/third-party/math/mathjax.js
vendored
Normal file
36
assets/js/third-party/math/mathjax.js
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
/* global NexT, CONFIG, MathJax */
|
||||
|
||||
document.addEventListener('page:loaded', () => {
|
||||
if (!CONFIG.enableMath) return;
|
||||
|
||||
if (typeof MathJax === 'undefined') {
|
||||
window.MathJax = {
|
||||
tex: {
|
||||
inlineMath: { '[+]': [['$', '$']] },
|
||||
tags : CONFIG.mathjax.tags
|
||||
},
|
||||
options: {
|
||||
renderActions: {
|
||||
insertedScript: [200, () => {
|
||||
document.querySelectorAll('mjx-container').forEach(node => {
|
||||
const target = node.parentNode;
|
||||
if (target.nodeName.toLowerCase() === 'li') {
|
||||
target.parentNode.classList.add('has-jax');
|
||||
}
|
||||
});
|
||||
}, '', false]
|
||||
}
|
||||
}
|
||||
};
|
||||
NexT.utils.getScript(CONFIG.mathjax.js, {
|
||||
attributes: {
|
||||
defer: true
|
||||
}
|
||||
});
|
||||
} else {
|
||||
MathJax.startup.document.state(0);
|
||||
MathJax.typesetClear();
|
||||
MathJax.texReset();
|
||||
MathJax.typesetPromise();
|
||||
}
|
||||
});
|
||||
7
assets/js/third-party/pace.js
vendored
Normal file
7
assets/js/third-party/pace.js
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
/* global Pace */
|
||||
|
||||
Pace.options.restartOnPushState = false;
|
||||
|
||||
document.addEventListener('pjax:send', () => {
|
||||
Pace.restart();
|
||||
});
|
||||
37
assets/js/third-party/quicklink.js
vendored
Normal file
37
assets/js/third-party/quicklink.js
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
/* global CONFIG, quicklink */
|
||||
|
||||
(function() {
|
||||
if (typeof CONFIG.quicklink.ignores === 'string') {
|
||||
const ignoresStr = `[${CONFIG.quicklink.ignores}]`;
|
||||
CONFIG.quicklink.ignores = JSON.parse(ignoresStr);
|
||||
}
|
||||
|
||||
let resetFn = null;
|
||||
|
||||
const onRefresh = () => {
|
||||
if (resetFn) resetFn();
|
||||
if (!CONFIG.quicklink.enable) return;
|
||||
|
||||
let ignoresArr = CONFIG.quicklink.ignores || [];
|
||||
if (!Array.isArray(ignoresArr)) {
|
||||
ignoresArr = [ignoresArr];
|
||||
}
|
||||
|
||||
resetFn = quicklink.listen({
|
||||
timeout : CONFIG.quicklink.timeout,
|
||||
priority: CONFIG.quicklink.priority,
|
||||
ignores : [
|
||||
uri => uri.includes('#'),
|
||||
uri => uri === CONFIG.quicklink.url,
|
||||
...ignoresArr
|
||||
]
|
||||
});
|
||||
};
|
||||
|
||||
if (CONFIG.quicklink.delay) {
|
||||
window.addEventListener('load', onRefresh);
|
||||
document.addEventListener('pjax:success', onRefresh);
|
||||
} else {
|
||||
document.addEventListener('page:loaded', onRefresh);
|
||||
}
|
||||
})();
|
||||
22
assets/js/third-party/rating.js
vendored
Normal file
22
assets/js/third-party/rating.js
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
/* global CONFIG, WPac */
|
||||
|
||||
(function() {
|
||||
const widgets = [{
|
||||
widget: 'Rating',
|
||||
id : CONFIG.rating.id,
|
||||
el : 'wpac-rating',
|
||||
color : CONFIG.rating.color
|
||||
}];
|
||||
|
||||
document.addEventListener('page:loaded', () => {
|
||||
if (!CONFIG.page.isPost) return;
|
||||
|
||||
const newWidgets = widgets.map(widget => ({ ...widget }));
|
||||
|
||||
if (window.WPac) {
|
||||
WPac.init(newWidgets);
|
||||
} else {
|
||||
window.wpac_init = newWidgets;
|
||||
}
|
||||
});
|
||||
})();
|
||||
130
assets/js/third-party/search/algolia-search.js
vendored
Normal file
130
assets/js/third-party/search/algolia-search.js
vendored
Normal file
@@ -0,0 +1,130 @@
|
||||
/* global instantsearch, algoliasearch, CONFIG, pjax */
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const { indexName, appID, apiKey, hits } = CONFIG.algolia;
|
||||
|
||||
const search = instantsearch({
|
||||
indexName,
|
||||
searchClient : algoliasearch(appID, apiKey),
|
||||
searchFunction: helper => {
|
||||
if (document.querySelector('.search-input').value) {
|
||||
helper.search();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (typeof pjax === 'object') {
|
||||
search.on('render', () => {
|
||||
pjax.refresh(document.querySelector('.algolia-hits'));
|
||||
});
|
||||
}
|
||||
|
||||
// Registering Widgets
|
||||
search.addWidgets([
|
||||
instantsearch.widgets.configure({
|
||||
hitsPerPage: hits.per_page || 10
|
||||
}),
|
||||
|
||||
instantsearch.widgets.searchBox({
|
||||
container : '.search-input-container',
|
||||
placeholder : CONFIG.i18n.placeholder,
|
||||
// Hide default icons of algolia search
|
||||
showReset : false,
|
||||
showSubmit : false,
|
||||
showLoadingIndicator: false,
|
||||
cssClasses : {
|
||||
input: 'search-input'
|
||||
}
|
||||
}),
|
||||
|
||||
instantsearch.widgets.stats({
|
||||
container: '.algolia-stats',
|
||||
templates: {
|
||||
text: data => {
|
||||
const stats = CONFIG.i18n.hits_time
|
||||
.replace('${hits}', data.nbHits)
|
||||
.replace('${time}', data.processingTimeMS);
|
||||
return `<span>${stats}</span>
|
||||
<img src="${CONFIG.images}/logo-algolia-nebula-blue-full.svg" alt="Algolia">`;
|
||||
}
|
||||
},
|
||||
cssClasses: {
|
||||
text: 'search-stats'
|
||||
}
|
||||
}),
|
||||
|
||||
instantsearch.widgets.hits({
|
||||
container : '.algolia-hits',
|
||||
escapeHTML: false,
|
||||
templates : {
|
||||
item: data => {
|
||||
const { title, excerpt, excerptStrip, contentStripTruncate } = data._highlightResult;
|
||||
let result = `<a href="${data.permalink}" class="search-result-title">${title.value}</a>`;
|
||||
const content = excerpt || excerptStrip || contentStripTruncate;
|
||||
if (content && content.value) {
|
||||
const div = document.createElement('div');
|
||||
div.innerHTML = content.value;
|
||||
result += `<a href="${data.permalink}"><p class="search-result">${div.textContent.substring(0, 100)}...</p></a>`;
|
||||
}
|
||||
return result;
|
||||
},
|
||||
empty: data => {
|
||||
return `<div class="algolia-hits-empty">
|
||||
${CONFIG.i18n.empty.replace('${query}', data.query)}
|
||||
</div>`;
|
||||
}
|
||||
},
|
||||
cssClasses: {
|
||||
list: 'search-result-list'
|
||||
}
|
||||
}),
|
||||
|
||||
instantsearch.widgets.pagination({
|
||||
container: '.algolia-pagination',
|
||||
scrollTo : false,
|
||||
showFirst: false,
|
||||
showLast : false,
|
||||
templates: {
|
||||
first : '<i class="fa fa-angle-double-left"></i>',
|
||||
last : '<i class="fa fa-angle-double-right"></i>',
|
||||
previous: '<i class="fa fa-angle-left"></i>',
|
||||
next : '<i class="fa fa-angle-right"></i>'
|
||||
},
|
||||
cssClasses: {
|
||||
list : ['pagination', 'algolia-pagination'],
|
||||
item : 'pagination-item',
|
||||
link : 'page-number',
|
||||
selectedItem: 'current',
|
||||
disabledItem: 'disabled-item'
|
||||
}
|
||||
})
|
||||
]);
|
||||
|
||||
search.start();
|
||||
|
||||
// Handle and trigger popup window
|
||||
document.querySelectorAll('.popup-trigger').forEach(element => {
|
||||
element.addEventListener('click', () => {
|
||||
document.body.classList.add('search-active');
|
||||
setTimeout(() => document.querySelector('.search-input').focus(), 500);
|
||||
});
|
||||
});
|
||||
|
||||
// Monitor main search box
|
||||
const onPopupClose = () => {
|
||||
document.body.classList.remove('search-active');
|
||||
};
|
||||
|
||||
document.querySelector('.search-pop-overlay').addEventListener('click', event => {
|
||||
if (event.target === document.querySelector('.search-pop-overlay')) {
|
||||
onPopupClose();
|
||||
}
|
||||
});
|
||||
document.querySelector('.popup-btn-close').addEventListener('click', onPopupClose);
|
||||
document.addEventListener('pjax:success', onPopupClose);
|
||||
window.addEventListener('keyup', event => {
|
||||
if (event.key === 'Escape') {
|
||||
onPopupClose();
|
||||
}
|
||||
});
|
||||
});
|
||||
99
assets/js/third-party/search/local-search.js
vendored
Normal file
99
assets/js/third-party/search/local-search.js
vendored
Normal file
@@ -0,0 +1,99 @@
|
||||
/* global CONFIG, pjax, LocalSearch */
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
if (!CONFIG.path) {
|
||||
// Search DB path
|
||||
console.warn('`hexo-generator-searchdb` plugin is not installed!');
|
||||
return;
|
||||
}
|
||||
const localSearch = new LocalSearch({
|
||||
path : CONFIG.path,
|
||||
top_n_per_article: CONFIG.localsearch.top_n_per_article,
|
||||
unescape : CONFIG.localsearch.unescape
|
||||
});
|
||||
|
||||
const input = document.querySelector('.search-input');
|
||||
|
||||
const inputEventFunction = () => {
|
||||
if (!localSearch.isfetched) return;
|
||||
const searchText = input.value.trim().toLowerCase();
|
||||
const keywords = searchText.split(/[-\s]+/);
|
||||
const container = document.querySelector('.search-result-container');
|
||||
let resultItems = [];
|
||||
if (searchText.length > 0) {
|
||||
// Perform local searching
|
||||
resultItems = localSearch.getResultItems(keywords);
|
||||
}
|
||||
if (keywords.length === 1 && keywords[0] === '') {
|
||||
container.classList.add('no-result');
|
||||
container.innerHTML = '<div class="search-result-icon"><i class="fa fa-search fa-5x"></i></div>';
|
||||
} else if (resultItems.length === 0) {
|
||||
container.classList.add('no-result');
|
||||
container.innerHTML = '<div class="search-result-icon"><i class="far fa-frown fa-5x"></i></div>';
|
||||
} else {
|
||||
resultItems.sort((left, right) => {
|
||||
if (left.includedCount !== right.includedCount) {
|
||||
return right.includedCount - left.includedCount;
|
||||
} else if (left.hitCount !== right.hitCount) {
|
||||
return right.hitCount - left.hitCount;
|
||||
}
|
||||
return right.id - left.id;
|
||||
});
|
||||
const stats = CONFIG.i18n.hits.replace('${hits}', resultItems.length);
|
||||
|
||||
container.classList.remove('no-result');
|
||||
container.innerHTML = `<div class="search-stats">${stats}</div>
|
||||
<hr>
|
||||
<ul class="search-result-list">${resultItems.map(result => result.item).join('')}</ul>`;
|
||||
if (typeof pjax === 'object') pjax.refresh(container);
|
||||
}
|
||||
};
|
||||
|
||||
localSearch.highlightSearchWords(document.querySelector('.post-body'));
|
||||
if (CONFIG.localsearch.preload) {
|
||||
localSearch.fetchData();
|
||||
}
|
||||
|
||||
if (CONFIG.localsearch.trigger === 'auto') {
|
||||
input.addEventListener('input', inputEventFunction);
|
||||
} else {
|
||||
document.querySelector('.search-icon').addEventListener('click', inputEventFunction);
|
||||
input.addEventListener('keypress', event => {
|
||||
if (event.key === 'Enter') {
|
||||
inputEventFunction();
|
||||
}
|
||||
});
|
||||
}
|
||||
window.addEventListener('search:loaded', inputEventFunction);
|
||||
|
||||
// Handle and trigger popup window
|
||||
document.querySelectorAll('.popup-trigger').forEach(element => {
|
||||
element.addEventListener('click', () => {
|
||||
document.body.classList.add('search-active');
|
||||
// Wait for search-popup animation to complete
|
||||
setTimeout(() => input.focus(), 500);
|
||||
if (!localSearch.isfetched) localSearch.fetchData();
|
||||
});
|
||||
});
|
||||
|
||||
// Monitor main search box
|
||||
const onPopupClose = () => {
|
||||
document.body.classList.remove('search-active');
|
||||
};
|
||||
|
||||
document.querySelector('.search-pop-overlay').addEventListener('click', event => {
|
||||
if (event.target === document.querySelector('.search-pop-overlay')) {
|
||||
onPopupClose();
|
||||
}
|
||||
});
|
||||
document.querySelector('.popup-btn-close').addEventListener('click', onPopupClose);
|
||||
document.addEventListener('pjax:success', () => {
|
||||
localSearch.highlightSearchWords(document.querySelector('.post-body'));
|
||||
onPopupClose();
|
||||
});
|
||||
window.addEventListener('keyup', event => {
|
||||
if (event.key === 'Escape') {
|
||||
onPopupClose();
|
||||
}
|
||||
});
|
||||
});
|
||||
64
assets/js/third-party/statistics/firestore.js
vendored
Normal file
64
assets/js/third-party/statistics/firestore.js
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
/* global CONFIG, firebase */
|
||||
|
||||
firebase.initializeApp({
|
||||
apiKey : CONFIG.firestore.apiKey,
|
||||
projectId: CONFIG.firestore.projectId
|
||||
});
|
||||
|
||||
(function() {
|
||||
const getCount = (doc, increaseCount) => {
|
||||
// IncreaseCount will be false when not in article page
|
||||
return doc.get().then(d => {
|
||||
// Has no data, initialize count
|
||||
let count = d.exists ? d.data().count : 0;
|
||||
// If first view this article
|
||||
if (increaseCount) {
|
||||
// Increase count
|
||||
count++;
|
||||
doc.set({
|
||||
count
|
||||
});
|
||||
}
|
||||
return count;
|
||||
});
|
||||
};
|
||||
|
||||
const appendCountTo = el => {
|
||||
return count => {
|
||||
el.innerText = count;
|
||||
};
|
||||
};
|
||||
|
||||
const db = firebase.firestore();
|
||||
const articles = db.collection(CONFIG.firestore.collection);
|
||||
|
||||
document.addEventListener('page:loaded', () => {
|
||||
|
||||
if (CONFIG.page.isPost) {
|
||||
// Fix issue #118
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent
|
||||
const title = document.querySelector('.post-title').textContent.trim();
|
||||
const doc = articles.doc(title);
|
||||
let increaseCount = CONFIG.hostname === location.hostname;
|
||||
if (localStorage.getItem(title)) {
|
||||
increaseCount = false;
|
||||
} else {
|
||||
// Mark as visited
|
||||
localStorage.setItem(title, true);
|
||||
}
|
||||
getCount(doc, increaseCount).then(appendCountTo(document.querySelector('.firestore-visitors-count')));
|
||||
} else if (CONFIG.page.isHome) {
|
||||
const promises = [...document.querySelectorAll('.post-title')].map(element => {
|
||||
const title = element.textContent.trim();
|
||||
const doc = articles.doc(title);
|
||||
return getCount(doc);
|
||||
});
|
||||
Promise.all(promises).then(counts => {
|
||||
const metas = document.querySelectorAll('.firestore-visitors-count');
|
||||
counts.forEach((val, idx) => {
|
||||
appendCountTo(metas[idx])(val);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
})();
|
||||
102
assets/js/third-party/statistics/lean-analytics.js
vendored
Normal file
102
assets/js/third-party/statistics/lean-analytics.js
vendored
Normal file
@@ -0,0 +1,102 @@
|
||||
/* global CONFIG */
|
||||
/* eslint-disable no-console */
|
||||
|
||||
(function() {
|
||||
const leancloudSelector = url => {
|
||||
url = encodeURI(url);
|
||||
return document.getElementById(url).querySelector('.leancloud-visitors-count');
|
||||
};
|
||||
|
||||
const addCount = Counter => {
|
||||
const visitors = document.querySelector('.leancloud_visitors');
|
||||
const url = decodeURI(visitors.id);
|
||||
const title = visitors.dataset.flagTitle;
|
||||
|
||||
Counter('get', `/classes/Counter?where=${encodeURIComponent(JSON.stringify({ url }))}`)
|
||||
.then(response => response.json())
|
||||
.then(({ results }) => {
|
||||
if (results.length > 0) {
|
||||
const counter = results[0];
|
||||
leancloudSelector(url).innerText = counter.time + 1;
|
||||
Counter('put', '/classes/Counter/' + counter.objectId, {
|
||||
time: {
|
||||
'__op' : 'Increment',
|
||||
'amount': 1
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Failed to save visitor count', error);
|
||||
});
|
||||
} else if (CONFIG.leancloud_visitors.security) {
|
||||
leancloudSelector(url).innerText = 'Counter not initialized! More info at console err msg.';
|
||||
console.error('ATTENTION! LeanCloud counter has security bug, see how to solve it here: https://github.com/theme-next/hexo-leancloud-counter-security. \n However, you can still use LeanCloud without security, by setting `security` option to `false`.');
|
||||
} else {
|
||||
Counter('post', '/classes/Counter', { title, url, time: 1 })
|
||||
.then(response => response.json())
|
||||
.then(() => {
|
||||
leancloudSelector(url).innerText = 1;
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Failed to create', error);
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('LeanCloud Counter Error', error);
|
||||
});
|
||||
};
|
||||
|
||||
const showTime = Counter => {
|
||||
const visitors = document.querySelectorAll('.leancloud_visitors');
|
||||
const entries = [...visitors].map(element => {
|
||||
return decodeURI(element.id);
|
||||
});
|
||||
|
||||
Counter('get', `/classes/Counter?where=${encodeURIComponent(JSON.stringify({ url: { '$in': entries } }))}`)
|
||||
.then(response => response.json())
|
||||
.then(({ results }) => {
|
||||
for (const url of entries) {
|
||||
const target = results.find(item => item.url === url);
|
||||
leancloudSelector(url).innerText = target ? target.time : 0;
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('LeanCloud Counter Error', error);
|
||||
});
|
||||
};
|
||||
|
||||
const { app_id, app_key, server_url } = CONFIG.leancloud_visitors;
|
||||
const fetchData = api_server => {
|
||||
const Counter = (method, url, data) => {
|
||||
return fetch(`${api_server}/1.1${url}`, {
|
||||
method,
|
||||
headers: {
|
||||
'X-LC-Id' : app_id,
|
||||
'X-LC-Key' : app_key,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
};
|
||||
if (CONFIG.page.isPost) {
|
||||
if (CONFIG.hostname !== location.hostname) return;
|
||||
addCount(Counter);
|
||||
} else if (document.querySelectorAll('.post-title-link').length >= 1) {
|
||||
showTime(Counter);
|
||||
}
|
||||
};
|
||||
|
||||
const api_server = app_id.slice(-9) === '-MdYXbMMI' ? `https://${app_id.slice(0, 8).toLowerCase()}.api.lncldglobal.com` : server_url;
|
||||
|
||||
document.addEventListener('page:loaded', () => {
|
||||
if (api_server) {
|
||||
fetchData(api_server);
|
||||
} else {
|
||||
fetch(`https://app-router.leancloud.cn/2/route?appId=${app_id}`)
|
||||
.then(response => response.json())
|
||||
.then(({ api_server }) => {
|
||||
fetchData(`https://${api_server}`);
|
||||
});
|
||||
}
|
||||
});
|
||||
})();
|
||||
32
assets/js/third-party/tags/mermaid.js
vendored
Normal file
32
assets/js/third-party/tags/mermaid.js
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
/* global NexT, CONFIG, mermaid */
|
||||
|
||||
document.addEventListener('page:loaded', () => {
|
||||
const mermaidElements = document.querySelectorAll('.mermaid');
|
||||
if (mermaidElements.length) {
|
||||
NexT.utils.getScript(CONFIG.mermaid.js, {
|
||||
condition: window.mermaid
|
||||
}).then(() => {
|
||||
mermaidElements.forEach(element => {
|
||||
const newElement = document.createElement('div');
|
||||
newElement.innerHTML = element.innerHTML;
|
||||
newElement.className = element.className;
|
||||
const parent = element.parentNode;
|
||||
// Fix issue #347
|
||||
// Support mermaid inside backtick code block
|
||||
if (parent.matches('pre')) {
|
||||
parent.parentNode.replaceChild(newElement, parent);
|
||||
} else {
|
||||
parent.replaceChild(newElement, element);
|
||||
}
|
||||
});
|
||||
mermaid.initialize({
|
||||
theme : CONFIG.darkmode && window.matchMedia('(prefers-color-scheme: dark)').matches ? CONFIG.mermaid.theme.dark : CONFIG.mermaid.theme.light,
|
||||
logLevel : 4,
|
||||
flowchart: { curve: 'linear' },
|
||||
gantt : { axisFormat: '%m/%d/%Y' },
|
||||
sequence : { actorMargin: 50 }
|
||||
});
|
||||
mermaid.init();
|
||||
});
|
||||
}
|
||||
});
|
||||
23
assets/js/third-party/tags/pdf.js
vendored
Normal file
23
assets/js/third-party/tags/pdf.js
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
/* global NexT, CONFIG, PDFObject */
|
||||
|
||||
document.addEventListener('page:loaded', () => {
|
||||
if (document.querySelectorAll('.pdf-container').length) {
|
||||
NexT.utils.getScript(CONFIG.pdf.object_url, {
|
||||
condition: window.PDFObject
|
||||
}).then(() => {
|
||||
document.querySelectorAll('.pdf-container').forEach(element => {
|
||||
PDFObject.embed(element.dataset.target, element, {
|
||||
pdfOpenParams: {
|
||||
navpanes : 0,
|
||||
toolbar : 0,
|
||||
statusbar: 0,
|
||||
pagemode : 'thumbs',
|
||||
view : 'FitH'
|
||||
},
|
||||
PDFJS_URL: CONFIG.pdf.url,
|
||||
height : element.dataset.height
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
399
assets/js/utils.js
Normal file
399
assets/js/utils.js
Normal file
@@ -0,0 +1,399 @@
|
||||
/* global NexT, CONFIG */
|
||||
|
||||
HTMLElement.prototype.wrap = function(wrapper) {
|
||||
this.parentNode.insertBefore(wrapper, this);
|
||||
this.parentNode.removeChild(this);
|
||||
wrapper.appendChild(this);
|
||||
};
|
||||
|
||||
(function() {
|
||||
const onPageLoaded = () => document.dispatchEvent(
|
||||
new Event('page:loaded', {
|
||||
bubbles: true
|
||||
})
|
||||
);
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('readystatechange', onPageLoaded, { once: true });
|
||||
} else {
|
||||
onPageLoaded();
|
||||
}
|
||||
document.addEventListener('pjax:success', onPageLoaded);
|
||||
})();
|
||||
|
||||
NexT.utils = {
|
||||
|
||||
registerExtURL: function() {
|
||||
document.querySelectorAll('span.exturl').forEach(element => {
|
||||
const link = document.createElement('a');
|
||||
// https://stackoverflow.com/questions/30106476/using-javascripts-atob-to-decode-base64-doesnt-properly-decode-utf-8-strings
|
||||
link.href = decodeURIComponent(atob(element.dataset.url).split('').map(c => {
|
||||
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
|
||||
}).join(''));
|
||||
link.rel = 'noopener external nofollow noreferrer';
|
||||
link.target = '_blank';
|
||||
link.className = element.className;
|
||||
link.title = element.title;
|
||||
link.innerHTML = element.innerHTML;
|
||||
element.parentNode.replaceChild(link, element);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* One-click copy code support.
|
||||
*/
|
||||
registerCopyCode: function() {
|
||||
let figure = document.querySelectorAll('figure.highlight');
|
||||
if (figure.length === 0) figure = document.querySelectorAll('pre:not(.mermaid)');
|
||||
figure.forEach(element => {
|
||||
element.querySelectorAll('.code .line span').forEach(span => {
|
||||
span.classList.forEach(name => {
|
||||
span.classList.replace(name, `hljs-${name}`);
|
||||
});
|
||||
});
|
||||
if (!CONFIG.copycode) return;
|
||||
element.insertAdjacentHTML('beforeend', '<div class="copy-btn"><i class="fa fa-copy fa-fw"></i></div>');
|
||||
const button = element.querySelector('.copy-btn');
|
||||
button.addEventListener('click', () => {
|
||||
const lines = element.querySelector('.code') || element.querySelector('code');
|
||||
const code = lines.innerText;
|
||||
if (navigator.clipboard) {
|
||||
// https://caniuse.com/mdn-api_clipboard_writetext
|
||||
navigator.clipboard.writeText(code).then(() => {
|
||||
button.querySelector('i').className = 'fa fa-check-circle fa-fw';
|
||||
}, () => {
|
||||
button.querySelector('i').className = 'fa fa-times-circle fa-fw';
|
||||
});
|
||||
} else {
|
||||
const ta = document.createElement('textarea');
|
||||
ta.style.top = window.scrollY + 'px'; // Prevent page scrolling
|
||||
ta.style.position = 'absolute';
|
||||
ta.style.opacity = '0';
|
||||
ta.readOnly = true;
|
||||
ta.value = code;
|
||||
document.body.append(ta);
|
||||
ta.select();
|
||||
ta.setSelectionRange(0, code.length);
|
||||
ta.readOnly = false;
|
||||
const result = document.execCommand('copy');
|
||||
button.querySelector('i').className = result ? 'fa fa-check-circle fa-fw' : 'fa fa-times-circle fa-fw';
|
||||
ta.blur(); // For iOS
|
||||
button.blur();
|
||||
document.body.removeChild(ta);
|
||||
}
|
||||
});
|
||||
element.addEventListener('mouseleave', () => {
|
||||
setTimeout(() => {
|
||||
button.querySelector('i').className = 'fa fa-copy fa-fw';
|
||||
}, 300);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
wrapTableWithBox: function() {
|
||||
document.querySelectorAll('table').forEach(element => {
|
||||
const box = document.createElement('div');
|
||||
box.className = 'table-container';
|
||||
element.wrap(box);
|
||||
});
|
||||
},
|
||||
|
||||
registerVideoIframe: function() {
|
||||
document.querySelectorAll('iframe').forEach(element => {
|
||||
const supported = [
|
||||
'www.youtube.com',
|
||||
'player.vimeo.com',
|
||||
'player.youku.com',
|
||||
'player.bilibili.com',
|
||||
'www.tudou.com'
|
||||
].some(host => element.src.includes(host));
|
||||
if (supported && !element.parentNode.matches('.video-container')) {
|
||||
const box = document.createElement('div');
|
||||
box.className = 'video-container';
|
||||
element.wrap(box);
|
||||
const width = Number(element.width);
|
||||
const height = Number(element.height);
|
||||
if (width && height) {
|
||||
box.style.paddingTop = (height / width * 100) + '%';
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
registerScrollPercent: function() {
|
||||
const backToTop = document.querySelector('.back-to-top');
|
||||
const readingProgressBar = document.querySelector('.reading-progress-bar');
|
||||
// For init back to top in sidebar if page was scrolled after page refresh.
|
||||
window.addEventListener('scroll', () => {
|
||||
if (backToTop || readingProgressBar) {
|
||||
const contentHeight = document.body.scrollHeight - window.innerHeight;
|
||||
const scrollPercent = contentHeight > 0 ? Math.min(100 * window.scrollY / contentHeight, 100) : 0;
|
||||
if (backToTop) {
|
||||
backToTop.classList.toggle('back-to-top-on', Math.round(scrollPercent) >= 5);
|
||||
backToTop.querySelector('span').innerText = Math.round(scrollPercent) + '%';
|
||||
}
|
||||
if (readingProgressBar) {
|
||||
readingProgressBar.style.setProperty('--progress', scrollPercent.toFixed(2) + '%');
|
||||
}
|
||||
}
|
||||
if (!Array.isArray(NexT.utils.sections)) return;
|
||||
let index = NexT.utils.sections.findIndex(element => {
|
||||
return element && element.getBoundingClientRect().top > 10;
|
||||
});
|
||||
if (index === -1) {
|
||||
index = NexT.utils.sections.length - 1;
|
||||
} else if (index > 0) {
|
||||
index--;
|
||||
}
|
||||
this.activateNavByIndex(index);
|
||||
}, { passive: true });
|
||||
|
||||
backToTop && backToTop.addEventListener('click', () => {
|
||||
window.anime({
|
||||
targets : document.scrollingElement,
|
||||
duration : 500,
|
||||
easing : 'linear',
|
||||
scrollTop: 0
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Tabs tag listener (without twitter bootstrap).
|
||||
*/
|
||||
registerTabsTag: function() {
|
||||
// Binding `nav-tabs` & `tab-content` by real time permalink changing.
|
||||
document.querySelectorAll('.tabs ul.nav-tabs .tab').forEach(element => {
|
||||
element.addEventListener('click', event => {
|
||||
event.preventDefault();
|
||||
// Prevent selected tab to select again.
|
||||
if (element.classList.contains('active')) return;
|
||||
const nav = element.parentNode;
|
||||
// Add & Remove active class on `nav-tabs` & `tab-content`.
|
||||
[...nav.children].forEach(target => {
|
||||
target.classList.toggle('active', target === element);
|
||||
});
|
||||
// https://stackoverflow.com/questions/20306204/using-queryselector-with-ids-that-are-numbers
|
||||
const tActive = document.getElementById(element.querySelector('a').getAttribute('href').replace('#', ''));
|
||||
[...tActive.parentNode.children].forEach(target => {
|
||||
target.classList.toggle('active', target === tActive);
|
||||
});
|
||||
// Trigger event
|
||||
tActive.dispatchEvent(new Event('tabs:click', {
|
||||
bubbles: true
|
||||
}));
|
||||
if (!CONFIG.stickytabs) return;
|
||||
const offset = nav.parentNode.getBoundingClientRect().top + window.scrollY + 10;
|
||||
window.anime({
|
||||
targets : document.scrollingElement,
|
||||
duration : 500,
|
||||
easing : 'linear',
|
||||
scrollTop: offset
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
window.dispatchEvent(new Event('tabs:register'));
|
||||
},
|
||||
|
||||
registerCanIUseTag: function() {
|
||||
// Get responsive height passed from iframe.
|
||||
window.addEventListener('message', ({ data }) => {
|
||||
if (typeof data === 'string' && data.includes('ciu_embed')) {
|
||||
const featureID = data.split(':')[1];
|
||||
const height = data.split(':')[2];
|
||||
document.querySelector(`iframe[data-feature=${featureID}]`).style.height = parseInt(height, 10) + 5 + 'px';
|
||||
}
|
||||
}, false);
|
||||
},
|
||||
|
||||
registerActiveMenuItem: function() {
|
||||
document.querySelectorAll('.menu-item a[href]').forEach(target => {
|
||||
const isSamePath = target.pathname === location.pathname || target.pathname === location.pathname.replace('index.html', '');
|
||||
const isSubPath = !CONFIG.root.startsWith(target.pathname) && location.pathname.startsWith(target.pathname);
|
||||
target.classList.toggle('menu-item-active', target.hostname === location.hostname && (isSamePath || isSubPath));
|
||||
});
|
||||
},
|
||||
|
||||
registerLangSelect: function() {
|
||||
const selects = document.querySelectorAll('.lang-select');
|
||||
selects.forEach(sel => {
|
||||
sel.value = CONFIG.page.lang;
|
||||
sel.addEventListener('change', () => {
|
||||
const target = sel.options[sel.selectedIndex];
|
||||
document.querySelectorAll('.lang-select-label span').forEach(span => {
|
||||
span.innerText = target.text;
|
||||
});
|
||||
// Disable Pjax to force refresh translation of menu item
|
||||
window.location.href = target.dataset.href;
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
registerSidebarTOC: function() {
|
||||
this.sections = [...document.querySelectorAll('.post-toc li a.nav-link')].map(element => {
|
||||
const target = document.getElementById(decodeURI(element.getAttribute('href')).replace('#', ''));
|
||||
// TOC item animation navigate.
|
||||
element.addEventListener('click', event => {
|
||||
event.preventDefault();
|
||||
const offset = target.getBoundingClientRect().top + window.scrollY;
|
||||
window.anime({
|
||||
targets : document.scrollingElement,
|
||||
duration : 500,
|
||||
easing : 'linear',
|
||||
scrollTop: offset,
|
||||
complete : () => {
|
||||
history.pushState(null, document.title, element.href);
|
||||
}
|
||||
});
|
||||
});
|
||||
return target;
|
||||
});
|
||||
},
|
||||
|
||||
registerPostReward: function() {
|
||||
const button = document.querySelector('.reward-container button');
|
||||
if (!button) return;
|
||||
button.addEventListener('click', () => {
|
||||
document.querySelector('.post-reward').classList.toggle('active');
|
||||
});
|
||||
},
|
||||
|
||||
activateNavByIndex: function(index) {
|
||||
const target = document.querySelectorAll('.post-toc li a.nav-link')[index];
|
||||
if (!target || target.classList.contains('active-current')) return;
|
||||
|
||||
document.querySelectorAll('.post-toc .active').forEach(element => {
|
||||
element.classList.remove('active', 'active-current');
|
||||
});
|
||||
target.classList.add('active', 'active-current');
|
||||
let parent = target.parentNode;
|
||||
while (!parent.matches('.post-toc')) {
|
||||
if (parent.matches('li')) parent.classList.add('active');
|
||||
parent = parent.parentNode;
|
||||
}
|
||||
// Scrolling to center active TOC element if TOC content is taller then viewport.
|
||||
const tocElement = document.querySelector('.sidebar-panel-container');
|
||||
if (!tocElement.parentNode.classList.contains('sidebar-toc-active')) return;
|
||||
window.anime({
|
||||
targets : tocElement,
|
||||
duration : 200,
|
||||
easing : 'linear',
|
||||
scrollTop: tocElement.scrollTop - (tocElement.offsetHeight / 2) + target.getBoundingClientRect().top - tocElement.getBoundingClientRect().top
|
||||
});
|
||||
},
|
||||
|
||||
updateSidebarPosition: function() {
|
||||
if (window.innerWidth < 992 || CONFIG.scheme === 'Pisces' || CONFIG.scheme === 'Gemini') return;
|
||||
// Expand sidebar on post detail page by default, when post has a toc.
|
||||
const hasTOC = document.querySelector('.post-toc');
|
||||
let display = CONFIG.page.sidebar;
|
||||
if (typeof display !== 'boolean') {
|
||||
// There's no definition sidebar in the page front-matter.
|
||||
display = CONFIG.sidebar.display === 'always' || (CONFIG.sidebar.display === 'post' && hasTOC);
|
||||
}
|
||||
if (display) {
|
||||
window.dispatchEvent(new Event('sidebar:show'));
|
||||
}
|
||||
},
|
||||
|
||||
activateSidebarPanel: function(index) {
|
||||
const duration = 200;
|
||||
const sidebar = document.querySelector('.sidebar-inner');
|
||||
const panel = document.querySelector('.sidebar-panel-container');
|
||||
const activeClassName = ['sidebar-toc-active', 'sidebar-overview-active'];
|
||||
|
||||
if (sidebar.classList.contains(activeClassName[index])) return;
|
||||
|
||||
window.anime({
|
||||
duration,
|
||||
targets : panel,
|
||||
easing : 'linear',
|
||||
opacity : 0,
|
||||
translateY: [0, -20],
|
||||
complete : () => {
|
||||
// Prevent adding TOC to Overview if Overview was selected when close & open sidebar.
|
||||
sidebar.classList.replace(activeClassName[1 - index], activeClassName[index]);
|
||||
window.anime({
|
||||
duration,
|
||||
targets : panel,
|
||||
easing : 'linear',
|
||||
opacity : [0, 1],
|
||||
translateY: [-20, 0]
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
getScript: function(src, options = {}, legacyCondition) {
|
||||
if (typeof options === 'function') {
|
||||
return this.getScript(src, {
|
||||
condition: legacyCondition
|
||||
}).then(options);
|
||||
}
|
||||
const {
|
||||
condition = false,
|
||||
attributes: {
|
||||
id = '',
|
||||
async = false,
|
||||
defer = false,
|
||||
crossOrigin = '',
|
||||
dataset = {},
|
||||
...otherAttributes
|
||||
} = {},
|
||||
parentNode = null
|
||||
} = options;
|
||||
return new Promise((resolve, reject) => {
|
||||
if (condition) {
|
||||
resolve();
|
||||
} else {
|
||||
const script = document.createElement('script');
|
||||
|
||||
if (id) script.id = id;
|
||||
if (crossOrigin) script.crossOrigin = crossOrigin;
|
||||
script.async = async;
|
||||
script.defer = defer;
|
||||
Object.assign(script.dataset, dataset);
|
||||
Object.entries(otherAttributes).forEach(([name, value]) => {
|
||||
script.setAttribute(name, String(value));
|
||||
});
|
||||
|
||||
script.onload = resolve;
|
||||
script.onerror = reject;
|
||||
|
||||
if (typeof src === 'object') {
|
||||
const { url, integrity } = src;
|
||||
script.src = url;
|
||||
if (integrity) {
|
||||
script.integrity = integrity;
|
||||
script.crossOrigin = 'anonymous';
|
||||
}
|
||||
} else {
|
||||
script.src = src;
|
||||
}
|
||||
(parentNode || document.head).appendChild(script);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
loadComments: function(selector, legacyCallback) {
|
||||
if (legacyCallback) {
|
||||
return this.loadComments(selector).then(legacyCallback);
|
||||
}
|
||||
return new Promise(resolve => {
|
||||
const element = document.querySelector(selector);
|
||||
if (!CONFIG.comments.lazyload || !element) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
const intersectionObserver = new IntersectionObserver((entries, observer) => {
|
||||
const entry = entries[0];
|
||||
if (!entry.isIntersecting) return;
|
||||
|
||||
resolve();
|
||||
observer.disconnect();
|
||||
});
|
||||
intersectionObserver.observe(element);
|
||||
});
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user