refactor(build): modularize JS code

- replace gulp with rollup
- remove JS output from repo
This commit is contained in:
Cotes Chung 2023-03-13 20:20:59 +08:00
parent 002f02533d
commit b69d3d7edd
No known key found for this signature in database
GPG Key ID: 0D9E54843167A808
55 changed files with 1267 additions and 1247 deletions

5
.gitignore vendored
View File

@ -10,10 +10,13 @@ _site
# RubyGems
*.gem
# npm dependencies
# NPM dependencies
node_modules
package-lock.json
# IDE configurations
.idea
.vscode
# Misc
assets/js/dist

View File

@ -5,60 +5,59 @@ theme: jekyll-theme-chirpy
# Change the following value to '/PROJECT_NAME' ONLY IF your site type is GitHub Pages Project sites
# and doesn't have a custom domain.
baseurl: ''
baseurl: ""
# The language of the webpage http://www.lingoes.net/en/translator/langcode.htm
# If it has the same name as one of the files in folder `_data/locales`, the layout language will also be changed,
# otherwise, the layout language will use the default value of 'en'.
lang: en
# Change to your timezone http://www.timezoneconverter.com/cgi-bin/findzone/findzone
timezone: Asia/Shanghai
# jekyll-seo-tag settings https://github.com/jekyll/jekyll-seo-tag/blob/master/docs/usage.md
# ↓ --------------------------
title: Chirpy # the main title
title: Chirpy # the main title
tagline: A text-focused Jekyll theme # it will display as the sub-title
tagline: A text-focused Jekyll theme # it will display as the sub-title
description: >- # used by seo meta and the atom feed
description: >- # used by seo meta and the atom feed
A minimal, responsive and feature-rich Jekyll theme for technical writing.
# fill in the protocol & hostname for your site, e.g., 'https://username.github.io'
url: ''
url: ""
github:
username: github_username # change to your github username
username: github_username # change to your github username
twitter:
username: twitter_username # change to your twitter username
username: twitter_username # change to your twitter username
social:
# Change to your full name.
# It will be displayed as the default author of the posts and the copyright owner in the Footer
name: your_full_name
email: example@domain.com # change to your email address
email: example@domain.com # change to your email address
links:
# The first element serves as the copyright owner's link
- https://twitter.com/username # change to your twitter homepage
- https://github.com/username # change to your github homepage
- https://twitter.com/username # change to your twitter homepage
- https://github.com/username # change to your github homepage
# Uncomment below to add more social links
# - https://www.facebook.com/username
# - https://www.linkedin.com/in/username
google_site_verification: # fill in to your verification string
google_site_verification: # fill in to your verification string
# ↑ --------------------------
# The end of `jekyll-seo-tag` settings
google_analytics:
id: # fill in your Google Analytics ID
id: # fill in your Google Analytics ID
# Google Analytics pageviews report settings
pv:
proxy_endpoint: # fill in the Google Analytics superProxy endpoint of Google App Engine
cache_path: # the local PV cache data, friendly to visitors from GFW region
proxy_endpoint: # fill in the Google Analytics superProxy endpoint of Google App Engine
cache_path: # the local PV cache data, friendly to visitors from GFW region
# Prefer color scheme setting.
#
@ -71,51 +70,51 @@ google_analytics:
# light - Use the light color scheme
# dark - Use the dark color scheme
#
theme_mode: # [light|dark]
theme_mode: # [light|dark]
# The CDN endpoint for images.
# Notice that once it is assigned, the CDN url
# will be added to all image (site avatar & posts' images) paths starting with '/'
#
# e.g. 'https://cdn.com'
img_cdn: 'https://chirpy-img.netlify.app'
img_cdn: "https://chirpy-img.netlify.app"
# the avatar on sidebar, support local or CORS resources
avatar: '/commons/avatar.jpg'
avatar: "/commons/avatar.jpg"
# boolean type, the global switch for ToC in posts.
toc: true
comments:
active: # The global switch for posts comments, e.g., 'disqus'. Keep it empty means disable
active: # The global switch for posts comments, e.g., 'disqus'. Keep it empty means disable
# The active options are as follows:
disqus:
shortname: # fill with the Disqus shortname. https://help.disqus.com/en/articles/1717111-what-s-a-shortname
shortname: # fill with the Disqus shortname. https://help.disqus.com/en/articles/1717111-what-s-a-shortname
# utterances settings https://utteranc.es/
utterances:
repo: # <gh-username>/<repo>
issue_term: # < url | pathname | title | ...>
repo: # <gh-username>/<repo>
issue_term: # < url | pathname | title | ...>
# Giscus options https://giscus.app
giscus:
repo: # <gh-username>/<repo>
repo: # <gh-username>/<repo>
repo_id:
category:
category_id:
mapping: # optional, default to 'pathname'
input_position: # optional, default to 'bottom'
lang: # optional, default to the value of `site.lang`
mapping: # optional, default to 'pathname'
input_position: # optional, default to 'bottom'
lang: # optional, default to the value of `site.lang`
reactions_enabled: # optional, default to the value of `1`
# Self-hosted static assets, optional https://github.com/cotes2020/chirpy-static-assets
assets:
self_host:
enabled: # boolean, keep empty means false
enabled: # boolean, keep empty means false
# specify the Jekyll environment, empty means both
# only works if `assets.self_host.enabled` is 'true'
env: # [development|production]
env: # [development|production]
pwa:
enabled: true # the option for PWA feature
enabled: true # the option for PWA feature
paginate: 10
@ -123,7 +122,7 @@ paginate: 10
kramdown:
syntax_highlighter: rouge
syntax_highlighter_opts: # Rouge Options https://github.com/jneen/rouge#full-options
syntax_highlighter_opts: # Rouge Options https://github.com/jneen/rouge#full-options
css_class: highlight
# default_lang: console
span:
@ -139,12 +138,12 @@ collections:
defaults:
- scope:
path: '' # An empty string here means all files in the project
path: "" # An empty string here means all files in the project
type: posts
values:
layout: post
comments: true # Enable comments in posts.
toc: true # Display TOC column in posts.
comments: true # Enable comments in posts.
toc: true # Display TOC column in posts.
# DO NOT modify the following parameter unless you are confident enough
# to update the code of all other post links in this project.
permalink: /posts/:title/
@ -153,8 +152,8 @@ defaults:
values:
comments: false
- scope:
path: ''
type: tabs # see `site.collections`
path: ""
type: tabs # see `site.collections`
values:
layout: page
permalink: /:title/
@ -180,13 +179,13 @@ compress_html:
envs: [development]
exclude:
- '*.gem'
- '*.gemspec'
- "*.gem"
- "*.gemspec"
- tools
- README.md
- CHANGELOG.md
- LICENSE
- gulpfile.js
- rollup.config.js
- node_modules
- package*.json

View File

@ -1,6 +1,4 @@
<!--
JS selector for site.
-->
<!-- JS selector for site. -->
<!-- layout specified -->
@ -8,51 +6,50 @@
{% if site.google_analytics.pv.proxy_endpoint or site.google_analytics.pv.cache_path %}
<!-- pv-report needs countup.js -->
<script async src="{{ site.data.assets[origin].countup.js | relative_url }}"></script>
<script defer src="{{ '/assets/js/dist/pvreport.min.js' | relative_url }}"></script>
{% endif %}
{% endif %}
{% if page.layout == 'post' or page.layout == 'page' %}
<!-- image lazy-loading & popup & clipboard -->
{% assign _urls = site.data.assets[origin].magnific-popup.js
| append: ',' | append: site.data.assets[origin].lazysizes.js
| append: ',' | append: site.data.assets[origin].clipboard.js
{% assign _urls = site.data.assets[origin]['magnific-popup'].js
| append: ','
| append: site.data.assets[origin].lazysizes.js
| append: ','
| append: site.data.assets[origin].clipboard.js
%}
{% include jsdelivr-combine.html urls=_urls %}
{% endif %}
{% if page.layout == 'home'
or page.layout == 'post'
or page.layout == 'archives'
or page.layout == 'category'
or page.layout == 'tag' %}
or page.layout == 'post'
or page.layout == 'archives'
or page.layout == 'category'
or page.layout == 'tag'
%}
{% assign locale = site.lang | split: '-' | first %}
{% assign _urls = site.data.assets[origin].dayjs.js.common
| append: ',' | append: site.data.assets[origin].dayjs.js.locale
| replace: ':LOCALE', locale
| append: ',' | append: site.data.assets[origin].dayjs.js.relativeTime
| append: ',' | append: site.data.assets[origin].dayjs.js.localizedFormat
| append: ','
| append: site.data.assets[origin].dayjs.js.locale
| replace: ':LOCALE', locale
| append: ','
| append: site.data.assets[origin].dayjs.js.relativeTime
| append: ','
| append: site.data.assets[origin].dayjs.js.localizedFormat
%}
{% include jsdelivr-combine.html urls=_urls %}
{% endif %}
{% if page.layout == 'home'
or page.layout == 'categories'
or page.layout == 'post'
or page.layout == 'page' %}
{% assign type = page.layout %}
{% elsif page.layout == 'archives'
or page.layout == 'category'
or page.layout == 'tag' %}
{% assign type = "misc" %}
{% else %}
{% assign type = "commons" %}
{% endif %}
{% case page.layout %}
{% when 'categories', 'post', 'page' %}
{% assign type = page.layout %}
{% when 'home', 'archives', 'category', 'tag' %}
{% assign type = 'misc' %}
{% else %}
{% assign type = 'commons' %}
{% endcase %}
{% capture script %}/assets/js/dist/{{ type }}.min.js{% endcapture %}
<script defer src="{{ script | relative_url }}"></script>
@ -60,23 +57,24 @@
{% if page.math %}
<!-- MathJax -->
<script>
/* see: <https://docs.mathjax.org/en/latest/options/input/tex.html#tex-options> */
MathJax = {
tex: {
inlineMath: [ /* start/end delimiter pairs for in-line math */
['$','$'],
['\\(','\\)']
],
displayMath: [ /* start/end delimiter pairs for display math */
['$$', '$$'],
['\\[', '\\]']
]
}
};
/* see: <https://docs.mathjax.org/en/latest/options/input/tex.html#tex-options> */
MathJax = {
tex: {
/* start/end delimiter pairs for in-line math */
inlineMath: [
['$', '$'],
['\\(', '\\)']
],
/* start/end delimiter pairs for display math */
displayMath: [
['$$', '$$'],
['\\[', '\\]']
]
}
};
</script>
<script src="{{ site.data.assets[origin].polyfill.js | relative_url }}"></script>
<script id="MathJax-script" async src="{{ site.data.assets[origin].mathjax.js | relative_url }}">
</script>
<script id="MathJax-script" async src="{{ site.data.assets[origin].mathjax.js | relative_url }}"></script>
{% endif %}
<!-- commons -->
@ -95,5 +93,4 @@
{% if site.google_analytics.id != empty and site.google_analytics.id %}
{% include google-analytics.html %}
{% endif %}
{% endif %}

