style: 4 space indents to the JS files
This commit is contained in:
parent
1fd665bf49
commit
339293d0d7
21 changed files with 913 additions and 912 deletions
|
@ -2,10 +2,13 @@ root = true
|
||||||
|
|
||||||
[*]
|
[*]
|
||||||
charset = utf-8
|
charset = utf-8
|
||||||
# 2 space indentation
|
|
||||||
indent_style = space
|
indent_style = space
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
trim_trailing_whitespace = true
|
trim_trailing_whitespace = true
|
||||||
# Unix-style newlines with a newline ending every file
|
# Unix-style newlines with a newline ending every file
|
||||||
end_of_line = lf
|
end_of_line = lf
|
||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
|
|
||||||
|
|
||||||
|
[*.js]
|
||||||
|
indent_size = 4
|
||||||
|
|
|
@ -1,20 +1,20 @@
|
||||||
/*
|
/**
|
||||||
Reference: https://bootsnipp.com/snippets/featured/link-to-top-page
|
* Reference: https://bootsnipp.com/snippets/featured/link-to-top-page
|
||||||
*/
|
*/
|
||||||
$(function() {
|
$(function() {
|
||||||
$(window).scroll(() => {
|
$(window).scroll(() => {
|
||||||
if ($(this).scrollTop() > 50 &&
|
if ($(this).scrollTop() > 50 &&
|
||||||
$("#sidebar-trigger").css("display") === "none") {
|
$("#sidebar-trigger").css("display") === "none") {
|
||||||
$("#back-to-top").fadeIn();
|
$("#back-to-top").fadeIn();
|
||||||
} else {
|
} else {
|
||||||
$("#back-to-top").fadeOut();
|
$("#back-to-top").fadeOut();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$("#back-to-top").click(() => {
|
$("#back-to-top").click(() => {
|
||||||
$("body,html").animate({
|
$("body,html").animate({
|
||||||
scrollTop: 0
|
scrollTop: 0
|
||||||
}, 800);
|
}, 800);
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
/*
|
/**
|
||||||
* Listener for theme mode toggle
|
* Listener for theme mode toggle
|
||||||
*/
|
*/
|
||||||
$(function() {
|
$(function () {
|
||||||
$(".mode-toggle").click((e) => {
|
$(".mode-toggle").click((e) => {
|
||||||
const $target = $(e.target);
|
const $target = $(e.target);
|
||||||
let $btn = ($target.prop("tagName") === "button".toUpperCase() ?
|
let $btn = ($target.prop("tagName") === "button".toUpperCase() ?
|
||||||
$target : $target.parent());
|
$target : $target.parent());
|
||||||
|
|
||||||
$btn.blur(); // remove the clicking outline
|
$btn.blur(); // remove the clicking outline
|
||||||
flipMode();
|
flipMode();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,35 +2,37 @@
|
||||||
* A tool for smooth scrolling and topbar switcher
|
* A tool for smooth scrolling and topbar switcher
|
||||||
*/
|
*/
|
||||||
const ScrollHelper = (function () {
|
const ScrollHelper = (function () {
|
||||||
const $body = $("body");
|
const $body = $("body");
|
||||||
const ATTR_TOPBAR_VISIBLE = "data-topbar-visible";
|
const ATTR_TOPBAR_VISIBLE = "data-topbar-visible";
|
||||||
const topbarHeight = $("#topbar-wrapper").outerHeight();
|
const topbarHeight = $("#topbar-wrapper").outerHeight();
|
||||||
|
|
||||||
let scrollUpCount = 0; // the number of times the scroll up was triggered by ToC or anchor
|
let scrollUpCount = 0; // the number of times the scroll up was triggered by ToC or anchor
|
||||||
let topbarLocked = false;
|
let topbarLocked = false;
|
||||||
let orientationLocked = false;
|
let orientationLocked = false;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
hideTopbar: () => $body.attr(ATTR_TOPBAR_VISIBLE, false),
|
hideTopbar: () => $body.attr(ATTR_TOPBAR_VISIBLE, false),
|
||||||
showTopbar: () => $body.attr(ATTR_TOPBAR_VISIBLE, true),
|
showTopbar: () => $body.attr(ATTR_TOPBAR_VISIBLE, true),
|
||||||
|
|
||||||
// scroll up
|
// scroll up
|
||||||
|
|
||||||
addScrollUpTask: () => {
|
addScrollUpTask: () => {
|
||||||
scrollUpCount += 1;
|
scrollUpCount += 1;
|
||||||
if (!topbarLocked) { topbarLocked = true; }
|
if (!topbarLocked) {
|
||||||
},
|
topbarLocked = true;
|
||||||
popScrollUpTask: () => scrollUpCount -= 1,
|
}
|
||||||
hasScrollUpTask: () => scrollUpCount > 0,
|
},
|
||||||
topbarLocked: () => topbarLocked === true,
|
popScrollUpTask: () => scrollUpCount -= 1,
|
||||||
unlockTopbar: () => topbarLocked = false,
|
hasScrollUpTask: () => scrollUpCount > 0,
|
||||||
getTopbarHeight: () => topbarHeight,
|
topbarLocked: () => topbarLocked === true,
|
||||||
|
unlockTopbar: () => topbarLocked = false,
|
||||||
|
getTopbarHeight: () => topbarHeight,
|
||||||
|
|
||||||
// orientation change
|
// orientation change
|
||||||
|
|
||||||
orientationLocked: () => orientationLocked === true,
|
orientationLocked: () => orientationLocked === true,
|
||||||
lockOrientation: () => orientationLocked = true,
|
lockOrientation: () => orientationLocked = true,
|
||||||
unLockOrientation: () => orientationLocked = false
|
unLockOrientation: () => orientationLocked = false
|
||||||
};
|
};
|
||||||
|
|
||||||
}());
|
}());
|
||||||
|
|
|
@ -1,129 +1,129 @@
|
||||||
/*
|
/**
|
||||||
* This script make #search-result-wrapper switch to unloaded or shown automatically.
|
* This script make #search-result-wrapper switch to unloaded or shown automatically.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
$(function() {
|
$(function () {
|
||||||
const btnSbTrigger = $("#sidebar-trigger");
|
const btnSbTrigger = $("#sidebar-trigger");
|
||||||
const btnSearchTrigger = $("#search-trigger");
|
const btnSearchTrigger = $("#search-trigger");
|
||||||
const btnCancel = $("#search-cancel");
|
const btnCancel = $("#search-cancel");
|
||||||
const main = $("#main");
|
const main = $("#main");
|
||||||
const topbarTitle = $("#topbar-title");
|
const topbarTitle = $("#topbar-title");
|
||||||
const searchWrapper = $("#search-wrapper");
|
const searchWrapper = $("#search-wrapper");
|
||||||
const resultWrapper = $("#search-result-wrapper");
|
const resultWrapper = $("#search-result-wrapper");
|
||||||
const results = $("#search-results");
|
const results = $("#search-results");
|
||||||
const input = $("#search-input");
|
const input = $("#search-input");
|
||||||
const hints = $("#search-hints");
|
const hints = $("#search-hints");
|
||||||
|
|
||||||
const scrollBlocker = (function () {
|
const scrollBlocker = (function () {
|
||||||
let offset = 0;
|
let offset = 0;
|
||||||
return {
|
return {
|
||||||
block() {
|
block() {
|
||||||
offset = window.scrollY;
|
offset = window.scrollY;
|
||||||
$("html,body").scrollTop(0);
|
$("html,body").scrollTop(0);
|
||||||
},
|
},
|
||||||
release() {
|
release() {
|
||||||
$("html,body").scrollTop(offset);
|
$("html,body").scrollTop(offset);
|
||||||
},
|
},
|
||||||
getOffset() {
|
getOffset() {
|
||||||
return offset;
|
return offset;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}());
|
}());
|
||||||
|
|
||||||
/*--- Actions in mobile screens (Sidebar hidden) ---*/
|
/*--- Actions in mobile screens (Sidebar hidden) ---*/
|
||||||
|
|
||||||
const mobileSearchBar = (function () {
|
const mobileSearchBar = (function () {
|
||||||
return {
|
return {
|
||||||
on() {
|
on() {
|
||||||
btnSbTrigger.addClass("unloaded");
|
btnSbTrigger.addClass("unloaded");
|
||||||
topbarTitle.addClass("unloaded");
|
topbarTitle.addClass("unloaded");
|
||||||
btnSearchTrigger.addClass("unloaded");
|
btnSearchTrigger.addClass("unloaded");
|
||||||
searchWrapper.addClass("d-flex");
|
searchWrapper.addClass("d-flex");
|
||||||
btnCancel.addClass("loaded");
|
btnCancel.addClass("loaded");
|
||||||
},
|
},
|
||||||
off() {
|
off() {
|
||||||
btnCancel.removeClass("loaded");
|
btnCancel.removeClass("loaded");
|
||||||
searchWrapper.removeClass("d-flex");
|
searchWrapper.removeClass("d-flex");
|
||||||
btnSbTrigger.removeClass("unloaded");
|
btnSbTrigger.removeClass("unloaded");
|
||||||
topbarTitle.removeClass("unloaded");
|
topbarTitle.removeClass("unloaded");
|
||||||
btnSearchTrigger.removeClass("unloaded");
|
btnSearchTrigger.removeClass("unloaded");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}());
|
}());
|
||||||
|
|
||||||
const resultSwitch = (function () {
|
const resultSwitch = (function () {
|
||||||
let visible = false;
|
let visible = false;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
on() {
|
on() {
|
||||||
if (!visible) {
|
if (!visible) {
|
||||||
// the block method must be called before $(#main) unloaded.
|
// the block method must be called before $(#main) unloaded.
|
||||||
scrollBlocker.block();
|
scrollBlocker.block();
|
||||||
resultWrapper.removeClass("unloaded");
|
resultWrapper.removeClass("unloaded");
|
||||||
main.addClass("unloaded");
|
main.addClass("unloaded");
|
||||||
visible = true;
|
visible = true;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
off() {
|
off() {
|
||||||
if (visible) {
|
if (visible) {
|
||||||
results.empty();
|
results.empty();
|
||||||
if (hints.hasClass("unloaded")) {
|
if (hints.hasClass("unloaded")) {
|
||||||
hints.removeClass("unloaded");
|
hints.removeClass("unloaded");
|
||||||
}
|
}
|
||||||
resultWrapper.addClass("unloaded");
|
resultWrapper.addClass("unloaded");
|
||||||
main.removeClass("unloaded");
|
main.removeClass("unloaded");
|
||||||
|
|
||||||
// now the release method must be called after $(#main) display
|
// now the release method must be called after $(#main) display
|
||||||
scrollBlocker.release();
|
scrollBlocker.release();
|
||||||
|
|
||||||
input.val("");
|
input.val("");
|
||||||
visible = false;
|
visible = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
isVisible() {
|
isVisible() {
|
||||||
return visible;
|
return visible;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
}());
|
}());
|
||||||
|
|
||||||
function isMobileView() {
|
function isMobileView() {
|
||||||
return btnCancel.hasClass("loaded");
|
return btnCancel.hasClass("loaded");
|
||||||
}
|
|
||||||
|
|
||||||
btnSearchTrigger.click(function() {
|
|
||||||
mobileSearchBar.on();
|
|
||||||
resultSwitch.on();
|
|
||||||
input.focus();
|
|
||||||
});
|
|
||||||
|
|
||||||
btnCancel.click(function() {
|
|
||||||
mobileSearchBar.off();
|
|
||||||
resultSwitch.off();
|
|
||||||
});
|
|
||||||
|
|
||||||
input.focus(function() {
|
|
||||||
searchWrapper.addClass("input-focus");
|
|
||||||
});
|
|
||||||
|
|
||||||
input.focusout(function() {
|
|
||||||
searchWrapper.removeClass("input-focus");
|
|
||||||
});
|
|
||||||
|
|
||||||
input.on("input", () => {
|
|
||||||
if (input.val() === "") {
|
|
||||||
if (isMobileView()) {
|
|
||||||
hints.removeClass("unloaded");
|
|
||||||
} else {
|
|
||||||
resultSwitch.off();
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
resultSwitch.on();
|
|
||||||
if (isMobileView()) {
|
|
||||||
hints.addClass("unloaded");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
btnSearchTrigger.click(function () {
|
||||||
|
mobileSearchBar.on();
|
||||||
|
resultSwitch.on();
|
||||||
|
input.focus();
|
||||||
|
});
|
||||||
|
|
||||||
|
btnCancel.click(function () {
|
||||||
|
mobileSearchBar.off();
|
||||||
|
resultSwitch.off();
|
||||||
|
});
|
||||||
|
|
||||||
|
input.focus(function () {
|
||||||
|
searchWrapper.addClass("input-focus");
|
||||||
|
});
|
||||||
|
|
||||||
|
input.focusout(function () {
|
||||||
|
searchWrapper.removeClass("input-focus");
|
||||||
|
});
|
||||||
|
|
||||||
|
input.on("input", () => {
|
||||||
|
if (input.val() === "") {
|
||||||
|
if (isMobileView()) {
|
||||||
|
hints.removeClass("unloaded");
|
||||||
|
} else {
|
||||||
|
resultSwitch.off();
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
resultSwitch.on();
|
||||||
|
if (isMobileView()) {
|
||||||
|
hints.addClass("unloaded");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,29 +2,27 @@
|
||||||
* Expand or close the sidebar in mobile screens.
|
* Expand or close the sidebar in mobile screens.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
$(function() {
|
$(function () {
|
||||||
|
const sidebarUtil = (function () {
|
||||||
|
const ATTR_DISPLAY = "sidebar-display";
|
||||||
|
let isExpanded = false;
|
||||||
|
const body = $("body");
|
||||||
|
|
||||||
const sidebarUtil = (function () {
|
return {
|
||||||
const ATTR_DISPLAY = "sidebar-display";
|
toggle() {
|
||||||
let isExpanded = false;
|
if (isExpanded === false) {
|
||||||
const body = $("body");
|
body.attr(ATTR_DISPLAY, "");
|
||||||
|
} else {
|
||||||
|
body.removeAttr(ATTR_DISPLAY);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
isExpanded = !isExpanded;
|
||||||
toggle() {
|
}
|
||||||
if (isExpanded === false) {
|
};
|
||||||
body.attr(ATTR_DISPLAY, "");
|
|
||||||
} else {
|
|
||||||
body.removeAttr(ATTR_DISPLAY);
|
|
||||||
}
|
|
||||||
|
|
||||||
isExpanded = !isExpanded;
|
}());
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
}());
|
$("#sidebar-trigger").click(sidebarUtil.toggle);
|
||||||
|
|
||||||
$("#sidebar-trigger").click(sidebarUtil.toggle);
|
|
||||||
|
|
||||||
$("#mask").click(sidebarUtil.toggle);
|
|
||||||
|
|
||||||
|
$("#mask").click(sidebarUtil.toggle);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
* Initial Bootstrap Tooltip.
|
* Initial Bootstrap Tooltip.
|
||||||
*/
|
*/
|
||||||
$(function () {
|
$(function () {
|
||||||
$("[data-toggle=\"tooltip\"]").tooltip();
|
$("[data-toggle=\"tooltip\"]").tooltip();
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,90 +1,89 @@
|
||||||
/*
|
/**
|
||||||
* Hide Header on scroll down
|
* Hide Header on scroll down
|
||||||
*/
|
*/
|
||||||
|
|
||||||
$(function() {
|
$(function () {
|
||||||
const $searchInput = $("#search-input");
|
const $searchInput = $("#search-input");
|
||||||
const delta = ScrollHelper.getTopbarHeight();
|
const delta = ScrollHelper.getTopbarHeight();
|
||||||
|
|
||||||
let didScroll;
|
let didScroll;
|
||||||
let lastScrollTop = 0;
|
let lastScrollTop = 0;
|
||||||
|
|
||||||
function hasScrolled() {
|
function hasScrolled() {
|
||||||
let st = $(this).scrollTop();
|
let st = $(this).scrollTop();
|
||||||
|
|
||||||
/* Make sure they scroll more than delta */
|
/* Make sure they scroll more than delta */
|
||||||
if (Math.abs(lastScrollTop - st) <= delta) {
|
if (Math.abs(lastScrollTop - st) <= delta) {
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
if (st > lastScrollTop ) { // Scroll Down
|
|
||||||
ScrollHelper.hideTopbar();
|
|
||||||
|
|
||||||
if ($searchInput.is(":focus")) {
|
|
||||||
$searchInput.blur(); /* remove focus */
|
|
||||||
}
|
|
||||||
|
|
||||||
} else { // Scroll up
|
|
||||||
// has not yet scrolled to the bottom of the screen, that is, there is still space for scrolling
|
|
||||||
if (st + $(window).height() < $(document).height()) {
|
|
||||||
|
|
||||||
if (ScrollHelper.hasScrollUpTask()) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ScrollHelper.topbarLocked()) { // avoid redundant scroll up event from smooth scrolling
|
if (st > lastScrollTop) { // Scroll Down
|
||||||
ScrollHelper.unlockTopbar();
|
ScrollHelper.hideTopbar();
|
||||||
} else {
|
|
||||||
if (ScrollHelper.orientationLocked()) { // avoid device auto scroll up on orientation change
|
if ($searchInput.is(":focus")) {
|
||||||
ScrollHelper.unLockOrientation();
|
$searchInput.blur(); /* remove focus */
|
||||||
} else {
|
}
|
||||||
ScrollHelper.showTopbar();
|
|
||||||
}
|
} else { // Scroll up
|
||||||
|
// has not yet scrolled to the bottom of the screen, that is, there is still space for scrolling
|
||||||
|
if (st + $(window).height() < $(document).height()) {
|
||||||
|
|
||||||
|
if (ScrollHelper.hasScrollUpTask()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ScrollHelper.topbarLocked()) { // avoid redundant scroll up event from smooth scrolling
|
||||||
|
ScrollHelper.unlockTopbar();
|
||||||
|
} else {
|
||||||
|
if (ScrollHelper.orientationLocked()) { // avoid device auto scroll up on orientation change
|
||||||
|
ScrollHelper.unLockOrientation();
|
||||||
|
} else {
|
||||||
|
ScrollHelper.showTopbar();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
lastScrollTop = st;
|
||||||
|
|
||||||
|
} // hasScrolled()
|
||||||
|
|
||||||
|
function handleLandscape() {
|
||||||
|
if ($(window).scrollTop() === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ScrollHelper.lockOrientation();
|
||||||
|
ScrollHelper.hideTopbar();
|
||||||
}
|
}
|
||||||
|
|
||||||
lastScrollTop = st;
|
if (screen.orientation) {
|
||||||
|
screen.orientation.onchange = () => {
|
||||||
|
const type = screen.orientation.type;
|
||||||
|
if (type === "landscape-primary" || type === "landscape-secondary") {
|
||||||
|
handleLandscape();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
} // hasScrolled()
|
} else {
|
||||||
|
// for the browsers that not support `window.screen.orientation` API
|
||||||
function handleLandscape() {
|
$(window).on("orientationchange", () => {
|
||||||
if ($(window).scrollTop() === 0) {
|
if ($(window).width() < $(window).height()) { // before rotating, it is still in portrait mode.
|
||||||
return;
|
handleLandscape();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
ScrollHelper.lockOrientation();
|
|
||||||
ScrollHelper.hideTopbar();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (screen.orientation) {
|
$(window).scroll(() => {
|
||||||
screen.orientation.onchange = () => {
|
if (didScroll) {
|
||||||
const type = screen.orientation.type;
|
return;
|
||||||
if (type === "landscape-primary" || type === "landscape-secondary") {
|
}
|
||||||
handleLandscape();
|
didScroll = true;
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// for the browsers that not support `window.screen.orientation` API
|
|
||||||
$(window).on("orientationchange",() => {
|
|
||||||
if ($(window).width() < $(window).height()) { // before rotating, it is still in portrait mode.
|
|
||||||
handleLandscape();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
$(window).scroll(() => {
|
|
||||||
if (didScroll) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
didScroll = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
setInterval(() => {
|
|
||||||
if (didScroll) {
|
|
||||||
hasScrolled();
|
|
||||||
didScroll = false;
|
|
||||||
}
|
|
||||||
}, 250);
|
|
||||||
|
|
||||||
|
setInterval(() => {
|
||||||
|
if (didScroll) {
|
||||||
|
hasScrolled();
|
||||||
|
didScroll = false;
|
||||||
|
}
|
||||||
|
}, 250);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,67 +1,67 @@
|
||||||
/*
|
/**
|
||||||
* Top bar title auto change while scrolling up/down in mobile screens.
|
* Top bar title auto change while scrolling up/down in mobile screens.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
$(function() {
|
$(function () {
|
||||||
const titleSelector = "div.post>h1:first-of-type";
|
const titleSelector = "div.post>h1:first-of-type";
|
||||||
const $pageTitle = $(titleSelector);
|
const $pageTitle = $(titleSelector);
|
||||||
const $topbarTitle = $("#topbar-title");
|
const $topbarTitle = $("#topbar-title");
|
||||||
|
|
||||||
if ($pageTitle.length === 0 /* on Home page */
|
if ($pageTitle.length === 0 /* on Home page */
|
||||||
|| $pageTitle.hasClass("dynamic-title")
|
|| $pageTitle.hasClass("dynamic-title")
|
||||||
|| $topbarTitle.is(":hidden")) {/* not in mobile views */
|
|| $topbarTitle.is(":hidden")) {/* not in mobile views */
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
const defaultTitleText = $topbarTitle.text().trim();
|
|
||||||
let pageTitleText = $pageTitle.text().trim();
|
|
||||||
let hasScrolled = false;
|
|
||||||
let lastScrollTop = 0;
|
|
||||||
|
|
||||||
if ($("#page-category").length || $("#page-tag").length) {
|
|
||||||
/* The title in Category or Tag page will be "<title> <count_of_posts>" */
|
|
||||||
if (/\s/.test(pageTitleText)) {
|
|
||||||
pageTitleText = pageTitleText.replace(/[0-9]/g, "").trim();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// When the page is scrolled down and then refreshed, the topbar title needs to be initialized
|
|
||||||
if ($pageTitle.offset().top < $(window).scrollTop()) {
|
|
||||||
$topbarTitle.text(pageTitleText);
|
|
||||||
}
|
|
||||||
|
|
||||||
let options = {
|
|
||||||
rootMargin: '-48px 0px 0px 0px', // 48px equals to the topbar height (3rem)
|
|
||||||
threshold: [0, 1]
|
|
||||||
};
|
|
||||||
|
|
||||||
let observer = new IntersectionObserver((entries) => {
|
|
||||||
if (!hasScrolled) {
|
|
||||||
hasScrolled = true;
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let curScrollTop = $(window).scrollTop();
|
const defaultTitleText = $topbarTitle.text().trim();
|
||||||
let isScrollDown = lastScrollTop < curScrollTop;
|
let pageTitleText = $pageTitle.text().trim();
|
||||||
lastScrollTop = curScrollTop;
|
let hasScrolled = false;
|
||||||
let heading = entries[0];
|
let lastScrollTop = 0;
|
||||||
|
|
||||||
if (isScrollDown) {
|
if ($("#page-category").length || $("#page-tag").length) {
|
||||||
if (heading.intersectionRatio === 0) {
|
/* The title in Category or Tag page will be "<title> <count_of_posts>" */
|
||||||
|
if (/\s/.test(pageTitleText)) {
|
||||||
|
pageTitleText = pageTitleText.replace(/[0-9]/g, "").trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// When the page is scrolled down and then refreshed, the topbar title needs to be initialized
|
||||||
|
if ($pageTitle.offset().top < $(window).scrollTop()) {
|
||||||
$topbarTitle.text(pageTitleText);
|
$topbarTitle.text(pageTitleText);
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (heading.intersectionRatio === 1) {
|
|
||||||
$topbarTitle.text(defaultTitleText);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, options);
|
|
||||||
|
|
||||||
observer.observe(document.querySelector(titleSelector));
|
let options = {
|
||||||
|
rootMargin: '-48px 0px 0px 0px', // 48px equals to the topbar height (3rem)
|
||||||
|
threshold: [0, 1]
|
||||||
|
};
|
||||||
|
|
||||||
/* Click title will scroll to top */
|
let observer = new IntersectionObserver((entries) => {
|
||||||
$topbarTitle.click(function() {
|
if (!hasScrolled) {
|
||||||
$("body,html").animate({scrollTop: 0}, 800);
|
hasScrolled = true;
|
||||||
});
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let curScrollTop = $(window).scrollTop();
|
||||||
|
let isScrollDown = lastScrollTop < curScrollTop;
|
||||||
|
lastScrollTop = curScrollTop;
|
||||||
|
let heading = entries[0];
|
||||||
|
|
||||||
|
if (isScrollDown) {
|
||||||
|
if (heading.intersectionRatio === 0) {
|
||||||
|
$topbarTitle.text(pageTitleText);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (heading.intersectionRatio === 1) {
|
||||||
|
$topbarTitle.text(defaultTitleText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, options);
|
||||||
|
|
||||||
|
observer.observe(document.querySelector(titleSelector));
|
||||||
|
|
||||||
|
/* Click title will scroll to top */
|
||||||
|
$topbarTitle.click(function () {
|
||||||
|
$("body,html").animate({scrollTop: 0}, 800);
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,30 +1,30 @@
|
||||||
/*
|
/**
|
||||||
* Tab 'Categories' expand/close effect.
|
* Tab 'Categories' expand/close effect.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
$(function() {
|
$(function () {
|
||||||
const childPrefix = "l_";
|
const childPrefix = "l_";
|
||||||
const parentPrefix = "h_";
|
const parentPrefix = "h_";
|
||||||
const collapse = $(".collapse");
|
const collapse = $(".collapse");
|
||||||
|
|
||||||
/* close up top-category */
|
/* close up top-category */
|
||||||
collapse.on("hide.bs.collapse", function () { /* Bootstrap collapse events. */
|
collapse.on("hide.bs.collapse", function () { /* Bootstrap collapse events. */
|
||||||
const parentId = parentPrefix + $(this).attr("id").substring(childPrefix.length);
|
const parentId = parentPrefix + $(this).attr("id").substring(childPrefix.length);
|
||||||
if (parentId) {
|
if (parentId) {
|
||||||
$(`#${parentId} .far.fa-folder-open`).attr("class", "far fa-folder fa-fw");
|
$(`#${parentId} .far.fa-folder-open`).attr("class", "far fa-folder fa-fw");
|
||||||
$(`#${parentId} i.fas`).addClass("rotate");
|
$(`#${parentId} i.fas`).addClass("rotate");
|
||||||
$(`#${parentId}`).removeClass("hide-border-bottom");
|
$(`#${parentId}`).removeClass("hide-border-bottom");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/* expand the top category */
|
/* expand the top category */
|
||||||
collapse.on("show.bs.collapse", function() {
|
collapse.on("show.bs.collapse", function () {
|
||||||
const parentId = parentPrefix + $(this).attr("id").substring(childPrefix.length);
|
const parentId = parentPrefix + $(this).attr("id").substring(childPrefix.length);
|
||||||
if (parentId) {
|
if (parentId) {
|
||||||
$(`#${parentId} .far.fa-folder`).attr("class", "far fa-folder-open fa-fw");
|
$(`#${parentId} .far.fa-folder`).attr("class", "far fa-folder-open fa-fw");
|
||||||
$(`#${parentId} i.fas`).removeClass("rotate");
|
$(`#${parentId} i.fas`).removeClass("rotate");
|
||||||
$(`#${parentId}`).addClass("hide-border-bottom");
|
$(`#${parentId}`).addClass("hide-border-bottom");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/*
|
/**
|
||||||
* Clipboard functions
|
* Clipboard functions
|
||||||
*
|
*
|
||||||
* Dependencies:
|
* Dependencies:
|
||||||
|
@ -6,128 +6,128 @@
|
||||||
* - clipboard.js (https://github.com/zenorocha/clipboard.js)
|
* - clipboard.js (https://github.com/zenorocha/clipboard.js)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
$(function() {
|
$(function () {
|
||||||
const btnSelector = '.code-header>button';
|
const btnSelector = '.code-header>button';
|
||||||
const ICON_SUCCESS = 'fas fa-check';
|
const ICON_SUCCESS = 'fas fa-check';
|
||||||
const ATTR_TIMEOUT = 'timeout';
|
const ATTR_TIMEOUT = 'timeout';
|
||||||
const ATTR_TITLE_SUCCEED = 'data-title-succeed';
|
const ATTR_TITLE_SUCCEED = 'data-title-succeed';
|
||||||
const ATTR_TITLE_ORIGIN = 'data-original-title';
|
const ATTR_TITLE_ORIGIN = 'data-original-title';
|
||||||
const TIMEOUT = 2000; // in milliseconds
|
const TIMEOUT = 2000; // in milliseconds
|
||||||
|
|
||||||
function isLocked(node) {
|
function isLocked(node) {
|
||||||
if ($(node)[0].hasAttribute(ATTR_TIMEOUT)) {
|
if ($(node)[0].hasAttribute(ATTR_TIMEOUT)) {
|
||||||
let timeout = $(node).attr(ATTR_TIMEOUT);
|
let timeout = $(node).attr(ATTR_TIMEOUT);
|
||||||
if (Number(timeout) > Date.now()) {
|
if (Number(timeout) > Date.now()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
function lock(node) {
|
|
||||||
$(node).attr(ATTR_TIMEOUT, Date.now() + TIMEOUT);
|
|
||||||
}
|
|
||||||
|
|
||||||
function unlock(node) {
|
|
||||||
$(node).removeAttr(ATTR_TIMEOUT);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --- Copy code block --- */
|
|
||||||
|
|
||||||
// Initial the clipboard.js object
|
|
||||||
const clipboard = new ClipboardJS(btnSelector, {
|
|
||||||
target(trigger) {
|
|
||||||
let codeBlock = trigger.parentNode.nextElementSibling;
|
|
||||||
return codeBlock.querySelector('code .rouge-code');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$(btnSelector).tooltip({
|
|
||||||
trigger: 'hover',
|
|
||||||
placement: 'left'
|
|
||||||
});
|
|
||||||
|
|
||||||
function getIcon(btn) {
|
|
||||||
let iconNode = $(btn).children();
|
|
||||||
return iconNode.attr('class');
|
|
||||||
}
|
|
||||||
|
|
||||||
const ICON_DEFAULT = getIcon(btnSelector);
|
|
||||||
|
|
||||||
function showTooltip(btn) {
|
|
||||||
const succeedTitle = $(btn).attr(ATTR_TITLE_SUCCEED);
|
|
||||||
$(btn).attr(ATTR_TITLE_ORIGIN, succeedTitle).tooltip('show');
|
|
||||||
}
|
|
||||||
|
|
||||||
function hideTooltip(btn) {
|
|
||||||
$(btn).tooltip('hide').removeAttr(ATTR_TITLE_ORIGIN);
|
|
||||||
}
|
|
||||||
|
|
||||||
function setSuccessIcon(btn) {
|
|
||||||
let btnNode = $(btn);
|
|
||||||
let iconNode = btnNode.children();
|
|
||||||
iconNode.attr('class', ICON_SUCCESS);
|
|
||||||
}
|
|
||||||
|
|
||||||
function resumeIcon(btn) {
|
|
||||||
let btnNode = $(btn);
|
|
||||||
let iconNode = btnNode.children();
|
|
||||||
iconNode.attr('class', ICON_DEFAULT);
|
|
||||||
}
|
|
||||||
|
|
||||||
clipboard.on('success', (e) => {
|
|
||||||
e.clearSelection();
|
|
||||||
|
|
||||||
const trigger = e.trigger;
|
|
||||||
if (isLocked(trigger)) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setSuccessIcon(trigger);
|
function lock(node) {
|
||||||
showTooltip(trigger);
|
$(node).attr(ATTR_TIMEOUT, Date.now() + TIMEOUT);
|
||||||
lock(trigger);
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
hideTooltip(trigger);
|
|
||||||
resumeIcon(trigger);
|
|
||||||
unlock(trigger);
|
|
||||||
}, TIMEOUT);
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
/* --- Post link sharing --- */
|
|
||||||
|
|
||||||
$('#copy-link').click((e) => {
|
|
||||||
|
|
||||||
let target = $(e.target);
|
|
||||||
|
|
||||||
if (isLocked(target)) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy URL to clipboard
|
function unlock(node) {
|
||||||
|
$(node).removeAttr(ATTR_TIMEOUT);
|
||||||
|
}
|
||||||
|
|
||||||
const url = window.location.href;
|
/* --- Copy code block --- */
|
||||||
const $temp = $("<input>");
|
|
||||||
|
|
||||||
$("body").append($temp);
|
// Initial the clipboard.js object
|
||||||
$temp.val(url).select();
|
const clipboard = new ClipboardJS(btnSelector, {
|
||||||
document.execCommand("copy");
|
target(trigger) {
|
||||||
$temp.remove();
|
let codeBlock = trigger.parentNode.nextElementSibling;
|
||||||
|
return codeBlock.querySelector('code .rouge-code');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Switch tooltip title
|
$(btnSelector).tooltip({
|
||||||
|
trigger: 'hover',
|
||||||
|
placement: 'left'
|
||||||
|
});
|
||||||
|
|
||||||
const defaultTitle = target.attr(ATTR_TITLE_ORIGIN);
|
function getIcon(btn) {
|
||||||
const succeedTitle = target.attr(ATTR_TITLE_SUCCEED);
|
let iconNode = $(btn).children();
|
||||||
|
return iconNode.attr('class');
|
||||||
|
}
|
||||||
|
|
||||||
target.attr(ATTR_TITLE_ORIGIN, succeedTitle).tooltip('show');
|
const ICON_DEFAULT = getIcon(btnSelector);
|
||||||
lock(target);
|
|
||||||
|
|
||||||
setTimeout(() => {
|
function showTooltip(btn) {
|
||||||
target.attr(ATTR_TITLE_ORIGIN, defaultTitle);
|
const succeedTitle = $(btn).attr(ATTR_TITLE_SUCCEED);
|
||||||
unlock(target);
|
$(btn).attr(ATTR_TITLE_ORIGIN, succeedTitle).tooltip('show');
|
||||||
}, TIMEOUT);
|
}
|
||||||
|
|
||||||
});
|
function hideTooltip(btn) {
|
||||||
|
$(btn).tooltip('hide').removeAttr(ATTR_TITLE_ORIGIN);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setSuccessIcon(btn) {
|
||||||
|
let btnNode = $(btn);
|
||||||
|
let iconNode = btnNode.children();
|
||||||
|
iconNode.attr('class', ICON_SUCCESS);
|
||||||
|
}
|
||||||
|
|
||||||
|
function resumeIcon(btn) {
|
||||||
|
let btnNode = $(btn);
|
||||||
|
let iconNode = btnNode.children();
|
||||||
|
iconNode.attr('class', ICON_DEFAULT);
|
||||||
|
}
|
||||||
|
|
||||||
|
clipboard.on('success', (e) => {
|
||||||
|
e.clearSelection();
|
||||||
|
|
||||||
|
const trigger = e.trigger;
|
||||||
|
if (isLocked(trigger)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setSuccessIcon(trigger);
|
||||||
|
showTooltip(trigger);
|
||||||
|
lock(trigger);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
hideTooltip(trigger);
|
||||||
|
resumeIcon(trigger);
|
||||||
|
unlock(trigger);
|
||||||
|
}, TIMEOUT);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
/* --- Post link sharing --- */
|
||||||
|
|
||||||
|
$('#copy-link').click((e) => {
|
||||||
|
|
||||||
|
let target = $(e.target);
|
||||||
|
|
||||||
|
if (isLocked(target)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy URL to clipboard
|
||||||
|
|
||||||
|
const url = window.location.href;
|
||||||
|
const $temp = $("<input>");
|
||||||
|
|
||||||
|
$("body").append($temp);
|
||||||
|
$temp.val(url).select();
|
||||||
|
document.execCommand("copy");
|
||||||
|
$temp.remove();
|
||||||
|
|
||||||
|
// Switch tooltip title
|
||||||
|
|
||||||
|
const defaultTitle = target.attr(ATTR_TITLE_ORIGIN);
|
||||||
|
const succeedTitle = target.attr(ATTR_TITLE_SUCCEED);
|
||||||
|
|
||||||
|
target.attr(ATTR_TITLE_ORIGIN, succeedTitle).tooltip('show');
|
||||||
|
lock(target);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
target.attr(ATTR_TITLE_ORIGIN, defaultTitle);
|
||||||
|
unlock(target);
|
||||||
|
}, TIMEOUT);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,47 +1,46 @@
|
||||||
/**
|
/**
|
||||||
Lazy load images (https://github.com/ApoorvSaxena/lozad.js)
|
Lazy load images (https://github.com/ApoorvSaxena/lozad.js)
|
||||||
and popup when clicked (https://github.com/dimsemenov/Magnific-Popup)
|
and popup when clicked (https://github.com/dimsemenov/Magnific-Popup)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
$(function() {
|
$(function () {
|
||||||
|
const IMG_SCOPE = '#main > div.row:first-child > div:first-child';
|
||||||
|
|
||||||
const IMG_SCOPE = '#main > div.row:first-child > div:first-child';
|
if ($(`${IMG_SCOPE} img`).length <= 0) {
|
||||||
|
return;
|
||||||
if ($(`${IMG_SCOPE} img`).length <= 0 ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* lazy loading */
|
|
||||||
|
|
||||||
const imgList = document.querySelectorAll(`${IMG_SCOPE} img[data-src]`);
|
|
||||||
const observer = lozad(imgList);
|
|
||||||
observer.observe();
|
|
||||||
|
|
||||||
/* popup */
|
|
||||||
|
|
||||||
$(`${IMG_SCOPE} p > img[data-src],${IMG_SCOPE} img[data-src].preview-img`).each(
|
|
||||||
function() {
|
|
||||||
let nextTag = $(this).next();
|
|
||||||
const title = nextTag.prop('tagName') === 'EM' ? nextTag.text() : '';
|
|
||||||
const src = $(this).attr('data-src'); // created by lozad.js
|
|
||||||
|
|
||||||
$(this).wrap(`<a href="${src}" title="${title}" class="popup"></a>`);
|
|
||||||
}
|
}
|
||||||
);
|
|
||||||
|
|
||||||
$('.popup').magnificPopup({
|
/* lazy loading */
|
||||||
type: 'image',
|
|
||||||
closeOnContentClick: true,
|
|
||||||
showCloseBtn: false,
|
|
||||||
zoom: {
|
|
||||||
enabled: true,
|
|
||||||
duration: 300,
|
|
||||||
easing: 'ease-in-out'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/* markup the image links */
|
const imgList = document.querySelectorAll(`${IMG_SCOPE} img[data-src]`);
|
||||||
|
const observer = lozad(imgList);
|
||||||
|
observer.observe();
|
||||||
|
|
||||||
$(`${IMG_SCOPE} a`).has('img').addClass('img-link');
|
/* popup */
|
||||||
|
|
||||||
|
$(`${IMG_SCOPE} p > img[data-src], ${IMG_SCOPE} img[data-src].preview-img`).each(
|
||||||
|
function () {
|
||||||
|
let nextTag = $(this).next();
|
||||||
|
const title = nextTag.prop('tagName') === 'EM' ? nextTag.text() : '';
|
||||||
|
const src = $(this).attr('data-src'); // created by lozad.js
|
||||||
|
|
||||||
|
$(this).wrap(`<a href="${src}" title="${title}" class="popup"></a>`);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
$('.popup').magnificPopup({
|
||||||
|
type: 'image',
|
||||||
|
closeOnContentClick: true,
|
||||||
|
showCloseBtn: false,
|
||||||
|
zoom: {
|
||||||
|
enabled: true,
|
||||||
|
duration: 300,
|
||||||
|
easing: 'ease-in-out'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/* markup the image links */
|
||||||
|
|
||||||
|
$(`${IMG_SCOPE} a`).has('img').addClass('img-link');
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -6,38 +6,38 @@
|
||||||
|
|
||||||
/* A tool for locale datetime */
|
/* A tool for locale datetime */
|
||||||
const LocaleHelper = (function () {
|
const LocaleHelper = (function () {
|
||||||
const locale = $('html').attr('lang').substr(0, 2);
|
const locale = $('html').attr('lang').substr(0, 2);
|
||||||
const attrTimestamp = 'data-ts';
|
const attrTimestamp = 'data-ts';
|
||||||
const attrDateFormat = 'data-df';
|
const attrDateFormat = 'data-df';
|
||||||
|
|
||||||
return {
|
return {
|
||||||
locale: () => locale,
|
locale: () => locale,
|
||||||
attrTimestamp: () => attrTimestamp,
|
attrTimestamp: () => attrTimestamp,
|
||||||
attrDateFormat: () => attrDateFormat,
|
attrDateFormat: () => attrDateFormat,
|
||||||
getTimestamp: ($elem) => Number($elem.attr(attrTimestamp)), // unix timestamp
|
getTimestamp: ($elem) => Number($elem.attr(attrTimestamp)), // unix timestamp
|
||||||
getDateFormat: ($elem) => $elem.attr(attrDateFormat)
|
getDateFormat: ($elem) => $elem.attr(attrDateFormat)
|
||||||
};
|
};
|
||||||
|
|
||||||
}());
|
}());
|
||||||
|
|
||||||
$(function() {
|
$(function () {
|
||||||
dayjs.locale(LocaleHelper.locale());
|
dayjs.locale(LocaleHelper.locale());
|
||||||
dayjs.extend(window.dayjs_plugin_localizedFormat);
|
dayjs.extend(window.dayjs_plugin_localizedFormat);
|
||||||
|
|
||||||
$(`[${LocaleHelper.attrTimestamp()}]`).each(function () {
|
$(`[${LocaleHelper.attrTimestamp()}]`).each(function () {
|
||||||
const date = dayjs.unix(LocaleHelper.getTimestamp($(this)));
|
const date = dayjs.unix(LocaleHelper.getTimestamp($(this)));
|
||||||
const text = date.format(LocaleHelper.getDateFormat($(this)));
|
const text = date.format(LocaleHelper.getDateFormat($(this)));
|
||||||
$(this).text(text);
|
$(this).text(text);
|
||||||
$(this).removeAttr(LocaleHelper.attrTimestamp());
|
$(this).removeAttr(LocaleHelper.attrTimestamp());
|
||||||
$(this).removeAttr(LocaleHelper.attrDateFormat());
|
$(this).removeAttr(LocaleHelper.attrDateFormat());
|
||||||
|
|
||||||
// setup tooltips
|
// setup tooltips
|
||||||
const tooltip = $(this).attr('data-toggle');
|
const tooltip = $(this).attr('data-toggle');
|
||||||
if (typeof tooltip === 'undefined' || tooltip !== 'tooltip') {
|
if (typeof tooltip === 'undefined' || tooltip !== 'tooltip') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const tooltipText = date.format('llll'); // see: https://day.js.org/docs/en/display/format#list-of-localized-formats
|
const tooltipText = date.format('llll'); // see: https://day.js.org/docs/en/display/format#list-of-localized-formats
|
||||||
$(this).attr('data-original-title', tooltipText);
|
$(this).attr('data-original-title', tooltipText);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/*
|
/**
|
||||||
* Count page views form GA or local cache file.
|
* Count page views form GA or local cache file.
|
||||||
*
|
*
|
||||||
* Dependencies:
|
* Dependencies:
|
||||||
|
@ -7,244 +7,244 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const getInitStatus = (function () {
|
const getInitStatus = (function () {
|
||||||
let hasInit = false;
|
let hasInit = false;
|
||||||
return () => {
|
return () => {
|
||||||
let ret = hasInit;
|
let ret = hasInit;
|
||||||
if (!hasInit) {
|
if (!hasInit) {
|
||||||
hasInit = true;
|
hasInit = true;
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
};
|
};
|
||||||
}());
|
}());
|
||||||
|
|
||||||
const PvOpts = (function () {
|
const PvOpts = (function () {
|
||||||
function getContent(selector) {
|
function getContent(selector) {
|
||||||
return $(selector).attr("content");
|
return $(selector).attr("content");
|
||||||
}
|
|
||||||
|
|
||||||
function hasContent(selector) {
|
|
||||||
let content = getContent(selector);
|
|
||||||
return (typeof content !== "undefined" && content !== false);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
getProxyMeta() {
|
|
||||||
return getContent("meta[name=pv-proxy-endpoint]");
|
|
||||||
},
|
|
||||||
getLocalMeta() {
|
|
||||||
return getContent("meta[name=pv-cache-path]");
|
|
||||||
},
|
|
||||||
hasProxyMeta() {
|
|
||||||
return hasContent("meta[name=pv-proxy-endpoint]");
|
|
||||||
},
|
|
||||||
hasLocalMeta() {
|
|
||||||
return hasContent("meta[name=pv-cache-path]");
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
function hasContent(selector) {
|
||||||
|
let content = getContent(selector);
|
||||||
|
return (typeof content !== "undefined" && content !== false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
getProxyMeta() {
|
||||||
|
return getContent("meta[name=pv-proxy-endpoint]");
|
||||||
|
},
|
||||||
|
getLocalMeta() {
|
||||||
|
return getContent("meta[name=pv-cache-path]");
|
||||||
|
},
|
||||||
|
hasProxyMeta() {
|
||||||
|
return hasContent("meta[name=pv-proxy-endpoint]");
|
||||||
|
},
|
||||||
|
hasLocalMeta() {
|
||||||
|
return hasContent("meta[name=pv-cache-path]");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
}());
|
}());
|
||||||
|
|
||||||
const PvStorage = (function () {
|
const PvStorage = (function () {
|
||||||
const Keys = {
|
const Keys = {
|
||||||
KEY_PV: "pv",
|
KEY_PV: "pv",
|
||||||
KEY_PV_SRC: "pv_src",
|
KEY_PV_SRC: "pv_src",
|
||||||
KEY_CREATION: "pv_created_date"
|
KEY_CREATION: "pv_created_date"
|
||||||
};
|
};
|
||||||
|
|
||||||
const Source = {
|
const Source = {
|
||||||
LOCAL: "same-origin",
|
LOCAL: "same-origin",
|
||||||
PROXY: "cors"
|
PROXY: "cors"
|
||||||
};
|
};
|
||||||
|
|
||||||
function get(key) {
|
function get(key) {
|
||||||
return localStorage.getItem(key);
|
return localStorage.getItem(key);
|
||||||
}
|
|
||||||
|
|
||||||
function set(key, val) {
|
|
||||||
localStorage.setItem(key, val);
|
|
||||||
}
|
|
||||||
|
|
||||||
function saveCache(pv, src) {
|
|
||||||
set(Keys.KEY_PV, pv);
|
|
||||||
set(Keys.KEY_PV_SRC, src);
|
|
||||||
set(Keys.KEY_CREATION, new Date().toJSON());
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
keysCount() {
|
|
||||||
return Object.keys(Keys).length;
|
|
||||||
},
|
|
||||||
hasCache() {
|
|
||||||
return (localStorage.getItem(Keys.KEY_PV) !== null);
|
|
||||||
},
|
|
||||||
getCache() {
|
|
||||||
return JSON.parse(localStorage.getItem(Keys.KEY_PV));
|
|
||||||
},
|
|
||||||
saveLocalCache(pv) {
|
|
||||||
saveCache(pv, Source.LOCAL);
|
|
||||||
},
|
|
||||||
saveProxyCache(pv) {
|
|
||||||
saveCache(pv, Source.PROXY);
|
|
||||||
},
|
|
||||||
isExpired() {
|
|
||||||
let date = new Date(get(Keys.KEY_CREATION));
|
|
||||||
date.setHours(date.getHours() + 1); // per hour
|
|
||||||
return Date.now() >= date.getTime();
|
|
||||||
},
|
|
||||||
isFromLocal() {
|
|
||||||
return get(Keys.KEY_PV_SRC) === Source.LOCAL;
|
|
||||||
},
|
|
||||||
isFromProxy() {
|
|
||||||
return get(Keys.KEY_PV_SRC) === Source.PROXY;
|
|
||||||
},
|
|
||||||
newerThan(pv) {
|
|
||||||
return PvStorage.getCache().totalsForAllResults["ga:pageviews"] > pv.totalsForAllResults["ga:pageviews"];
|
|
||||||
},
|
|
||||||
inspectKeys() {
|
|
||||||
if (localStorage.length !== PvStorage.keysCount()) {
|
|
||||||
localStorage.clear();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for(let i = 0; i < localStorage.length; i++){
|
|
||||||
const key = localStorage.key(i);
|
|
||||||
switch (key) {
|
|
||||||
case Keys.KEY_PV:
|
|
||||||
case Keys.KEY_PV_SRC:
|
|
||||||
case Keys.KEY_CREATION:
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
localStorage.clear();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
function set(key, val) {
|
||||||
|
localStorage.setItem(key, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveCache(pv, src) {
|
||||||
|
set(Keys.KEY_PV, pv);
|
||||||
|
set(Keys.KEY_PV_SRC, src);
|
||||||
|
set(Keys.KEY_CREATION, new Date().toJSON());
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
keysCount() {
|
||||||
|
return Object.keys(Keys).length;
|
||||||
|
},
|
||||||
|
hasCache() {
|
||||||
|
return (localStorage.getItem(Keys.KEY_PV) !== null);
|
||||||
|
},
|
||||||
|
getCache() {
|
||||||
|
return JSON.parse(localStorage.getItem(Keys.KEY_PV));
|
||||||
|
},
|
||||||
|
saveLocalCache(pv) {
|
||||||
|
saveCache(pv, Source.LOCAL);
|
||||||
|
},
|
||||||
|
saveProxyCache(pv) {
|
||||||
|
saveCache(pv, Source.PROXY);
|
||||||
|
},
|
||||||
|
isExpired() {
|
||||||
|
let date = new Date(get(Keys.KEY_CREATION));
|
||||||
|
date.setHours(date.getHours() + 1); // per hour
|
||||||
|
return Date.now() >= date.getTime();
|
||||||
|
},
|
||||||
|
isFromLocal() {
|
||||||
|
return get(Keys.KEY_PV_SRC) === Source.LOCAL;
|
||||||
|
},
|
||||||
|
isFromProxy() {
|
||||||
|
return get(Keys.KEY_PV_SRC) === Source.PROXY;
|
||||||
|
},
|
||||||
|
newerThan(pv) {
|
||||||
|
return PvStorage.getCache().totalsForAllResults["ga:pageviews"] > pv.totalsForAllResults["ga:pageviews"];
|
||||||
|
},
|
||||||
|
inspectKeys() {
|
||||||
|
if (localStorage.length !== PvStorage.keysCount()) {
|
||||||
|
localStorage.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < localStorage.length; i++) {
|
||||||
|
const key = localStorage.key(i);
|
||||||
|
switch (key) {
|
||||||
|
case Keys.KEY_PV:
|
||||||
|
case Keys.KEY_PV_SRC:
|
||||||
|
case Keys.KEY_CREATION:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
localStorage.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
}()); /* PvStorage */
|
}()); /* PvStorage */
|
||||||
|
|
||||||
function countUp(min, max, destId) {
|
function countUp(min, max, destId) {
|
||||||
if (min < max) {
|
if (min < max) {
|
||||||
let numAnim = new CountUp(destId, min, max);
|
let numAnim = new CountUp(destId, min, max);
|
||||||
if (!numAnim.error) {
|
if (!numAnim.error) {
|
||||||
numAnim.start();
|
numAnim.start();
|
||||||
} else {
|
} else {
|
||||||
console.error(numAnim.error);
|
console.error(numAnim.error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function countPV(path, rows) {
|
function countPV(path, rows) {
|
||||||
let count = 0;
|
let count = 0;
|
||||||
|
|
||||||
if (typeof rows !== "undefined" ) {
|
if (typeof rows !== "undefined") {
|
||||||
for (let i = 0; i < rows.length; ++i) {
|
for (let i = 0; i < rows.length; ++i) {
|
||||||
const gaPath = rows[parseInt(i, 10)][0];
|
const gaPath = rows[parseInt(i, 10)][0];
|
||||||
if (gaPath === path) { /* path format see: site.permalink */
|
if (gaPath === path) { /* path format see: site.permalink */
|
||||||
count += parseInt(rows[parseInt(i, 10)][1], 10);
|
count += parseInt(rows[parseInt(i, 10)][1], 10);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
function tacklePV(rows, path, elem, hasInit) {
|
function tacklePV(rows, path, elem, hasInit) {
|
||||||
let count = countPV(path, rows);
|
let count = countPV(path, rows);
|
||||||
count = (count === 0 ? 1 : count);
|
count = (count === 0 ? 1 : count);
|
||||||
|
|
||||||
if (!hasInit) {
|
if (!hasInit) {
|
||||||
elem.text(new Intl.NumberFormat().format(count));
|
elem.text(new Intl.NumberFormat().format(count));
|
||||||
} else {
|
} else {
|
||||||
const initCount = parseInt(elem.text().replace(/,/g, ""), 10);
|
const initCount = parseInt(elem.text().replace(/,/g, ""), 10);
|
||||||
if (count > initCount) {
|
if (count > initCount) {
|
||||||
countUp(initCount, count, elem.attr("id"));
|
countUp(initCount, count, elem.attr("id"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function displayPageviews(data) {
|
function displayPageviews(data) {
|
||||||
if (typeof data === "undefined") {
|
if (typeof data === "undefined") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let hasInit = getInitStatus();
|
let hasInit = getInitStatus();
|
||||||
const rows = data.rows; /* could be undefined */
|
const rows = data.rows; /* could be undefined */
|
||||||
|
|
||||||
if ($("#post-list").length > 0) { /* the Home page */
|
if ($("#post-list").length > 0) { /* the Home page */
|
||||||
$(".post-preview").each(function() {
|
$(".post-preview").each(function () {
|
||||||
const path = $(this).find("a").attr("href");
|
const path = $(this).find("a").attr("href");
|
||||||
tacklePV(rows, path, $(this).find(".pageviews"), hasInit);
|
tacklePV(rows, path, $(this).find(".pageviews"), hasInit);
|
||||||
});
|
});
|
||||||
|
|
||||||
} else if ($(".post").length > 0) { /* the post */
|
} else if ($(".post").length > 0) { /* the post */
|
||||||
const path = window.location.pathname;
|
const path = window.location.pathname;
|
||||||
tacklePV(rows, path, $("#pv"), hasInit);
|
tacklePV(rows, path, $("#pv"), hasInit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function fetchProxyPageviews() {
|
function fetchProxyPageviews() {
|
||||||
if (PvOpts.hasProxyMeta()) {
|
if (PvOpts.hasProxyMeta()) {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: "GET",
|
type: "GET",
|
||||||
url: PvOpts.getProxyMeta(),
|
url: PvOpts.getProxyMeta(),
|
||||||
dataType: "jsonp",
|
dataType: "jsonp",
|
||||||
jsonpCallback: "displayPageviews",
|
jsonpCallback: "displayPageviews",
|
||||||
success: (data) => {
|
success: (data) => {
|
||||||
PvStorage.saveProxyCache(JSON.stringify(data));
|
PvStorage.saveProxyCache(JSON.stringify(data));
|
||||||
},
|
},
|
||||||
error: (jqXHR, textStatus, errorThrown) => {
|
error: (jqXHR, textStatus, errorThrown) => {
|
||||||
console.log("Failed to load pageviews from proxy server: " + errorThrown);
|
console.log("Failed to load pageviews from proxy server: " + errorThrown);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function fetchLocalPageviews(hasCache = false) {
|
function fetchLocalPageviews(hasCache = false) {
|
||||||
return fetch(PvOpts.getLocalMeta())
|
return fetch(PvOpts.getLocalMeta())
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
if (hasCache) {
|
if (hasCache) {
|
||||||
// The cache from the proxy will sometimes be more recent than the local one
|
// The cache from the proxy will sometimes be more recent than the local one
|
||||||
if (PvStorage.isFromProxy() && PvStorage.newerThan(data)) {
|
if (PvStorage.isFromProxy() && PvStorage.newerThan(data)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
displayPageviews(data);
|
displayPageviews(data);
|
||||||
PvStorage.saveLocalCache(JSON.stringify(data));
|
PvStorage.saveLocalCache(JSON.stringify(data));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
$(function() {
|
$(function () {
|
||||||
if ($(".pageviews").length <= 0) {
|
if ($(".pageviews").length <= 0) {
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
PvStorage.inspectKeys();
|
|
||||||
|
|
||||||
if (PvStorage.hasCache()) {
|
|
||||||
displayPageviews(PvStorage.getCache());
|
|
||||||
|
|
||||||
if (PvStorage.isExpired()) {
|
|
||||||
if (PvOpts.hasLocalMeta()) {
|
|
||||||
fetchLocalPageviews(true).then(fetchProxyPageviews);
|
|
||||||
} else {
|
|
||||||
fetchProxyPageviews();
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
if (PvStorage.isFromLocal()) {
|
|
||||||
fetchProxyPageviews();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} else { // no cached
|
PvStorage.inspectKeys();
|
||||||
|
|
||||||
if (PvOpts.hasLocalMeta()) {
|
if (PvStorage.hasCache()) {
|
||||||
fetchLocalPageviews().then(fetchProxyPageviews);
|
displayPageviews(PvStorage.getCache());
|
||||||
} else {
|
|
||||||
fetchProxyPageviews();
|
if (PvStorage.isExpired()) {
|
||||||
|
if (PvOpts.hasLocalMeta()) {
|
||||||
|
fetchLocalPageviews(true).then(fetchProxyPageviews);
|
||||||
|
} else {
|
||||||
|
fetchProxyPageviews();
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if (PvStorage.isFromLocal()) {
|
||||||
|
fetchProxyPageviews();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else { // no cached
|
||||||
|
|
||||||
|
if (PvOpts.hasLocalMeta()) {
|
||||||
|
fetchLocalPageviews().then(fetchProxyPageviews);
|
||||||
|
} else {
|
||||||
|
fetchProxyPageviews();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,96 +1,96 @@
|
||||||
/*
|
/**
|
||||||
Safari doesn't support CSS `scroll-behavior: smooth`,
|
Safari doesn't support CSS `scroll-behavior: smooth`,
|
||||||
so here is a compatible solution for all browser to smooth scrolling
|
so here is a compatible solution for all browser to smooth scrolling
|
||||||
|
|
||||||
See: <https://css-tricks.com/snippets/jquery/smooth-scrolling/>
|
See: <https://css-tricks.com/snippets/jquery/smooth-scrolling/>
|
||||||
|
|
||||||
Warning: It must be called after all `<a>` tags (e.g., the dynamic TOC) are ready.
|
Warning: It must be called after all `<a>` tags (e.g., the dynamic TOC) are ready.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
$(function() {
|
$(function () {
|
||||||
const $topbarTitle = $("#topbar-title");
|
const $topbarTitle = $("#topbar-title");
|
||||||
const REM = 16; // in pixels
|
const REM = 16; // in pixels
|
||||||
const ATTR_SCROLL_FOCUS = "scroll-focus";
|
const ATTR_SCROLL_FOCUS = "scroll-focus";
|
||||||
|
|
||||||
$("a[href*='#']")
|
$("a[href*='#']")
|
||||||
.not("[href='#']")
|
.not("[href='#']")
|
||||||
.not("[href='#0']")
|
.not("[href='#0']")
|
||||||
.click(function(event) {
|
.click(function (event) {
|
||||||
if (this.pathname.replace(/^\//, "") !==
|
if (this.pathname.replace(/^\//, "") !==
|
||||||
location.pathname.replace(/^\//, "")) {
|
location.pathname.replace(/^\//, "")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (location.hostname !== this.hostname) {
|
if (location.hostname !== this.hostname) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const hash = decodeURI(this.hash);
|
const hash = decodeURI(this.hash);
|
||||||
let toFootnoteRef = RegExp(/^#fnref:/).test(hash);
|
let toFootnoteRef = RegExp(/^#fnref:/).test(hash);
|
||||||
let toFootnote = toFootnoteRef ? false : RegExp(/^#fn:/).test(hash);
|
let toFootnote = toFootnoteRef ? false : RegExp(/^#fn:/).test(hash);
|
||||||
let selector = hash.includes(":") ? hash.replace(/:/g, "\\:") : hash;
|
let selector = hash.includes(":") ? hash.replace(/:/g, "\\:") : hash;
|
||||||
let $target = $(selector);
|
let $target = $(selector);
|
||||||
|
|
||||||
let isMobileViews = $topbarTitle.is(":visible");
|
let isMobileViews = $topbarTitle.is(":visible");
|
||||||
let isPortrait = $(window).width() < $(window).height();
|
let isPortrait = $(window).width() < $(window).height();
|
||||||
|
|
||||||
if (typeof $target === "undefined") {
|
if (typeof $target === "undefined") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
if (history.pushState) { /* add hash to URL */
|
if (history.pushState) { /* add hash to URL */
|
||||||
history.pushState(null, null, hash);
|
history.pushState(null, null, hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
let curOffset = $(window).scrollTop();
|
let curOffset = $(window).scrollTop();
|
||||||
let destOffset = $target.offset().top -= REM / 2;
|
let destOffset = $target.offset().top -= REM / 2;
|
||||||
|
|
||||||
if (destOffset < curOffset) { // scroll up
|
if (destOffset < curOffset) { // scroll up
|
||||||
ScrollHelper.hideTopbar();
|
ScrollHelper.hideTopbar();
|
||||||
ScrollHelper.addScrollUpTask();
|
ScrollHelper.addScrollUpTask();
|
||||||
|
|
||||||
if (isMobileViews && isPortrait) {
|
if (isMobileViews && isPortrait) {
|
||||||
destOffset -= ScrollHelper.getTopbarHeight();
|
destOffset -= ScrollHelper.getTopbarHeight();
|
||||||
}
|
}
|
||||||
|
|
||||||
} else { // scroll down
|
} else { // scroll down
|
||||||
if (isMobileViews && isPortrait) {
|
if (isMobileViews && isPortrait) {
|
||||||
destOffset -= ScrollHelper.getTopbarHeight();
|
destOffset -= ScrollHelper.getTopbarHeight();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$("html").animate({
|
$("html").animate({
|
||||||
scrollTop: destOffset
|
scrollTop: destOffset
|
||||||
}, 500, () => {
|
}, 500, () => {
|
||||||
$target.focus();
|
$target.focus();
|
||||||
|
|
||||||
/* clean up old scroll mark */
|
/* clean up old scroll mark */
|
||||||
if ($(`[${ATTR_SCROLL_FOCUS}=true]`).length) {
|
if ($(`[${ATTR_SCROLL_FOCUS}=true]`).length) {
|
||||||
$(`[${ATTR_SCROLL_FOCUS}=true]`).attr(ATTR_SCROLL_FOCUS, false);
|
$(`[${ATTR_SCROLL_FOCUS}=true]`).attr(ATTR_SCROLL_FOCUS, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Clean :target links */
|
/* Clean :target links */
|
||||||
if ($(":target").length) { /* element that visited by the URL with hash */
|
if ($(":target").length) { /* element that visited by the URL with hash */
|
||||||
$(":target").attr(ATTR_SCROLL_FOCUS, false);
|
$(":target").attr(ATTR_SCROLL_FOCUS, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* set scroll mark to footnotes */
|
/* set scroll mark to footnotes */
|
||||||
if (toFootnote || toFootnoteRef) {
|
if (toFootnote || toFootnoteRef) {
|
||||||
$target.attr(ATTR_SCROLL_FOCUS, true);
|
$target.attr(ATTR_SCROLL_FOCUS, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($target.is(":focus")) { /* Checking if the target was focused */
|
if ($target.is(":focus")) { /* Checking if the target was focused */
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
$target.attr("tabindex", "-1"); /* Adding tabindex for elements not focusable */
|
$target.attr("tabindex", "-1"); /* Adding tabindex for elements not focusable */
|
||||||
$target.focus(); /* Set focus again */
|
$target.focus(); /* Set focus again */
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ScrollHelper.hasScrollUpTask()) {
|
if (ScrollHelper.hasScrollUpTask()) {
|
||||||
ScrollHelper.popScrollUpTask();
|
ScrollHelper.popScrollUpTask();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}); /* click() */
|
}); /* click() */
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,51 +5,50 @@ layout: compress
|
||||||
---
|
---
|
||||||
|
|
||||||
const resource = [
|
const resource = [
|
||||||
|
/* --- CSS --- */
|
||||||
|
'{{ "/assets/css/style.css" | relative_url }}',
|
||||||
|
|
||||||
/* --- CSS --- */
|
/* --- PWA --- */
|
||||||
'{{ "/assets/css/style.css" | relative_url }}',
|
'{{ "/app.js" | relative_url }}',
|
||||||
|
'{{ "/sw.js" | relative_url }}',
|
||||||
|
|
||||||
/* --- PWA --- */
|
/* --- HTML --- */
|
||||||
'{{ "/app.js" | relative_url }}',
|
'{{ "/index.html" | relative_url }}',
|
||||||
'{{ "/sw.js" | relative_url }}',
|
'{{ "/404.html" | relative_url }}',
|
||||||
|
|
||||||
/* --- HTML --- */
|
{% for tab in site.tabs %}
|
||||||
'{{ "/index.html" | relative_url }}',
|
'{{ tab.url | relative_url }}',
|
||||||
'{{ "/404.html" | relative_url }}',
|
{% endfor %}
|
||||||
{% for tab in site.tabs %}
|
|
||||||
'{{ tab.url | relative_url }}',
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
/* --- Favicons & compressed JS --- */
|
|
||||||
{% assign cache_list = site.static_files | where: 'swcache', true %}
|
|
||||||
{% for file in cache_list %}
|
|
||||||
'{{ file.path | relative_url }}'{%- unless forloop.last -%},{%- endunless -%}
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
|
/* --- Favicons & compressed JS --- */
|
||||||
|
{% assign cache_list = site.static_files | where: 'swcache', true %}
|
||||||
|
{% for file in cache_list %}
|
||||||
|
'{{ file.path | relative_url }}'{%- unless forloop.last -%},{%- endunless -%}
|
||||||
|
{% endfor %}
|
||||||
];
|
];
|
||||||
|
|
||||||
/* The request url with below domain will be cached */
|
/* The request url with below domain will be cached */
|
||||||
const allowedDomains = [
|
const allowedDomains = [
|
||||||
{% if site.google_analytics.id != empty and site.google_analytics.id %}
|
{% if site.google_analytics.id != empty and site.google_analytics.id %}
|
||||||
'www.googletagmanager.com',
|
'www.googletagmanager.com',
|
||||||
'www.google-analytics.com',
|
'www.google-analytics.com',
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
'{{ site.url | split: "//" | last }}',
|
'{{ site.url | split: "//" | last }}',
|
||||||
|
|
||||||
{% if site.img_cdn contains '//' and site.img_cdn %}
|
{% if site.img_cdn contains '//' and site.img_cdn %}
|
||||||
'{{ site.img_cdn | split: '//' | last | split: '/' | first }}',
|
'{{ site.img_cdn | split: '//' | last | split: '/' | first }}',
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
'fonts.gstatic.com',
|
'fonts.gstatic.com',
|
||||||
'fonts.googleapis.com',
|
'fonts.googleapis.com',
|
||||||
'cdn.jsdelivr.net',
|
'cdn.jsdelivr.net',
|
||||||
'polyfill.io'
|
'polyfill.io'
|
||||||
];
|
];
|
||||||
|
|
||||||
/* Requests that include the following path will be banned */
|
/* Requests that include the following path will be banned */
|
||||||
const denyUrls = [
|
const denyUrls = [
|
||||||
{% if site.google_analytics.pv.cache_path %}
|
{% if site.google_analytics.pv.cache_path %}
|
||||||
'{{ site.google_analytics.pv.cache_path | absolute_url }}'
|
'{{ site.google_analytics.pv.cache_path | absolute_url }}'
|
||||||
{% endif %}
|
{% endif %}
|
||||||
];
|
];
|
||||||
|
|
2
assets/js/dist/page.min.js
vendored
2
assets/js/dist/page.min.js
vendored
File diff suppressed because one or more lines are too long
2
assets/js/dist/post.min.js
vendored
2
assets/js/dist/post.min.js
vendored
File diff suppressed because one or more lines are too long
|
@ -7,41 +7,41 @@ const $notification = $('#notification');
|
||||||
const $btnRefresh = $('#notification .toast-body>button');
|
const $btnRefresh = $('#notification .toast-body>button');
|
||||||
|
|
||||||
if ('serviceWorker' in navigator) {
|
if ('serviceWorker' in navigator) {
|
||||||
/* Registering Service Worker */
|
/* Registering Service Worker */
|
||||||
navigator.serviceWorker.register('{{ "/sw.js" | relative_url }}')
|
navigator.serviceWorker.register('{{ "/sw.js" | relative_url }}')
|
||||||
.then(registration => {
|
.then(registration => {
|
||||||
|
|
||||||
/* in case the user ignores the notification */
|
/* in case the user ignores the notification */
|
||||||
if (registration.waiting) {
|
if (registration.waiting) {
|
||||||
$notification.toast('show');
|
$notification.toast('show');
|
||||||
}
|
|
||||||
|
|
||||||
registration.addEventListener('updatefound', () => {
|
|
||||||
registration.installing.addEventListener('statechange', () => {
|
|
||||||
if (registration.waiting) {
|
|
||||||
if (navigator.serviceWorker.controller) {
|
|
||||||
$notification.toast('show');
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
registration.addEventListener('updatefound', () => {
|
||||||
|
registration.installing.addEventListener('statechange', () => {
|
||||||
|
if (registration.waiting) {
|
||||||
|
if (navigator.serviceWorker.controller) {
|
||||||
|
$notification.toast('show');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$btnRefresh.click(() => {
|
||||||
|
if (registration.waiting) {
|
||||||
|
registration.waiting.postMessage('SKIP_WAITING');
|
||||||
|
}
|
||||||
|
$notification.toast('hide');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
$btnRefresh.click(() => {
|
let refreshing = false;
|
||||||
if (registration.waiting) {
|
|
||||||
registration.waiting.postMessage('SKIP_WAITING');
|
/* Detect controller change and refresh all the opened tabs */
|
||||||
|
navigator.serviceWorker.addEventListener('controllerchange', () => {
|
||||||
|
if (!refreshing) {
|
||||||
|
window.location.reload();
|
||||||
|
refreshing = true;
|
||||||
}
|
}
|
||||||
$notification.toast('hide');
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
let refreshing = false;
|
|
||||||
|
|
||||||
/* Detect controller change and refresh all the opened tabs */
|
|
||||||
navigator.serviceWorker.addEventListener('controllerchange', () => {
|
|
||||||
if (!refreshing) {
|
|
||||||
window.location.reload();
|
|
||||||
refreshing = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,81 +9,82 @@ self.importScripts('{{ "/assets/js/data/swcache.js" | relative_url }}');
|
||||||
const cacheName = 'chirpy-{{ "now" | date: "%Y%m%d.%H%M%S" }}';
|
const cacheName = 'chirpy-{{ "now" | date: "%Y%m%d.%H%M%S" }}';
|
||||||
|
|
||||||
function verifyDomain(url) {
|
function verifyDomain(url) {
|
||||||
for (const domain of allowedDomains) {
|
for (const domain of allowedDomains) {
|
||||||
const regex = RegExp(`^http(s)?:\/\/${domain}\/`);
|
const regex = RegExp(`^http(s)?:\/\/${domain}\/`);
|
||||||
if (regex.test(url)) {
|
if (regex.test(url)) {
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isExcluded(url) {
|
function isExcluded(url) {
|
||||||
for (const item of denyUrls) {
|
for (const item of denyUrls) {
|
||||||
if (url === item) {
|
if (url === item) {
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
return false;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.addEventListener('install', event => {
|
self.addEventListener('install', event => {
|
||||||
event.waitUntil(
|
event.waitUntil(
|
||||||
caches.open(cacheName).then(cache => {
|
caches.open(cacheName).then(cache => {
|
||||||
return cache.addAll(resource);
|
return cache.addAll(resource);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
self.addEventListener('activate', event => {
|
self.addEventListener('activate', event => {
|
||||||
event.waitUntil(
|
event.waitUntil(
|
||||||
caches.keys().then(keyList => {
|
caches.keys().then(keyList => {
|
||||||
return Promise.all(
|
return Promise.all(
|
||||||
keyList.map(key => {
|
keyList.map(key => {
|
||||||
if (key !== cacheName) {
|
if (key !== cacheName) {
|
||||||
return caches.delete(key);
|
return caches.delete(key);
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
self.addEventListener('message', (event) => {
|
self.addEventListener('message', (event) => {
|
||||||
if (event.data === 'SKIP_WAITING') {
|
if (event.data === 'SKIP_WAITING') {
|
||||||
self.skipWaiting();
|
self.skipWaiting();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
self.addEventListener('fetch', event => {
|
self.addEventListener('fetch', event => {
|
||||||
event.respondWith(
|
event.respondWith(
|
||||||
caches.match(event.request).then(response => {
|
caches.match(event.request).then(response => {
|
||||||
if (response) {
|
if (response) {
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
return fetch(event.request).then(response => {
|
return fetch(event.request).then(response => {
|
||||||
const url = event.request.url;
|
const url = event.request.url;
|
||||||
|
|
||||||
if (event.request.method !== 'GET' ||
|
if (event.request.method !== 'GET' ||
|
||||||
!verifyDomain(url) ||
|
!verifyDomain(url) ||
|
||||||
isExcluded(url)) {
|
isExcluded(url)) {
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
see: <https://developers.google.com/web/fundamentals/primers/service-workers#cache_and_return_requests>
|
see: <https://developers.google.com/web/fundamentals/primers/service-workers#cache_and_return_requests>
|
||||||
*/
|
*/
|
||||||
let responseToCache = response.clone();
|
let responseToCache = response.clone();
|
||||||
|
|
||||||
caches.open(cacheName).then(cache => {
|
caches.open(cacheName).then(cache => {
|
||||||
/* console.log('[sw] Caching new resource: ' + event.request.url); */
|
/* console.log('[sw] Caching new resource: ' + event.request.url); */
|
||||||
cache.put(event.request, responseToCache);
|
cache.put(event.request, responseToCache);
|
||||||
});
|
});
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -4,9 +4,9 @@ permalink: '/unregister.js'
|
||||||
---
|
---
|
||||||
|
|
||||||
if ('serviceWorker' in navigator) {
|
if ('serviceWorker' in navigator) {
|
||||||
navigator.serviceWorker.getRegistrations().then((registrations) => {
|
navigator.serviceWorker.getRegistrations().then((registrations) => {
|
||||||
for (let reg of registrations) {
|
for (let reg of registrations) {
|
||||||
reg.unregister();
|
reg.unregister();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue