Merge branch 'feature/esm'
This commit is contained in:
commit
016399a6db
58 changed files with 1350 additions and 1297 deletions
24
.github/workflows/ci.yml
vendored
24
.github/workflows/ci.yml
vendored
|
@ -1,18 +1,18 @@
|
||||||
name: 'CI'
|
name: "CI"
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches-ignore:
|
branches-ignore:
|
||||||
- 'production'
|
- "production"
|
||||||
- 'docs'
|
- "docs"
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- '.github/**'
|
- ".github/**"
|
||||||
- '!.github/workflows/ci.yml'
|
- "!.github/workflows/ci.yml"
|
||||||
- '.gitignore'
|
- ".gitignore"
|
||||||
- 'README.md'
|
- "README.md"
|
||||||
- 'LICENSE'
|
- "LICENSE"
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
- '**'
|
- "**"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
@ -34,5 +34,11 @@ jobs:
|
||||||
ruby-version: ${{ matrix.ruby }}
|
ruby-version: ${{ matrix.ruby }}
|
||||||
bundler-cache: true
|
bundler-cache: true
|
||||||
|
|
||||||
|
- name: Setup Node
|
||||||
|
uses: actions/setup-node@v3
|
||||||
|
|
||||||
|
- name: Build Assets
|
||||||
|
run: npm i && npm run build
|
||||||
|
|
||||||
- name: Test Site
|
- name: Test Site
|
||||||
run: bash tools/test
|
run: bash tools/test
|
||||||
|
|
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -10,10 +10,13 @@ _site
|
||||||
# RubyGems
|
# RubyGems
|
||||||
*.gem
|
*.gem
|
||||||
|
|
||||||
# npm dependencies
|
# NPM dependencies
|
||||||
node_modules
|
node_modules
|
||||||
package-lock.json
|
package-lock.json
|
||||||
|
|
||||||
# IDE configurations
|
# IDE configurations
|
||||||
.idea
|
.idea
|
||||||
.vscode
|
.vscode
|
||||||
|
|
||||||
|
# Misc
|
||||||
|
assets/js/dist
|
||||||
|
|
19
_config.yml
19
_config.yml
|
@ -5,14 +5,13 @@ theme: jekyll-theme-chirpy
|
||||||
|
|
||||||
# Change the following value to '/PROJECT_NAME' ONLY IF your site type is GitHub Pages Project sites
|
# Change the following value to '/PROJECT_NAME' ONLY IF your site type is GitHub Pages Project sites
|
||||||
# and doesn't have a custom domain.
|
# and doesn't have a custom domain.
|
||||||
baseurl: ''
|
baseurl: ""
|
||||||
|
|
||||||
# The language of the webpage › http://www.lingoes.net/en/translator/langcode.htm
|
# 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,
|
# 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'.
|
# otherwise, the layout language will use the default value of 'en'.
|
||||||
lang: en
|
lang: en
|
||||||
|
|
||||||
|
|
||||||
# Change to your timezone › http://www.timezoneconverter.com/cgi-bin/findzone/findzone
|
# Change to your timezone › http://www.timezoneconverter.com/cgi-bin/findzone/findzone
|
||||||
timezone: Asia/Shanghai
|
timezone: Asia/Shanghai
|
||||||
|
|
||||||
|
@ -27,7 +26,7 @@ description: >- # used by seo meta and the atom feed
|
||||||
A minimal, responsive and feature-rich Jekyll theme for technical writing.
|
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'
|
# fill in the protocol & hostname for your site, e.g., 'https://username.github.io'
|
||||||
url: ''
|
url: ""
|
||||||
|
|
||||||
github:
|
github:
|
||||||
username: github_username # change to your github username
|
username: github_username # change to your github username
|
||||||
|
@ -78,10 +77,10 @@ theme_mode: # [light|dark]
|
||||||
# will be added to all image (site avatar & posts' images) paths starting with '/'
|
# will be added to all image (site avatar & posts' images) paths starting with '/'
|
||||||
#
|
#
|
||||||
# e.g. 'https://cdn.com'
|
# 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
|
# 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.
|
# boolean type, the global switch for ToC in posts.
|
||||||
toc: true
|
toc: true
|
||||||
|
@ -139,7 +138,7 @@ collections:
|
||||||
|
|
||||||
defaults:
|
defaults:
|
||||||
- scope:
|
- 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
|
type: posts
|
||||||
values:
|
values:
|
||||||
layout: post
|
layout: post
|
||||||
|
@ -153,7 +152,7 @@ defaults:
|
||||||
values:
|
values:
|
||||||
comments: false
|
comments: false
|
||||||
- scope:
|
- scope:
|
||||||
path: ''
|
path: ""
|
||||||
type: tabs # see `site.collections`
|
type: tabs # see `site.collections`
|
||||||
values:
|
values:
|
||||||
layout: page
|
layout: page
|
||||||
|
@ -180,13 +179,13 @@ compress_html:
|
||||||
envs: [development]
|
envs: [development]
|
||||||
|
|
||||||
exclude:
|
exclude:
|
||||||
- '*.gem'
|
- "*.gem"
|
||||||
- '*.gemspec'
|
- "*.gemspec"
|
||||||
- tools
|
- tools
|
||||||
- README.md
|
- README.md
|
||||||
- CHANGELOG.md
|
- CHANGELOG.md
|
||||||
- LICENSE
|
- LICENSE
|
||||||
- gulpfile.js
|
- rollup.config.js
|
||||||
- node_modules
|
- node_modules
|
||||||
- package*.json
|
- package*.json
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
<!--
|
<!-- JS selector for site. -->
|
||||||
JS selector for site.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<!-- layout specified -->
|
<!-- layout specified -->
|
||||||
|
|
||||||
|
@ -8,15 +6,16 @@
|
||||||
{% if site.google_analytics.pv.proxy_endpoint or site.google_analytics.pv.cache_path %}
|
{% if site.google_analytics.pv.proxy_endpoint or site.google_analytics.pv.cache_path %}
|
||||||
<!-- pv-report needs countup.js -->
|
<!-- pv-report needs countup.js -->
|
||||||
<script async src="{{ site.data.assets[origin].countup.js | relative_url }}"></script>
|
<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 %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if page.layout == 'post' or page.layout == 'page' %}
|
{% if page.layout == 'post' or page.layout == 'page' %}
|
||||||
<!-- image lazy-loading & popup & clipboard -->
|
<!-- image lazy-loading & popup & clipboard -->
|
||||||
{% assign _urls = site.data.assets[origin].magnific-popup.js
|
{% assign _urls = site.data.assets[origin]['magnific-popup'].js
|
||||||
| append: ',' | append: site.data.assets[origin].lazysizes.js
|
| append: ','
|
||||||
| append: ',' | append: site.data.assets[origin].clipboard.js
|
| append: site.data.assets[origin].lazysizes.js
|
||||||
|
| append: ','
|
||||||
|
| append: site.data.assets[origin].clipboard.js
|
||||||
%}
|
%}
|
||||||
|
|
||||||
{% include jsdelivr-combine.html urls=_urls %}
|
{% include jsdelivr-combine.html urls=_urls %}
|
||||||
|
@ -26,33 +25,31 @@
|
||||||
or page.layout == 'post'
|
or page.layout == 'post'
|
||||||
or page.layout == 'archives'
|
or page.layout == 'archives'
|
||||||
or page.layout == 'category'
|
or page.layout == 'category'
|
||||||
or page.layout == 'tag' %}
|
or page.layout == 'tag'
|
||||||
|
%}
|
||||||
{% assign locale = site.lang | split: '-' | first %}
|
{% assign locale = site.lang | split: '-' | first %}
|
||||||
|
|
||||||
{% assign _urls = site.data.assets[origin].dayjs.js.common
|
{% assign _urls = site.data.assets[origin].dayjs.js.common
|
||||||
| append: ',' | append: site.data.assets[origin].dayjs.js.locale
|
| append: ','
|
||||||
|
| append: site.data.assets[origin].dayjs.js.locale
|
||||||
| replace: ':LOCALE', locale
|
| replace: ':LOCALE', locale
|
||||||
| append: ',' | append: site.data.assets[origin].dayjs.js.relativeTime
|
| append: ','
|
||||||
| append: ',' | append: site.data.assets[origin].dayjs.js.localizedFormat
|
| append: site.data.assets[origin].dayjs.js.relativeTime
|
||||||
|
| append: ','
|
||||||
|
| append: site.data.assets[origin].dayjs.js.localizedFormat
|
||||||
%}
|
%}
|
||||||
|
|
||||||
{% include jsdelivr-combine.html urls=_urls %}
|
{% include jsdelivr-combine.html urls=_urls %}
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if page.layout == 'home'
|
{% case page.layout %}
|
||||||
or page.layout == 'categories'
|
{% when 'categories', 'post', 'page' %}
|
||||||
or page.layout == 'post'
|
|
||||||
or page.layout == 'page' %}
|
|
||||||
{% assign type = page.layout %}
|
{% assign type = page.layout %}
|
||||||
{% elsif page.layout == 'archives'
|
{% when 'home', 'archives', 'category', 'tag' %}
|
||||||
or page.layout == 'category'
|
{% assign type = 'misc' %}
|
||||||
or page.layout == 'tag' %}
|
{% else %}
|
||||||
{% assign type = "misc" %}
|
{% assign type = 'commons' %}
|
||||||
{% else %}
|
{% endcase %}
|
||||||
{% assign type = "commons" %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% capture script %}/assets/js/dist/{{ type }}.min.js{% endcapture %}
|
{% capture script %}/assets/js/dist/{{ type }}.min.js{% endcapture %}
|
||||||
<script defer src="{{ script | relative_url }}"></script>
|
<script defer src="{{ script | relative_url }}"></script>
|
||||||
|
@ -63,11 +60,13 @@
|
||||||
/* see: <https://docs.mathjax.org/en/latest/options/input/tex.html#tex-options> */
|
/* see: <https://docs.mathjax.org/en/latest/options/input/tex.html#tex-options> */
|
||||||
MathJax = {
|
MathJax = {
|
||||||
tex: {
|
tex: {
|
||||||
inlineMath: [ /* start/end delimiter pairs for in-line math */
|
/* start/end delimiter pairs for in-line math */
|
||||||
['$','$'],
|
inlineMath: [
|
||||||
['\\(','\\)']
|
['$', '$'],
|
||||||
|
['\\(', '\\)']
|
||||||
],
|
],
|
||||||
displayMath: [ /* start/end delimiter pairs for display math */
|
/* start/end delimiter pairs for display math */
|
||||||
|
displayMath: [
|
||||||
['$$', '$$'],
|
['$$', '$$'],
|
||||||
['\\[', '\\]']
|
['\\[', '\\]']
|
||||||
]
|
]
|
||||||
|
@ -75,8 +74,7 @@
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<script src="{{ site.data.assets[origin].polyfill.js | relative_url }}"></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 id="MathJax-script" async src="{{ site.data.assets[origin].mathjax.js | relative_url }}"></script>
|
||||||
</script>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<!-- commons -->
|
<!-- commons -->
|
||||||
|
@ -95,5 +93,4 @@
|
||||||
{% if site.google_analytics.id != empty and site.google_analytics.id %}
|
{% if site.google_analytics.id != empty and site.google_analytics.id %}
|
||||||
{% include google-analytics.html %}
|
{% include google-analytics.html %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
@ -26,13 +26,12 @@
|
||||||
let self = this;
|
let self = this;
|
||||||
|
|
||||||
/* always follow the system prefers */
|
/* always follow the system prefers */
|
||||||
this.sysDarkPrefers.addEventListener("change", () => {
|
this.sysDarkPrefers.addEventListener('change', () => {
|
||||||
if (self.hasMode) {
|
if (self.hasMode) {
|
||||||
if (self.isDarkMode) {
|
if (self.isDarkMode) {
|
||||||
if (!self.isSysDarkPrefer) {
|
if (!self.isSysDarkPrefer) {
|
||||||
self.setDark();
|
self.setDark();
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if (self.isSysDarkPrefer) {
|
if (self.isSysDarkPrefer) {
|
||||||
self.setLight();
|
self.setLight();
|
||||||
|
@ -43,9 +42,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
self.notify();
|
self.notify();
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
} /* constructor() */
|
} /* constructor() */
|
||||||
|
|
||||||
get sysDarkPrefers() { return window.matchMedia("(prefers-color-scheme: dark)"); }
|
get sysDarkPrefers() { return window.matchMedia("(prefers-color-scheme: dark)"); }
|
||||||
|
@ -62,8 +59,7 @@
|
||||||
|
|
||||||
/* get the current mode on screen */
|
/* get the current mode on screen */
|
||||||
get modeStatus() {
|
get modeStatus() {
|
||||||
if (this.isDarkMode
|
if (this.isDarkMode || (!this.hasMode && this.isSysDarkPrefer)) {
|
||||||
|| (!this.hasMode && this.isSysDarkPrefer)) {
|
|
||||||
return ModeToggle.DARK_MODE;
|
return ModeToggle.DARK_MODE;
|
||||||
} else {
|
} else {
|
||||||
return ModeToggle.LIGHT_MODE;
|
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 */
|
} /* ModeToggle */
|
||||||
|
|
||||||
const toggle = new ModeToggle();
|
const modeToggle = 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() */
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
3
_javascript/_copyright
Normal file
3
_javascript/_copyright
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
Chirpy v<%= pkg.version %> (<%= pkg.homepage %>)
|
||||||
|
© 2019 <%= pkg.author %>
|
||||||
|
<%= pkg.license %> Licensed
|
7
_javascript/categories.js
Normal file
7
_javascript/categories.js
Normal 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
5
_javascript/commons.js
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
import { basic, initSidebar, initTopbar } from './modules/layouts';
|
||||||
|
|
||||||
|
basic();
|
||||||
|
initSidebar();
|
||||||
|
initTopbar();
|
|
@ -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;
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -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();
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -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
|
|
||||||
};
|
|
||||||
|
|
||||||
}());
|
|
|
@ -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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
|
@ -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);
|
|
||||||
});
|
|
|
@ -1,6 +0,0 @@
|
||||||
/**
|
|
||||||
* Initial Bootstrap Tooltip.
|
|
||||||
*/
|
|
||||||
$(function () {
|
|
||||||
$("[data-toggle=\"tooltip\"]").tooltip();
|
|
||||||
});
|
|
|
@ -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);
|
|
||||||
});
|
|
|
@ -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);
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
|
@ -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
7
_javascript/misc.js
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import { basic, initSidebar, initTopbar } from './modules/layouts';
|
||||||
|
import { initLocaleDatetime } from './modules/plugins';
|
||||||
|
|
||||||
|
basic();
|
||||||
|
initSidebar();
|
||||||
|
initTopbar();
|
||||||
|
initLocaleDatetime();
|
26
_javascript/modules/components/back-to-top.js
Normal file
26
_javascript/modules/components/back-to-top.js
Normal 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;
|
||||||
|
});
|
||||||
|
}
|
36
_javascript/modules/components/category-collapse.js
Normal file
36
_javascript/modules/components/category-collapse.js
Normal 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');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
118
_javascript/modules/components/clipboard.js
Normal file
118
_javascript/modules/components/clipboard.js
Normal 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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
68
_javascript/modules/components/convert-title.js
Normal file
68
_javascript/modules/components/convert-title.js
Normal 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);
|
||||||
|
});
|
||||||
|
}
|
27
_javascript/modules/components/img-extra.js
Normal file
27
_javascript/modules/components/img-extra.js
Normal 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');
|
||||||
|
});
|
||||||
|
}
|
50
_javascript/modules/components/locale-datetime.js
Normal file
50
_javascript/modules/components/locale-datetime.js
Normal 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);
|
||||||
|
});
|
||||||
|
}
|
21
_javascript/modules/components/mode-watcher.js
Normal file
21
_javascript/modules/components/mode-watcher.js
Normal 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
|
||||||
|
});
|
||||||
|
}
|
254
_javascript/modules/components/pageviews.js
Normal file
254
_javascript/modules/components/pageviews.js
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
122
_javascript/modules/components/search-display.js
Normal file
122
_javascript/modules/components/search-display.js
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
25
_javascript/modules/components/sidebar.js
Normal file
25
_javascript/modules/components/sidebar.js
Normal 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);
|
||||||
|
}
|
109
_javascript/modules/components/smooth-scroll.js
Normal file
109
_javascript/modules/components/smooth-scroll.js
Normal 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() */
|
||||||
|
}
|
6
_javascript/modules/components/tooltip-loader.js
Normal file
6
_javascript/modules/components/tooltip-loader.js
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
/**
|
||||||
|
* Initial Bootstrap Tooltip.
|
||||||
|
*/
|
||||||
|
export function loadTooptip() {
|
||||||
|
$('[data-toggle="tooltip"]').tooltip();
|
||||||
|
}
|
93
_javascript/modules/components/topbar-switcher.js
Normal file
93
_javascript/modules/components/topbar-switcher.js
Normal 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);
|
||||||
|
}
|
64
_javascript/modules/components/utils/scroll-helper.js
Normal file
64
_javascript/modules/components/utils/scroll-helper.js
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
3
_javascript/modules/layouts.js
Normal file
3
_javascript/modules/layouts.js
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export { basic } from './layouts/basic';
|
||||||
|
export { initSidebar } from './layouts/sidebar';
|
||||||
|
export { initTopbar } from './layouts/topbar';
|
7
_javascript/modules/layouts/basic.js
Normal file
7
_javascript/modules/layouts/basic.js
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import { back2top } from '../components/back-to-top';
|
||||||
|
import { loadTooptip } from '../components/tooltip-loader';
|
||||||
|
|
||||||
|
export function basic() {
|
||||||
|
back2top();
|
||||||
|
loadTooptip();
|
||||||
|
}
|
7
_javascript/modules/layouts/sidebar.js
Normal file
7
_javascript/modules/layouts/sidebar.js
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import { modeWatcher } from '../components/mode-watcher';
|
||||||
|
import { sidebarExpand } from '../components/sidebar';
|
||||||
|
|
||||||
|
export function initSidebar() {
|
||||||
|
modeWatcher();
|
||||||
|
sidebarExpand();
|
||||||
|
}
|
9
_javascript/modules/layouts/topbar.js
Normal file
9
_javascript/modules/layouts/topbar.js
Normal 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();
|
||||||
|
}
|
6
_javascript/modules/plugins.js
Normal file
6
_javascript/modules/plugins.js
Normal 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
9
_javascript/page.js
Normal 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
17
_javascript/post.js
Normal 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();
|
|
@ -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");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
|
@ -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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -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');
|
|
||||||
});
|
|
||||||
|
|
||||||
})();
|
|
|
@ -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);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
|
@ -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() */
|
|
||||||
});
|
|
6
assets/js/dist/categories.min.js
vendored
6
assets/js/dist/categories.min.js
vendored
|
@ -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"))})});
|
|
6
assets/js/dist/commons.min.js
vendored
6
assets/js/dist/commons.min.js
vendored
|
@ -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)})}});
|
|
6
assets/js/dist/home.min.js
vendored
6
assets/js/dist/home.min.js
vendored
|
@ -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))})});
|
|
6
assets/js/dist/misc.min.js
vendored
6
assets/js/dist/misc.min.js
vendored
|
@ -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))})});
|
|
6
assets/js/dist/page.min.js
vendored
6
assets/js/dist/page.min.js
vendored
File diff suppressed because one or more lines are too long
6
assets/js/dist/post.min.js
vendored
6
assets/js/dist/post.min.js
vendored
File diff suppressed because one or more lines are too long
6
assets/js/dist/pvreport.min.js
vendored
6
assets/js/dist/pvreport.min.js
vendored
|
@ -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())});
|
|
|
@ -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;
|
|
|
@ -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
|
|
||||||
);
|
|
||||||
};
|
|
24
package.json
24
package.json
|
@ -11,19 +11,25 @@
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/cotes2020/jekyll-theme-chirpy/issues"
|
"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": {
|
"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",
|
"test": "npx stylelint _sass/**/*.scss",
|
||||||
"fixlint": "npx stylelint _sass/**/*.scss --fix"
|
"fixlint": "npm run test -- --fix"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"gulp": "^4.0.2",
|
"@babel/core": "^7.21.0",
|
||||||
"gulp-concat": "^2.6.1",
|
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
||||||
"gulp-insert": "^0.5.0",
|
"@babel/preset-env": "^7.20.2",
|
||||||
"gulp-rename": "^2.0.0",
|
"@rollup/plugin-babel": "^6.0.3",
|
||||||
"gulp-uglify": "^3.0.2",
|
"@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": "^15.2.0",
|
||||||
"stylelint-config-standard-scss": "^7.0.1",
|
"stylelint-config-standard-scss": "^7.0.1"
|
||||||
"uglify-js": "^3.17.4"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
42
rollup.config.js
Normal file
42
rollup.config.js
Normal 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')
|
||||||
|
];
|
66
tools/init
66
tools/init
|
@ -4,9 +4,15 @@
|
||||||
|
|
||||||
set -eu
|
set -eu
|
||||||
|
|
||||||
|
# CLI Dependencies
|
||||||
|
CLI=("git" "npm")
|
||||||
|
|
||||||
ACTIONS_WORKFLOW=pages-deploy.yml
|
ACTIONS_WORKFLOW=pages-deploy.yml
|
||||||
|
|
||||||
TEMP_SUFFIX="to-delete" # temporary file suffixes that make `sed -i` compatible with BSD and Linux
|
# temporary file suffixes that make `sed -i` compatible with BSD and Linux
|
||||||
|
TEMP_SUFFIX="to-delete"
|
||||||
|
|
||||||
|
_no_gh=false
|
||||||
|
|
||||||
help() {
|
help() {
|
||||||
echo "Usage:"
|
echo "Usage:"
|
||||||
|
@ -18,14 +24,32 @@ help() {
|
||||||
echo " -h, --help Print this help information."
|
echo " -h, --help Print this help information."
|
||||||
}
|
}
|
||||||
|
|
||||||
check_status() {
|
# BSD and GNU compatible sed
|
||||||
|
_sedi() {
|
||||||
|
regex=$1
|
||||||
|
file=$2
|
||||||
|
sed -i.$TEMP_SUFFIX "$regex" "$file"
|
||||||
|
rm -f "$file".$TEMP_SUFFIX
|
||||||
|
}
|
||||||
|
|
||||||
|
_check_cli() {
|
||||||
|
for i in "${!CLI[@]}"; do
|
||||||
|
cli="${CLI[$i]}"
|
||||||
|
if ! command -v "$cli" &>/dev/null; then
|
||||||
|
echo "Command '$cli' not found! Hint: you should install it."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
_check_status() {
|
||||||
if [[ -n $(git status . -s) ]]; then
|
if [[ -n $(git status . -s) ]]; then
|
||||||
echo "Error: Commit unstaged files first, and then run this tool again."
|
echo "Error: Commit unstaged files first, and then run this tool again."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
check_init() {
|
_check_init() {
|
||||||
local _has_inited=false
|
local _has_inited=false
|
||||||
|
|
||||||
if [[ ! -d .github ]]; then # using option `--no-gh`
|
if [[ ! -d .github ]]; then # using option `--no-gh`
|
||||||
|
@ -47,9 +71,10 @@ check_init() {
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
checkout_latest_tag() {
|
check_env() {
|
||||||
tag=$(git describe --tags "$(git rev-list --tags --max-count=1)")
|
_check_cli
|
||||||
git reset --hard "$tag"
|
_check_status
|
||||||
|
_check_init
|
||||||
}
|
}
|
||||||
|
|
||||||
init_files() {
|
init_files() {
|
||||||
|
@ -63,25 +88,30 @@ init_files() {
|
||||||
mv ./${ACTIONS_WORKFLOW}.hook .github/workflows/${ACTIONS_WORKFLOW}
|
mv ./${ACTIONS_WORKFLOW}.hook .github/workflows/${ACTIONS_WORKFLOW}
|
||||||
|
|
||||||
## Cleanup image settings in site config
|
## Cleanup image settings in site config
|
||||||
sed -i.$TEMP_SUFFIX "s/^img_cdn:.*/img_cdn:/;s/^avatar:.*/avatar:/" _config.yml
|
_sedi "s/^img_cdn:.*/img_cdn:/;s/^avatar:.*/avatar:/" _config.yml
|
||||||
rm -f _config.yml.$TEMP_SUFFIX
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# remove the other fies
|
# remove the other fies
|
||||||
rm -rf _posts/*
|
rm -rf _posts/*
|
||||||
|
|
||||||
# save changes
|
# build assest
|
||||||
git add -A
|
npm i && npm run build
|
||||||
git commit -m "chore: initialize the environment" -q
|
|
||||||
|
|
||||||
echo "[INFO] Initialization successful!"
|
# track the js output
|
||||||
|
_sedi "/^assets.*\/dist/d" .gitignore
|
||||||
}
|
}
|
||||||
|
|
||||||
check_status
|
commit() {
|
||||||
|
git add -A
|
||||||
|
git commit -m "chore: initialize the environment" -q
|
||||||
|
echo -e "\n[INFO] Initialization successful!\n"
|
||||||
|
}
|
||||||
|
|
||||||
check_init
|
main() {
|
||||||
|
check_env
|
||||||
_no_gh=false
|
init_files
|
||||||
|
commit
|
||||||
|
}
|
||||||
|
|
||||||
while (($#)); do
|
while (($#)); do
|
||||||
opt="$1"
|
opt="$1"
|
||||||
|
@ -102,6 +132,4 @@ while (($#)); do
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
checkout_latest_tag
|
main
|
||||||
|
|
||||||
init_files
|
|
||||||
|
|
|
@ -29,7 +29,6 @@ NODE_CONFIG="package.json"
|
||||||
|
|
||||||
FILES=(
|
FILES=(
|
||||||
"_sass/jekyll-theme-chirpy.scss"
|
"_sass/jekyll-theme-chirpy.scss"
|
||||||
"_javascript/copyright"
|
|
||||||
"$GEM_SPEC"
|
"$GEM_SPEC"
|
||||||
"$NODE_CONFIG"
|
"$NODE_CONFIG"
|
||||||
)
|
)
|
||||||
|
@ -69,17 +68,24 @@ _check_git() {
|
||||||
}
|
}
|
||||||
|
|
||||||
_check_src() {
|
_check_src() {
|
||||||
if [[ ! -f $1 && ! -d $1 ]]; then
|
for i in "${!FILES[@]}"; do
|
||||||
echo -e "Error: Missing file \"$1\"!\n"
|
_src="${FILES[$i]}"
|
||||||
|
if [[ ! -f $_src && ! -d $_src ]]; then
|
||||||
|
echo -e "Error: Missing file \"$_src\"!\n"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_check_command() {
|
_check_command() {
|
||||||
if ! command -v "$1" &>/dev/null; then
|
for i in "${!TOOLS[@]}"; do
|
||||||
echo "Command '$1' not found"
|
cli="${TOOLS[$i]}"
|
||||||
|
if ! command -v "$cli" &>/dev/null; then
|
||||||
|
echo "Command '$cli' not found!"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
_check_node_packages() {
|
_check_node_packages() {
|
||||||
|
@ -89,20 +95,13 @@ _check_node_packages() {
|
||||||
}
|
}
|
||||||
|
|
||||||
check() {
|
check() {
|
||||||
|
_check_command
|
||||||
_check_git
|
_check_git
|
||||||
|
_check_src
|
||||||
for i in "${!FILES[@]}"; do
|
|
||||||
_check_src "${FILES[$i]}"
|
|
||||||
done
|
|
||||||
|
|
||||||
for i in "${!TOOLS[@]}"; do
|
|
||||||
_check_command "${TOOLS[$i]}"
|
|
||||||
done
|
|
||||||
|
|
||||||
_check_node_packages
|
_check_node_packages
|
||||||
}
|
}
|
||||||
|
|
||||||
_bump_file() {
|
_bump_files() {
|
||||||
for i in "${!FILES[@]}"; do
|
for i in "${!FILES[@]}"; do
|
||||||
if [[ ${FILES[$i]} == "$NODE_CONFIG" ]]; then
|
if [[ ${FILES[$i]} == "$NODE_CONFIG" ]]; then
|
||||||
continue
|
continue
|
||||||
|
@ -111,7 +110,7 @@ _bump_file() {
|
||||||
sed -i "s/v[[:digit:]]\+\.[[:digit:]]\+\.[[:digit:]]\+/v$1/" "${FILES[$i]}"
|
sed -i "s/v[[:digit:]]\+\.[[:digit:]]\+\.[[:digit:]]\+/v$1/" "${FILES[$i]}"
|
||||||
done
|
done
|
||||||
|
|
||||||
npx gulp
|
npm run build
|
||||||
}
|
}
|
||||||
|
|
||||||
_bump_gemspec() {
|
_bump_gemspec() {
|
||||||
|
@ -127,7 +126,7 @@ _bump_gemspec() {
|
||||||
#
|
#
|
||||||
# 2. Create a commit to save the changes.
|
# 2. Create a commit to save the changes.
|
||||||
bump() {
|
bump() {
|
||||||
_bump_file "$1"
|
_bump_files "$1"
|
||||||
_bump_gemspec "$1"
|
_bump_gemspec "$1"
|
||||||
|
|
||||||
if [[ $opt_pre = false && -n $(git status . -s) ]]; then
|
if [[ $opt_pre = false && -n $(git status . -s) ]]; then
|
||||||
|
|
Loading…
Reference in a new issue