View File

@ -26,13 +26,12 @@
let self = this;
/* always follow the system prefers */
this.sysDarkPrefers.addEventListener("change", () => {
this.sysDarkPrefers.addEventListener('change', () => {
if (self.hasMode) {
if (self.isDarkMode) {
if (!self.isSysDarkPrefer) {
self.setDark();
}
} else {
if (self.isSysDarkPrefer) {
self.setLight();
@ -43,9 +42,7 @@
}
self.notify();
});
} /* constructor() */
get sysDarkPrefers() { return window.matchMedia("(prefers-color-scheme: dark)"); }
@ -62,8 +59,7 @@
/* get the current mode on screen */
get modeStatus() {
if (this.isDarkMode
|| (!this.hasMode && this.isSysDarkPrefer)) {
if (this.isDarkMode || (!this.hasMode && this.isSysDarkPrefer)) {
return ModeToggle.DARK_MODE;
} else {
return ModeToggle.LIGHT_MODE;
@ -93,37 +89,32 @@
}, "*");
}
flipMode() {
if (this.hasMode) {
if (this.isSysDarkPrefer) {
if (this.isLightMode) {
this.clearMode();
} else {
this.setLight();
}
} else {
if (this.isDarkMode) {
this.clearMode();
} else {
this.setDark();
}
}
} else {
if (this.isSysDarkPrefer) {
this.setLight();
} else {
this.setDark();
}
}
this.notify();
} /* flipMode() */
} /* ModeToggle */
const toggle = new ModeToggle();
function flipMode() {
if (toggle.hasMode) {
if (toggle.isSysDarkPrefer) {
if (toggle.isLightMode) {
toggle.clearMode();
} else {
toggle.setLight();
}
} else {
if (toggle.isDarkMode) {
toggle.clearMode();
} else {
toggle.setDark();
}
}
} else {
if (toggle.isSysDarkPrefer) {
toggle.setLight();
} else {
toggle.setDark();
}
}
toggle.notify();
} /* flipMode() */
const modeToggle = new ModeToggle();
</script>

3
_javascript/_copyright Normal file
View File

@ -0,0 +1,3 @@
Chirpy v<%= pkg.version %> (<%= pkg.homepage %>)
© 2019 <%= pkg.author %>
<%= pkg.license %> Licensed

View File

@ -0,0 +1,7 @@
import { basic, initSidebar, initTopbar } from './modules/layouts';
import { categoryCollapse } from './modules/plugins';
basic();
initSidebar();
initTopbar();
categoryCollapse();

5
_javascript/commons.js Normal file
View File

@ -0,0 +1,5 @@
import { basic, initSidebar, initTopbar } from './modules/layouts';
basic();
initSidebar();
initTopbar();

View File

@ -1,20 +0,0 @@
/**
* Reference: https://bootsnipp.com/snippets/featured/link-to-top-page
*/
$(function() {
$(window).on('scroll',() => {
if ($(this).scrollTop() > 50 &&
$("#sidebar-trigger").css("display") === "none") {
$("#back-to-top").fadeIn();
} else {
$("#back-to-top").fadeOut();
}
});
$("#back-to-top").on('click',() => {
$("body,html").animate({
scrollTop: 0
}, 800);
return false;
});
});

View File

@ -1,13 +0,0 @@
/**
* Listener for theme mode toggle
*/
$(function () {
$(".mode-toggle").on('click',(e) => {
const $target = $(e.target);
let $btn = ($target.prop("tagName") === "button".toUpperCase() ?
$target : $target.parent());
$btn.trigger('blur'); // remove the clicking outline
flipMode();
});
});

View File

@ -1,38 +0,0 @@
/**
* A tool for smooth scrolling and topbar switcher
*/
const ScrollHelper = (function () {
const $body = $("body");
const ATTR_TOPBAR_VISIBLE = "data-topbar-visible";
const topbarHeight = $("#topbar-wrapper").outerHeight();
let scrollUpCount = 0; // the number of times the scroll up was triggered by ToC or anchor
let topbarLocked = false;
let orientationLocked = false;
return {
hideTopbar: () => $body.attr(ATTR_TOPBAR_VISIBLE, 'false'),
showTopbar: () => $body.attr(ATTR_TOPBAR_VISIBLE, 'true'),
// scroll up
addScrollUpTask: () => {
scrollUpCount += 1;
if (!topbarLocked) {
topbarLocked = true;
}
},
popScrollUpTask: () => scrollUpCount -= 1,
hasScrollUpTask: () => scrollUpCount > 0,
topbarLocked: () => topbarLocked === true,
unlockTopbar: () => topbarLocked = false,
getTopbarHeight: () => topbarHeight,
// orientation change
orientationLocked: () => orientationLocked === true,
lockOrientation: () => orientationLocked = true,
unLockOrientation: () => orientationLocked = false
};
}());

View File

@ -1,126 +0,0 @@
/**
* This script make #search-result-wrapper switch to unloaded or shown automatically.
*/
$(function () {
const btnSbTrigger = $("#sidebar-trigger");
const btnSearchTrigger = $("#search-trigger");
const btnCancel = $("#search-cancel");
const main = $("#main");
const topbarTitle = $("#topbar-title");
const searchWrapper = $("#search-wrapper");
const resultWrapper = $("#search-result-wrapper");
const results = $("#search-results");
const input = $("#search-input");
const hints = $("#search-hints");
const scrollBlocker = (function () {
let offset = 0;
return {
block() {
offset = window.scrollY;
$("html,body").scrollTop(0);
},
release() {
$("html,body").scrollTop(offset);
},
getOffset() {
return offset;
}
};
}());
/*--- Actions in mobile screens (Sidebar hidden) ---*/
const mobileSearchBar = (function () {
return {
on() {
btnSbTrigger.addClass("unloaded");
topbarTitle.addClass("unloaded");
btnSearchTrigger.addClass("unloaded");
searchWrapper.addClass("d-flex");
btnCancel.addClass("loaded");
},
off() {
btnCancel.removeClass("loaded");
searchWrapper.removeClass("d-flex");
btnSbTrigger.removeClass("unloaded");
topbarTitle.removeClass("unloaded");
btnSearchTrigger.removeClass("unloaded");
}
};
}());
const resultSwitch = (function () {
let visible = false;
return {
on() {
if (!visible) {
// the block method must be called before $(#main) unloaded.
scrollBlocker.block();
resultWrapper.removeClass("unloaded");
main.addClass("unloaded");
visible = true;
}
},
off() {
if (visible) {
results.empty();
if (hints.hasClass("unloaded")) {
hints.removeClass("unloaded");
}
resultWrapper.addClass("unloaded");
main.removeClass("unloaded");
// now the release method must be called after $(#main) display
scrollBlocker.release();
input.val("");
visible = false;
}
}
};
}());
function isMobileView() {
return btnCancel.hasClass("loaded");
}
btnSearchTrigger.on('click',function () {
mobileSearchBar.on();
resultSwitch.on();
input.trigger('focus');
});
btnCancel.on('click',function () {
mobileSearchBar.off();
resultSwitch.off();
});
input.on('focus',function () {
searchWrapper.addClass("input-focus");
});
input.on('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");
}
}
});
});

View File

@ -1,28 +0,0 @@
/**
* Expand or close the sidebar in mobile screens.
*/
$(function () {
const sidebarUtil = (function () {
const ATTR_DISPLAY = "sidebar-display";
let isExpanded = false;
const body = $("body");
return {
toggle() {
if (isExpanded === false) {
body.attr(ATTR_DISPLAY, "");
} else {
body.removeAttr(ATTR_DISPLAY);
}
isExpanded = !isExpanded;
}
};
}());
$("#sidebar-trigger").on('click', sidebarUtil.toggle);
$("#mask").on('click', sidebarUtil.toggle);
});

View File

@ -1,6 +0,0 @@
/**
* Initial Bootstrap Tooltip.
*/
$(function () {
$("[data-toggle=\"tooltip\"]").tooltip();
});

View File

@ -1,89 +0,0 @@
/**
* Hide Header on scroll down
*/
$(function () {
const $searchInput = $("#search-input");
const delta = ScrollHelper.getTopbarHeight();
let didScroll;
let lastScrollTop = 0;
function hasScrolled() {
let st = $(this).scrollTop();
/* Make sure they scroll more than delta */
if (Math.abs(lastScrollTop - st) <= delta) {
return;
}
if (st > lastScrollTop) { // Scroll Down
ScrollHelper.hideTopbar();
if ($searchInput.is(":focus")) {
$searchInput.trigger('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
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();
}
if (screen.orientation) {
screen.orientation.onchange = () => {
const type = screen.orientation.type;
if (type === "landscape-primary" || type === "landscape-secondary") {
handleLandscape();
}
};
} 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).on('scroll',() => {
if (didScroll) {
return;
}
didScroll = true;
});
setInterval(() => {
if (didScroll) {
hasScrolled();
didScroll = false;
}
}, 250);
});

View File

@ -1,67 +0,0 @@
/**
* Top bar title auto change while scrolling up/down in mobile screens.
*/
$(function () {
const titleSelector = "div.post>h1:first-of-type";
const $pageTitle = $(titleSelector);
const $topbarTitle = $("#topbar-title");
if ($pageTitle.length === 0 /* on Home page */
|| $pageTitle.hasClass("dynamic-title")
|| $topbarTitle.is(":hidden")) {/* not in mobile views */
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();
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.on('click', function () {
$("body,html").animate({scrollTop: 0}, 800);
});
});

View File

@ -1,5 +0,0 @@
/*!
* Chirpy v5.5.2 (https://github.com/cotes2020/jekyll-theme-chirpy/)
* © 2019 Cotes Chung
* MIT Licensed
*/

7
_javascript/misc.js Normal file
View File

@ -0,0 +1,7 @@
import { basic, initSidebar, initTopbar } from './modules/layouts';
import { initLocaleDatetime } from './modules/plugins';
basic();
initSidebar();
initTopbar();
initLocaleDatetime();

View File

@ -0,0 +1,26 @@
/**
* Reference: https://bootsnipp.com/snippets/featured/link-to-top-page
*/
export function back2top() {
$(window).on('scroll', () => {
if (
$(window).scrollTop() > 50 &&
$('#sidebar-trigger').css('display') === 'none'
) {
$('#back-to-top').fadeIn();
} else {
$('#back-to-top').fadeOut();
}
});
$('#back-to-top').on('click', () => {
$('body,html').animate(
{
scrollTop: 0
},
800
);
return false;
});
}

View File

@ -0,0 +1,36 @@
/**
* Tab 'Categories' expand/close effect.
*/
const childPrefix = 'l_';
const parentPrefix = 'h_';
const collapse = $('.collapse');
export function categoryCollapse() {
/* close up top-category */
collapse.on('hide.bs.collapse', function () {
/* Bootstrap collapse events. */ const parentId =
parentPrefix + $(this).attr('id').substring(childPrefix.length);
if (parentId) {
$(`#${parentId} .far.fa-folder-open`).attr(
'class',
'far fa-folder fa-fw'
);
$(`#${parentId} i.fas`).addClass('rotate');
$(`#${parentId}`).removeClass('hide-border-bottom');
}
});
/* expand the top category */
collapse.on('show.bs.collapse', function () {
const parentId =
parentPrefix + $(this).attr('id').substring(childPrefix.length);
if (parentId) {
$(`#${parentId} .far.fa-folder`).attr(
'class',
'far fa-folder-open fa-fw'
);
$(`#${parentId} i.fas`).removeClass('rotate');
$(`#${parentId}`).addClass('hide-border-bottom');
}
});
}

View File

@ -0,0 +1,118 @@
/**
* Clipboard functions
*
* Dependencies:
* - popper.js (https://github.com/popperjs/popper-core)
* - clipboard.js (https://github.com/zenorocha/clipboard.js)
*/
const btnSelector = '.code-header>button';
const ICON_SUCCESS = 'fas fa-check';
const ATTR_TIMEOUT = 'timeout';
const ATTR_TITLE_SUCCEED = 'data-title-succeed';
const ATTR_TITLE_ORIGIN = 'data-original-title';
const TIMEOUT = 2000; // in milliseconds
function isLocked(node) {
if ($(node)[0].hasAttribute(ATTR_TIMEOUT)) {
let timeout = $(node).attr(ATTR_TIMEOUT);
if (Number(timeout) > Date.now()) {
return true;
}
}
return false;
}
function lock(node) {
$(node).attr(ATTR_TIMEOUT, Date.now() + TIMEOUT);
}
function unlock(node) {
$(node).removeAttr(ATTR_TIMEOUT);
}
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);
}
export function initClipboard() {
// 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'
});
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').on('click', (e) => {
let target = $(e.target);
if (isLocked(target)) {
return;
}
// Copy URL to clipboard
navigator.clipboard.writeText(window.location.href).then(() => {
const defaultTitle = target.attr(ATTR_TITLE_ORIGIN);
const succeedTitle = target.attr(ATTR_TITLE_SUCCEED);
// Switch tooltip title
target.attr(ATTR_TITLE_ORIGIN, succeedTitle).tooltip('show');
lock(target);
setTimeout(() => {
target.attr(ATTR_TITLE_ORIGIN, defaultTitle);
unlock(target);
}, TIMEOUT);
});
});
}

