diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fcfc88a..b51c183 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,18 +1,18 @@ -name: 'CI' +name: "CI" on: push: branches-ignore: - - 'production' - - 'docs' + - "production" + - "docs" paths-ignore: - - '.github/**' - - '!.github/workflows/ci.yml' - - '.gitignore' - - 'README.md' - - 'LICENSE' + - ".github/**" + - "!.github/workflows/ci.yml" + - ".gitignore" + - "README.md" + - "LICENSE" pull_request: paths: - - '**' + - "**" jobs: build: @@ -26,7 +26,7 @@ jobs: - name: Checkout uses: actions/checkout@v3 with: - fetch-depth: 0 # for posts's lastmod + fetch-depth: 0 # for posts's lastmod - name: Setup Ruby uses: ruby/setup-ruby@v1 @@ -34,5 +34,11 @@ jobs: ruby-version: ${{ matrix.ruby }} bundler-cache: true + - name: Setup Node + uses: actions/setup-node@v3 + + - name: Build Assets + run: npm i && npm run build + - name: Test Site run: bash tools/test diff --git a/.gitignore b/.gitignore index 301b844..0124b68 100644 --- a/.gitignore +++ b/.gitignore @@ -10,10 +10,13 @@ _site # RubyGems *.gem -# npm dependencies +# NPM dependencies node_modules package-lock.json # IDE configurations .idea .vscode + +# Misc +assets/js/dist diff --git a/_config.yml b/_config.yml index df912c6..5d1483d 100644 --- a/_config.yml +++ b/_config.yml @@ -5,60 +5,59 @@ theme: jekyll-theme-chirpy # Change the following value to '/PROJECT_NAME' ONLY IF your site type is GitHub Pages Project sites # and doesn't have a custom domain. -baseurl: '' +baseurl: "" # The language of the webpage › http://www.lingoes.net/en/translator/langcode.htm # If it has the same name as one of the files in folder `_data/locales`, the layout language will also be changed, # otherwise, the layout language will use the default value of 'en'. lang: en - # Change to your timezone › http://www.timezoneconverter.com/cgi-bin/findzone/findzone timezone: Asia/Shanghai # jekyll-seo-tag settings › https://github.com/jekyll/jekyll-seo-tag/blob/master/docs/usage.md # ↓ -------------------------- -title: Chirpy # the main title +title: Chirpy # the main title -tagline: A text-focused Jekyll theme # it will display as the sub-title +tagline: A text-focused Jekyll theme # it will display as the sub-title -description: >- # used by seo meta and the atom feed +description: >- # used by seo meta and the atom feed A minimal, responsive and feature-rich Jekyll theme for technical writing. # fill in the protocol & hostname for your site, e.g., 'https://username.github.io' -url: '' +url: "" github: - username: github_username # change to your github username + username: github_username # change to your github username twitter: - username: twitter_username # change to your twitter username + username: twitter_username # change to your twitter username social: # Change to your full name. # It will be displayed as the default author of the posts and the copyright owner in the Footer name: your_full_name - email: example@domain.com # change to your email address + email: example@domain.com # change to your email address links: # The first element serves as the copyright owner's link - - https://twitter.com/username # change to your twitter homepage - - https://github.com/username # change to your github homepage + - https://twitter.com/username # change to your twitter homepage + - https://github.com/username # change to your github homepage # Uncomment below to add more social links # - https://www.facebook.com/username # - https://www.linkedin.com/in/username -google_site_verification: # fill in to your verification string +google_site_verification: # fill in to your verification string # ↑ -------------------------- # The end of `jekyll-seo-tag` settings google_analytics: - id: # fill in your Google Analytics ID + id: # fill in your Google Analytics ID # Google Analytics pageviews report settings pv: - proxy_endpoint: # fill in the Google Analytics superProxy endpoint of Google App Engine - cache_path: # the local PV cache data, friendly to visitors from GFW region + proxy_endpoint: # fill in the Google Analytics superProxy endpoint of Google App Engine + cache_path: # the local PV cache data, friendly to visitors from GFW region # Prefer color scheme setting. # @@ -71,51 +70,51 @@ google_analytics: # light - Use the light color scheme # dark - Use the dark color scheme # -theme_mode: # [light|dark] +theme_mode: # [light|dark] # The CDN endpoint for images. # Notice that once it is assigned, the CDN url # will be added to all image (site avatar & posts' images) paths starting with '/' # # e.g. 'https://cdn.com' -img_cdn: 'https://chirpy-img.netlify.app' +img_cdn: "https://chirpy-img.netlify.app" # the avatar on sidebar, support local or CORS resources -avatar: '/commons/avatar.jpg' +avatar: "/commons/avatar.jpg" # boolean type, the global switch for ToC in posts. toc: true comments: - active: # The global switch for posts comments, e.g., 'disqus'. Keep it empty means disable + active: # The global switch for posts comments, e.g., 'disqus'. Keep it empty means disable # The active options are as follows: disqus: - shortname: # fill with the Disqus shortname. › https://help.disqus.com/en/articles/1717111-what-s-a-shortname + shortname: # fill with the Disqus shortname. › https://help.disqus.com/en/articles/1717111-what-s-a-shortname # utterances settings › https://utteranc.es/ utterances: - repo: # / - issue_term: # < url | pathname | title | ...> + repo: # / + issue_term: # < url | pathname | title | ...> # Giscus options › https://giscus.app giscus: - repo: # / + repo: # / repo_id: category: category_id: - mapping: # optional, default to 'pathname' - input_position: # optional, default to 'bottom' - lang: # optional, default to the value of `site.lang` + mapping: # optional, default to 'pathname' + input_position: # optional, default to 'bottom' + lang: # optional, default to the value of `site.lang` reactions_enabled: # optional, default to the value of `1` # Self-hosted static assets, optional › https://github.com/cotes2020/chirpy-static-assets assets: self_host: - enabled: # boolean, keep empty means false + enabled: # boolean, keep empty means false # specify the Jekyll environment, empty means both # only works if `assets.self_host.enabled` is 'true' - env: # [development|production] + env: # [development|production] pwa: - enabled: true # the option for PWA feature + enabled: true # the option for PWA feature paginate: 10 @@ -123,7 +122,7 @@ paginate: 10 kramdown: syntax_highlighter: rouge - syntax_highlighter_opts: # Rouge Options › https://github.com/jneen/rouge#full-options + syntax_highlighter_opts: # Rouge Options › https://github.com/jneen/rouge#full-options css_class: highlight # default_lang: console span: @@ -139,12 +138,12 @@ collections: defaults: - scope: - path: '' # An empty string here means all files in the project + path: "" # An empty string here means all files in the project type: posts values: layout: post - comments: true # Enable comments in posts. - toc: true # Display TOC column in posts. + comments: true # Enable comments in posts. + toc: true # Display TOC column in posts. # DO NOT modify the following parameter unless you are confident enough # to update the code of all other post links in this project. permalink: /posts/:title/ @@ -153,8 +152,8 @@ defaults: values: comments: false - scope: - path: '' - type: tabs # see `site.collections` + path: "" + type: tabs # see `site.collections` values: layout: page permalink: /:title/ @@ -180,13 +179,13 @@ compress_html: envs: [development] exclude: - - '*.gem' - - '*.gemspec' + - "*.gem" + - "*.gemspec" - tools - README.md - CHANGELOG.md - LICENSE - - gulpfile.js + - rollup.config.js - node_modules - package*.json diff --git a/_includes/js-selector.html b/_includes/js-selector.html index c760968..05ed99e 100644 --- a/_includes/js-selector.html +++ b/_includes/js-selector.html @@ -1,6 +1,4 @@ - + @@ -8,51 +6,50 @@ {% if site.google_analytics.pv.proxy_endpoint or site.google_analytics.pv.cache_path %} - {% endif %} {% endif %} {% if page.layout == 'post' or page.layout == 'page' %} - {% assign _urls = site.data.assets[origin].magnific-popup.js - | append: ',' | append: site.data.assets[origin].lazysizes.js - | append: ',' | append: site.data.assets[origin].clipboard.js + {% assign _urls = site.data.assets[origin]['magnific-popup'].js + | append: ',' + | append: site.data.assets[origin].lazysizes.js + | append: ',' + | append: site.data.assets[origin].clipboard.js %} {% include jsdelivr-combine.html urls=_urls %} {% endif %} {% if page.layout == 'home' - or page.layout == 'post' - or page.layout == 'archives' - or page.layout == 'category' - or page.layout == 'tag' %} - + or page.layout == 'post' + or page.layout == 'archives' + or page.layout == 'category' + or page.layout == 'tag' +%} {% assign locale = site.lang | split: '-' | first %} {% assign _urls = site.data.assets[origin].dayjs.js.common - | append: ',' | append: site.data.assets[origin].dayjs.js.locale - | replace: ':LOCALE', locale - | append: ',' | append: site.data.assets[origin].dayjs.js.relativeTime - | append: ',' | append: site.data.assets[origin].dayjs.js.localizedFormat + | append: ',' + | append: site.data.assets[origin].dayjs.js.locale + | replace: ':LOCALE', locale + | append: ',' + | append: site.data.assets[origin].dayjs.js.relativeTime + | append: ',' + | append: site.data.assets[origin].dayjs.js.localizedFormat %} {% include jsdelivr-combine.html urls=_urls %} - {% endif %} -{% if page.layout == 'home' - or page.layout == 'categories' - or page.layout == 'post' - or page.layout == 'page' %} - {% assign type = page.layout %} -{% elsif page.layout == 'archives' - or page.layout == 'category' - or page.layout == 'tag' %} - {% assign type = "misc" %} -{% else %} - {% assign type = "commons" %} -{% endif %} +{% case page.layout %} + {% when 'categories', 'post', 'page' %} + {% assign type = page.layout %} + {% when 'home', 'archives', 'category', 'tag' %} + {% assign type = 'misc' %} + {% else %} + {% assign type = 'commons' %} +{% endcase %} {% capture script %}/assets/js/dist/{{ type }}.min.js{% endcapture %} @@ -60,23 +57,24 @@ {% if page.math %} - + {% endif %} @@ -95,5 +93,4 @@ {% if site.google_analytics.id != empty and site.google_analytics.id %} {% include google-analytics.html %} {% endif %} - {% endif %} diff --git a/_includes/mode-toggle.html b/_includes/mode-toggle.html index 63b2538..ada0061 100644 --- a/_includes/mode-toggle.html +++ b/_includes/mode-toggle.html @@ -26,13 +26,12 @@ let self = this; /* always follow the system prefers */ - this.sysDarkPrefers.addEventListener("change", () => { + this.sysDarkPrefers.addEventListener('change', () => { if (self.hasMode) { if (self.isDarkMode) { if (!self.isSysDarkPrefer) { self.setDark(); } - } else { if (self.isSysDarkPrefer) { self.setLight(); @@ -43,9 +42,7 @@ } self.notify(); - }); - } /* constructor() */ get sysDarkPrefers() { return window.matchMedia("(prefers-color-scheme: dark)"); } @@ -62,8 +59,7 @@ /* get the current mode on screen */ get modeStatus() { - if (this.isDarkMode - || (!this.hasMode && this.isSysDarkPrefer)) { + if (this.isDarkMode || (!this.hasMode && this.isSysDarkPrefer)) { return ModeToggle.DARK_MODE; } else { return ModeToggle.LIGHT_MODE; @@ -93,37 +89,32 @@ }, "*"); } + flipMode() { + if (this.hasMode) { + if (this.isSysDarkPrefer) { + if (this.isLightMode) { + this.clearMode(); + } else { + this.setLight(); + } + } else { + if (this.isDarkMode) { + this.clearMode(); + } else { + this.setDark(); + } + } + } else { + if (this.isSysDarkPrefer) { + this.setLight(); + } else { + this.setDark(); + } + } + + this.notify(); + } /* flipMode() */ } /* ModeToggle */ - const toggle = new ModeToggle(); - - function flipMode() { - if (toggle.hasMode) { - if (toggle.isSysDarkPrefer) { - if (toggle.isLightMode) { - toggle.clearMode(); - } else { - toggle.setLight(); - } - - } else { - if (toggle.isDarkMode) { - toggle.clearMode(); - } else { - toggle.setDark(); - } - } - - } else { - if (toggle.isSysDarkPrefer) { - toggle.setLight(); - } else { - toggle.setDark(); - } - } - - toggle.notify(); - - } /* flipMode() */ - + const modeToggle = new ModeToggle(); diff --git a/_javascript/_copyright b/_javascript/_copyright new file mode 100644 index 0000000..dedc8ed --- /dev/null +++ b/_javascript/_copyright @@ -0,0 +1,3 @@ +Chirpy v<%= pkg.version %> (<%= pkg.homepage %>) +© 2019 <%= pkg.author %> +<%= pkg.license %> Licensed diff --git a/_javascript/categories.js b/_javascript/categories.js new file mode 100644 index 0000000..15d8251 --- /dev/null +++ b/_javascript/categories.js @@ -0,0 +1,7 @@ +import { basic, initSidebar, initTopbar } from './modules/layouts'; +import { categoryCollapse } from './modules/plugins'; + +basic(); +initSidebar(); +initTopbar(); +categoryCollapse(); diff --git a/_javascript/commons.js b/_javascript/commons.js new file mode 100644 index 0000000..05a9765 --- /dev/null +++ b/_javascript/commons.js @@ -0,0 +1,5 @@ +import { basic, initSidebar, initTopbar } from './modules/layouts'; + +basic(); +initSidebar(); +initTopbar(); diff --git a/_javascript/commons/back-to-top.js b/_javascript/commons/back-to-top.js deleted file mode 100644 index e577e8f..0000000 --- a/_javascript/commons/back-to-top.js +++ /dev/null @@ -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; - }); -}); diff --git a/_javascript/commons/mode-toggle.js b/_javascript/commons/mode-toggle.js deleted file mode 100644 index a83bc58..0000000 --- a/_javascript/commons/mode-toggle.js +++ /dev/null @@ -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(); - }); -}); diff --git a/_javascript/commons/scroll-helper.js b/_javascript/commons/scroll-helper.js deleted file mode 100644 index 55c1b49..0000000 --- a/_javascript/commons/scroll-helper.js +++ /dev/null @@ -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 - }; - -}()); diff --git a/_javascript/commons/search-display.js b/_javascript/commons/search-display.js deleted file mode 100644 index 536cfbc..0000000 --- a/_javascript/commons/search-display.js +++ /dev/null @@ -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"); - } - } - }); - -}); diff --git a/_javascript/commons/sidebar.js b/_javascript/commons/sidebar.js deleted file mode 100644 index 66d20df..0000000 --- a/_javascript/commons/sidebar.js +++ /dev/null @@ -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); -}); diff --git a/_javascript/commons/tooltip-loader.js b/_javascript/commons/tooltip-loader.js deleted file mode 100644 index 0b2f0b1..0000000 --- a/_javascript/commons/tooltip-loader.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Initial Bootstrap Tooltip. - */ -$(function () { - $("[data-toggle=\"tooltip\"]").tooltip(); -}); diff --git a/_javascript/commons/topbar-switcher.js b/_javascript/commons/topbar-switcher.js deleted file mode 100644 index e4c2b97..0000000 --- a/_javascript/commons/topbar-switcher.js +++ /dev/null @@ -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); -}); diff --git a/_javascript/commons/topbar-title.js b/_javascript/commons/topbar-title.js deleted file mode 100644 index 42e3c2d..0000000 --- a/_javascript/commons/topbar-title.js +++ /dev/null @@ -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 " <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); - }); - -}); diff --git a/_javascript/copyright b/_javascript/copyright deleted file mode 100644 index 4775b32..0000000 --- a/_javascript/copyright +++ /dev/null @@ -1,5 +0,0 @@ -/*! - * Chirpy v5.5.2 (https://github.com/cotes2020/jekyll-theme-chirpy/) - * © 2019 Cotes Chung - * MIT Licensed - */ diff --git a/_javascript/misc.js b/_javascript/misc.js new file mode 100644 index 0000000..c7a19d6 --- /dev/null +++ b/_javascript/misc.js @@ -0,0 +1,7 @@ +import { basic, initSidebar, initTopbar } from './modules/layouts'; +import { initLocaleDatetime } from './modules/plugins'; + +basic(); +initSidebar(); +initTopbar(); +initLocaleDatetime(); diff --git a/_javascript/modules/components/back-to-top.js b/_javascript/modules/components/back-to-top.js new file mode 100644 index 0000000..2519a9f --- /dev/null +++ b/_javascript/modules/components/back-to-top.js @@ -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; + }); +} diff --git a/_javascript/modules/components/category-collapse.js b/_javascript/modules/components/category-collapse.js new file mode 100644 index 0000000..d6027a1 --- /dev/null +++ b/_javascript/modules/components/category-collapse.js @@ -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'); + } + }); +} diff --git a/_javascript/modules/components/clipboard.js b/_javascript/modules/components/clipboard.js new file mode 100644 index 0000000..cff2d09 --- /dev/null +++ b/_javascript/modules/components/clipboard.js @@ -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); + }); + }); +} diff --git a/_javascript/modules/components/convert-title.js b/_javascript/modules/components/convert-title.js new file mode 100644 index 0000000..649f7be --- /dev/null +++ b/_javascript/modules/components/convert-title.js @@ -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); + }); +} diff --git a/_javascript/modules/components/img-extra.js b/_javascript/modules/components/img-extra.js new file mode 100644 index 0000000..47b8404 --- /dev/null +++ b/_javascript/modules/components/img-extra.js @@ -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'); + }); +} diff --git a/_javascript/modules/components/locale-datetime.js b/_javascript/modules/components/locale-datetime.js new file mode 100644 index 0000000..7bab64b --- /dev/null +++ b/_javascript/modules/components/locale-datetime.js @@ -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); + }); +} diff --git a/_javascript/modules/components/mode-watcher.js b/_javascript/modules/components/mode-watcher.js new file mode 100644 index 0000000..7b2298a --- /dev/null +++ b/_javascript/modules/components/mode-watcher.js @@ -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 + }); +} diff --git a/_javascript/modules/components/pageviews.js b/_javascript/modules/components/pageviews.js new file mode 100644 index 0000000..99e72ce --- /dev/null +++ b/_javascript/modules/components/pageviews.js @@ -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(); + } + } +} diff --git a/_javascript/modules/components/search-display.js b/_javascript/modules/components/search-display.js new file mode 100644 index 0000000..2e38f5e --- /dev/null +++ b/_javascript/modules/components/search-display.js @@ -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); + } + } + }); +} diff --git a/_javascript/modules/components/sidebar.js b/_javascript/modules/components/sidebar.js new file mode 100644 index 0000000..9d8567e --- /dev/null +++ b/_javascript/modules/components/sidebar.js @@ -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); +} diff --git a/_javascript/modules/components/smooth-scroll.js b/_javascript/modules/components/smooth-scroll.js new file mode 100644 index 0000000..09f75d0 --- /dev/null +++ b/_javascript/modules/components/smooth-scroll.js @@ -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() */ +} diff --git a/_javascript/modules/components/tooltip-loader.js b/_javascript/modules/components/tooltip-loader.js new file mode 100644 index 0000000..809487a --- /dev/null +++ b/_javascript/modules/components/tooltip-loader.js @@ -0,0 +1,6 @@ +/** + * Initial Bootstrap Tooltip. + */ +export function loadTooptip() { + $('[data-toggle="tooltip"]').tooltip(); +} diff --git a/_javascript/modules/components/topbar-switcher.js b/_javascript/modules/components/topbar-switcher.js new file mode 100644 index 0000000..f3eebb7 --- /dev/null +++ b/_javascript/modules/components/topbar-switcher.js @@ -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); +} diff --git a/_javascript/modules/components/utils/scroll-helper.js b/_javascript/modules/components/utils/scroll-helper.js new file mode 100644 index 0000000..78ad9c8 --- /dev/null +++ b/_javascript/modules/components/utils/scroll-helper.js @@ -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; + } +} diff --git a/_javascript/modules/layouts.js b/_javascript/modules/layouts.js new file mode 100644 index 0000000..28f7962 --- /dev/null +++ b/_javascript/modules/layouts.js @@ -0,0 +1,3 @@ +export { basic } from './layouts/basic'; +export { initSidebar } from './layouts/sidebar'; +export { initTopbar } from './layouts/topbar'; diff --git a/_javascript/modules/layouts/basic.js b/_javascript/modules/layouts/basic.js new file mode 100644 index 0000000..fb36a8b --- /dev/null +++ b/_javascript/modules/layouts/basic.js @@ -0,0 +1,7 @@ +import { back2top } from '../components/back-to-top'; +import { loadTooptip } from '../components/tooltip-loader'; + +export function basic() { + back2top(); + loadTooptip(); +} diff --git a/_javascript/modules/layouts/sidebar.js b/_javascript/modules/layouts/sidebar.js new file mode 100644 index 0000000..8795693 --- /dev/null +++ b/_javascript/modules/layouts/sidebar.js @@ -0,0 +1,7 @@ +import { modeWatcher } from '../components/mode-watcher'; +import { sidebarExpand } from '../components/sidebar'; + +export function initSidebar() { + modeWatcher(); + sidebarExpand(); +} diff --git a/_javascript/modules/layouts/topbar.js b/_javascript/modules/layouts/topbar.js new file mode 100644 index 0000000..76549bf --- /dev/null +++ b/_javascript/modules/layouts/topbar.js @@ -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(); +} diff --git a/_javascript/modules/plugins.js b/_javascript/modules/plugins.js new file mode 100644 index 0000000..48ada6f --- /dev/null +++ b/_javascript/modules/plugins.js @@ -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'; diff --git a/_javascript/page.js b/_javascript/page.js new file mode 100644 index 0000000..0d497f0 --- /dev/null +++ b/_javascript/page.js @@ -0,0 +1,9 @@ +import { basic, initSidebar, initTopbar } from './modules/layouts'; +import { imgExtra, initClipboard, smoothScroll } from './modules/plugins'; + +basic(); +initSidebar(); +initTopbar(); +imgExtra(); +initClipboard(); +smoothScroll(); diff --git a/_javascript/post.js b/_javascript/post.js new file mode 100644 index 0000000..2f8cc5c --- /dev/null +++ b/_javascript/post.js @@ -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(); diff --git a/_javascript/utils/category-collapse.js b/_javascript/utils/category-collapse.js deleted file mode 100644 index 965bcfd..0000000 --- a/_javascript/utils/category-collapse.js +++ /dev/null @@ -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"); - } - }); - -}); diff --git a/_javascript/utils/clipboard.js b/_javascript/utils/clipboard.js deleted file mode 100644 index 73f33fd..0000000 --- a/_javascript/utils/clipboard.js +++ /dev/null @@ -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); - }); - }); -}); diff --git a/_javascript/utils/img-extra.js b/_javascript/utils/img-extra.js deleted file mode 100644 index 90a3f49..0000000 --- a/_javascript/utils/img-extra.js +++ /dev/null @@ -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'); - }); - -})(); diff --git a/_javascript/utils/locale-datetime.js b/_javascript/utils/locale-datetime.js deleted file mode 100644 index bfd3a66..0000000 --- a/_javascript/utils/locale-datetime.js +++ /dev/null @@ -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); - }); -}); diff --git a/_javascript/utils/pageviews.js b/_javascript/utils/pageviews.js deleted file mode 100644 index 1e875d8..0000000 --- a/_javascript/utils/pageviews.js +++ /dev/null @@ -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(); - } - } - -}); diff --git a/_javascript/utils/smooth-scroll.js b/_javascript/utils/smooth-scroll.js deleted file mode 100644 index bd45b67..0000000 --- a/_javascript/utils/smooth-scroll.js +++ /dev/null @@ -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() */ -}); diff --git a/assets/js/dist/categories.min.js b/assets/js/dist/categories.min.js deleted file mode 100644 index db5a001..0000000 --- a/assets/js/dist/categories.min.js +++ /dev/null @@ -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"))})}); diff --git a/assets/js/dist/commons.min.js b/assets/js/dist/commons.min.js deleted file mode 100644 index e3bc18e..0000000 --- a/assets/js/dist/commons.min.js +++ /dev/null @@ -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)})}}); diff --git a/assets/js/dist/home.min.js b/assets/js/dist/home.min.js deleted file mode 100644 index 3190dd8..0000000 --- a/assets/js/dist/home.min.js +++ /dev/null @@ -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))})}); diff --git a/assets/js/dist/misc.min.js b/assets/js/dist/misc.min.js deleted file mode 100644 index 3190dd8..0000000 --- a/assets/js/dist/misc.min.js +++ /dev/null @@ -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))})}); diff --git a/assets/js/dist/page.min.js b/assets/js/dist/page.min.js deleted file mode 100644 index 7eae8c8..0000000 --- a/assets/js/dist/page.min.js +++ /dev/null @@ -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 r=0,a=!1,l=!1;return{hideTopbar:()=>e.attr(t,"false"),showTopbar:()=>e.attr(t,"true"),addScrollUpTask:()=>{r+=1,a=a||!0},popScrollUpTask:()=>--r,hasScrollUpTask:()=>0<r,topbarLocked:()=>!0===a,unlockTopbar:()=>a=!1,getTopbarHeight:()=>o,orientationLocked:()=>!0===l,lockOrientation:()=>l=!0,unLockOrientation:()=>l=!1}}();$(function(){const e=$("#sidebar-trigger"),t=$("#search-trigger"),o=$("#search-cancel"),r=$("#main"),a=$("#topbar-title"),l=$("#search-wrapper"),n=$("#search-result-wrapper"),i=$("#search-results"),s=$("#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"),a.addClass("unloaded"),t.addClass("unloaded"),l.addClass("d-flex"),o.addClass("loaded")},off(){o.removeClass("loaded"),l.removeClass("d-flex"),e.removeClass("unloaded"),a.removeClass("unloaded"),t.removeClass("unloaded")}},u=function(){let e=!1;return{on(){e||(d.block(),n.removeClass("unloaded"),r.addClass("unloaded"),e=!0)},off(){e&&(i.empty(),c.hasClass("unloaded")&&c.removeClass("unloaded"),n.addClass("unloaded"),r.removeClass("unloaded"),d.release(),s.val(""),e=!1)}}}();function f(){return o.hasClass("loaded")}t.on("click",function(){p.on(),u.on(),s.trigger("focus")}),o.on("click",function(){p.off(),u.off()}),s.on("focus",function(){l.addClass("input-focus")}),s.on("focusout",function(){l.removeClass("input-focus")}),s.on("input",()=>{""===s.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,r=0;function a(){0!==$(window).scrollTop()&&(ScrollHelper.lockOrientation(),ScrollHelper.hideTopbar())}screen.orientation?screen.orientation.onchange=()=>{var e=screen.orientation.type;"landscape-primary"!==e&&"landscape-secondary"!==e||a()}:$(window).on("orientationchange",()=>{$(window).width()<$(window).height()&&a()}),$(window).on("scroll",()=>{e=e||!0}),setInterval(()=>{e&&(!function(){var e=$(this).scrollTop();if(!(Math.abs(r-e)<=o)){if(e>r)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()}r=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 i=n.text().trim();let r=t.text().trim(),a=!1,l=0;($("#page-category").length||$("#page-tag").length)&&/\s/.test(r)&&(r=r.replace(/[0-9]/g,"").trim()),t.offset().top<$(window).scrollTop()&&n.text(r);new IntersectionObserver(e=>{var t,o;a?(o=$(window).scrollTop(),t=l<o,l=o,o=e[0],t?0===o.intersectionRatio&&n.text(r):1===o.intersectionRatio&&n.text(i)):a=!0},{rootMargin:"-48px 0px 0px 0px",threshold:[0,1]}).observe(document.querySelector(e)),n.on("click",function(){$("body,html").animate({scrollTop:0},800)})}}),$("#core-wrapper img[data-src]")<=0||($(".popup").magnificPopup({type:"image",closeOnContentClick:!0,showCloseBtn:!1,zoom:{enabled:!0,duration:300,easing:"ease-in-out"}}),document.addEventListener("lazyloaded",function(e){$(e.target).parent().removeClass("shimmer")})),$(function(){var e=".code-header>button";const t="timeout",r="data-title-succeed",a="data-original-title";function l(e){if($(e)[0].hasAttribute(t)){e=$(e).attr(t);if(Number(e)>Date.now())return 1}}function n(e){$(e).attr(t,Date.now()+2e3)}function i(e){$(e).removeAttr(t)}var o=new ClipboardJS(e,{target(e){return e.parentNode.nextElementSibling.querySelector("code .rouge-code")}});$(e).tooltip({trigger:"hover",placement:"left"});const s=$(e).children().attr("class");o.on("success",e=>{e.clearSelection();const t=e.trigger;var o;l(t)||(e=t,$(e).children().attr("class","fas fa-check"),e=t,o=$(e).attr(r),$(e).attr(a,o).tooltip("show"),n(t),setTimeout(()=>{var e;e=t,$(e).tooltip("hide").removeAttr(a),e=t,$(e).children().attr("class",s),i(t)},2e3))}),$("#copy-link").on("click",e=>{let o=$(e.target);l(o)||navigator.clipboard.writeText(window.location.href).then(()=>{const e=o.attr(a);var t=o.attr(r);o.attr(a,t).tooltip("show"),n(o),setTimeout(()=>{o.attr(a,e),i(o)},2e3)})})}),$(function(){const e=$("#topbar-title"),s="scroll-focus";$("a[href*='#']").not("[href='#']").not("[href='#0']").on("click",function(a){if(this.pathname.replace(/^\//,"")===location.pathname.replace(/^\//,"")&&location.hostname===this.hostname){var l=decodeURI(this.hash);let t=RegExp(/^#fnref:/).test(l),o=!t&&RegExp(/^#fn:/).test(l);var n="#"+$.escapeSelector(l.substring(1));let r=$(n);var n=e.is(":visible"),i=$(window).width()<$(window).height();if(void 0!==r){a.preventDefault(),history.pushState&&history.pushState(null,null,l);a=$(window).scrollTop();let e=r.offset().top-=8;e<a&&(ScrollHelper.hideTopbar(),ScrollHelper.addScrollUpTask()),n&&i&&(e-=ScrollHelper.getTopbarHeight()),$("html").animate({scrollTop:e},500,()=>{r.trigger("focus");var e=$(`[${s}=true]`),e=(e.length&&e.attr(s,"false"),$(":target"));if(e.length&&e.attr(s,"false"),(o||t)&&r.attr(s,"true"),r.is(":focus"))return!1;r.attr("tabindex","-1"),r.trigger("focus"),ScrollHelper.hasScrollUpTask()&&ScrollHelper.popScrollUpTask()})}}})}); diff --git a/assets/js/dist/post.min.js b/assets/js/dist/post.min.js deleted file mode 100644 index f6c6ba2..0000000 --- a/assets/js/dist/post.min.js +++ /dev/null @@ -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",t=>{t=$(t.target);(t.prop("tagName")==="button".toUpperCase()?t:t.parent()).trigger("blur"),flipMode()})});const ScrollHelper=function(){const t=$("body"),e="data-topbar-visible",o=$("#topbar-wrapper").outerHeight();let r=0,a=!1,l=!1;return{hideTopbar:()=>t.attr(e,"false"),showTopbar:()=>t.attr(e,"true"),addScrollUpTask:()=>{r+=1,a=a||!0},popScrollUpTask:()=>--r,hasScrollUpTask:()=>0<r,topbarLocked:()=>!0===a,unlockTopbar:()=>a=!1,getTopbarHeight:()=>o,orientationLocked:()=>!0===l,lockOrientation:()=>l=!0,unLockOrientation:()=>l=!1}}(),LocaleHelper=($(function(){const t=$("#sidebar-trigger"),e=$("#search-trigger"),o=$("#search-cancel"),r=$("#main"),a=$("#topbar-title"),l=$("#search-wrapper"),n=$("#search-result-wrapper"),i=$("#search-results"),s=$("#search-input"),c=$("#search-hints"),d=function(){let t=0;return{block(){t=window.scrollY,$("html,body").scrollTop(0)},release(){$("html,body").scrollTop(t)},getOffset(){return t}}}(),p={on(){t.addClass("unloaded"),a.addClass("unloaded"),e.addClass("unloaded"),l.addClass("d-flex"),o.addClass("loaded")},off(){o.removeClass("loaded"),l.removeClass("d-flex"),t.removeClass("unloaded"),a.removeClass("unloaded"),e.removeClass("unloaded")}},u=function(){let t=!1;return{on(){t||(d.block(),n.removeClass("unloaded"),r.addClass("unloaded"),t=!0)},off(){t&&(i.empty(),c.hasClass("unloaded")&&c.removeClass("unloaded"),n.addClass("unloaded"),r.removeClass("unloaded"),d.release(),s.val(""),t=!1)}}}();function h(){return o.hasClass("loaded")}e.on("click",function(){p.on(),u.on(),s.trigger("focus")}),o.on("click",function(){p.off(),u.off()}),s.on("focus",function(){l.addClass("input-focus")}),s.on("focusout",function(){l.removeClass("input-focus")}),s.on("input",()=>{""===s.val()?h()?c.removeClass("unloaded"):u.off():(u.on(),h()&&c.addClass("unloaded"))})}),$(function(){var t=function(){const t="sidebar-display";let e=!1;const o=$("body");return{toggle(){!1===e?o.attr(t,""):o.removeAttr(t),e=!e}}}();$("#sidebar-trigger").on("click",t.toggle),$("#mask").on("click",t.toggle)}),$(function(){$('[data-toggle="tooltip"]').tooltip()}),$(function(){const e=$("#search-input"),o=ScrollHelper.getTopbarHeight();let t,r=0;function a(){0!==$(window).scrollTop()&&(ScrollHelper.lockOrientation(),ScrollHelper.hideTopbar())}screen.orientation?screen.orientation.onchange=()=>{var t=screen.orientation.type;"landscape-primary"!==t&&"landscape-secondary"!==t||a()}:$(window).on("orientationchange",()=>{$(window).width()<$(window).height()&&a()}),$(window).on("scroll",()=>{t=t||!0}),setInterval(()=>{t&&(!function(){var t=$(this).scrollTop();if(!(Math.abs(r-t)<=o)){if(t>r)ScrollHelper.hideTopbar(),e.is(":focus")&&e.trigger("blur");else if(t+$(window).height()<$(document).height()){if(ScrollHelper.hasScrollUpTask())return;ScrollHelper.topbarLocked()?ScrollHelper.unlockTopbar():ScrollHelper.orientationLocked()?ScrollHelper.unLockOrientation():ScrollHelper.showTopbar()}r=t}}(),t=!1)},250)}),$(function(){var t="div.post>h1:first-of-type",e=$(t);const n=$("#topbar-title");if(0!==e.length&&!e.hasClass("dynamic-title")&&!n.is(":hidden")){const i=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(t=>{var e,o;a?(o=$(window).scrollTop(),e=l<o,l=o,o=t[0],e?0===o.intersectionRatio&&n.text(r):1===o.intersectionRatio&&n.text(i)):a=!0},{rootMargin:"-48px 0px 0px 0px",threshold:[0,1]}).observe(document.querySelector(t)),n.on("click",function(){$("body,html").animate({scrollTop:0},800)})}}),$("#core-wrapper img[data-src]")<=0||($(".popup").magnificPopup({type:"image",closeOnContentClick:!0,showCloseBtn:!1,zoom:{enabled:!0,duration:300,easing:"ease-in-out"}}),document.addEventListener("lazyloaded",function(t){$(t.target).parent().removeClass("shimmer")})),function(){const t=$("html").attr("lang").substring(0,2),e="data-ts",o="data-df";return{locale:()=>t,attrTimestamp:()=>e,attrDateFormat:()=>o,getTimestamp:t=>Number(t.attr(e)),getDateFormat:t=>t.attr(o)}}());$(function(){dayjs.locale(LocaleHelper.locale()),dayjs.extend(window.dayjs_plugin_localizedFormat),$(`[${LocaleHelper.attrTimestamp()}]`).each(function(){var t=dayjs.unix(LocaleHelper.getTimestamp($(this))),e=t.format(LocaleHelper.getDateFormat($(this))),e=($(this).text(e),$(this).removeAttr(LocaleHelper.attrTimestamp()),$(this).removeAttr(LocaleHelper.attrDateFormat()),$(this).attr("data-toggle"));void 0!==e&&"tooltip"===e&&(e=t.format("llll"),$(this).attr("data-original-title",e))})}),$(function(){var t=".code-header>button";const e="timeout",r="data-title-succeed",a="data-original-title";function l(t){if($(t)[0].hasAttribute(e)){t=$(t).attr(e);if(Number(t)>Date.now())return 1}}function n(t){$(t).attr(e,Date.now()+2e3)}function i(t){$(t).removeAttr(e)}var o=new ClipboardJS(t,{target(t){return t.parentNode.nextElementSibling.querySelector("code .rouge-code")}});$(t).tooltip({trigger:"hover",placement:"left"});const s=$(t).children().attr("class");o.on("success",t=>{t.clearSelection();const e=t.trigger;var o;l(e)||(t=e,$(t).children().attr("class","fas fa-check"),t=e,o=$(t).attr(r),$(t).attr(a,o).tooltip("show"),n(e),setTimeout(()=>{var t;t=e,$(t).tooltip("hide").removeAttr(a),t=e,$(t).children().attr("class",s),i(e)},2e3))}),$("#copy-link").on("click",t=>{let o=$(t.target);l(o)||navigator.clipboard.writeText(window.location.href).then(()=>{const t=o.attr(a);var e=o.attr(r);o.attr(a,e).tooltip("show"),n(o),setTimeout(()=>{o.attr(a,t),i(o)},2e3)})})}),$(function(){const t=$("#topbar-title"),s="scroll-focus";$("a[href*='#']").not("[href='#']").not("[href='#0']").on("click",function(a){if(this.pathname.replace(/^\//,"")===location.pathname.replace(/^\//,"")&&location.hostname===this.hostname){var l=decodeURI(this.hash);let e=RegExp(/^#fnref:/).test(l),o=!e&&RegExp(/^#fn:/).test(l);var n="#"+$.escapeSelector(l.substring(1));let r=$(n);var n=t.is(":visible"),i=$(window).width()<$(window).height();if(void 0!==r){a.preventDefault(),history.pushState&&history.pushState(null,null,l);a=$(window).scrollTop();let t=r.offset().top-=8;t<a&&(ScrollHelper.hideTopbar(),ScrollHelper.addScrollUpTask()),n&&i&&(t-=ScrollHelper.getTopbarHeight()),$("html").animate({scrollTop:t},500,()=>{r.trigger("focus");var t=$(`[${s}=true]`),t=(t.length&&t.attr(s,"false"),$(":target"));if(t.length&&t.attr(s,"false"),(o||e)&&r.attr(s,"true"),r.is(":focus"))return!1;r.attr("tabindex","-1"),r.trigger("focus"),ScrollHelper.hasScrollUpTask()&&ScrollHelper.popScrollUpTask()})}}})}); diff --git a/assets/js/dist/pvreport.min.js b/assets/js/dist/pvreport.min.js deleted file mode 100644 index 4886fd8..0000000 --- a/assets/js/dist/pvreport.min.js +++ /dev/null @@ -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())}); diff --git a/gulpfile.js/index.js b/gulpfile.js/index.js deleted file mode 100644 index 14692fb..0000000 --- a/gulpfile.js/index.js +++ /dev/null @@ -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; diff --git a/gulpfile.js/tasks/js.js b/gulpfile.js/tasks/js.js deleted file mode 100644 index 3db0065..0000000 --- a/gulpfile.js/tasks/js.js +++ /dev/null @@ -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 - ); -}; diff --git a/package.json b/package.json index a9bfc3f..e95e75c 100644 --- a/package.json +++ b/package.json @@ -11,19 +11,25 @@ "bugs": { "url": "https://github.com/cotes2020/jekyll-theme-chirpy/issues" }, - "homepage": "https://github.com/cotes2020/jekyll-theme-chirpy#readme", + "homepage": "https://github.com/cotes2020/jekyll-theme-chirpy/", "scripts": { + "prebuild": "npx rimraf assets/js/dist", + "build": "NODE_ENV=production npx rollup -c --bundleConfigAsCjs", + "prewatch": "npx rimraf assets/js/dist", + "watch": "npx rollup -c --bundleConfigAsCjs -w", "test": "npx stylelint _sass/**/*.scss", - "fixlint": "npx stylelint _sass/**/*.scss --fix" + "fixlint": "npm run test -- --fix" }, "devDependencies": { - "gulp": "^4.0.2", - "gulp-concat": "^2.6.1", - "gulp-insert": "^0.5.0", - "gulp-rename": "^2.0.0", - "gulp-uglify": "^3.0.2", + "@babel/core": "^7.21.0", + "@babel/plugin-proposal-class-properties": "^7.18.6", + "@babel/preset-env": "^7.20.2", + "@rollup/plugin-babel": "^6.0.3", + "@rollup/plugin-terser": "^0.4.0", + "rimraf": "^4.4.0", + "rollup": "^3.19.1", + "rollup-plugin-license": "^3.0.1", "stylelint": "^15.2.0", - "stylelint-config-standard-scss": "^7.0.1", - "uglify-js": "^3.17.4" + "stylelint-config-standard-scss": "^7.0.1" } } diff --git a/rollup.config.js b/rollup.config.js new file mode 100644 index 0000000..f662358 --- /dev/null +++ b/rollup.config.js @@ -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') +]; diff --git a/tools/init b/tools/init index 7cf7324..e5178db 100755 --- a/tools/init +++ b/tools/init @@ -4,9 +4,15 @@ set -eu +# CLI Dependencies +CLI=("git" "npm") + 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() { echo "Usage:" @@ -18,14 +24,32 @@ help() { 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 echo "Error: Commit unstaged files first, and then run this tool again." exit 1 fi } -check_init() { +_check_init() { local _has_inited=false if [[ ! -d .github ]]; then # using option `--no-gh` @@ -47,9 +71,10 @@ check_init() { fi } -checkout_latest_tag() { - tag=$(git describe --tags "$(git rev-list --tags --max-count=1)") - git reset --hard "$tag" +check_env() { + _check_cli + _check_status + _check_init } init_files() { @@ -63,25 +88,30 @@ init_files() { mv ./${ACTIONS_WORKFLOW}.hook .github/workflows/${ACTIONS_WORKFLOW} ## Cleanup image settings in site config - sed -i.$TEMP_SUFFIX "s/^img_cdn:.*/img_cdn:/;s/^avatar:.*/avatar:/" _config.yml - rm -f _config.yml.$TEMP_SUFFIX + _sedi "s/^img_cdn:.*/img_cdn:/;s/^avatar:.*/avatar:/" _config.yml fi # remove the other fies rm -rf _posts/* - # save changes - git add -A - git commit -m "chore: initialize the environment" -q + # build assest + npm i && npm run build - 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 - -_no_gh=false +main() { + check_env + init_files + commit +} while (($#)); do opt="$1" @@ -102,6 +132,4 @@ while (($#)); do esac done -checkout_latest_tag - -init_files +main diff --git a/tools/release b/tools/release index f6bc6d0..8efb264 100755 --- a/tools/release +++ b/tools/release @@ -29,7 +29,6 @@ NODE_CONFIG="package.json" FILES=( "_sass/jekyll-theme-chirpy.scss" - "_javascript/copyright" "$GEM_SPEC" "$NODE_CONFIG" ) @@ -69,17 +68,24 @@ _check_git() { } _check_src() { - if [[ ! -f $1 && ! -d $1 ]]; then - echo -e "Error: Missing file \"$1\"!\n" - exit 1 - fi + for i in "${!FILES[@]}"; do + _src="${FILES[$i]}" + if [[ ! -f $_src && ! -d $_src ]]; then + echo -e "Error: Missing file \"$_src\"!\n" + exit 1 + fi + done + } _check_command() { - if ! command -v "$1" &>/dev/null; then - echo "Command '$1' not found" - exit 1 - fi + for i in "${!TOOLS[@]}"; do + cli="${TOOLS[$i]}" + if ! command -v "$cli" &>/dev/null; then + echo "Command '$cli' not found!" + exit 1 + fi + done } _check_node_packages() { @@ -89,20 +95,13 @@ _check_node_packages() { } check() { + _check_command _check_git - - for i in "${!FILES[@]}"; do - _check_src "${FILES[$i]}" - done - - for i in "${!TOOLS[@]}"; do - _check_command "${TOOLS[$i]}" - done - + _check_src _check_node_packages } -_bump_file() { +_bump_files() { for i in "${!FILES[@]}"; do if [[ ${FILES[$i]} == "$NODE_CONFIG" ]]; then continue @@ -111,7 +110,7 @@ _bump_file() { sed -i "s/v[[:digit:]]\+\.[[:digit:]]\+\.[[:digit:]]\+/v$1/" "${FILES[$i]}" done - npx gulp + npm run build } _bump_gemspec() { @@ -127,7 +126,7 @@ _bump_gemspec() { # # 2. Create a commit to save the changes. bump() { - _bump_file "$1" + _bump_files "$1" _bump_gemspec "$1" if [[ $opt_pre = false && -n $(git status . -s) ]]; then