View File

@ -0,0 +1,68 @@
/**
* Top bar title auto change while scrolling up/down in mobile screens.
*/
const titleSelector = 'div.post>h1:first-of-type';
const $pageTitle = $(titleSelector);
const $topbarTitle = $('#topbar-title');
const defaultTitleText = $topbarTitle.text().trim();
export function convertTitle() {
if (
$pageTitle.length === 0 /* on Home page */ ||
$pageTitle.hasClass('dynamic-title') ||
$topbarTitle.is(':hidden')
) {
/* not in mobile views */
return;
}
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();
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.on('click', function () {
$('body,html').animate({ scrollTop: 0 }, 800);
});
}

View File

@ -0,0 +1,27 @@
/**
* Set up image stuff
*/
export function imgExtra() {
if ($('#core-wrapper img[data-src]') <= 0) {
return;
}
/* See: <https://github.com/dimsemenov/Magnific-Popup> */
$('.popup').magnificPopup({
type: 'image',
closeOnContentClick: true,
showCloseBtn: false,
zoom: {
enabled: true,
duration: 300,
easing: 'ease-in-out'
}
});
/* Stop shimmer when image loaded */
document.addEventListener('lazyloaded', function (e) {
const $img = $(e.target);
$img.parent().removeClass('shimmer');
});
}

View File

@ -0,0 +1,50 @@
/**
* Update month/day to locale datetime
*
* Requirement: <https://github.com/iamkun/dayjs>
*/
/* A tool for locale datetime */
class LocaleHelper {
static get attrTimestamp() {
return 'data-ts';
}
static get attrDateFormat() {
return 'data-df';
}
static get locale() {
return $('html').attr('lang').substring(0, 2);
}
static getTimestamp(elem) {
return Number(elem.attr(LocaleHelper.attrTimestamp)); // unix timestamp
}
static getDateFormat(elem) {
return elem.attr(LocaleHelper.attrDateFormat);
}
}
export function initLocaleDatetime() {
dayjs.locale(LocaleHelper.locale);
dayjs.extend(window.dayjs_plugin_localizedFormat);
$(`[${LocaleHelper.attrTimestamp}]`).each(function () {
const date = dayjs.unix(LocaleHelper.getTimestamp($(this)));
const text = date.format(LocaleHelper.getDateFormat($(this)));
$(this).text(text);
$(this).removeAttr(LocaleHelper.attrTimestamp);
$(this).removeAttr(LocaleHelper.attrDateFormat);
// setup tooltips
const tooltip = $(this).attr('data-toggle');
if (typeof tooltip === 'undefined' || tooltip !== 'tooltip') {
return;
}
const tooltipText = date.format('llll'); // see: https://day.js.org/docs/en/display/format#list-of-localized-formats
$(this).attr('data-original-title', tooltipText);
});
}

View File

@ -0,0 +1,21 @@
/**
* Add listener for theme mode toggle
*/
const $toggleElem = $('.mode-toggle');
export function modeWatcher() {
if ($toggleElem.length === 0) {
return;
}
$toggleElem.off().on('click', (e) => {
const $target = $(e.target);
let $btn =
$target.prop('tagName') === 'button'.toUpperCase()
? $target
: $target.parent();
modeToggle.flipMode(); // modeToggle: `_includes/mode-toggle.html`
$btn.trigger('blur'); // remove the clicking outline
});
}

View File

@ -0,0 +1,254 @@
/**
* Count page views form GA or local cache file.
*
* Dependencies:
* - jQuery
* - countUp.js <https://github.com/inorganik/countUp.js>
*/
const getInitStatus = (function () {
let hasInit = false;
return () => {
let ret = hasInit;
if (!hasInit) {
hasInit = true;
}
return ret;
};
})();
const PvOpts = (function () {
function getContent(selector) {
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]');
}
};
})();
const PvStorage = (function () {
const Keys = {
KEY_PV: 'pv',
KEY_PV_SRC: 'pv_src',
KEY_CREATION: 'pv_created_date'
};
const Source = {
LOCAL: 'same-origin',
PROXY: 'cors'
};
function get(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;
}
}
}
};
})(); /* PvStorage */
function countUp(min, max, destId) {
if (min < max) {
let numAnim = new CountUp(destId, min, max);
if (!numAnim.error) {
numAnim.start();
} else {
console.error(numAnim.error);
}
}
}
function countPV(path, rows) {
let count = 0;
if (typeof rows !== 'undefined') {
for (let i = 0; i < rows.length; ++i) {
const gaPath = rows[parseInt(i, 10)][0];
if (gaPath === path) {
/* path format see: site.permalink */
count += parseInt(rows[parseInt(i, 10)][1], 10);
break;
}
}
}
return count;
}
function tacklePV(rows, path, elem, hasInit) {
let count = countPV(path, rows);
count = count === 0 ? 1 : count;
if (!hasInit) {
elem.text(new Intl.NumberFormat().format(count));
} else {
const initCount = parseInt(elem.text().replace(/,/g, ''), 10);
if (count > initCount) {
countUp(initCount, count, elem.attr('id'));
}
}
}
function displayPageviews(data) {
if (typeof data === 'undefined') {
return;
}
let hasInit = getInitStatus();
const rows = data.rows; /* could be undefined */
if ($('#post-list').length > 0) {
/* the Home page */
$('.post-preview').each(function () {
const path = $(this).find('a').attr('href');
tacklePV(rows, path, $(this).find('.pageviews'), hasInit);
});
} else if ($('.post').length > 0) {
/* the post */
const path = window.location.pathname;
tacklePV(rows, path, $('#pv'), hasInit);
}
}
function fetchProxyPageviews() {
if (PvOpts.hasProxyMeta()) {
$.ajax({
type: 'GET',
url: PvOpts.getProxyMeta(),
dataType: 'jsonp',
jsonpCallback: 'displayPageviews',
success: (data) => {
PvStorage.saveProxyCache(JSON.stringify(data));
},
error: (jqXHR, textStatus, errorThrown) => {
console.log(
'Failed to load pageviews from proxy server: ' + errorThrown
);
}
});
}
}
function fetchLocalPageviews(hasCache = false) {
return fetch(PvOpts.getLocalMeta())
.then((response) => response.json())
.then((data) => {
if (hasCache) {
// The cache from the proxy will sometimes be more recent than the local one
if (PvStorage.isFromProxy() && PvStorage.newerThan(data)) {
return;
}
}
displayPageviews(data);
PvStorage.saveLocalCache(JSON.stringify(data));
});
}
export function initPageviews() {
if ($('.pageviews').length <= 0) {
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
if (PvOpts.hasLocalMeta()) {
fetchLocalPageviews().then(fetchProxyPageviews);
} else {
fetchProxyPageviews();
}
}
}

View File

@ -0,0 +1,122 @@
/**
* This script make #search-result-wrapper switch to unloaded or shown automatically.
*/
const $btnSbTrigger = $('#sidebar-trigger');
const $btnSearchTrigger = $('#search-trigger');
const $btnCancel = $('#search-cancel');
const $main = $('#main');
const $topbarTitle = $('#topbar-title');
const $searchWrapper = $('#search-wrapper');
const $resultWrapper = $('#search-result-wrapper');
const $results = $('#search-results');
const $input = $('#search-input');
const $hints = $('#search-hints');
const $viewport = $('html,body');
// class names
const C_LOADED = 'loaded';
const C_UNLOADED = 'unloaded';
const C_FOCUS = 'input-focus';
const C_FLEX = 'd-flex';
class ScrollBlocker {
static offset = 0;
static resultVisible = false;
static on() {
ScrollBlocker.offset = window.scrollY;
$viewport.scrollTop(0);
}
static off() {
$viewport.scrollTop(ScrollBlocker.offset);
}
}
/*--- Actions in mobile screens (Sidebar hidden) ---*/
class MobileSearchBar {
static on() {
$btnSbTrigger.addClass(C_UNLOADED);
$topbarTitle.addClass(C_UNLOADED);
$btnSearchTrigger.addClass(C_UNLOADED);
$searchWrapper.addClass(C_FLEX);
$btnCancel.addClass(C_LOADED);
}
static off() {
$btnCancel.removeClass(C_LOADED);
$searchWrapper.removeClass(C_FLEX);
$btnSbTrigger.removeClass(C_UNLOADED);
$topbarTitle.removeClass(C_UNLOADED);
$btnSearchTrigger.removeClass(C_UNLOADED);
}
}
class ResultSwitch {
static on() {
if (!ScrollBlocker.resultVisible) {
// the block method must be called before $(#main) unloaded.
ScrollBlocker.on();
$resultWrapper.removeClass(C_UNLOADED);
$main.addClass(C_UNLOADED);
ScrollBlocker.resultVisible = true;
}
}
static off() {
if (ScrollBlocker.resultVisible) {
$results.empty();
if ($hints.hasClass(C_UNLOADED)) {
$hints.removeClass(C_UNLOADED);
}
$resultWrapper.addClass(C_UNLOADED);
$main.removeClass(C_UNLOADED);
// now the release method must be called after $(#main) display
ScrollBlocker.off();
$input.val('');
ScrollBlocker.resultVisible = false;
}
}
}
function isMobileView() {
return $btnCancel.hasClass(C_LOADED);
}
export function displaySearch() {
$btnSearchTrigger.on('click', function () {
MobileSearchBar.on();
ResultSwitch.on();
$input.trigger('focus');
});
$btnCancel.on('click', function () {
MobileSearchBar.off();
ResultSwitch.off();
});
$input.on('focus', function () {
$searchWrapper.addClass(C_FOCUS);
});
$input.on('focusout', function () {
$searchWrapper.removeClass(C_FOCUS);
});
$input.on('input', () => {
if ($input.val() === '') {
if (isMobileView()) {
$hints.removeClass(C_UNLOADED);
} else {
ResultSwitch.off();
}
} else {
ResultSwitch.on();
if (isMobileView()) {
$hints.addClass(C_UNLOADED);
}
}
});
}

View File

@ -0,0 +1,25 @@
/**
* Expand or close the sidebar in mobile screens.
*/
const $body = $('body');
const ATTR_DISPLAY = 'sidebar-display';
class SidebarUtil {
static isExpanded = false;
static toggle() {
if (SidebarUtil.isExpanded === false) {
$body.attr(ATTR_DISPLAY, '');
} else {
$body.removeAttr(ATTR_DISPLAY);
}
SidebarUtil.isExpanded = !SidebarUtil.isExpanded;
}
}
export function sidebarExpand() {
$('#sidebar-trigger').on('click', SidebarUtil.toggle);
$('#mask').on('click', SidebarUtil.toggle);
}

View File

@ -0,0 +1,109 @@
/**
Safari doesn't support CSS `scroll-behavior: smooth`,
so here is a compatible solution for all browser to 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.
*/
import ScrollHelper from './utils/scroll-helper';
export function smoothScroll() {
const $topbarTitle = $('#topbar-title');
const REM = 16; // in pixels
const ATTR_SCROLL_FOCUS = 'scroll-focus';
const SCOPE = "a[href*='#']:not([href='#']):not([href='#0'])";
$(SCOPE).on('click', function (event) {
if (
this.pathname.replace(/^\//, '') !== location.pathname.replace(/^\//, '')
) {
return;
}
if (location.hostname !== this.hostname) {
return;
}
const hash = decodeURI(this.hash);
let toFootnoteRef = RegExp(/^#fnref:/).test(hash);
let toFootnote = toFootnoteRef ? false : RegExp(/^#fn:/).test(hash);
let selector = '#' + $.escapeSelector(hash.substring(1));
let $target = $(selector);
let isMobileViews = $topbarTitle.is(':visible');
let isPortrait = $(window).width() < $(window).height();
if (typeof $target === 'undefined') {
return;
}
event.preventDefault();
if (history.pushState) {
/* add hash to URL */
history.pushState(null, null, hash);
}
let curOffset = $(window).scrollTop();
let destOffset = ($target.offset().top -= REM / 2);
if (destOffset < curOffset) {
// scroll up
ScrollHelper.hideTopbar();
ScrollHelper.addScrollUpTask();
if (isMobileViews && isPortrait) {
destOffset -= ScrollHelper.getTopbarHeight();
}
} else {
// scroll down
if (isMobileViews && isPortrait) {
destOffset -= ScrollHelper.getTopbarHeight();
}
}
$('html').animate(
{
scrollTop: destOffset
},
500,
() => {
$target.trigger('focus');
/* clean up old scroll mark */
const $scroll_focus = $(`[${ATTR_SCROLL_FOCUS}=true]`);
if ($scroll_focus.length) {
$scroll_focus.attr(ATTR_SCROLL_FOCUS, 'false');
}
/* Clean :target links */
const $target_links = $(':target');
if ($target_links.length) {
/* element that visited by the URL with hash */
$target_links.attr(ATTR_SCROLL_FOCUS, 'false');
}
/* set scroll mark to footnotes */
if (toFootnote || toFootnoteRef) {
$target.attr(ATTR_SCROLL_FOCUS, 'true');
}
if ($target.is(':focus')) {
/* Checking if the target was focused */
return false;
} else {
$target.attr(
'tabindex',
'-1'
); /* Adding tabindex for elements not focusable */
$target.trigger('focus'); /* Set focus again */
}
if (ScrollHelper.hasScrollUpTask()) {
ScrollHelper.popScrollUpTask();
}
}
);
}); /* click() */
}

View File

@ -0,0 +1,6 @@
/**
* Initial Bootstrap Tooltip.
*/
export function loadTooptip() {
$('[data-toggle="tooltip"]').tooltip();
}

View File

@ -0,0 +1,93 @@
/**
* Hide Header on scroll down
*/
import ScrollHelper from './utils/scroll-helper';
const $searchInput = $('#search-input');
const delta = ScrollHelper.getTopbarHeight();
let didScroll;
let lastScrollTop = 0;
function hasScrolled() {
let st = $(window).scrollTop();
/* Make sure they scroll more than delta */
if (Math.abs(lastScrollTop - st) <= delta) {
return;
}
if (st > lastScrollTop) {
/* Scroll down */
ScrollHelper.hideTopbar();
if ($searchInput.is(':focus')) {
$searchInput.trigger('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
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();
}
export function switchTopbar() {
const orientation = screen.orientation;
if (orientation) {
orientation.onchange = () => {
const type = orientation.type;
if (type === 'landscape-primary' || type === 'landscape-secondary') {
handleLandscape();
}
};
} 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).on('scroll', () => {
if (didScroll) {
return;
}
didScroll = true;
});
setInterval(() => {
if (didScroll) {
hasScrolled();
didScroll = false;
}
}, 250);
}

View File

@ -0,0 +1,64 @@
/**
* A tool for smooth scrolling and topbar switcher
*/
const ATTR_TOPBAR_VISIBLE = 'data-topbar-visible';
const $body = $('body');
const $topbarWrapper = $('#topbar-wrapper');
export default class ScrollHelper {
static scrollUpCount = 0; // the number of times the scroll up was triggered by ToC or anchor
static topbarIsLocked = false;
static orientationIsLocked = false;
static hideTopbar() {
$body.attr(ATTR_TOPBAR_VISIBLE, 'false');
}
static showTopbar() {
$body.attr(ATTR_TOPBAR_VISIBLE, 'true');
}
// scroll up
static addScrollUpTask() {
ScrollHelper.scrollUpCount += 1;
if (!ScrollHelper.topbarIsLocked) {
ScrollHelper.topbarIsLocked = true;
}
}
static popScrollUpTask() {
ScrollHelper.scrollUpCount -= 1;
}
static hasScrollUpTask() {
return ScrollHelper.scrollUpCount > 0;
}
static topbarLocked() {
return ScrollHelper.topbarIsLocked === true;
}
static unlockTopbar() {
ScrollHelper.topbarIsLocked = false;
}
static getTopbarHeight() {
return $topbarWrapper.outerHeight();
}
// orientation change
static orientationLocked() {
return ScrollHelper.orientationIsLocked === true;
}
static lockOrientation() {
ScrollHelper.orientationIsLocked = true;
}
static unLockOrientation() {
ScrollHelper.orientationIsLocked = false;
}
}

View File

@ -0,0 +1,3 @@
export { basic } from './layouts/basic';
export { initSidebar } from './layouts/sidebar';
export { initTopbar } from './layouts/topbar';

View File

@ -0,0 +1,7 @@
import { back2top } from '../components/back-to-top';
import { loadTooptip } from '../components/tooltip-loader';
export function basic() {
back2top();
loadTooptip();
}

View File

@ -0,0 +1,7 @@
import { modeWatcher } from '../components/mode-watcher';
import { sidebarExpand } from '../components/sidebar';
export function initSidebar() {
modeWatcher();
sidebarExpand();
}

View File

@ -0,0 +1,9 @@
import { convertTitle } from '../components/convert-title';
import { displaySearch } from '../components/search-display';
import { switchTopbar } from '../components/topbar-switcher';
export function initTopbar() {
convertTitle();
displaySearch();
switchTopbar();
}

View File

@ -0,0 +1,6 @@
export { categoryCollapse } from './components/category-collapse';
export { initClipboard } from './components/clipboard';
export { imgExtra } from './components/img-extra';
export { initLocaleDatetime } from './components/locale-datetime';
export { initPageviews } from './components/pageviews';
export { smoothScroll } from './components/smooth-scroll';

9
_javascript/page.js Normal file
View File

@ -0,0 +1,9 @@
import { basic, initSidebar, initTopbar } from './modules/layouts';
import { imgExtra, initClipboard, smoothScroll } from './modules/plugins';
basic();
initSidebar();
initTopbar();
imgExtra();
initClipboard();
smoothScroll();

17
_javascript/post.js Normal file
View File

@ -0,0 +1,17 @@
import { basic, initSidebar, initTopbar } from './modules/layouts';
import {
imgExtra,
initLocaleDatetime,
initClipboard,
smoothScroll,
initPageviews
} from './modules/plugins';
basic();
initSidebar();
initTopbar();
imgExtra();
initLocaleDatetime();
initClipboard();
smoothScroll();
initPageviews();

View File

@ -1,30 +0,0 @@
/**
* Tab 'Categories' expand/close effect.
*/
$(function () {
const childPrefix = "l_";
const parentPrefix = "h_";
const collapse = $(".collapse");
/* close up top-category */
collapse.on("hide.bs.collapse", function () { /* Bootstrap collapse events. */
const parentId = parentPrefix + $(this).attr("id").substring(childPrefix.length);
if (parentId) {
$(`#${parentId} .far.fa-folder-open`).attr("class", "far fa-folder fa-fw");
$(`#${parentId} i.fas`).addClass("rotate");
$(`#${parentId}`).removeClass("hide-border-bottom");
}
});
/* expand the top category */
collapse.on("show.bs.collapse", function () {
const parentId = parentPrefix + $(this).attr("id").substring(childPrefix.length);
if (parentId) {
$(`#${parentId} .far.fa-folder`).attr("class", "far fa-folder-open fa-fw");
$(`#${parentId} i.fas`).removeClass("rotate");
$(`#${parentId}`).addClass("hide-border-bottom");
}
});
});

View File

@ -1,123 +0,0 @@
/**
* Clipboard functions
*
* Dependencies:
* - popper.js (https://github.com/popperjs/popper-core)
* - clipboard.js (https://github.com/zenorocha/clipboard.js)
*/
$(function () {
const btnSelector = '.code-header>button';
const ICON_SUCCESS = 'fas fa-check';
const ATTR_TIMEOUT = 'timeout';
const ATTR_TITLE_SUCCEED = 'data-title-succeed';
const ATTR_TITLE_ORIGIN = 'data-original-title';
const TIMEOUT = 2000; // in milliseconds
function isLocked(node) {
if ($(node)[0].hasAttribute(ATTR_TIMEOUT)) {
let timeout = $(node).attr(ATTR_TIMEOUT);
if (Number(timeout) > Date.now()) {
return true;
}
}
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);
showTooltip(trigger);
lock(trigger);
setTimeout(() => {
hideTooltip(trigger);
resumeIcon(trigger);
unlock(trigger);
}, TIMEOUT);
});
/* --- Post link sharing --- */
$('#copy-link').on('click',(e) => {
let target = $(e.target);
if (isLocked(target)) {
return;
}
// Copy URL to clipboard
navigator.clipboard
.writeText(window.location.href)
.then(() => {
const defaultTitle = target.attr(ATTR_TITLE_ORIGIN);
const succeedTitle = target.attr(ATTR_TITLE_SUCCEED);
// Switch tooltip title
target.attr(ATTR_TITLE_ORIGIN, succeedTitle).tooltip('show');
lock(target);
setTimeout(() => {
target.attr(ATTR_TITLE_ORIGIN, defaultTitle);
unlock(target);
}, TIMEOUT);
});
});
});

View File

@ -1,28 +0,0 @@
/**
* Set up image stuff
*/
(function() {
if ($('#core-wrapper img[data-src]') <= 0) {
return;
}
/* See: <https://github.com/dimsemenov/Magnific-Popup> */
$('.popup').magnificPopup({
type: 'image',
closeOnContentClick: true,
showCloseBtn: false,
zoom: {
enabled: true,
duration: 300,
easing: 'ease-in-out'
}
});
/* Stop shimmer when image loaded */
document.addEventListener('lazyloaded', function(e) {
const $img = $(e.target);
$img.parent().removeClass('shimmer');
});
})();

View File

@ -1,43 +0,0 @@
/**
* Update month/day to locale datetime
*
* Requirement: <https://github.com/iamkun/dayjs>
*/
/* A tool for locale datetime */
const LocaleHelper = (function () {
const locale = $('html').attr('lang').substring(0, 2);
const attrTimestamp = 'data-ts';
const attrDateFormat = 'data-df';
return {
locale: () => locale,
attrTimestamp: () => attrTimestamp,
attrDateFormat: () => attrDateFormat,
getTimestamp: ($elem) => Number($elem.attr(attrTimestamp)), // unix timestamp
getDateFormat: ($elem) => $elem.attr(attrDateFormat)
};
}());
$(function () {
dayjs.locale(LocaleHelper.locale());
dayjs.extend(window.dayjs_plugin_localizedFormat);
$(`[${LocaleHelper.attrTimestamp()}]`).each(function () {
const date = dayjs.unix(LocaleHelper.getTimestamp($(this)));
const text = date.format(LocaleHelper.getDateFormat($(this)));
$(this).text(text);
$(this).removeAttr(LocaleHelper.attrTimestamp());
$(this).removeAttr(LocaleHelper.attrDateFormat());
// setup tooltips
const tooltip = $(this).attr('data-toggle');
if (typeof tooltip === 'undefined' || tooltip !== 'tooltip') {
return;
}
const tooltipText = date.format('llll'); // see: https://day.js.org/docs/en/display/format#list-of-localized-formats
$(this).attr('data-original-title', tooltipText);
});
});

View File

@ -1,250 +0,0 @@
/**
* Count page views form GA or local cache file.
*
* Dependencies:
* - jQuery
* - countUp.js <https://github.com/inorganik/countUp.js>
*/
const getInitStatus = (function () {
let hasInit = false;
return () => {
let ret = hasInit;
if (!hasInit) {
hasInit = true;
}
return ret;
};
}());
const PvOpts = (function () {
function getContent(selector) {
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]");
}
};
}());
const PvStorage = (function () {
const Keys = {
KEY_PV: "pv",
KEY_PV_SRC: "pv_src",
KEY_CREATION: "pv_created_date"
};
const Source = {
LOCAL: "same-origin",
PROXY: "cors"
};
function get(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;
}
}
}
};
}()); /* PvStorage */
function countUp(min, max, destId) {
if (min < max) {
let numAnim = new CountUp(destId, min, max);
if (!numAnim.error) {
numAnim.start();
} else {
console.error(numAnim.error);
}
}
}
function countPV(path, rows) {
let count = 0;
if (typeof rows !== "undefined") {
for (let i = 0; i < rows.length; ++i) {
const gaPath = rows[parseInt(i, 10)][0];
if (gaPath === path) { /* path format see: site.permalink */
count += parseInt(rows[parseInt(i, 10)][1], 10);
break;
}
}
}
return count;
}
function tacklePV(rows, path, elem, hasInit) {
let count = countPV(path, rows);
count = (count === 0 ? 1 : count);
if (!hasInit) {
elem.text(new Intl.NumberFormat().format(count));
} else {
const initCount = parseInt(elem.text().replace(/,/g, ""), 10);
if (count > initCount) {
countUp(initCount, count, elem.attr("id"));
}
}
}
function displayPageviews(data) {
if (typeof data === "undefined") {
return;
}
let hasInit = getInitStatus();
const rows = data.rows; /* could be undefined */
if ($("#post-list").length > 0) { /* the Home page */
$(".post-preview").each(function () {
const path = $(this).find("a").attr("href");
tacklePV(rows, path, $(this).find(".pageviews"), hasInit);
});
} else if ($(".post").length > 0) { /* the post */
const path = window.location.pathname;
tacklePV(rows, path, $("#pv"), hasInit);
}
}
function fetchProxyPageviews() {
if (PvOpts.hasProxyMeta()) {
$.ajax({
type: "GET",
url: PvOpts.getProxyMeta(),
dataType: "jsonp",
jsonpCallback: "displayPageviews",
success: (data) => {
PvStorage.saveProxyCache(JSON.stringify(data));
},
error: (jqXHR, textStatus, errorThrown) => {
console.log("Failed to load pageviews from proxy server: " + errorThrown);
}
});
}
}
function fetchLocalPageviews(hasCache = false) {
return fetch(PvOpts.getLocalMeta())
.then(response => response.json())
.then(data => {
if (hasCache) {
// The cache from the proxy will sometimes be more recent than the local one
if (PvStorage.isFromProxy() && PvStorage.newerThan(data)) {
return;
}
}
displayPageviews(data);
PvStorage.saveLocalCache(JSON.stringify(data));
});
}
$(function () {
if ($(".pageviews").length <= 0) {
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
if (PvOpts.hasLocalMeta()) {
fetchLocalPageviews().then(fetchProxyPageviews);
} else {
fetchProxyPageviews();
}
}
});

View File

@ -1,98 +0,0 @@
/**
Safari doesn't support CSS `scroll-behavior: smooth`,
so here is a compatible solution for all browser to 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.
*/
$(function () {
const $topbarTitle = $("#topbar-title");
const REM = 16; // in pixels
const ATTR_SCROLL_FOCUS = "scroll-focus";
$("a[href*='#']")
.not("[href='#']")
.not("[href='#0']")
.on('click', function (event) {
if (this.pathname.replace(/^\//, "") !==
location.pathname.replace(/^\//, "")) {
return;
}
if (location.hostname !== this.hostname) {
return;
}
const hash = decodeURI(this.hash);
let toFootnoteRef = RegExp(/^#fnref:/).test(hash);
let toFootnote = toFootnoteRef ? false : RegExp(/^#fn:/).test(hash);
let selector = '#' + $.escapeSelector(hash.substring(1));
let $target = $(selector);
let isMobileViews = $topbarTitle.is(":visible");
let isPortrait = $(window).width() < $(window).height();
if (typeof $target === "undefined") {
return;
}
event.preventDefault();
if (history.pushState) { /* add hash to URL */
history.pushState(null, null, hash);
}
let curOffset = $(window).scrollTop();
let destOffset = $target.offset().top -= REM / 2;
if (destOffset < curOffset) { // scroll up
ScrollHelper.hideTopbar();
ScrollHelper.addScrollUpTask();
if (isMobileViews && isPortrait) {
destOffset -= ScrollHelper.getTopbarHeight();
}
} else { // scroll down
if (isMobileViews && isPortrait) {
destOffset -= ScrollHelper.getTopbarHeight();
}
}
$("html").animate({
scrollTop: destOffset
}, 500, () => {
$target.trigger("focus");
/* clean up old scroll mark */
const $scroll_focus = $(`[${ATTR_SCROLL_FOCUS}=true]`);
if ($scroll_focus.length) {
$scroll_focus.attr(ATTR_SCROLL_FOCUS, "false");
}
/* Clean :target links */
const $target_links = $(":target");
if ($target_links.length) { /* element that visited by the URL with hash */
$target_links.attr(ATTR_SCROLL_FOCUS, "false");
}
/* set scroll mark to footnotes */
if (toFootnote || toFootnoteRef) {
$target.attr(ATTR_SCROLL_FOCUS, "true");
}
if ($target.is(":focus")) { /* Checking if the target was focused */
return false;
} else {
$target.attr("tabindex", "-1"); /* Adding tabindex for elements not focusable */
$target.trigger("focus"); /* Set focus again */
}
if (ScrollHelper.hasScrollUpTask()) {
ScrollHelper.popScrollUpTask();
}
});
}); /* click() */
});

View File

@ -1,6 +0,0 @@
/*!
* Chirpy v5.5.2 (https://github.com/cotes2020/jekyll-theme-chirpy/)
* © 2019 Cotes Chung
* MIT Licensed
*/
$(function(){$(window).on("scroll",()=>{50<$(this).scrollTop()&&"none"===$("#sidebar-trigger").css("display")?$("#back-to-top").fadeIn():$("#back-to-top").fadeOut()}),$("#back-to-top").on("click",()=>($("body,html").animate({scrollTop:0},800),!1))}),$(function(){$(".mode-toggle").on("click",o=>{o=$(o.target);(o.prop("tagName")==="button".toUpperCase()?o:o.parent()).trigger("blur"),flipMode()})});const ScrollHelper=function(){const o=$("body"),e="data-topbar-visible",t=$("#topbar-wrapper").outerHeight();let r=0,a=!1,l=!1;return{hideTopbar:()=>o.attr(e,"false"),showTopbar:()=>o.attr(e,"true"),addScrollUpTask:()=>{r+=1,a=a||!0},popScrollUpTask:()=>--r,hasScrollUpTask:()=>0<r,topbarLocked:()=>!0===a,unlockTopbar:()=>a=!1,getTopbarHeight:()=>t,orientationLocked:()=>!0===l,lockOrientation:()=>l=!0,unLockOrientation:()=>l=!1}}();$(function(){const o=$("#sidebar-trigger"),e=$("#search-trigger"),t=$("#search-cancel"),r=$("#main"),a=$("#topbar-title"),l=$("#search-wrapper"),n=$("#search-result-wrapper"),s=$("#search-results"),i=$("#search-input"),c=$("#search-hints"),d=function(){let o=0;return{block(){o=window.scrollY,$("html,body").scrollTop(0)},release(){$("html,body").scrollTop(o)},getOffset(){return o}}}(),p={on(){o.addClass("unloaded"),a.addClass("unloaded"),e.addClass("unloaded"),l.addClass("d-flex"),t.addClass("loaded")},off(){t.removeClass("loaded"),l.removeClass("d-flex"),o.removeClass("unloaded"),a.removeClass("unloaded"),e.removeClass("unloaded")}},f=function(){let o=!1;return{on(){o||(d.block(),n.removeClass("unloaded"),r.addClass("unloaded"),o=!0)},off(){o&&(s.empty(),c.hasClass("unloaded")&&c.removeClass("unloaded"),n.addClass("unloaded"),r.removeClass("unloaded"),d.release(),i.val(""),o=!1)}}}();function u(){return t.hasClass("loaded")}e.on("click",function(){p.on(),f.on(),i.trigger("focus")}),t.on("click",function(){p.off(),f.off()}),i.on("focus",function(){l.addClass("input-focus")}),i.on("focusout",function(){l.removeClass("input-focus")}),i.on("input",()=>{""===i.val()?u()?c.removeClass("unloaded"):f.off():(f.on(),u()&&c.addClass("unloaded"))})}),$(function(){var o=function(){const o="sidebar-display";let e=!1;const t=$("body");return{toggle(){!1===e?t.attr(o,""):t.removeAttr(o),e=!e}}}();$("#sidebar-trigger").on("click",o.toggle),$("#mask").on("click",o.toggle)}),$(function(){$('[data-toggle="tooltip"]').tooltip()}),$(function(){const e=$("#search-input"),t=ScrollHelper.getTopbarHeight();let o,r=0;function a(){0!==$(window).scrollTop()&&(ScrollHelper.lockOrientation(),ScrollHelper.hideTopbar())}screen.orientation?screen.orientation.onchange=()=>{var o=screen.orientation.type;"landscape-primary"!==o&&"landscape-secondary"!==o||a()}:$(window).on("orientationchange",()=>{$(window).width()<$(window).height()&&a()}),$(window).on("scroll",()=>{o=o||!0}),setInterval(()=>{o&&(!function(){var o=$(this).scrollTop();if(!(Math.abs(r-o)<=t)){if(o>r)ScrollHelper.hideTopbar(),e.is(":focus")&&e.trigger("blur");else if(o+$(window).height()<$(document).height()){if(ScrollHelper.hasScrollUpTask())return;ScrollHelper.topbarLocked()?ScrollHelper.unlockTopbar():ScrollHelper.orientationLocked()?ScrollHelper.unLockOrientation():ScrollHelper.showTopbar()}r=o}}(),o=!1)},250)}),$(function(){var o="div.post>h1:first-of-type",e=$(o);const n=$("#topbar-title");if(0!==e.length&&!e.hasClass("dynamic-title")&&!n.is(":hidden")){const s=n.text().trim();let r=e.text().trim(),a=!1,l=0;($("#page-category").length||$("#page-tag").length)&&/\s/.test(r)&&(r=r.replace(/[0-9]/g,"").trim()),e.offset().top<$(window).scrollTop()&&n.text(r);new IntersectionObserver(o=>{var e,t;a?(t=$(window).scrollTop(),e=l<t,l=t,t=o[0],e?0===t.intersectionRatio&&n.text(r):1===t.intersectionRatio&&n.text(s)):a=!0},{rootMargin:"-48px 0px 0px 0px",threshold:[0,1]}).observe(document.querySelector(o)),n.on("click",function(){$("body,html").animate({scrollTop:0},800)})}}),$(function(){var o=$(".collapse");o.on("hide.bs.collapse",function(){var o="h_"+$(this).attr("id").substring("l_".length);o&&($(`#${o} .far.fa-folder-open`).attr("class","far fa-folder fa-fw"),$(`#${o} i.fas`).addClass("rotate"),$("#"+o).removeClass("hide-border-bottom"))}),o.on("show.bs.collapse",function(){var o="h_"+$(this).attr("id").substring("l_".length);o&&($(`#${o} .far.fa-folder`).attr("class","far fa-folder-open fa-fw"),$(`#${o} i.fas`).removeClass("rotate"),$("#"+o).addClass("hide-border-bottom"))})});

View File

@ -1,6 +0,0 @@
/*!
* Chirpy v5.5.2 (https://github.com/cotes2020/jekyll-theme-chirpy/)
* © 2019 Cotes Chung
* MIT Licensed
*/
$(function(){$(window).on("scroll",()=>{50<$(this).scrollTop()&&"none"===$("#sidebar-trigger").css("display")?$("#back-to-top").fadeIn():$("#back-to-top").fadeOut()}),$("#back-to-top").on("click",()=>($("body,html").animate({scrollTop:0},800),!1))}),$(function(){$(".mode-toggle").on("click",o=>{o=$(o.target);(o.prop("tagName")==="button".toUpperCase()?o:o.parent()).trigger("blur"),flipMode()})});const ScrollHelper=function(){const o=$("body"),e="data-topbar-visible",t=$("#topbar-wrapper").outerHeight();let r=0,l=!1,n=!1;return{hideTopbar:()=>o.attr(e,"false"),showTopbar:()=>o.attr(e,"true"),addScrollUpTask:()=>{r+=1,l=l||!0},popScrollUpTask:()=>--r,hasScrollUpTask:()=>0<r,topbarLocked:()=>!0===l,unlockTopbar:()=>l=!1,getTopbarHeight:()=>t,orientationLocked:()=>!0===n,lockOrientation:()=>n=!0,unLockOrientation:()=>n=!1}}();$(function(){const o=$("#sidebar-trigger"),e=$("#search-trigger"),t=$("#search-cancel"),r=$("#main"),l=$("#topbar-title"),n=$("#search-wrapper"),a=$("#search-result-wrapper"),s=$("#search-results"),i=$("#search-input"),c=$("#search-hints"),d=function(){let o=0;return{block(){o=window.scrollY,$("html,body").scrollTop(0)},release(){$("html,body").scrollTop(o)},getOffset(){return o}}}(),p={on(){o.addClass("unloaded"),l.addClass("unloaded"),e.addClass("unloaded"),n.addClass("d-flex"),t.addClass("loaded")},off(){t.removeClass("loaded"),n.removeClass("d-flex"),o.removeClass("unloaded"),l.removeClass("unloaded"),e.removeClass("unloaded")}},u=function(){let o=!1;return{on(){o||(d.block(),a.removeClass("unloaded"),r.addClass("unloaded"),o=!0)},off(){o&&(s.empty(),c.hasClass("unloaded")&&c.removeClass("unloaded"),a.addClass("unloaded"),r.removeClass("unloaded"),d.release(),i.val(""),o=!1)}}}();function f(){return t.hasClass("loaded")}e.on("click",function(){p.on(),u.on(),i.trigger("focus")}),t.on("click",function(){p.off(),u.off()}),i.on("focus",function(){n.addClass("input-focus")}),i.on("focusout",function(){n.removeClass("input-focus")}),i.on("input",()=>{""===i.val()?f()?c.removeClass("unloaded"):u.off():(u.on(),f()&&c.addClass("unloaded"))})}),$(function(){var o=function(){const o="sidebar-display";let e=!1;const t=$("body");return{toggle(){!1===e?t.attr(o,""):t.removeAttr(o),e=!e}}}();$("#sidebar-trigger").on("click",o.toggle),$("#mask").on("click",o.toggle)}),$(function(){$('[data-toggle="tooltip"]').tooltip()}),$(function(){const e=$("#search-input"),t=ScrollHelper.getTopbarHeight();let o,r=0;function l(){0!==$(window).scrollTop()&&(ScrollHelper.lockOrientation(),ScrollHelper.hideTopbar())}screen.orientation?screen.orientation.onchange=()=>{var o=screen.orientation.type;"landscape-primary"!==o&&"landscape-secondary"!==o||l()}:$(window).on("orientationchange",()=>{$(window).width()<$(window).height()&&l()}),$(window).on("scroll",()=>{o=o||!0}),setInterval(()=>{o&&(!function(){var o=$(this).scrollTop();if(!(Math.abs(r-o)<=t)){if(o>r)ScrollHelper.hideTopbar(),e.is(":focus")&&e.trigger("blur");else if(o+$(window).height()<$(document).height()){if(ScrollHelper.hasScrollUpTask())return;ScrollHelper.topbarLocked()?ScrollHelper.unlockTopbar():ScrollHelper.orientationLocked()?ScrollHelper.unLockOrientation():ScrollHelper.showTopbar()}r=o}}(),o=!1)},250)}),$(function(){var o="div.post>h1:first-of-type",e=$(o);const a=$("#topbar-title");if(0!==e.length&&!e.hasClass("dynamic-title")&&!a.is(":hidden")){const s=a.text().trim();let r=e.text().trim(),l=!1,n=0;($("#page-category").length||$("#page-tag").length)&&/\s/.test(r)&&(r=r.replace(/[0-9]/g,"").trim()),e.offset().top<$(window).scrollTop()&&a.text(r);new IntersectionObserver(o=>{var e,t;l?(t=$(window).scrollTop(),e=n<t,n=t,t=o[0],e?0===t.intersectionRatio&&a.text(r):1===t.intersectionRatio&&a.text(s)):l=!0},{rootMargin:"-48px 0px 0px 0px",threshold:[0,1]}).observe(document.querySelector(o)),a.on("click",function(){$("body,html").animate({scrollTop:0},800)})}});

View File

@ -1,6 +0,0 @@
/*!
* Chirpy v5.5.2 (https://github.com/cotes2020/jekyll-theme-chirpy/)
* © 2019 Cotes Chung
* MIT Licensed
*/
$(function(){$(window).on("scroll",()=>{50<$(this).scrollTop()&&"none"===$("#sidebar-trigger").css("display")?$("#back-to-top").fadeIn():$("#back-to-top").fadeOut()}),$("#back-to-top").on("click",()=>($("body,html").animate({scrollTop:0},800),!1))}),$(function(){$(".mode-toggle").on("click",e=>{e=$(e.target);(e.prop("tagName")==="button".toUpperCase()?e:e.parent()).trigger("blur"),flipMode()})});const ScrollHelper=function(){const e=$("body"),t="data-topbar-visible",o=$("#topbar-wrapper").outerHeight();let a=0,r=!1,l=!1;return{hideTopbar:()=>e.attr(t,"false"),showTopbar:()=>e.attr(t,"true"),addScrollUpTask:()=>{a+=1,r=r||!0},popScrollUpTask:()=>--a,hasScrollUpTask:()=>0<a,topbarLocked:()=>!0===r,unlockTopbar:()=>r=!1,getTopbarHeight:()=>o,orientationLocked:()=>!0===l,lockOrientation:()=>l=!0,unLockOrientation:()=>l=!1}}(),LocaleHelper=($(function(){const e=$("#sidebar-trigger"),t=$("#search-trigger"),o=$("#search-cancel"),a=$("#main"),r=$("#topbar-title"),l=$("#search-wrapper"),n=$("#search-result-wrapper"),s=$("#search-results"),i=$("#search-input"),c=$("#search-hints"),d=function(){let e=0;return{block(){e=window.scrollY,$("html,body").scrollTop(0)},release(){$("html,body").scrollTop(e)},getOffset(){return e}}}(),p={on(){e.addClass("unloaded"),r.addClass("unloaded"),t.addClass("unloaded"),l.addClass("d-flex"),o.addClass("loaded")},off(){o.removeClass("loaded"),l.removeClass("d-flex"),e.removeClass("unloaded"),r.removeClass("unloaded"),t.removeClass("unloaded")}},u=function(){let e=!1;return{on(){e||(d.block(),n.removeClass("unloaded"),a.addClass("unloaded"),e=!0)},off(){e&&(s.empty(),c.hasClass("unloaded")&&c.removeClass("unloaded"),n.addClass("unloaded"),a.removeClass("unloaded"),d.release(),i.val(""),e=!1)}}}();function f(){return o.hasClass("loaded")}t.on("click",function(){p.on(),u.on(),i.trigger("focus")}),o.on("click",function(){p.off(),u.off()}),i.on("focus",function(){l.addClass("input-focus")}),i.on("focusout",function(){l.removeClass("input-focus")}),i.on("input",()=>{""===i.val()?f()?c.removeClass("unloaded"):u.off():(u.on(),f()&&c.addClass("unloaded"))})}),$(function(){var e=function(){const e="sidebar-display";let t=!1;const o=$("body");return{toggle(){!1===t?o.attr(e,""):o.removeAttr(e),t=!t}}}();$("#sidebar-trigger").on("click",e.toggle),$("#mask").on("click",e.toggle)}),$(function(){$('[data-toggle="tooltip"]').tooltip()}),$(function(){const t=$("#search-input"),o=ScrollHelper.getTopbarHeight();let e,a=0;function r(){0!==$(window).scrollTop()&&(ScrollHelper.lockOrientation(),ScrollHelper.hideTopbar())}screen.orientation?screen.orientation.onchange=()=>{var e=screen.orientation.type;"landscape-primary"!==e&&"landscape-secondary"!==e||r()}:$(window).on("orientationchange",()=>{$(window).width()<$(window).height()&&r()}),$(window).on("scroll",()=>{e=e||!0}),setInterval(()=>{e&&(!function(){var e=$(this).scrollTop();if(!(Math.abs(a-e)<=o)){if(e>a)ScrollHelper.hideTopbar(),t.is(":focus")&&t.trigger("blur");else if(e+$(window).height()<$(document).height()){if(ScrollHelper.hasScrollUpTask())return;ScrollHelper.topbarLocked()?ScrollHelper.unlockTopbar():ScrollHelper.orientationLocked()?ScrollHelper.unLockOrientation():ScrollHelper.showTopbar()}a=e}}(),e=!1)},250)}),$(function(){var e="div.post>h1:first-of-type",t=$(e);const n=$("#topbar-title");if(0!==t.length&&!t.hasClass("dynamic-title")&&!n.is(":hidden")){const s=n.text().trim();let a=t.text().trim(),r=!1,l=0;($("#page-category").length||$("#page-tag").length)&&/\s/.test(a)&&(a=a.replace(/[0-9]/g,"").trim()),t.offset().top<$(window).scrollTop()&&n.text(a);new IntersectionObserver(e=>{var t,o;r?(o=$(window).scrollTop(),t=l<o,l=o,o=e[0],t?0===o.intersectionRatio&&n.text(a):1===o.intersectionRatio&&n.text(s)):r=!0},{rootMargin:"-48px 0px 0px 0px",threshold:[0,1]}).observe(document.querySelector(e)),n.on("click",function(){$("body,html").animate({scrollTop:0},800)})}}),function(){const e=$("html").attr("lang").substring(0,2),t="data-ts",o="data-df";return{locale:()=>e,attrTimestamp:()=>t,attrDateFormat:()=>o,getTimestamp:e=>Number(e.attr(t)),getDateFormat:e=>e.attr(o)}}());$(function(){dayjs.locale(LocaleHelper.locale()),dayjs.extend(window.dayjs_plugin_localizedFormat),$(`[${LocaleHelper.attrTimestamp()}]`).each(function(){var e=dayjs.unix(LocaleHelper.getTimestamp($(this))),t=e.format(LocaleHelper.getDateFormat($(this))),t=($(this).text(t),$(this).removeAttr(LocaleHelper.attrTimestamp()),$(this).removeAttr(LocaleHelper.attrDateFormat()),$(this).attr("data-toggle"));void 0!==t&&"tooltip"===t&&(t=e.format("llll"),$(this).attr("data-original-title",t))})});

View File

@ -1,6 +0,0 @@
/*!
* Chirpy v5.5.2 (https://github.com/cotes2020/jekyll-theme-chirpy/)
* © 2019 Cotes Chung
* MIT Licensed
*/
$(function(){$(window).on("scroll",()=>{50<$(this).scrollTop()&&"none"===$("#sidebar-trigger").css("display")?$("#back-to-top").fadeIn():$("#back-to-top").fadeOut()}),$("#back-to-top").on("click",()=>($("body,html").animate({scrollTop:0},800),!1))}),$(function(){$(".mode-toggle").on("click",e=>{e=$(e.target);(e.prop("tagName")==="button".toUpperCase()?e:e.parent()).trigger("blur"),flipMode()})});const ScrollHelper=function(){const e=$("body"),t="data-topbar-visible",o=$("#topbar-wrapper").outerHeight();let a=0,r=!1,l=!1;return{hideTopbar:()=>e.attr(t,"false"),showTopbar:()=>e.attr(t,"true"),addScrollUpTask:()=>{a+=1,r=r||!0},popScrollUpTask:()=>--a,hasScrollUpTask:()=>0<a,topbarLocked:()=>!0===r,unlockTopbar:()=>r=!1,getTopbarHeight:()=>o,orientationLocked:()=>!0===l,lockOrientation:()=>l=!0,unLockOrientation:()=>l=!1}}(),LocaleHelper=($(function(){const e=$("#sidebar-trigger"),t=$("#search-trigger"),o=$("#search-cancel"),a=$("#main"),r=$("#topbar-title"),l=$("#search-wrapper"),n=$("#search-result-wrapper"),s=$("#search-results"),i=$("#search-input"),c=$("#search-hints"),d=function(){let e=0;return{block(){e=window.scrollY,$("html,body").scrollTop(0)},release(){$("html,body").scrollTop(e)},getOffset(){return e}}}(),p={on(){e.addClass("unloaded"),r.addClass("unloaded"),t.addClass("unloaded"),l.addClass("d-flex"),o.addClass("loaded")},off(){o.removeClass("loaded"),l.removeClass("d-flex"),e.removeClass("unloaded"),r.removeClass("unloaded"),t.removeClass("unloaded")}},u=function(){let e=!1;return{on(){e||(d.block(),n.removeClass("unloaded"),a.addClass("unloaded"),e=!0)},off(){e&&(s.empty(),c.hasClass("unloaded")&&c.removeClass("unloaded"),n.addClass("unloaded"),a.removeClass("unloaded"),d.release(),i.val(""),e=!1)}}}();function f(){return o.hasClass("loaded")}t.on("click",function(){p.on(),u.on(),i.trigger("focus")}),o.on("click",function(){p.off(),u.off()}),i.on("focus",function(){l.addClass("input-focus")}),i.on("focusout",function(){l.removeClass("input-focus")}),i.on("input",()=>{""===i.val()?f()?c.removeClass("unloaded"):u.off():(u.on(),f()&&c.addClass("unloaded"))})}),$(function(){var e=function(){const e="sidebar-display";let t=!1;const o=$("body");return{toggle(){!1===t?o.attr(e,""):o.removeAttr(e),t=!t}}}();$("#sidebar-trigger").on("click",e.toggle),$("#mask").on("click",e.toggle)}),$(function(){$('[data-toggle="tooltip"]').tooltip()}),$(function(){const t=$("#search-input"),o=ScrollHelper.getTopbarHeight();let e,a=0;function r(){0!==$(window).scrollTop()&&(ScrollHelper.lockOrientation(),ScrollHelper.hideTopbar())}screen.orientation?screen.orientation.onchange=()=>{var e=screen.orientation.type;"landscape-primary"!==e&&"landscape-secondary"!==e||r()}:$(window).on("orientationchange",()=>{$(window).width()<$(window).height()&&r()}),$(window).on("scroll",()=>{e=e||!0}),setInterval(()=>{e&&(!function(){var e=$(this).scrollTop();if(!(Math.abs(a-e)<=o)){if(e>a)ScrollHelper.hideTopbar(),t.is(":focus")&&t.trigger("blur");else if(e+$(window).height()<$(document).height()){if(ScrollHelper.hasScrollUpTask())return;ScrollHelper.topbarLocked()?ScrollHelper.unlockTopbar():ScrollHelper.orientationLocked()?ScrollHelper.unLockOrientation():ScrollHelper.showTopbar()}a=e}}(),e=!1)},250)}),$(function(){var e="div.post>h1:first-of-type",t=$(e);const n=$("#topbar-title");if(0!==t.length&&!t.hasClass("dynamic-title")&&!n.is(":hidden")){const s=n.text().trim();let a=t.text().trim(),r=!1,l=0;($("#page-category").length||$("#page-tag").length)&&/\s/.test(a)&&(a=a.replace(/[0-9]/g,"").trim()),t.offset().top<$(window).scrollTop()&&n.text(a);new IntersectionObserver(e=>{var t,o;r?(o=$(window).scrollTop(),t=l<o,l=o,o=e[0],t?0===o.intersectionRatio&&n.text(a):1===o.intersectionRatio&&n.text(s)):r=!0},{rootMargin:"-48px 0px 0px 0px",threshold:[0,1]}).observe(document.querySelector(e)),n.on("click",function(){$("body,html").animate({scrollTop:0},800)})}}),function(){const e=$("html").attr("lang").substring(0,2),t="data-ts",o="data-df";return{locale:()=>e,attrTimestamp:()=>t,attrDateFormat:()=>o,getTimestamp:e=>Number(e.attr(t)),getDateFormat:e=>e.attr(o)}}());$(function(){dayjs.locale(LocaleHelper.locale()),dayjs.extend(window.dayjs_plugin_localizedFormat),$(`[${LocaleHelper.attrTimestamp()}]`).each(function(){var e=dayjs.unix(LocaleHelper.getTimestamp($(this))),t=e.format(LocaleHelper.getDateFormat($(this))),t=($(this).text(t),$(this).removeAttr(LocaleHelper.attrTimestamp()),$(this).removeAttr(LocaleHelper.attrDateFormat()),$(this).attr("data-toggle"));void 0!==t&&"tooltip"===t&&(t=e.format("llll"),$(this).attr("data-original-title",t))})});

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,6 +0,0 @@
/*!
* Chirpy v5.5.2 (https://github.com/cotes2020/jekyll-theme-chirpy/)
* © 2019 Cotes Chung
* MIT Licensed
*/
const getInitStatus=function(){let t=!1;return()=>{var e=t;return t=t||!0,e}}(),PvOpts=function(){function t(e){return $(e).attr("content")}function e(e){e=t(e);return void 0!==e&&!1!==e}return{getProxyMeta(){return t("meta[name=pv-proxy-endpoint]")},getLocalMeta(){return t("meta[name=pv-cache-path]")},hasProxyMeta(){return e("meta[name=pv-proxy-endpoint]")},hasLocalMeta(){return e("meta[name=pv-cache-path]")}}}(),PvStorage=function(){const a={KEY_PV:"pv",KEY_PV_SRC:"pv_src",KEY_CREATION:"pv_created_date"},t={LOCAL:"same-origin",PROXY:"cors"};function r(e){return localStorage.getItem(e)}function o(e,t){localStorage.setItem(e,t)}function n(e,t){o(a.KEY_PV,e),o(a.KEY_PV_SRC,t),o(a.KEY_CREATION,(new Date).toJSON())}return{keysCount(){return Object.keys(a).length},hasCache(){return null!==localStorage.getItem(a.KEY_PV)},getCache(){return JSON.parse(localStorage.getItem(a.KEY_PV))},saveLocalCache(e){n(e,t.LOCAL)},saveProxyCache(e){n(e,t.PROXY)},isExpired(){var e=new Date(r(a.KEY_CREATION));return e.setHours(e.getHours()+1),Date.now()>=e.getTime()},isFromLocal(){return r(a.KEY_PV_SRC)===t.LOCAL},isFromProxy(){return r(a.KEY_PV_SRC)===t.PROXY},newerThan(e){return PvStorage.getCache().totalsForAllResults["ga:pageviews"]>e.totalsForAllResults["ga:pageviews"]},inspectKeys(){if(localStorage.length!==PvStorage.keysCount())localStorage.clear();else for(let e=0;e<localStorage.length;e++)switch(localStorage.key(e)){case a.KEY_PV:case a.KEY_PV_SRC:case a.KEY_CREATION:break;default:return void localStorage.clear()}}}}();function countUp(e,t,a){e<t&&((a=new CountUp(a,e,t)).error?console.error(a.error):a.start())}function countPV(t,a){let r=0;if(void 0!==a)for(let e=0;e<a.length;++e)if(a[parseInt(e,10)][0]===t){r+=parseInt(a[parseInt(e,10)][1],10);break}return r}function tacklePV(e,t,a,r){t=0===(t=countPV(t,e))?1:t;r?(e=parseInt(a.text().replace(/,/g,""),10))<t&&countUp(e,t,a.attr("id")):a.text((new Intl.NumberFormat).format(t))}function displayPageviews(e){if(void 0!==e){let t=getInitStatus();const a=e.rows;0<$("#post-list").length?$(".post-preview").each(function(){var e=$(this).find("a").attr("href");tacklePV(a,e,$(this).find(".pageviews"),t)}):0<$(".post").length&&(e=window.location.pathname,tacklePV(a,e,$("#pv"),t))}}function fetchProxyPageviews(){PvOpts.hasProxyMeta()&&$.ajax({type:"GET",url:PvOpts.getProxyMeta(),dataType:"jsonp",jsonpCallback:"displayPageviews",success:e=>{PvStorage.saveProxyCache(JSON.stringify(e))},error:(e,t,a)=>{console.log("Failed to load pageviews from proxy server: "+a)}})}function fetchLocalPageviews(t=!1){return fetch(PvOpts.getLocalMeta()).then(e=>e.json()).then(e=>{t&&PvStorage.isFromProxy()&&PvStorage.newerThan(e)||(displayPageviews(e),PvStorage.saveLocalCache(JSON.stringify(e)))})}$(function(){$(".pageviews").length<=0||(PvStorage.inspectKeys(),PvStorage.hasCache()?(displayPageviews(PvStorage.getCache()),PvStorage.isExpired()?PvOpts.hasLocalMeta()?fetchLocalPageviews(!0).then(fetchProxyPageviews):fetchProxyPageviews():PvStorage.isFromLocal()&&fetchProxyPageviews()):PvOpts.hasLocalMeta()?fetchLocalPageviews().then(fetchProxyPageviews):fetchProxyPageviews())});

View File

@ -1,10 +0,0 @@
#!/usr/bin/env node
"use strict";
const js = require('./tasks/js');
exports.default = js.build;
/* keep-alive develop mode, without uglify */
exports.dev = js.liveRebuild;

View File

@ -1,101 +0,0 @@
#!/usr/bin/env node
"use strict";
const { src, dest, watch, series, parallel} = require('gulp');
const concat = require('gulp-concat');
const rename = require("gulp-rename");
const uglify = require('gulp-uglify');
const insert = require('gulp-insert');
const fs = require('fs');
const JS_SRC = '_javascript';
const JS_DEST = `assets/js/dist`;
function concatJs(files, output) {
return src(files)
.pipe(concat(output))
.pipe(rename({ extname: '.min.js' }))
.pipe(dest(JS_DEST));
}
function minifyJs() {
return src(`${ JS_DEST }/*.js`)
.pipe(insert.prepend(fs.readFileSync(`${ JS_SRC }/copyright`, 'utf8')))
.pipe(uglify({output: {comments: /^!|@preserve|@license|@cc_on/i}}))
.pipe(insert.append('\n'))
.pipe(dest(JS_DEST));
}
const commonsJs = () => {
return concatJs(`${JS_SRC}/commons/*.js`, 'commons');
};
const homeJs = () => {
return concatJs([
`${JS_SRC}/commons/*.js`,
`${JS_SRC}/utils/locale-datetime.js`
],
'home'
);
};
const postJs = () => {
return concatJs([
`${JS_SRC}/commons/*.js`,
`${JS_SRC}/utils/img-extra.js`,
`${JS_SRC}/utils/locale-datetime.js`,
`${JS_SRC}/utils/clipboard.js`,
// 'smooth-scroll.js' must be called after ToC is ready
`${JS_SRC}/utils/smooth-scroll.js`
], 'post'
);
};
const categoriesJs = () => {
return concatJs([
`${JS_SRC}/commons/*.js`,
`${JS_SRC}/utils/category-collapse.js`
], 'categories'
);
};
const pageJs = () => {
return concatJs([
`${JS_SRC}/commons/*.js`,
`${JS_SRC}/utils/img-extra.js`,
`${JS_SRC}/utils/clipboard.js`,
`${JS_SRC}/utils/smooth-scroll.js`
], 'page'
);
};
const miscJs = () => {
return concatJs([
`${JS_SRC}/commons/*.js`,
`${JS_SRC}/utils/locale-datetime.js`
], 'misc'
);
};
// GA pageviews report
const pvreportJs = () => {
return concatJs(`${JS_SRC}/utils/pageviews.js`, 'pvreport');
};
const buildJs = parallel(
commonsJs, homeJs, postJs, categoriesJs, pageJs, miscJs, pvreportJs);
exports.build = series(buildJs, minifyJs);
exports.liveRebuild = () => {
buildJs();
watch([
`${ JS_SRC }/commons/*.js`,
`${ JS_SRC }/utils/*.js`
],
buildJs
);
};

View File

@ -11,19 +11,25 @@
"bugs": {
"url": "https://github.com/cotes2020/jekyll-theme-chirpy/issues"
},
"homepage": "https://github.com/cotes2020/jekyll-theme-chirpy#readme",
"homepage": "https://github.com/cotes2020/jekyll-theme-chirpy/",
"scripts": {
"prebuild": "npx rimraf assets/js/dist",
"build": "NODE_ENV=production npx rollup -c --bundleConfigAsCjs",
"prewatch": "npx rimraf assets/js/dist",
"watch": "npx rollup -c --bundleConfigAsCjs -w",
"test": "npx stylelint _sass/**/*.scss",
"fixlint": "npx stylelint _sass/**/*.scss --fix"
"fixlint": "npm run test -- --fix"
},
"devDependencies": {
"gulp": "^4.0.2",
"gulp-concat": "^2.6.1",
"gulp-insert": "^0.5.0",
"gulp-rename": "^2.0.0",
"gulp-uglify": "^3.0.2",
"@babel/core": "^7.21.0",
"@babel/plugin-proposal-class-properties": "^7.18.6",
"@babel/preset-env": "^7.20.2",
"@rollup/plugin-babel": "^6.0.3",
"@rollup/plugin-terser": "^0.4.0",
"rimraf": "^4.4.0",
"rollup": "^3.19.1",
"rollup-plugin-license": "^3.0.1",
"stylelint": "^15.2.0",
"stylelint-config-standard-scss": "^7.0.1",
"uglify-js": "^3.17.4"
"stylelint-config-standard-scss": "^7.0.1"
}
}

42
rollup.config.js Normal file
View File

@ -0,0 +1,42 @@
import babel from '@rollup/plugin-babel';
import terser from '@rollup/plugin-terser';
import license from 'rollup-plugin-license';
import path from 'path';
const JS_SRC = '_javascript';
const JS_DIST = 'assets/js/dist';
const isProd = process.env.NODE_ENV === 'production';
function build(filename) {
return {
input: [`${JS_SRC}/${filename}.js`],
output: {
file: `${JS_DIST}/${filename}.min.js`,
format: 'iife',
name: 'Chirpy',
sourcemap: !isProd
},
plugins: [
babel({
babelHelpers: 'bundled',
presets: ['@babel/env'],
plugins: ['@babel/plugin-proposal-class-properties']
}),
license({
banner: {
commentStyle: 'ignored',
content: { file: path.join(__dirname, JS_SRC, '_copyright') }
}
}),
isProd && terser()
]
};
}
export default [
build('commons'),
build('categories'),
build('page'),
build('post'),
build('misc')
];