Merge branch 'master' into production

This commit is contained in:
Cotes Chung 2024-05-11 15:03:29 +08:00
commit 09b300bc62
No known key found for this signature in database
GPG key ID: 0D9E54843167A808
97 changed files with 1341 additions and 929 deletions

View file

@ -9,15 +9,6 @@ body:
[contributing guidelines](https://github.com/cotes2020/jekyll-theme-chirpy/blob/master/docs/CONTRIBUTING.md). [contributing guidelines](https://github.com/cotes2020/jekyll-theme-chirpy/blob/master/docs/CONTRIBUTING.md).
required: true required: true
- type: dropdown
attributes:
label: What is the topic?
options:
- Sharing tips and tricks
- Just chatting
validations:
required: true
- type: textarea - type: textarea
attributes: attributes:
label: Description label: Description

7
.github/DISCUSSION_TEMPLATE/ideas.yml vendored Normal file
View file

@ -0,0 +1,7 @@
body:
- type: textarea
attributes:
label: Description
description: Please describe in detail what you want to share.
validations:
required: true

View file

@ -2,10 +2,6 @@ version: 2
updates: updates:
- package-ecosystem: "bundler" - package-ecosystem: "bundler"
directory: "/" directory: "/"
versioning-strategy: increase
groups:
bundler:
dependency-type: "production"
schedule: schedule:
interval: "weekly" interval: "weekly"
- package-ecosystem: "npm" - package-ecosystem: "npm"
@ -13,7 +9,10 @@ updates:
versioning-strategy: increase versioning-strategy: increase
groups: groups:
npm: npm:
dependency-type: "development" update-types:
- "major"
- "minor"
- "patch"
schedule: schedule:
interval: "weekly" interval: "weekly"
- package-ecosystem: "github-actions" - package-ecosystem: "github-actions"

View file

@ -1,17 +1,37 @@
name: CD name: CD
on: on:
push: push:
tags:
- "v[0-9]+.[0-9]+.[0-9]+"
branches: branches:
- docs - production
tags-ignore:
- "**"
jobs: jobs:
launch: release:
permissions:
contents: write
issues: write
pull-requests: write
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- run: | - uses: actions/checkout@v4
curl -X POST -H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${{ secrets.GH_PAT }}" \ - uses: ruby/setup-ruby@v1
https://api.github.com/repos/${{ secrets.BUILDER }}/dispatches \ with:
-d '{"event_type":"deploy", "client_payload":{"branch": "${{ github.ref_name }}"}}' ruby-version: 3.3
bundler-cache: true
- uses: actions/setup-node@v4
with:
node-version: latest
- run: npm install
- run: npx semantic-release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GEM_HOST_API_KEY: ${{ secrets.GEM_HOST_API_KEY }}
publish:
needs: release
uses: ./.github/workflows/publish.yml

View file

@ -1,9 +1,8 @@
name: "CI" name: "CI"
on: on:
push: push:
branches-ignore: branches:
- "production" - "master"
- "docs"
paths-ignore: paths-ignore:
- ".github/**" - ".github/**"
- "!.github/workflows/ci.yml" - "!.github/workflows/ci.yml"
@ -12,8 +11,6 @@ on:
- "README.md" - "README.md"
- "LICENSE" - "LICENSE"
pull_request: pull_request:
paths:
- "**"
jobs: jobs:
build: build:
@ -21,7 +18,7 @@ jobs:
strategy: strategy:
matrix: matrix:
ruby: ["3.0", "3.1", "3.2"] ruby: ["3.1", "3.2", "3.3"]
steps: steps:
- name: Checkout - name: Checkout
@ -37,6 +34,8 @@ jobs:
- name: Setup Node - name: Setup Node
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with:
node-version: latest
- name: Build Assets - name: Build Assets
run: npm i && npm run build run: npm i && npm run build

View file

@ -2,6 +2,7 @@ name: "CodeQL"
on: on:
push: push:
branches: ["master"]
paths: ["_javascript/**/*.js"] paths: ["_javascript/**/*.js"]
pull_request: pull_request:
paths: ["_javascript/**/*.js"] paths: ["_javascript/**/*.js"]

View file

@ -6,6 +6,4 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: - uses: wagoid/commitlint-github-action@v6
fetch-depth: 0
- uses: wagoid/commitlint-github-action@v5

17
.github/workflows/publish.yml vendored Normal file
View file

@ -0,0 +1,17 @@
name: Publish
on:
push:
branches:
- docs
workflow_call:
jobs:
launch:
runs-on: ubuntu-latest
steps:
- run: |
curl -X POST -H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${{ secrets.GH_PAT }}" \
https://api.github.com/repos/${{ secrets.BUILDER }}/dispatches \
-d '{"event_type":"deploy", "client_payload":{"branch": "${{ github.ref_name }}"}}'

View file

@ -42,7 +42,7 @@ jobs:
- name: Setup Ruby - name: Setup Ruby
uses: ruby/setup-ruby@v1 uses: ruby/setup-ruby@v1
with: with:
ruby-version: 3.2 ruby-version: 3.3
bundler-cache: true bundler-cache: true
- name: Build site - name: Build site
@ -53,7 +53,7 @@ jobs:
- name: Test site - name: Test site
run: | run: |
bundle exec htmlproofer _site \ bundle exec htmlproofer _site \
\-\-disable-external=true \ \-\-disable-external \
\-\-ignore-urls "/^http:\/\/127.0.0.1/,/^http:\/\/0.0.0.0/,/^http:\/\/localhost/" \-\-ignore-urls "/^http:\/\/127.0.0.1/,/^http:\/\/0.0.0.0/,/^http:\/\/localhost/"
- name: Upload site artifact - name: Upload site artifact

View file

@ -2,14 +2,10 @@ name: "Style Lint"
on: on:
push: push:
branches-ignore: branches: ["master"]
- "production" paths: ["_sass/**/*.scss"]
- "docs"
paths:
- "_sass/**/*.scss"
pull_request: pull_request:
paths: paths: ["_sass/**/*.scss"]
- "_sass/**/*.scss"
jobs: jobs:
stylelint: stylelint:
@ -21,5 +17,7 @@ jobs:
- name: Setup Node - name: Setup Node
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with:
node-version: latest
- run: npm i - run: npm i
- run: npm test - run: npm test

5
.gitignore vendored
View file

@ -5,6 +5,7 @@ Gemfile.lock
# Jekyll cache # Jekyll cache
.jekyll-cache .jekyll-cache
.jekyll-metadata
_site _site
# RubyGems # RubyGems
@ -16,6 +17,10 @@ package-lock.json
# IDE configurations # IDE configurations
.idea .idea
.vscode
!.vscode/settings.json
!.vscode/extensions.json
# Misc # Misc
_sass/dist
assets/js/dist assets/js/dist

8
.markdownlint.json Normal file
View file

@ -0,0 +1,8 @@
{
"commands-show-output": false,
"blanks-around-fences": false,
"line-length": false,
"no-inline-html": {
"allowed_elements": ["kbd", "sub"]
}
}

View file

@ -7,6 +7,7 @@
// Common formatter // Common formatter
"esbenp.prettier-vscode", "esbenp.prettier-vscode",
"foxundermoon.shell-format", "foxundermoon.shell-format",
"stylelint.vscode-stylelint" "stylelint.vscode-stylelint",
"yzhang.markdown-all-in-one"
] ]
} }

View file

@ -7,6 +7,9 @@
"files.associations": { "files.associations": {
"*.html": "liquid" "*.html": "liquid"
}, },
"[markdown]": {
"editor.defaultFormatter": "yzhang.markdown-all-in-one"
},
// Formatter // Formatter
"[html][liquid]": { "[html][liquid]": {
"editor.defaultFormatter": "Shopify.theme-check-vscode" "editor.defaultFormatter": "Shopify.theme-check-vscode"

16
Gemfile
View file

@ -5,19 +5,5 @@ source "https://rubygems.org"
gemspec gemspec
group :test do group :test do
gem "html-proofer", "~> 4.4" gem "html-proofer", "~> 5.0"
end end
# Windows and JRuby does not include zoneinfo files, so bundle the tzinfo-data gem
# and associated library.
platforms :mingw, :x64_mingw, :mswin, :jruby do
gem "tzinfo", ">= 1", "< 3"
gem "tzinfo-data"
end
# Performance-booster for watching directories on Windows
gem "wdm", "~> 0.1.1", :platforms => [:mingw, :x64_mingw, :mswin]
# Lock `http_parser.rb` gem to `v0.6.x` on JRuby builds since newer versions of the gem
# do not have a Java counterpart.
gem "http_parser.rb", "~> 0.6.0", :platforms => [:jruby]

View file

@ -1,5 +1,7 @@
<!-- markdownlint-disable-next-line -->
<div align="center"> <div align="center">
<!-- markdownlint-disable-next-line -->
# Chirpy Jekyll Theme # Chirpy Jekyll Theme
A minimal, responsive, and feature-rich Jekyll theme for technical writing. A minimal, responsive, and feature-rich Jekyll theme for technical writing.
@ -18,7 +20,7 @@
## Features ## Features
- Dark / Light Theme Mode - Dark Theme
- Localized UI language - Localized UI language
- Pinned Posts on Home Page - Pinned Posts on Home Page
- Hierarchical Categories - Hierarchical Categories
@ -28,13 +30,13 @@
- Syntax Highlighting - Syntax Highlighting
- Mathematical Expressions - Mathematical Expressions
- Mermaid Diagrams & Flowcharts - Mermaid Diagrams & Flowcharts
- Dark / Light Mode Images - Dark Mode Images
- Embed Videos - Embed Media
- Disqus / Giscus / Utterances Comments - Comment Systems
- Built-in Search - Built-in Search
- Atom Feeds - Atom Feeds
- PWA - PWA
- Google Analytics / GoatCounter - Web Analytics
- SEO & Performance Optimization - SEO & Performance Optimization
## Documentation ## Documentation
@ -54,7 +56,7 @@ For details, see the "[Contributing Guidelines][contribute-guide]".
Thanks to [all the contributors][contributors] involved in the development of the project! Thanks to [all the contributors][contributors] involved in the development of the project!
[![all-contributors](https://contrib.rocks/image?repo=cotes2020/jekyll-theme-chirpy&columns=16)][contributors] [![all-contributors](https://contrib.rocks/image?repo=cotes2020/jekyll-theme-chirpy&columns=16)][contributors]
<sub> Made with [contrib.rocks](https://contrib.rocks)</sub> <sub> — Made with [contrib.rocks](https://contrib.rocks)</sub>
### Third-Party Assets ### Third-Party Assets

View file

@ -44,16 +44,36 @@ social:
# - https://www.facebook.com/username # - https://www.facebook.com/username
# - https://www.linkedin.com/in/username # - https://www.linkedin.com/in/username
google_site_verification: # fill in to your verification string # Site Verification Settings
webmaster_verifications:
google: # fill in your Google verification code
bing: # fill in your Bing verification code
alexa: # fill in your Alexa verification code
yandex: # fill in your Yandex verification code
baidu: # fill in your Baidu verification code
facebook: # fill in your Facebook verification code
# ↑ -------------------------- # ↑ --------------------------
# The end of `jekyll-seo-tag` settings # The end of `jekyll-seo-tag` settings
google_analytics: # Web Analytics Settings
analytics:
google:
id: # fill in your Google Analytics ID id: # fill in your Google Analytics ID
goatcounter:
id: # fill in your GoatCounter ID
umami:
id: # fill in your Umami ID
domain: # fill in your Umami domain
matomo:
id: # fill in your Matomo ID
domain: # fill in your Matomo domain
cloudflare:
id: # fill in your Cloudflare Web Analytics token
goatcounter: # Pageviews settings
id: # fill in your Goatcounter ID pageviews:
provider: # now only supports 'goatcounter'
# Prefer color scheme setting. # Prefer color scheme setting.
# #
@ -68,12 +88,12 @@ goatcounter:
# #
theme_mode: # [light | dark] theme_mode: # [light | dark]
# The CDN endpoint for images. # The CDN endpoint for media resources.
# Notice that once it is assigned, the CDN url # Notice that once it is assigned, the CDN url
# will be added to all image (site avatar & posts' images) paths starting with '/' # will be added to all media resources (site avatar, posts' images, audio and video files) paths starting with '/'
# #
# e.g. 'https://cdn.com' # e.g. 'https://cdn.com'
img_cdn: "https://chirpy-img.netlify.app" cdn: "https://chirpy-img.netlify.app"
# the avatar on sidebar, support local or CORS resources # the avatar on sidebar, support local or CORS resources
avatar: "/commons/avatar.jpg" avatar: "/commons/avatar.jpg"
@ -86,8 +106,9 @@ social_preview_image: # string, local or CORS resources
toc: true toc: true
comments: comments:
active: # The global switch for posts comments, e.g., 'disqus'. Keep it empty means disable # Global switch for the post comment system. Keeping it empty means disabled.
# The active options are as follows: provider: # [disqus | utterances | giscus]
# The provider options are as follows:
disqus: 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 settings https://utteranc.es/
@ -101,6 +122,7 @@ comments:
category: category:
category_id: category_id:
mapping: # optional, default to 'pathname' mapping: # optional, default to 'pathname'
strict: # optional, default to '0'
input_position: # optional, default to 'bottom' input_position: # optional, default to 'bottom'
lang: # optional, default to the value of `site.lang` lang: # optional, default to the value of `site.lang`
reactions_enabled: # optional, default to the value of `1` reactions_enabled: # optional, default to the value of `1`
@ -131,6 +153,7 @@ baseurl: ""
# ------------ The following options are not recommended to be modified ------------------ # ------------ The following options are not recommended to be modified ------------------
kramdown: kramdown:
footnote_backlink: "&#8617;&#xfe0e;"
syntax_highlighter: rouge 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 css_class: highlight
@ -191,7 +214,7 @@ exclude:
- tools - tools
- README.md - README.md
- LICENSE - LICENSE
- rollup.config.js - "*.config.js"
- package*.json - package*.json
jekyll-archives: jekyll-archives:

18
_data/media.yml Normal file
View file

@ -0,0 +1,18 @@
- extension: mp3
mime_type: mpeg
- extension: mov
mime_type: quicktime
- extension: avi
mime_type: x-msvideo
- extension: mkv
mime_type: x-matroska
- extension: ogv
mime_type: ogg
- extension: weba
mime_type: webm
- extension: 3gp
mime_type: 3gpp
- extension: 3g2
mime_type: 3gpp2
- extension: mid
mime_type: midi

View file

@ -4,13 +4,6 @@ webfonts: /assets/lib/fonts/main.css
# Libraries # Libraries
jquery:
js: /assets/lib/jquery/jquery.min.js
bootstrap:
css: /assets/lib/bootstrap/bootstrap.min.css
js: /assets/lib/bootstrap/bootstrap.bundle.min.js
toc: toc:
css: /assets/lib/tocbot/tocbot.min.css css: /assets/lib/tocbot/tocbot.min.css
js: /assets/lib/tocbot/tocbot.min.js js: /assets/lib/tocbot/tocbot.min.js
@ -31,9 +24,9 @@ dayjs:
relativeTime: /assets/lib/dayjs/plugin/relativeTime.min.js relativeTime: /assets/lib/dayjs/plugin/relativeTime.min.js
localizedFormat: /assets/lib/dayjs/plugin/localizedFormat.min.js localizedFormat: /assets/lib/dayjs/plugin/localizedFormat.min.js
magnific-popup: glightbox:
css: /assets/lib/magnific-popup/magnific-popup.css css: /assets/lib/glightbox/glightbox.min.css
js: /assets/lib/magnific-popup/jquery.magnific-popup.min.js js: /assets/lib/glightbox/glightbox.min.js
lazy-polyfill: lazy-polyfill:
css: /assets/lib/loading-attribute-polyfill/loading-attribute-polyfill.min.css css: /assets/lib/loading-attribute-polyfill/loading-attribute-polyfill.min.css

View file

@ -1,29 +1,24 @@
# CDNs # Resource Hints
resource_hints:
cdns:
# Google Fonts
- url: https://fonts.googleapis.com - url: https://fonts.googleapis.com
links:
- rel: preconnect
- rel: dns-prefetch
- url: https://fonts.gstatic.com - url: https://fonts.gstatic.com
args: crossorigin links:
- url: https://fonts.googleapis.com - rel: preconnect
# jsDelivr CDN opts: [crossorigin]
- rel: dns-prefetch
- url: https://cdn.jsdelivr.net - url: https://cdn.jsdelivr.net
# polyfill.io for math (cdnjs.cloudflare.com/polyfill) links:
- url: https://cdnjs.cloudflare.com - rel: preconnect
- rel: dns-prefetch
# fonts # Web Fonts
webfonts: https://fonts.googleapis.com/css2?family=Lato:wght@300;400&family=Source+Sans+Pro:wght@400;600;700;900&display=swap
webfonts: https://fonts.googleapis.com/css2?family=Lato&family=Source+Sans+Pro:wght@400;600;700;900&display=swap
# Libraries # Libraries
jquery:
js: https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js
bootstrap:
css: https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css
js: https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js
toc: toc:
css: https://cdn.jsdelivr.net/npm/tocbot@4.25.0/dist/tocbot.min.css css: https://cdn.jsdelivr.net/npm/tocbot@4.25.0/dist/tocbot.min.css
js: https://cdn.jsdelivr.net/npm/tocbot@4.25.0/dist/tocbot.min.js js: https://cdn.jsdelivr.net/npm/tocbot@4.25.0/dist/tocbot.min.js
@ -44,9 +39,9 @@ dayjs:
relativeTime: https://cdn.jsdelivr.net/npm/dayjs@1.11.10/plugin/relativeTime.min.js relativeTime: https://cdn.jsdelivr.net/npm/dayjs@1.11.10/plugin/relativeTime.min.js
localizedFormat: https://cdn.jsdelivr.net/npm/dayjs@1.11.10/plugin/localizedFormat.min.js localizedFormat: https://cdn.jsdelivr.net/npm/dayjs@1.11.10/plugin/localizedFormat.min.js
magnific-popup: glightbox:
css: https://cdn.jsdelivr.net/npm/magnific-popup@1.1.0/dist/magnific-popup.min.css css: https://cdn.jsdelivr.net/npm/glightbox@3.3.0/dist/css/glightbox.min.css
js: https://cdn.jsdelivr.net/npm/magnific-popup@1.1.0/dist/jquery.magnific-popup.min.js js: https://cdn.jsdelivr.net/npm/glightbox@3.3.0/dist/js/glightbox.min.js
lazy-polyfill: lazy-polyfill:
css: https://cdn.jsdelivr.net/npm/loading-attribute-polyfill@2.1.1/dist/loading-attribute-polyfill.min.css css: https://cdn.jsdelivr.net/npm/loading-attribute-polyfill@2.1.1/dist/loading-attribute-polyfill.min.css

View file

@ -22,7 +22,7 @@ platforms:
# #
# - type: Weibo # - type: Weibo
# icon: "fab fa-weibo" # icon: "fab fa-weibo"
# link: "http://service.weibo.com/share/share.php?title=TITLE&url=URL" # link: "https://service.weibo.com/share/share.php?title=TITLE&url=URL"
# #
# - type: Mastodon # - type: Mastodon
# icon: "fa-brands fa-mastodon" # icon: "fa-brands fa-mastodon"

View file

@ -0,0 +1,7 @@
<!-- Cloudflare Web Analytics -->
<script
defer
src="https://static.cloudflareinsights.com/beacon.min.js"
data-cf-beacon='{"token": "{{ site.analytics.cloudflare.id }}"}'
></script>
<!-- End Cloudflare Web Analytics -->

View file

@ -0,0 +1,6 @@
<!-- GoatCounter -->
<script
async
src="https://gc.zgo.at/count.js"
data-goatcounter="https://{{ site.analytics.goatcounter.id }}.goatcounter.com/count"
></script>

View file

@ -0,0 +1,13 @@
<!-- Global site tag (gtag.js) - Google Analytics -->
<script defer src="https://www.googletagmanager.com/gtag/js?id={{ site.analytics.google.id }}"></script>
<script>
document.addEventListener('DOMContentLoaded', function (event) {
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', '{{ site.analytics.google.id }}');
});
</script>

View file

@ -0,0 +1,14 @@
<!-- Matomo -->
<script type="text/javascript">
var _paq = window._paq = window._paq || [];
_paq.push(['trackPageView']);
_paq.push(['enableLinkTracking']);
(function() {
var u="//{{ site.analytics.matomo.domain }}/";
_paq.push(['setTrackerUrl', u+'matomo.php']);
_paq.push(['setSiteId', {{ site.analytics.matomo.id }}]);
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
g.type='text/javascript'; g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s);
})();
</script>
<!-- End Matomo Code -->

View file

@ -0,0 +1,6 @@
<!-- Umami -->
<script
defer
src="{{ site.analytics.umami.domain }}/script.js"
data-website-id="{{ site.analytics.umami.id }}"
></script>

View file

@ -1,5 +1,5 @@
<!-- The comments switcher --> <!-- The comments switcher -->
{% if page.comments and site.comments.active %} {% if page.comments and site.comments.provider %}
{% capture path %}comments/{{ site.comments.active }}.html{% endcapture %} {% capture path %}comments/{{ site.comments.provider }}.html{% endcapture %}
{% include {{ path }} %} {% include {{ path }} %}
{% endif %} {% endif %}

View file

@ -10,7 +10,7 @@
this.page.identifier = '{{ page.url }}'; this.page.identifier = '{{ page.url }}';
}; };
/* Lazy loading */ {%- comment -%} Lazy loading {%- endcomment -%}
var disqus_observer = new IntersectionObserver( var disqus_observer = new IntersectionObserver(
function (entries) { function (entries) {
if (entries[0].isIntersecting) { if (entries[0].isIntersecting) {
@ -28,12 +28,12 @@
{ threshold: [0] } { threshold: [0] }
); );
disqus_observer.observe(document.querySelector('#disqus_thread')); disqus_observer.observe(document.getElementById('disqus_thread'));
/* Auto switch theme */ {%- comment -%} Auto switch theme {%- endcomment -%}
function reloadDisqus() { function reloadDisqus() {
if (event.source === window && event.data && event.data.direction === ModeToggle.ID) { if (event.source === window && event.data && event.data.direction === ModeToggle.ID) {
/* Disqus hasn't been loaded */ {%- comment -%} Disqus hasn't been loaded {%- endcomment -%}
if (typeof DISQUS === 'undefined') { if (typeof DISQUS === 'undefined') {
return; return;
} }
@ -44,7 +44,7 @@
} }
} }
if (document.querySelector('.mode-toggle')) { if (document.getElementById('mode-toggle')) {
window.addEventListener('message', reloadDisqus); window.addEventListener('message', reloadDisqus);
} }
</script> </script>

View file

@ -2,7 +2,6 @@
<script type="text/javascript"> <script type="text/javascript">
(function () { (function () {
const origin = 'https://giscus.app'; const origin = 'https://giscus.app';
const iframe = 'iframe.giscus-frame';
const lightTheme = 'light'; const lightTheme = 'light';
const darkTheme = 'dark_dimmed'; const darkTheme = 'dark_dimmed';
@ -25,6 +24,7 @@
'data-category': '{{ site.comments.giscus.category }}', 'data-category': '{{ site.comments.giscus.category }}',
'data-category-id': '{{ site.comments.giscus.category_id }}', 'data-category-id': '{{ site.comments.giscus.category_id }}',
'data-mapping': '{{ site.comments.giscus.mapping | default: 'pathname' }}', 'data-mapping': '{{ site.comments.giscus.mapping | default: 'pathname' }}',
'data-strict' : '{{ site.comments.giscus.strict | default: '0' }}',
'data-reactions-enabled': '{{ site.comments.giscus.reactions_enabled | default: '1' }}', 'data-reactions-enabled': '{{ site.comments.giscus.reactions_enabled | default: '1' }}',
'data-emit-metadata': '0', 'data-emit-metadata': '0',
'data-theme': initTheme, 'data-theme': initTheme,
@ -47,7 +47,7 @@
event.data && event.data &&
event.data.direction === ModeToggle.ID event.data.direction === ModeToggle.ID
) { ) {
/* global theme mode changed */ {%- comment -%} global theme mode changed {%- endcomment -%}
const mode = event.data.message; const mode = event.data.message;
const theme = mode === ModeToggle.DARK_MODE ? darkTheme : lightTheme; const theme = mode === ModeToggle.DARK_MODE ? darkTheme : lightTheme;
@ -57,7 +57,7 @@
} }
}; };
const giscus = document.querySelector(iframe).contentWindow; const giscus = document.getElementsByClassName('giscus-frame')[0].contentWindow;
giscus.postMessage({ giscus: message }, origin); giscus.postMessage({ giscus: message }, origin);
} }
}); });

View file

@ -10,7 +10,6 @@
<script type="text/javascript"> <script type="text/javascript">
(function () { (function () {
const origin = 'https://utteranc.es'; const origin = 'https://utteranc.es';
const iframe = 'iframe.utterances-frame';
const lightTheme = 'github-light'; const lightTheme = 'github-light';
const darkTheme = 'github-dark'; const darkTheme = 'github-dark';
let initTheme = lightTheme; let initTheme = lightTheme;
@ -26,12 +25,12 @@
addEventListener('message', (event) => { addEventListener('message', (event) => {
let theme; let theme;
/* credit to <https://github.com/utterance/utterances/issues/170#issuecomment-594036347> */ {%- comment -%} credit to <https://github.com/utterance/utterances/issues/170#issuecomment-594036347> {%- endcomment -%}
if (event.origin === origin) { if (event.origin === origin) {
/* page initial */ {%- comment -%} page initial {%- endcomment -%}
theme = initTheme; theme = initTheme;
} else if (event.source === window && event.data && event.data.direction === ModeToggle.ID) { } else if (event.source === window && event.data && event.data.direction === ModeToggle.ID) {
/* global theme mode changed */ {%- comment -%} global theme mode changed {%- endcomment -%}
const mode = event.data.message; const mode = event.data.message;
theme = mode === ModeToggle.DARK_MODE ? darkTheme : lightTheme; theme = mode === ModeToggle.DARK_MODE ? darkTheme : lightTheme;
} else { } else {
@ -43,7 +42,7 @@
theme: theme theme: theme
}; };
const utterances = document.querySelector(iframe).contentWindow; const utterances = document.getElementsByClassName('utterances-frame')[0].contentWindow;
utterances.postMessage(message, origin); utterances.postMessage(message, origin);
}); });
})(); })();

View file

@ -0,0 +1,35 @@
{% assign src = include.src | strip %}
{% assign title = include.title | strip %}
{% assign types = include.types | default: '' | strip | split: '|' %}
{% unless src contains '://' %}
{%- capture src -%}
{% include media-url.html src=src %}
{%- endcapture -%}
{% endunless %}
<p>
<audio class="embed-audio" controls>
{% assign extension = src | split: '.' | last %}
{% assign types = extension | concat: types %}
{% assign ext_size = extension | size %}
{% assign src_size = src | size %}
{% assign slice_size = src_size | minus: ext_size %}
{% assign filepath = src | slice: 0, slice_size %}
{% for type in types %}
{% assign src = filepath | append: type %}
{% assign media_item = site.data.media | find: 'extension', type %}
{% assign mime_type = media_item.mime_type | default: type %}
<source src="{{ src }}" type="audio/{{ mime_type }}">
{% endfor %}
Your browser does not support the audio tag. Here is a
<a href="{{ src | strip }}">link to the audio file</a> instead.
</audio>
{% if title %}
<em>{{ title }}</em>
{% endif %}
</p>

View file

@ -1,10 +1,9 @@
<iframe <iframe
class="embed-video bilibili" class="embed-video"
loading="lazy" loading="lazy"
src="https://player.bilibili.com/player.html?bvid={{ include.id }}" src="https://player.bilibili.com/player.html?bvid={{ include.id }}"
scrolling="no" scrolling="no"
border="0" frameborder="0"
frameborder="no"
framespacing="0" framespacing="0"
allowfullscreen="true" allowfullscreen="true"
></iframe> ></iframe>

View file

@ -0,0 +1,59 @@
{% assign video_url = include.src %}
{% assign title = include.title %}
{% assign poster_url = include.poster %}
{% assign types = include.types | default: '' | strip | split: '|' %}
{% unless video_url contains '://' %}
{%- capture video_url -%}
{% include media-url.html src=video_url %}
{%- endcapture -%}
{% endunless %}
{% if poster_url %}
{% unless poster_url contains '://' %}
{%- capture poster_url -%}
{% include media-url.html src=poster_url subpath=page.media_subpath %}
{%- endcapture -%}
{% endunless %}
{% assign poster = 'poster="' | append: poster_url | append: '"' %}
{% endif %}
{% assign attributes = 'controls' %}
{% if include.autoplay %}
{% assign attributes = attributes | append: ' ' | append: 'autoplay' %}
{% endif %}
{% if include.loop %}
{% assign attributes = attributes | append: ' ' | append: 'loop' %}
{% endif %}
{% if include.muted %}
{% assign attributes = attributes | append: ' ' | append: 'muted' %}
{% endif %}
<p>
<video class="embed-video file" {{ poster }} {{ attributes }}>
{% assign extension = video_url | split: '.' | last %}
{% assign types = extension | concat: types %}
{% assign ext_size = extension | size %}
{% assign src_size = video_url | size %}
{% assign slice_size = src_size | minus: ext_size %}
{% assign filepath = video_url | slice: 0, slice_size %}
{% for type in types %}
{% assign src = filepath | append: type %}
{% assign media_item = site.data.media | find: 'extension', type %}
{% assign mime_type = media_item.mime_type | default: type %}
<source src="{{ src }}" type="video/{{ mime_type }}">
{% endfor %}
Your browser does not support the video tag. Here is a
<a href="{{ video_url | strip }}">link to the video file</a> instead.
</video>
{% if title %}
<em>{{ title }}</em>
{% endif %}
</p>

View file

@ -1,5 +1,5 @@
<iframe <iframe
class="embed-video youtube" class="embed-video"
loading="lazy" loading="lazy"
src="https://www.youtube.com/embed/{{ include.id }}" src="https://www.youtube.com/embed/{{ include.id }}"
title="YouTube video player" title="YouTube video player"

View file

@ -34,7 +34,14 @@
{%- endcapture -%} {%- endcapture -%}
{%- capture _theme -%} {%- capture _theme -%}
<a href="https://github.com/cotes2020/jekyll-theme-chirpy" target="_blank" rel="noopener">Chirpy</a> <a
data-bs-toggle="tooltip"
data-bs-placement="top"
title="v{{ theme.version }}"
href="https://github.com/cotes2020/jekyll-theme-chirpy"
target="_blank"
rel="noopener"
>Chirpy</a>
{%- endcapture -%} {%- endcapture -%}
{{ site.data.locales[include.lang].meta | replace: ':PLATFORM', _platform | replace: ':THEME', _theme }} {{ site.data.locales[include.lang].meta | replace: ':PLATFORM', _platform | replace: ':THEME', _theme }}

View file

@ -1,8 +0,0 @@
<!-- GoatCounter -->
<script
data-goatcounter="https://{{ site.goatcounter.id }}.goatcounter.com/count"
async
src="https://gc.zgo.at/count.js"
></script>

View file

@ -1,14 +0,0 @@
<!--
The GA snippet
-->
<!-- Global site tag (gtag.js) - Google Analytics -->
<script defer src="https://www.googletagmanager.com/gtag/js?id={{ site.google_analytics.id }}"></script>
<script>
document.addEventListener("DOMContentLoaded", function(event) {
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '{{ site.google_analytics.id }}');
});
</script>

View file

@ -20,7 +20,7 @@
{% unless src contains '://' %} {% unless src contains '://' %}
{%- capture img_url -%} {%- capture img_url -%}
{% include img-url.html src=src img_path=page.img_path absolute=true %} {% include media-url.html src=src subpath=page.media_subpath absolute=true %}
{%- endcapture -%} {%- endcapture -%}
{%- capture old_url -%}{{ src | absolute_url }}{%- endcapture -%} {%- capture old_url -%}{{ src | absolute_url }}{%- endcapture -%}
@ -31,7 +31,7 @@
{% elsif site.social_preview_image %} {% elsif site.social_preview_image %}
{%- capture img_url -%} {%- capture img_url -%}
{% include img-url.html src=site.social_preview_image absolute=true %} {% include media-url.html src=site.social_preview_image absolute=true %}
{%- endcapture -%} {%- endcapture -%}
{%- capture og_image -%} {%- capture og_image -%}
@ -59,34 +59,30 @@
{% include_cached favicons.html %} {% include_cached favicons.html %}
{% if site.resources.ignore_env != jekyll.environment and site.resources.self_hosted %} <!-- Resource Hints -->
<link href="{{ site.data.origin[type].webfonts | relative_url }}" rel="stylesheet"> {% unless site.assets.self_host.enabled %}
{% for hint in site.data.origin.cors.resource_hints %}
{% else %} {% for link in hint.links %}
{% for cdn in site.data.origin[type].cdns %} <link rel="{{ link.rel }}" href="{{ hint.url }}" {{ link.opts | join: ' ' }}>
<link rel="preconnect" href="{{ cdn.url }}" {{ cdn.args }}>
<link rel="dns-prefetch" href="{{ cdn.url }}" {{ cdn.args }}>
{% endfor %} {% endfor %}
{% endfor %}
<link rel="stylesheet" href="{{ site.data.origin[type].webfonts | relative_url }}"> {% endunless %}
{% endif %}
<!-- GA -->
{% if jekyll.environment == 'production' and site.google_analytics.id != empty and site.google_analytics.id %}
<link rel="preconnect" href="https://www.google-analytics.com" crossorigin="use-credentials">
<link rel="dns-prefetch" href="https://www.google-analytics.com">
<link rel="preconnect" href="https://www.googletagmanager.com" crossorigin="anonymous">
<link rel="dns-prefetch" href="https://www.googletagmanager.com">
{% endif %}
<!-- Bootstrap --> <!-- Bootstrap -->
<link rel="stylesheet" href="{{ site.data.origin[type].bootstrap.css | relative_url }}"> {% unless jekyll.environment == 'production' %}
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css">
{% endunless %}
<!-- Font Awesome --> <!-- Theme style -->
<link rel="stylesheet" href="{{ '/assets/css/:THEME.css' | replace: ':THEME', site.theme | relative_url }}">
<!-- Web Font -->
<link rel="stylesheet" href="{{ site.data.origin[type].webfonts | relative_url }}">
<!-- Font Awesome Icons -->
<link rel="stylesheet" href="{{ site.data.origin[type].fontawesome.css | relative_url }}"> <link rel="stylesheet" href="{{ site.data.origin[type].fontawesome.css | relative_url }}">
<link rel="stylesheet" href="{{ '/assets/css/:THEME.css' | replace: ':THEME', site.theme | relative_url }}"> <!-- 3rd-party Dependencies -->
{% if site.toc and page.toc %} {% if site.toc and page.toc %}
<link rel="stylesheet" href="{{ site.data.origin[type].toc.css | relative_url }}"> <link rel="stylesheet" href="{{ site.data.origin[type].toc.css | relative_url }}">
@ -97,8 +93,8 @@
{% endif %} {% endif %}
{% if page.layout == 'page' or page.layout == 'post' %} {% if page.layout == 'page' or page.layout == 'post' %}
<!-- Manific Popup --> <!-- Image Popup -->
<link rel="stylesheet" href="{{ site.data.origin[type].magnific-popup.css | relative_url }}"> <link rel="stylesheet" href="{{ site.data.origin[type].glightbox.css | relative_url }}">
{% endif %} {% endif %}
<!-- JavaScript --> <!-- JavaScript -->

View file

@ -2,12 +2,7 @@
<!-- commons --> <!-- commons -->
{% assign urls = site.data.origin[type].jquery.js {% assign urls = site.data.origin[type].search.js %}
| append: ','
| append: site.data.origin[type].bootstrap.js
| append: ','
| append: site.data.origin[type].search.js
%}
<!-- layout specified --> <!-- layout specified -->
@ -20,7 +15,7 @@
<!-- image lazy-loading & popup & clipboard --> <!-- image lazy-loading & popup & clipboard -->
{% assign urls = urls {% assign urls = urls
| append: ',' | append: ','
| append: site.data.origin[type]['magnific-popup'].js | append: site.data.origin[type].glightbox.js
| append: ',' | append: ','
| append: site.data.origin[type].clipboard.js | append: site.data.origin[type].clipboard.js
%} %}
@ -33,7 +28,7 @@
or page.layout == 'category' or page.layout == 'category'
or page.layout == 'tag' or page.layout == 'tag'
%} %}
{% assign locale = site.lang | split: '-' | first %} {% assign locale = include.lang | split: '-' | first %}
{% assign urls = urls {% assign urls = urls
| append: ',' | append: ','
@ -68,46 +63,45 @@
{% endcase %} {% endcase %}
{% capture script %}{{ js_dist }}{{ js }}.min.js{% endcapture %} {% capture script %}{{ js_dist }}{{ js }}.min.js{% endcapture %}
<script defer src="{{ script | relative_url }}"></script> <script src="{{ script | relative_url }}"></script>
{% if page.math %} {% if page.math %}
<!-- MathJax --> <!-- MathJax -->
<script> <script src="{{ '/assets/js/data/mathjax.js' | relative_url }}"></script>
/* see: <https://docs.mathjax.org/en/latest/options/input/tex.html#tex-options> */
MathJax = {
tex: {
/* start/end delimiter pairs for in-line math */
inlineMath: [
['$', '$'],
['\\(', '\\)']
],
/* start/end delimiter pairs for display math */
displayMath: [
['$$', '$$'],
['\\[', '\\]']
],
/* equation numbering */
tags: 'ams'
}
};
</script>
<script src="https://cdnjs.cloudflare.com/polyfill/v3/polyfill.min.js?features=es6"></script> <script src="https://cdnjs.cloudflare.com/polyfill/v3/polyfill.min.js?features=es6"></script>
<script id="MathJax-script" async src="{{ site.data.origin[type].mathjax.js | relative_url }}"></script> <script id="MathJax-script" async src="{{ site.data.origin[type].mathjax.js | relative_url }}"></script>
{% endif %} {% endif %}
<!-- Pageviews -->
{% if page.layout == 'post' %}
{% assign provider = site.pageviews.provider %}
{% if provider and provider != empty %}
{% case provider %}
{% when 'goatcounter' %}
{% if site.analytics[provider].id != empty and site.analytics[provider].id %}
{% include pageviews/{{ provider }}.html %}
{% endif %}
{% endcase %}
{% endif %}
{% endif %}
{% if page.mermaid %}
{% include mermaid.html %}
{% endif %}
{% if jekyll.environment == 'production' %} {% if jekyll.environment == 'production' %}
<!-- PWA --> <!-- PWA -->
{% if site.pwa.enabled %} {% if site.pwa.enabled %}
<script defer src="{{ 'app.min.js' | prepend: js_dist | relative_url }}"></script> <script defer src="{{ 'app.min.js' | prepend: js_dist | relative_url }}"></script>
{% endif %} {% endif %}
<!-- GA --> <!-- Web Analytics -->
{% if site.google_analytics.id != empty and site.google_analytics.id %} {% for analytics in site.analytics %}
{% include google-analytics.html %} {% capture str %}{{ analytics }}{% endcapture %}
{% endif %} {% assign type = str | split: '{' | first %}
{% if site.analytics[type].id and site.analytics[type].id != empty %}
<!-- GoatCounter --> {% include analytics/{{ type }}.html %}
{% if site.goatcounter.id != empty and site.goatcounter.id %}
{% include goatcounter.html %}
{% endif %} {% endif %}
{% endfor %}
{% endif %} {% endif %}

View file

@ -1,7 +1,9 @@
{% comment %} {% comment %}
Detect appearance language and return it through variable "lang" Detect appearance language and return it through variable "lang"
{% endcomment %} {% endcomment %}
{% if site.data.locales[site.lang] %} {% if site.data.locales[page.lang] %}
{% assign lang = page.lang %}
{% elsif site.data.locales[site.lang] %}
{% assign lang = site.lang %} {% assign lang = site.lang %}
{% else %} {% else %}
{% assign lang = 'en' %} {% assign lang = 'en' %}

View file

@ -1,25 +1,25 @@
{%- comment -%} {%- comment -%}
Generate image final URL based on `site.img_cdn`, `page.img_path` Generate media resource final URL based on `site.cdn`, `page.media_subpath`
Arguments: Arguments:
src - required, basic image path src - required, basic media resources path
img_path - optional, relative path of image subpath - optional, relative path of media resources
absolute - optional, boolean, if true, generate absolute URL absolute - optional, boolean, if true, generate absolute URL
Return: Return:
image URL media resources URL
{%- endcomment -%} {%- endcomment -%}
{% assign url = include.src %} {% assign url = include.src %}
{%- if url -%} {%- if url -%}
{% unless url contains ':' %} {% unless url contains ':' %}
{%- comment -%} Add page image path prefix {%- endcomment -%} {%- comment -%} Add media resources subpath prefix {%- endcomment -%}
{% assign url = include.img_path | default: '' | append: '/' | append: url %} {% assign url = include.subpath | default: '' | append: '/' | append: url %}
{%- comment -%} Prepend CND URL {%- endcomment -%} {%- comment -%} Prepend CND URL {%- endcomment -%}
{% if site.img_cdn %} {% if site.cdn %}
{% assign url = site.img_cdn | append: '/' | append: url %} {% assign url = site.cdn | append: '/' | append: url %}
{% endif %} {% endif %}
{% assign url = url | replace: '///', '/' | replace: '//', '/' | replace: ':/', '://' %} {% assign url = url | replace: '///', '/' | replace: '//', '/' | replace: ':/', '://' %}

View file

@ -1,6 +1,5 @@
<!-- mermaid-js loader --> <!-- mermaid-js loader -->
<script type="text/javascript"> <script type="text/javascript">
(function () {
function updateMermaid(event) { function updateMermaid(event) {
if (event.source === window && event.data && event.data.direction === ModeToggle.ID) { if (event.source === window && event.data && event.data.direction === ModeToggle.ID) {
const mode = event.data.message; const mode = event.data.message;
@ -12,11 +11,15 @@
let expectedTheme = mode === ModeToggle.DARK_MODE ? 'dark' : 'default'; let expectedTheme = mode === ModeToggle.DARK_MODE ? 'dark' : 'default';
let config = { theme: expectedTheme }; let config = { theme: expectedTheme };
/* Re-render the SVG <https://github.com/mermaid-js/mermaid/issues/311#issuecomment-332557344> */ {%- comment -%}
$('.mermaid').each(function () { Re-render the SVG <https://github.com/mermaid-js/mermaid/issues/311#issuecomment-332557344>
let svgCode = $(this).prev().children().html(); {%- endcomment -%}
$(this).removeAttr('data-processed'); const mermaidList = document.getElementsByClassName('mermaid');
$(this).html(svgCode);
[...mermaidList].forEach((elem) => {
const svgCode = elem.previousSibling.children.item(0).innerHTML;
elem.innerHTML = svgCode;
elem.removeAttribute('data-processed');
}); });
mermaid.initialize(config); mermaid.initialize(config);
@ -24,6 +27,7 @@
} }
} }
(function () {
let initTheme = 'default'; let initTheme = 'default';
const html = document.documentElement; const html = document.documentElement;
@ -35,15 +39,16 @@
} }
let mermaidConf = { let mermaidConf = {
theme: initTheme /* <default|dark|forest|neutral> */ theme: initTheme {%- comment -%} <default | dark | forest | neutral> {%- endcomment -%}
}; };
/* Create mermaid tag */ {%- comment -%} Create mermaid tag {%- endcomment -%}
document.querySelectorAll('pre>code.language-mermaid').forEach((elem) => { const basicList = document.getElementsByClassName('language-mermaid');
[...basicList].forEach((elem) => {
const svgCode = elem.textContent; const svgCode = elem.textContent;
const backup = elem.parentElement; const backup = elem.parentElement;
backup.classList.add('unloaded'); backup.classList.add('d-none');
/* create mermaid node */ {%- comment -%} create mermaid node {%- endcomment -%}
let mermaid = document.createElement('pre'); let mermaid = document.createElement('pre');
mermaid.classList.add('mermaid'); mermaid.classList.add('mermaid');
const text = document.createTextNode(svgCode); const text = document.createTextNode(svgCode);
@ -52,7 +57,6 @@
}); });
mermaid.initialize(mermaidConf); mermaid.initialize(mermaidConf);
window.addEventListener('message', updateMermaid); window.addEventListener('message', updateMermaid);
})(); })();
</script> </script>

View file

@ -19,45 +19,32 @@
} }
constructor() { constructor() {
if (this.hasMode) { let self = this;
if (this.isDarkMode) {
if (!this.isSysDarkPrefer) { {%- comment -%} always follow the system prefers {%- endcomment -%}
this.setDark(); this.sysDarkPrefers.addEventListener('change', () => {
if (self.hasMode) {
self.clearMode();
} }
self.notify();
});
if (!this.hasMode) {
return;
}
if (this.isDarkMode) {
this.setDark();
} else { } else {
if (this.isSysDarkPrefer) {
this.setLight(); this.setLight();
} }
} }
}
let self = this;
/* always follow the system prefers */
this.sysDarkPrefers.addEventListener('change', () => {
if (self.hasMode) {
if (self.isDarkMode) {
if (!self.isSysDarkPrefer) {
self.setDark();
}
} else {
if (self.isSysDarkPrefer) {
self.setLight();
}
}
self.clearMode();
}
self.notify();
});
} /* constructor() */
get sysDarkPrefers() { get sysDarkPrefers() {
return window.matchMedia('(prefers-color-scheme: dark)'); return window.matchMedia('(prefers-color-scheme: dark)');
} }
get isSysDarkPrefer() { get isPreferDark() {
return this.sysDarkPrefers.matches; return this.sysDarkPrefers.matches;
} }
@ -65,10 +52,6 @@
return this.mode === ModeToggle.DARK_MODE; return this.mode === ModeToggle.DARK_MODE;
} }
get isLightMode() {
return this.mode === ModeToggle.LIGHT_MODE;
}
get hasMode() { get hasMode() {
return this.mode != null; return this.mode != null;
} }
@ -77,12 +60,12 @@
return sessionStorage.getItem(ModeToggle.MODE_KEY); return sessionStorage.getItem(ModeToggle.MODE_KEY);
} }
/* get the current mode on screen */ {%- comment -%} get the current mode on screen {%- endcomment -%}
get modeStatus() { get modeStatus() {
if (this.isDarkMode || (!this.hasMode && this.isSysDarkPrefer)) { if (this.hasMode) {
return ModeToggle.DARK_MODE; return this.mode;
} else { } else {
return ModeToggle.LIGHT_MODE; return this.isPreferDark ? ModeToggle.DARK_MODE : ModeToggle.LIGHT_MODE;
} }
} }
@ -101,7 +84,9 @@
sessionStorage.removeItem(ModeToggle.MODE_KEY); sessionStorage.removeItem(ModeToggle.MODE_KEY);
} }
/* Notify another plugins that the theme mode has changed */ {%- comment -%}
Notify another plugins that the theme mode has changed
{%- endcomment -%}
notify() { notify() {
window.postMessage( window.postMessage(
{ {
@ -114,21 +99,9 @@
flipMode() { flipMode() {
if (this.hasMode) { if (this.hasMode) {
if (this.isSysDarkPrefer) {
if (this.isLightMode) {
this.clearMode(); this.clearMode();
} else { } else {
this.setLight(); if (this.isPreferDark) {
}
} else {
if (this.isDarkMode) {
this.clearMode();
} else {
this.setDark();
}
}
} else {
if (this.isSysDarkPrefer) {
this.setLight(); this.setLight();
} else { } else {
this.setDark(); this.setDark();
@ -136,8 +109,8 @@
} }
this.notify(); this.notify();
} /* flipMode() */ }
} /* ModeToggle */ }
const modeToggle = new ModeToggle(); const modeToggle = new ModeToggle();
</script> </script>

View file

@ -0,0 +1,18 @@
<!-- Display GoatCounter pageviews -->
<script>
let pv = document.getElementById('pageviews');
if (pv !== null) {
const uri = location.pathname.replace(/\/$/, '');
const url = `https://{{ site.analytics.goatcounter.id }}.goatcounter.com/counter/${encodeURIComponent(uri)}.json`;
fetch(url)
.then((response) => response.json())
.then((data) => {
pv.innerText = new Intl.NumberFormat().format(data.count);
})
.catch((error) => {
pv.innerText = '1';
});
}
</script>

View file

@ -0,0 +1,16 @@
{%- comment -%}
Get post description or generate it from the post content.
{%- endcomment -%}
{%- assign max_length = include.max_length | default: 200 -%}
{%- capture description -%}
{%- if post.description -%}
{{- post.description -}}
{%- else -%}
{%- include no-linenos.html content=post.content -%}
{{- content | markdownify | strip_html -}}
{%- endif -%}
{%- endcapture -%}
{{- description | strip | truncate: max_length | escape -}}

View file

@ -97,7 +97,7 @@
{% assign _lazyload = true %} {% assign _lazyload = true %}
{%- capture _img_url -%} {%- capture _img_url -%}
{% include img-url.html src=_src img_path=page.img_path %} {% include media-url.html src=_src subpath=page.media_subpath %}
{%- endcapture -%} {%- endcapture -%}
{% assign _path_prefix = _img_url | remove: _src %} {% assign _path_prefix = _img_url | remove: _src %}

View file

@ -21,6 +21,7 @@
{% assign match_posts = match_posts | push: site.tags[tag] | uniq %} {% assign match_posts = match_posts | push: site.tags[tag] | uniq %}
{% endfor %} {% endfor %}
{% assign match_posts = match_posts | reverse %}
{% assign last_index = match_posts.size | minus: 1 %} {% assign last_index = match_posts.size | minus: 1 %}
{% assign score_list = '' | split: '' %} {% assign score_list = '' | split: '' %}
@ -81,10 +82,7 @@
{% include datetime.html date=post.date lang=include.lang %} {% include datetime.html date=post.date lang=include.lang %}
<h4 class="pt-0 my-2">{{ post.title }}</h4> <h4 class="pt-0 my-2">{{ post.title }}</h4>
<div class="text-muted"> <div class="text-muted">
<p> <p>{% include post-description.html %}</p>
{% include no-linenos.html content=post.content %}
{{ content | markdownify | strip_html | truncate: 200 | escape }}
</p>
</div> </div>
</div> </div>
</a> </a>

View file

@ -19,7 +19,7 @@
{% capture not_found %}<p class="mt-5">{{ site.data.locales[include.lang].search.no_results }}</p>{% endcapture %} {% capture not_found %}<p class="mt-5">{{ site.data.locales[include.lang].search.no_results }}</p>{% endcapture %}
<script> <script>
/* Note: dependent library will be loaded in `js-selector.html` */ {%- comment -%} Note: dependent library will be loaded in `js-selector.html` {%- endcomment -%}
SimpleJekyllSearch({ SimpleJekyllSearch({
searchInput: document.getElementById('search-input'), searchInput: document.getElementById('search-input'),
resultsContainer: document.getElementById('search-results'), resultsContainer: document.getElementById('search-results'),

View file

@ -1,6 +1,6 @@
<!-- The Search results --> <!-- The Search results -->
<div id="search-result-wrapper" class="d-flex justify-content-center unloaded"> <div id="search-result-wrapper" class="d-flex justify-content-center d-none">
<div class="col-11 content"> <div class="col-11 content">
<div id="search-hints"> <div id="search-hints">
{% include_cached trending-tags.html %} {% include_cached trending-tags.html %}

View file

@ -5,7 +5,7 @@
<a href="{{ '/' | relative_url }}" id="avatar" class="rounded-circle"> <a href="{{ '/' | relative_url }}" id="avatar" class="rounded-circle">
{%- if site.avatar != empty and site.avatar -%} {%- if site.avatar != empty and site.avatar -%}
{%- capture avatar_url -%} {%- capture avatar_url -%}
{% include img-url.html src=site.avatar %} {% include media-url.html src=site.avatar %}
{%- endcapture -%} {%- endcapture -%}
<img src="{{- avatar_url -}}" width="112" height="112" alt="avatar" onerror="this.style.display='none'"> <img src="{{- avatar_url -}}" width="112" height="112" alt="avatar" onerror="this.style.display='none'">
{%- endif -%} {%- endif -%}
@ -44,7 +44,7 @@
<div class="sidebar-bottom d-flex flex-wrap align-items-center w-100"> <div class="sidebar-bottom d-flex flex-wrap align-items-center w-100">
{% unless site.theme_mode %} {% unless site.theme_mode %}
<button type="button" class="mode-toggle btn" aria-label="Switch Mode"> <button type="button" class="btn btn-link nav-link" aria-label="Switch Mode" id="mode-toggle">
<i class="fas fa-adjust"></i> <i class="fas fa-adjust"></i>
</button> </button>

View file

@ -6,8 +6,8 @@
{% endif %} {% endif %}
{% if enable_toc %} {% if enable_toc %}
<section id="toc-wrapper" class="ps-0 pe-4"> <section id="toc-wrapper" class="d-none ps-0 pe-4">
<h2 class="panel-heading ps-3 pt-2 mb-2">{{- site.data.locales[include.lang].panel.toc -}}</h2> <h2 class="panel-heading ps-3 mb-2">{{- site.data.locales[include.lang].panel.toc -}}</h2>
<nav id="toc"></nav> <nav id="toc"></nav>
</section> </section>
{% endif %} {% endif %}

View file

@ -59,7 +59,7 @@
<i class="fas fa-search fa-fw"></i> <i class="fas fa-search fa-fw"></i>
</button> </button>
<search class="align-items-center ms-3 ms-lg-0"> <search id="search" class="align-items-center ms-3 ms-lg-0">
<i class="fas fa-search fa-fw"></i> <i class="fas fa-search fa-fw"></i>
<input <input
class="form-control" class="form-control"

View file

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

View file

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

View file

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

View file

@ -3,18 +3,17 @@
*/ */
export function back2top() { export function back2top() {
const $window = $(window); const btn = document.getElementById('back-to-top');
const $btn = $('#back-to-top');
$window.on('scroll', () => { window.addEventListener('scroll', () => {
if ($window.scrollTop() > 50) { if (window.scrollY > 50) {
$btn.fadeIn(); btn.classList.add('show');
} else { } else {
$btn.fadeOut(); btn.classList.remove('show');
} }
}); });
$btn.on('click', () => { btn.addEventListener('click', () => {
$window.scrollTop(0); window.scrollTo({ top: 0 });
}); });
} }

View file

@ -1,36 +1,36 @@
/** /**
* Tab 'Categories' expand/close effect. * Tab 'Categories' expand/close effect.
*/ */
import 'bootstrap/js/src/collapse.js';
const childPrefix = 'l_'; const childPrefix = 'l_';
const parentPrefix = 'h_'; const parentPrefix = 'h_';
const collapse = $('.collapse'); const children = document.getElementsByClassName('collapse');
export function categoryCollapse() { export function categoryCollapse() {
/* close up top-category */ [...children].forEach((elem) => {
collapse.on('hide.bs.collapse', function () { const id = parentPrefix + elem.id.substring(childPrefix.length);
/* Bootstrap collapse events. */ const parentId = const parent = document.getElementById(id);
parentPrefix + $(this).attr('id').substring(childPrefix.length);
if (parentId) { // collapse sub-categories
$(`#${parentId} .far.fa-folder-open`).attr( elem.addEventListener('hide.bs.collapse', () => {
'class', if (parent) {
'far fa-folder fa-fw' parent.querySelector('.far.fa-folder-open').className =
); 'far fa-folder fa-fw';
$(`#${parentId} i.fas`).addClass('rotate'); parent.querySelector('.fas.fa-angle-down').classList.add('rotate');
$(`#${parentId}`).removeClass('hide-border-bottom'); parent.classList.remove('hide-border-bottom');
} }
}); });
/* expand the top category */ // expand sub-categories
collapse.on('show.bs.collapse', function () { elem.addEventListener('show.bs.collapse', () => {
const parentId = if (parent) {
parentPrefix + $(this).attr('id').substring(childPrefix.length); parent.querySelector('.far.fa-folder').className =
if (parentId) { 'far fa-folder-open fa-fw';
$(`#${parentId} .far.fa-folder`).attr( parent.querySelector('.fas.fa-angle-down').classList.remove('rotate');
'class', parent.classList.add('hide-border-bottom');
'far fa-folder-open fa-fw'
);
$(`#${parentId} i.fas`).removeClass('rotate');
$(`#${parentId}`).addClass('hide-border-bottom');
} }
}); });
});
} }

View file

@ -2,85 +2,88 @@
* Clipboard functions * Clipboard functions
* *
* Dependencies: * Dependencies:
* - popper.js (https://github.com/popperjs/popper-core) * clipboard.js (https://github.com/zenorocha/clipboard.js)
* - clipboard.js (https://github.com/zenorocha/clipboard.js)
*/ */
import Tooltip from 'bootstrap/js/src/tooltip';
const clipboardSelector = '.code-header>button'; const clipboardSelector = '.code-header>button';
const ICON_DEFAULT = 'far fa-clipboard';
const ICON_SUCCESS = 'fas fa-check'; const ICON_SUCCESS = 'fas fa-check';
const ATTR_TIMEOUT = 'timeout'; const ATTR_TIMEOUT = 'timeout';
const ATTR_TITLE_SUCCEED = 'data-title-succeed'; const ATTR_TITLE_SUCCEED = 'data-title-succeed';
const ATTR_TITLE_ORIGIN = 'data-bs-original-title'; const ATTR_TITLE_ORIGIN = 'data-bs-original-title';
const TIMEOUT = 2000; // in milliseconds const TIMEOUT = 2000; // in milliseconds
function isLocked(node) { function isLocked(node) {
if ($(node)[0].hasAttribute(ATTR_TIMEOUT)) { if (node.hasAttribute(ATTR_TIMEOUT)) {
let timeout = $(node).attr(ATTR_TIMEOUT); let timeout = node.getAttribute(ATTR_TIMEOUT);
if (Number(timeout) > Date.now()) { if (Number(timeout) > Date.now()) {
return true; return true;
} }
} }
return false; return false;
} }
function lock(node) { function lock(node) {
$(node).attr(ATTR_TIMEOUT, Date.now() + TIMEOUT); node.setAttribute(ATTR_TIMEOUT, Date.now() + TIMEOUT);
} }
function unlock(node) { function unlock(node) {
$(node).removeAttr(ATTR_TIMEOUT); node.removeAttribute(ATTR_TIMEOUT);
} }
function getIcon(btn) {
let iconNode = $(btn).children();
return iconNode.attr('class');
}
const ICON_DEFAULT = getIcon(clipboardSelector);
function showTooltip(btn) { function showTooltip(btn) {
const succeedTitle = $(btn).attr(ATTR_TITLE_SUCCEED); const succeedTitle = btn.getAttribute(ATTR_TITLE_SUCCEED);
$(btn).attr(ATTR_TITLE_ORIGIN, succeedTitle).tooltip('show'); btn.setAttribute(ATTR_TITLE_ORIGIN, succeedTitle);
Tooltip.getInstance(btn).show();
} }
function hideTooltip(btn) { function hideTooltip(btn) {
$(btn).tooltip('hide').removeAttr(ATTR_TITLE_ORIGIN); Tooltip.getInstance(btn).hide();
btn.removeAttribute(ATTR_TITLE_ORIGIN);
} }
function setSuccessIcon(btn) { function setSuccessIcon(btn) {
let btnNode = $(btn); const icon = btn.children[0];
let iconNode = btnNode.children(); icon.setAttribute('class', ICON_SUCCESS);
iconNode.attr('class', ICON_SUCCESS);
} }
function resumeIcon(btn) { function resumeIcon(btn) {
let btnNode = $(btn); const icon = btn.children[0];
let iconNode = btnNode.children(); icon.setAttribute('class', ICON_DEFAULT);
iconNode.attr('class', ICON_DEFAULT);
} }
export function initClipboard() { function setCodeClipboard() {
const clipboardList = document.querySelectorAll(clipboardSelector);
if (clipboardList.length === 0) {
return;
}
// Initial the clipboard.js object // Initial the clipboard.js object
if ($(clipboardSelector).length) {
const clipboard = new ClipboardJS(clipboardSelector, { const clipboard = new ClipboardJS(clipboardSelector, {
target(trigger) { target: (trigger) => {
let codeBlock = trigger.parentNode.nextElementSibling; const codeBlock = trigger.parentNode.nextElementSibling;
return codeBlock.querySelector('code .rouge-code'); return codeBlock.querySelector('code .rouge-code');
} }
}); });
const clipboardList = document.querySelectorAll(clipboardSelector);
[...clipboardList].map( [...clipboardList].map(
(elem) => (elem) =>
new bootstrap.Tooltip(elem, { new Tooltip(elem, {
placement: 'left' placement: 'left'
}) })
); );
clipboard.on('success', (e) => { clipboard.on('success', (e) => {
const trigger = e.trigger;
e.clearSelection(); e.clearSelection();
const trigger = e.trigger;
if (isLocked(trigger)) { if (isLocked(trigger)) {
return; return;
} }
@ -95,14 +98,17 @@ export function initClipboard() {
unlock(trigger); unlock(trigger);
}, TIMEOUT); }, TIMEOUT);
}); });
}
function setLinkClipboard() {
const btnCopyLink = document.getElementById('copy-link');
if (btnCopyLink === null) {
return;
} }
/* --- Post link sharing --- */ btnCopyLink.addEventListener('click', (e) => {
const target = e.target;
const btnCopyLink = $('#copy-link');
btnCopyLink.on('click', (e) => {
let target = $(e.target);
if (isLocked(target)) { if (isLocked(target)) {
return; return;
@ -110,21 +116,28 @@ export function initClipboard() {
// Copy URL to clipboard // Copy URL to clipboard
navigator.clipboard.writeText(window.location.href).then(() => { navigator.clipboard.writeText(window.location.href).then(() => {
const defaultTitle = target.attr(ATTR_TITLE_ORIGIN); const defaultTitle = target.getAttribute(ATTR_TITLE_ORIGIN);
const succeedTitle = target.attr(ATTR_TITLE_SUCCEED); const succeedTitle = target.getAttribute(ATTR_TITLE_SUCCEED);
// Switch tooltip title // Switch tooltip title
target.attr(ATTR_TITLE_ORIGIN, succeedTitle).tooltip('show'); target.setAttribute(ATTR_TITLE_ORIGIN, succeedTitle);
Tooltip.getInstance(target).show();
lock(target); lock(target);
setTimeout(() => { setTimeout(() => {
target.attr(ATTR_TITLE_ORIGIN, defaultTitle); target.setAttribute(ATTR_TITLE_ORIGIN, defaultTitle);
unlock(target); unlock(target);
}, TIMEOUT); }, TIMEOUT);
}); });
}); });
btnCopyLink.on('mouseleave', function (e) { btnCopyLink.addEventListener('mouseleave', (e) => {
const target = $(e.target); Tooltip.getInstance(e.target).hide();
target.tooltip('hide');
}); });
} }
export function initClipboard() {
setCodeClipboard();
setLinkClipboard();
}

View file

@ -11,7 +11,7 @@ const cover = {
}; };
function removeCover(clzss) { function removeCover(clzss) {
$(this).parent().removeClass(clzss); this.parentElement.classList.remove(clzss);
} }
function handleImage() { function handleImage() {
@ -30,32 +30,38 @@ function handleImage() {
* Switches the LQIP with the real image URL. * Switches the LQIP with the real image URL.
*/ */
function switchLQIP() { function switchLQIP() {
const $img = $(this); const src = this.getAttribute(ATTR_DATA_SRC);
const src = $img.attr(ATTR_DATA_SRC); this.setAttribute('src', encodeURI(src));
this.removeAttribute(ATTR_DATA_SRC);
$img.attr('src', encodeURI(src));
$img.removeAttr(ATTR_DATA_SRC);
} }
export function loadImg() { export function loadImg() {
const $images = $('article img'); const images = document.querySelectorAll('article img');
if ($images.length) { if (images.length === 0) {
$images.on('load', handleImage); return;
} }
images.forEach((img) => {
img.addEventListener('load', handleImage);
});
// Images loaded from the browser cache do not trigger the 'load' event // Images loaded from the browser cache do not trigger the 'load' event
$('article img[loading="lazy"]').each(function () { document.querySelectorAll('article img[loading="lazy"]').forEach((img) => {
if (this.complete) { if (img.complete) {
removeCover.call(this, cover.SHIMMER); removeCover.call(img, cover.SHIMMER);
} }
}); });
// LQIPs set by the data URI or WebP will not trigger the 'load' event, // LQIPs set by the data URI or WebP will not trigger the 'load' event,
// so manually convert the URI to the URL of a high-resolution image. // so manually convert the URI to the URL of a high-resolution image.
const $lqips = $(`article img[${ATTR_DATA_LQIP}="true"]`); const lqips = document.querySelectorAll(
`article img[${ATTR_DATA_LQIP}="true"]`
);
if ($lqips.length) { if (lqips.length) {
$lqips.each(switchLQIP); lqips.forEach((lqip) => {
switchLQIP.call(lqip);
});
} }
} }

View file

@ -1,22 +1,15 @@
/** /**
* Set up image popup * Set up image popup
* *
* See: https://github.com/dimsemenov/Magnific-Popup * Dependencies: https://github.com/biati-digital/glightbox
*/ */
const IMG_CLASS = 'popup';
export function imgPopup() { export function imgPopup() {
if ($('.popup') <= 0) { if (document.getElementsByClassName(IMG_CLASS).length === 0) {
return; return;
} }
$('.popup').magnificPopup({ GLightbox({ selector: `.${IMG_CLASS}` });
type: 'image',
closeOnContentClick: true,
showCloseBtn: false,
zoom: {
enabled: true,
duration: 300,
easing: 'ease-in-out'
}
});
} }

View file

@ -15,15 +15,15 @@ class LocaleHelper {
} }
static get locale() { static get locale() {
return $('html').attr('lang').substring(0, 2); return document.documentElement.getAttribute('lang').substring(0, 2);
} }
static getTimestamp(elem) { static getTimestamp(elem) {
return Number(elem.attr(LocaleHelper.attrTimestamp)); // unix timestamp return Number(elem.getAttribute(this.attrTimestamp)); // unix timestamp
} }
static getDateFormat(elem) { static getDateFormat(elem) {
return elem.attr(LocaleHelper.attrDateFormat); return elem.getAttribute(this.attrDateFormat);
} }
} }
@ -31,21 +31,23 @@ export function initLocaleDatetime() {
dayjs.locale(LocaleHelper.locale); dayjs.locale(LocaleHelper.locale);
dayjs.extend(window.dayjs_plugin_localizedFormat); dayjs.extend(window.dayjs_plugin_localizedFormat);
$(`[${LocaleHelper.attrTimestamp}]`).each(function () { document
const date = dayjs.unix(LocaleHelper.getTimestamp($(this))); .querySelectorAll(`[${LocaleHelper.attrTimestamp}]`)
const text = date.format(LocaleHelper.getDateFormat($(this))); .forEach((elem) => {
$(this).text(text); const date = dayjs.unix(LocaleHelper.getTimestamp(elem));
$(this).removeAttr(LocaleHelper.attrTimestamp); const text = date.format(LocaleHelper.getDateFormat(elem));
$(this).removeAttr(LocaleHelper.attrDateFormat); elem.textContent = text;
elem.removeAttribute(LocaleHelper.attrTimestamp);
elem.removeAttribute(LocaleHelper.attrDateFormat);
// setup tooltips // setup tooltips
const tooltip = $(this).attr('data-bs-toggle'); if (
if (typeof tooltip === 'undefined' || tooltip !== 'tooltip') { elem.hasAttribute('data-bs-toggle') &&
return; elem.getAttribute('data-bs-toggle') === 'tooltip'
) {
// see: https://day.js.org/docs/en/display/format#list-of-localized-formats
const tooltipText = date.format('llll');
elem.setAttribute('data-bs-title', tooltipText);
} }
const tooltipText = date.format('llll'); // see: https://day.js.org/docs/en/display/format#list-of-localized-formats
$(this).attr('data-bs-title', tooltipText);
new bootstrap.Tooltip($(this));
}); });
} }

View file

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

View file

@ -1,121 +1,109 @@
/** /**
* This script make #search-result-wrapper switch to unloaded or shown automatically. * 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 $content = $('#main-wrapper>.container>.row');
const $topbarTitle = $('#topbar-title');
const $search = $('search');
const $resultWrapper = $('#search-result-wrapper');
const $results = $('#search-results');
const $input = $('#search-input');
const $hints = $('#search-hints');
const $viewport = $('html,body');
// class names const btnSbTrigger = document.getElementById('sidebar-trigger');
const C_LOADED = 'loaded'; const btnSearchTrigger = document.getElementById('search-trigger');
const C_UNLOADED = 'unloaded'; const btnCancel = document.getElementById('search-cancel');
const C_FOCUS = 'input-focus'; const content = document.querySelectorAll('#main-wrapper>.container>.row');
const C_FLEX = 'd-flex'; const topbarTitle = document.getElementById('topbar-title');
const search = document.getElementById('search');
const resultWrapper = document.getElementById('search-result-wrapper');
const results = document.getElementById('search-results');
const input = document.getElementById('search-input');
const hints = document.getElementById('search-hints');
class ScrollBlocker { // CSS class names
static offset = 0; const LOADED = 'd-block';
static resultVisible = false; const UNLOADED = 'd-none';
const FOCUS = 'input-focus';
const FLEX = 'd-flex';
static on() { /* Actions in mobile screens (Sidebar hidden) */
ScrollBlocker.offset = window.scrollY;
$viewport.scrollTop(0);
}
static off() {
$viewport.scrollTop(ScrollBlocker.offset);
}
}
/*--- Actions in mobile screens (Sidebar hidden) ---*/
class MobileSearchBar { class MobileSearchBar {
static on() { static on() {
$btnSbTrigger.addClass(C_UNLOADED); btnSbTrigger.classList.add(UNLOADED);
$topbarTitle.addClass(C_UNLOADED); topbarTitle.classList.add(UNLOADED);
$btnSearchTrigger.addClass(C_UNLOADED); btnSearchTrigger.classList.add(UNLOADED);
$search.addClass(C_FLEX); search.classList.add(FLEX);
$btnCancel.addClass(C_LOADED); btnCancel.classList.add(LOADED);
} }
static off() { static off() {
$btnCancel.removeClass(C_LOADED); btnCancel.classList.remove(LOADED);
$search.removeClass(C_FLEX); search.classList.remove(FLEX);
$btnSbTrigger.removeClass(C_UNLOADED); btnSbTrigger.classList.remove(UNLOADED);
$topbarTitle.removeClass(C_UNLOADED); topbarTitle.classList.remove(UNLOADED);
$btnSearchTrigger.removeClass(C_UNLOADED); btnSearchTrigger.classList.remove(UNLOADED);
} }
} }
class ResultSwitch { class ResultSwitch {
static resultVisible = false;
static on() { static on() {
if (!ScrollBlocker.resultVisible) { if (!this.resultVisible) {
// the block method must be called before $(#main-wrapper>.container) unloaded. resultWrapper.classList.remove(UNLOADED);
ScrollBlocker.on(); content.forEach((el) => {
$resultWrapper.removeClass(C_UNLOADED); el.classList.add(UNLOADED);
$content.addClass(C_UNLOADED); });
ScrollBlocker.resultVisible = true; this.resultVisible = true;
} }
} }
static off() { static off() {
if (ScrollBlocker.resultVisible) { if (this.resultVisible) {
$results.empty(); results.innerHTML = '';
if ($hints.hasClass(C_UNLOADED)) {
$hints.removeClass(C_UNLOADED); if (hints.classList.contains(UNLOADED)) {
hints.classList.remove(UNLOADED);
} }
$resultWrapper.addClass(C_UNLOADED);
$content.removeClass(C_UNLOADED);
// now the release method must be called after $(#main-wrapper>.container) display resultWrapper.classList.add(UNLOADED);
ScrollBlocker.off(); content.forEach((el) => {
el.classList.remove(UNLOADED);
$input.val(''); });
ScrollBlocker.resultVisible = false; input.textContent = '';
this.resultVisible = false;
} }
} }
} }
function isMobileView() { function isMobileView() {
return $btnCancel.hasClass(C_LOADED); return btnCancel.classList.contains(LOADED);
} }
export function displaySearch() { export function displaySearch() {
$btnSearchTrigger.on('click', function () { btnSearchTrigger.addEventListener('click', () => {
MobileSearchBar.on(); MobileSearchBar.on();
ResultSwitch.on(); ResultSwitch.on();
$input.trigger('focus'); input.focus();
}); });
$btnCancel.on('click', function () { btnCancel.addEventListener('click', () => {
MobileSearchBar.off(); MobileSearchBar.off();
ResultSwitch.off(); ResultSwitch.off();
}); });
$input.on('focus', function () { input.addEventListener('focus', () => {
$search.addClass(C_FOCUS); search.classList.add(FOCUS);
}); });
$input.on('focusout', function () { input.addEventListener('focusout', () => {
$search.removeClass(C_FOCUS); search.classList.remove(FOCUS);
}); });
$input.on('input', () => { input.addEventListener('input', () => {
if ($input.val() === '') { if (input.value === '') {
if (isMobileView()) { if (isMobileView()) {
$hints.removeClass(C_UNLOADED); hints.classList.remove(UNLOADED);
} else { } else {
ResultSwitch.off(); ResultSwitch.off();
} }
} else { } else {
ResultSwitch.on(); ResultSwitch.on();
if (isMobileView()) { if (isMobileView()) {
$hints.addClass(C_UNLOADED); hints.classList.add(UNLOADED);
} }
} }
}); });

View file

@ -2,7 +2,6 @@
* Expand or close the sidebar in mobile screens. * Expand or close the sidebar in mobile screens.
*/ */
const $body = $('body');
const ATTR_DISPLAY = 'sidebar-display'; const ATTR_DISPLAY = 'sidebar-display';
class SidebarUtil { class SidebarUtil {
@ -10,9 +9,9 @@ class SidebarUtil {
static toggle() { static toggle() {
if (SidebarUtil.isExpanded === false) { if (SidebarUtil.isExpanded === false) {
$body.attr(ATTR_DISPLAY, ''); document.body.setAttribute(ATTR_DISPLAY, '');
} else { } else {
$body.removeAttr(ATTR_DISPLAY); document.body.removeAttribute(ATTR_DISPLAY);
} }
SidebarUtil.isExpanded = !SidebarUtil.isExpanded; SidebarUtil.isExpanded = !SidebarUtil.isExpanded;
@ -20,6 +19,9 @@ class SidebarUtil {
} }
export function sidebarExpand() { export function sidebarExpand() {
$('#sidebar-trigger').on('click', SidebarUtil.toggle); document
$('#mask').on('click', SidebarUtil.toggle); .getElementById('sidebar-trigger')
.addEventListener('click', SidebarUtil.toggle);
document.getElementById('mask').addEventListener('click', SidebarUtil.toggle);
} }

View file

@ -9,5 +9,7 @@ export function toc() {
orderedList: false, orderedList: false,
scrollSmooth: false scrollSmooth: false
}); });
document.getElementById('toc-wrapper').classList.remove('d-none');
} }
} }

View file

@ -1,12 +1,11 @@
/** import Tooltip from 'bootstrap/js/src/tooltip';
* Initial Bootstrap Tooltip.
*/
export function loadTooptip() { export function loadTooptip() {
const tooltipTriggerList = document.querySelectorAll( const tooltipTriggerList = document.querySelectorAll(
'[data-bs-toggle="tooltip"]' '[data-bs-toggle="tooltip"]'
); );
[...tooltipTriggerList].map( [...tooltipTriggerList].map(
(tooltipTriggerEl) => new bootstrap.Tooltip(tooltipTriggerEl) (tooltipTriggerEl) => new Tooltip(tooltipTriggerEl)
); );
} }

View file

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

View file

@ -7,11 +7,11 @@ import {
toc toc
} from './modules/plugins'; } from './modules/plugins';
initSidebar();
initTopbar();
loadImg(); loadImg();
toc();
imgPopup(); imgPopup();
initSidebar();
initLocaleDatetime(); initLocaleDatetime();
initClipboard(); initClipboard();
toc(); initTopbar();
basic(); basic();

View file

@ -0,0 +1,3 @@
---
permalink: /:basename
---

View file

@ -1,43 +1,40 @@
--- import { pwa, baseurl } from '../../_config.yml';
layout: compress import Toast from 'bootstrap/js/src/toast';
permalink: /assets/js/dist/:basename.min.js
---
if ('serviceWorker' in navigator) { if ('serviceWorker' in navigator) {
const isEnabled = '{{ site.pwa.enabled }}' === 'true'; if (pwa.enabled) {
const swUrl = `${baseurl}/sw.min.js`;
if (isEnabled) { const notification = document.getElementById('notification');
const swUrl = '{{ '/sw.min.js' | relative_url }}'; const btnRefresh = notification.querySelector('.toast-body>button');
const $notification = $('#notification'); const popupWindow = Toast.getOrCreateInstance(notification);
const $btnRefresh = $('#notification .toast-body>button');
navigator.serviceWorker.register(swUrl).then((registration) => { navigator.serviceWorker.register(swUrl).then((registration) => {
{% comment %}In case the user ignores the notification{% endcomment %} // In case the user ignores the notification
if (registration.waiting) { if (registration.waiting) {
$notification.toast('show'); popupWindow.show();
} }
registration.addEventListener('updatefound', () => { registration.addEventListener('updatefound', () => {
registration.installing.addEventListener('statechange', () => { registration.installing.addEventListener('statechange', () => {
if (registration.waiting) { if (registration.waiting) {
if (navigator.serviceWorker.controller) { if (navigator.serviceWorker.controller) {
$notification.toast('show'); popupWindow.show();
} }
} }
}); });
}); });
$btnRefresh.on('click', () => { btnRefresh.addEventListener('click', () => {
if (registration.waiting) { if (registration.waiting) {
registration.waiting.postMessage('SKIP_WAITING'); registration.waiting.postMessage('SKIP_WAITING');
} }
$notification.toast('hide'); popupWindow.hide();
}); });
}); });
let refreshing = false; let refreshing = false;
{% comment %}Detect controller change and refresh all the opened tabs{% endcomment %} // Detect controller change and refresh all the opened tabs
navigator.serviceWorker.addEventListener('controllerchange', () => { navigator.serviceWorker.addEventListener('controllerchange', () => {
if (!refreshing) { if (!refreshing) {
window.location.reload(); window.location.reload();

View file

@ -1,29 +1,10 @@
--- import { baseurl } from '../../_config.yml';
layout: compress
permalink: /:basename.min.js
# PWA service worker
---
const swconfUrl = '{{ '/assets/js/data/swconf.js' | relative_url }}'; importScripts(`${baseurl}/assets/js/data/swconf.js`);
importScripts(swconfUrl);
const purge = swconf.purge; const purge = swconf.purge;
function verifyHost(url) {
for (const host of swconf.allowHosts) {
const regex = RegExp(`^http(s)?://${host}/`);
if (regex.test(url)) {
return true;
}
}
return false;
}
function verifyUrl(url) { function verifyUrl(url) {
if (!verifyHost(url)) {
return false;
}
const requestPath = new URL(url).pathname; const requestPath = new URL(url).pathname;
for (const path of swconf.denyPaths) { for (const path of swconf.denyPaths) {
@ -34,10 +15,6 @@ function verifyUrl(url) {
return true; return true;
} }
if (!purge) {
swconf.allowHosts.push(location.host);
}
self.addEventListener('install', (event) => { self.addEventListener('install', (event) => {
if (purge) { if (purge) {
return; return;
@ -75,6 +52,10 @@ self.addEventListener('message', (event) => {
}); });
self.addEventListener('fetch', (event) => { self.addEventListener('fetch', (event) => {
if (event.request.headers.has('range')) {
return;
}
event.respondWith( event.respondWith(
caches.match(event.request).then((response) => { caches.match(event.request).then((response) => {
if (response) { if (response) {
@ -88,7 +69,7 @@ self.addEventListener('fetch', (event) => {
return response; return response;
} }
{% comment %}See: <https://developers.google.com/web/fundamentals/primers/service-workers#cache_and_return_requests>{% endcomment %} // See: <https://developers.google.com/web/fundamentals/primers/service-workers#cache_and_return_requests>
let responseToCache = response.clone(); let responseToCache = response.clone();
caches.open(swconf.cacheName).then((cache) => { caches.open(swconf.cacheName).then((cache) => {

View file

@ -1,10 +1,10 @@
--- ---
# Jekyll layout that compresses HTML # Jekyll layout that compresses HTML
# v3.1.0 # v3.2.0
# http://jch.penibelst.de/ # http://jch.penibelst.de/
# © 20142015 Anatol Broder # © 20142015 Anatol Broder
# MIT License # MIT License
--- ---
{% capture _LINE_FEED %} {% capture _LINE_FEED %}
{% endcapture %}{% if site.compress_html.ignore.envs contains jekyll.environment or site.compress_html.ignore.envs == "all" %}{{ content }}{% else %}{% capture _content %}{{ content }}{% endcapture %}{% assign _profile = site.compress_html.profile %}{% if site.compress_html.endings == "all" %}{% assign _endings = "html head body li dt dd optgroup option colgroup caption thead tbody tfoot tr td th" | split: " " %}{% else %}{% assign _endings = site.compress_html.endings %}{% endif %}{% for _element in _endings %}{% capture _end %}</{{ _element }}>{% endcapture %}{% assign _content = _content | remove: _end %}{% endfor %}{% if _profile and _endings %}{% assign _profile_endings = _content | size | plus: 1 %}{% endif %}{% for _element in site.compress_html.startings %}{% capture _start %}<{{ _element }}>{% endcapture %}{% assign _content = _content | remove: _start %}{% endfor %}{% if _profile and site.compress_html.startings %}{% assign _profile_startings = _content | size | plus: 1 %}{% endif %}{% if site.compress_html.comments == "all" %}{% assign _comments = "<!-- -->" | split: " " %}{% else %}{% assign _comments = site.compress_html.comments %}{% endif %}{% if _comments.size == 2 %}{% capture _comment_befores %}.{{ _content }}{% endcapture %}{% assign _comment_befores = _comment_befores | split: _comments.first %}{% for _comment_before in _comment_befores %}{% if forloop.first %}{% continue %}{% endif %}{% capture _comment_outside %}{% if _carry %}{{ _comments.first }}{% endif %}{{ _comment_before }}{% endcapture %}{% capture _comment %}{% unless _carry %}{{ _comments.first }}{% endunless %}{{ _comment_outside | split: _comments.last | first }}{% if _comment_outside contains _comments.last %}{{ _comments.last }}{% assign _carry = false %}{% else %}{% assign _carry = true %}{% endif %}{% endcapture %}{% assign _content = _content | remove_first: _comment %}{% endfor %}{% if _profile %}{% assign _profile_comments = _content | size | plus: 1 %}{% endif %}{% endif %}{% assign _pre_befores = _content | split: "<pre" %}{% assign _content = "" %}{% for _pre_before in _pre_befores %}{% assign _pres = _pre_before | split: "</pre>" %}{% assign _pres_after = "" %}{% if _pres.size != 0 %}{% if site.compress_html.blanklines %}{% assign _lines = _pres.last | split: _LINE_FEED %}{% capture _pres_after %}{% for _line in _lines %}{% assign _trimmed = _line | split: " " | join: " " %}{% if _trimmed != empty or forloop.last %}{% unless forloop.first %}{{ _LINE_FEED }}{% endunless %}{{ _line }}{% endif %}{% endfor %}{% endcapture %}{% else %}{% assign _pres_after = _pres.last | split: " " | join: " " %}{% endif %}{% endif %}{% capture _content %}{{ _content }}{% if _pre_before contains "</pre>" %}<pre{{ _pres.first }}</pre>{% endif %}{% unless _pre_before contains "</pre>" and _pres.size == 1 %}{{ _pres_after }}{% endunless %}{% endcapture %}{% endfor %}{% if _profile %}{% assign _profile_collapse = _content | size | plus: 1 %}{% endif %}{% if site.compress_html.clippings == "all" %}{% assign _clippings = "html head title base link meta style body article section nav aside h1 h2 h3 h4 h5 h6 hgroup header footer address p hr blockquote ol ul li dl dt dd figure figcaption main div table caption colgroup col tbody thead tfoot tr td th" | split: " " %}{% else %}{% assign _clippings = site.compress_html.clippings %}{% endif %}{% for _element in _clippings %}{% assign _edges = " <e;<e; </e>;</e>;</e> ;</e>" | replace: "e", _element | split: ";" %}{% assign _content = _content | replace: _edges[0], _edges[1] | replace: _edges[2], _edges[3] | replace: _edges[4], _edges[5] %}{% endfor %}{% if _profile and _clippings %}{% assign _profile_clippings = _content | size | plus: 1 %}{% endif %}{{ _content }}{% if _profile %} <table id="compress_html_profile_{{ site.time | date: "%Y%m%d" }}" class="compress_html_profile"> <thead> <tr> <td>Step <td>Bytes <tbody> <tr> <td>raw <td>{{ content | size }}{% if _profile_endings %} <tr> <td>endings <td>{{ _profile_endings }}{% endif %}{% if _profile_startings %} <tr> <td>startings <td>{{ _profile_startings }}{% endif %}{% if _profile_comments %} <tr> <td>comments <td>{{ _profile_comments }}{% endif %}{% if _profile_collapse %} <tr> <td>collapse <td>{{ _profile_collapse }}{% endif %}{% if _profile_clippings %} <tr> <td>clippings <td>{{ _profile_clippings }}{% endif %} </table>{% endif %}{% endif %} {% endcapture %}{% if site.compress_html.ignore.envs contains jekyll.environment or site.compress_html.ignore.envs == "all" or page.compress_html == false %}{{ content }}{% else %}{% capture _content %}{{ content }}{% endcapture %}{% assign _profile = site.compress_html.profile %}{% if site.compress_html.endings == "all" %}{% assign _endings = "html head body li dt dd optgroup option colgroup caption thead tbody tfoot tr td th" | split: " " %}{% else %}{% assign _endings = site.compress_html.endings %}{% endif %}{% for _element in _endings %}{% capture _end %}</{{ _element }}>{% endcapture %}{% assign _content = _content | remove: _end %}{% endfor %}{% if _profile and _endings %}{% assign _profile_endings = _content | size | plus: 1 %}{% endif %}{% for _element in site.compress_html.startings %}{% capture _start %}<{{ _element }}>{% endcapture %}{% assign _content = _content | remove: _start %}{% endfor %}{% if _profile and site.compress_html.startings %}{% assign _profile_startings = _content | size | plus: 1 %}{% endif %}{% if site.compress_html.comments == "all" %}{% assign _comments = "<!-- -->" | split: " " %}{% else %}{% assign _comments = site.compress_html.comments %}{% endif %}{% if _comments.size == 2 %}{% capture _comment_befores %}.{{ _content }}{% endcapture %}{% assign _comment_befores = _comment_befores | split: _comments.first %}{% for _comment_before in _comment_befores %}{% if forloop.first %}{% continue %}{% endif %}{% capture _comment_outside %}{% if _carry %}{{ _comments.first }}{% endif %}{{ _comment_before }}{% endcapture %}{% capture _comment %}{% unless _carry %}{{ _comments.first }}{% endunless %}{{ _comment_outside | split: _comments.last | first }}{% if _comment_outside contains _comments.last %}{{ _comments.last }}{% assign _carry = false %}{% else %}{% assign _carry = true %}{% endif %}{% endcapture %}{% assign _content = _content | remove_first: _comment %}{% endfor %}{% if _profile %}{% assign _profile_comments = _content | size | plus: 1 %}{% endif %}{% endif %}{% assign _pre_befores = _content | split: "<pre" %}{% assign _content = "" %}{% for _pre_before in _pre_befores %}{% assign _pres = _pre_before | split: "</pre>" %}{% assign _pres_after = "" %}{% if _pres.size != 0 %}{% if site.compress_html.blanklines %}{% assign _lines = _pres.last | split: _LINE_FEED %}{% capture _pres_after %}{% for _line in _lines %}{% assign _trimmed = _line | split: " " | join: " " %}{% if _trimmed != empty or forloop.last %}{% unless forloop.first %}{{ _LINE_FEED }}{% endunless %}{{ _line }}{% endif %}{% endfor %}{% endcapture %}{% else %}{% assign _pres_after = _pres.last | split: " " | join: " " %}{% endif %}{% endif %}{% capture _content %}{{ _content }}{% if _pre_before contains "</pre>" %}<pre{{ _pres.first }}</pre>{% endif %}{% unless _pre_before contains "</pre>" and _pres.size == 1 %}{{ _pres_after }}{% endunless %}{% endcapture %}{% endfor %}{% if _profile %}{% assign _profile_collapse = _content | size | plus: 1 %}{% endif %}{% if site.compress_html.clippings == "all" %}{% assign _clippings = "html head title base link meta style body article section nav aside h1 h2 h3 h4 h5 h6 hgroup header footer address p hr blockquote ol ul li dl dt dd figure figcaption main div table caption colgroup col tbody thead tfoot tr td th" | split: " " %}{% else %}{% assign _clippings = site.compress_html.clippings %}{% endif %}{% for _element in _clippings %}{% assign _edges = " <e;<e; </e>;</e>;</e> ;</e>" | replace: "e", _element | split: ";" %}{% assign _content = _content | replace: _edges[0], _edges[1] | replace: _edges[2], _edges[3] | replace: _edges[4], _edges[5] %}{% endfor %}{% if _profile and _clippings %}{% assign _profile_clippings = _content | size | plus: 1 %}{% endif %}{{ _content }}{% if _profile %} <table id="compress_html_profile_{{ site.time | date: "%Y%m%d" }}" class="compress_html_profile"> <thead> <tr> <td>Step <td>Bytes <tbody> <tr> <td>raw <td>{{ content | size }}{% if _profile_endings %} <tr> <td>endings <td>{{ _profile_endings }}{% endif %}{% if _profile_startings %} <tr> <td>startings <td>{{ _profile_startings }}{% endif %}{% if _profile_comments %} <tr> <td>comments <td>{{ _profile_comments }}{% endif %}{% if _profile_collapse %} <tr> <td>collapse <td>{{ _profile_collapse }}{% endif %}{% if _profile_clippings %} <tr> <td>clippings <td>{{ _profile_clippings }}{% endif %} </table>{% endif %}{% endif %}

View file

@ -13,7 +13,7 @@ layout: compress
{% endif %} {% endif %}
<!-- `site.alt_lang` can specify a language different from the UI --> <!-- `site.alt_lang` can specify a language different from the UI -->
<html lang="{{ site.alt_lang | default: site.lang }}" {{ prefer_mode }}> <html lang="{{ page.lang | default: site.alt_lang | default: site.lang }}" {{ prefer_mode }}>
{% include head.html %} {% include head.html %}
<body> <body>
@ -75,13 +75,8 @@ layout: compress
{% endif %} {% endif %}
<!-- JavaScripts --> <!-- JavaScripts -->
{% include js-selector.html lang=lang %}
{% include js-selector.html %} {% include_cached search-loader.html lang=lang %}
{% if page.mermaid %}
{% include mermaid.html %}
{% endif %}
{% include_cached search-loader.html %}
</body> </body>
</html> </html>

View file

@ -49,7 +49,7 @@ refactor: true
{% if post.image %} {% if post.image %}
{% assign src = post.image.path | default: post.image %} {% assign src = post.image.path | default: post.image %}
{% unless src contains '//' %} {% unless src contains '//' %}
{% assign src = post.img_path | append: '/' | append: src | replace: '//', '/' %} {% assign src = post.media_subpath | append: '/' | append: src | replace: '//', '/' %}
{% endunless %} {% endunless %}
{% assign alt = post.image.alt | xml_escape | default: 'Preview Image' %} {% assign alt = post.image.alt | xml_escape | default: 'Preview Image' %}
@ -72,10 +72,7 @@ refactor: true
<h1 class="card-title my-2 mt-md-0">{{ post.title }}</h1> <h1 class="card-title my-2 mt-md-0">{{ post.title }}</h1>
<div class="card-text content mt-0 mb-3"> <div class="card-text content mt-0 mb-3">
<p> <p>{% include post-description.html %}</p>
{% include no-linenos.html content=post.content %}
{{ content | markdownify | strip_html | truncate: 200 | escape }}
</p>
</div> </div>
<div class="post-meta flex-grow-1 d-flex align-items-end"> <div class="post-meta flex-grow-1 d-flex align-items-end">

View file

@ -14,6 +14,9 @@ tail_includes:
<article class="px-1"> <article class="px-1">
<header> <header>
<h1 data-toc-skip>{{ page.title }}</h1> <h1 data-toc-skip>{{ page.title }}</h1>
{% if page.description %}
<p class="post-desc fw-light mb-4">{{ page.description }}</p>
{% endif %}
<div class="post-meta text-muted"> <div class="post-meta text-muted">
<!-- published date --> <!-- published date -->
@ -74,12 +77,22 @@ tail_includes:
</em> </em>
</span> </span>
<div>
<!-- pageviews -->
{% if site.pageviews.provider and site.analytics[site.pageviews.provider].id %}
<span>
<em id="pageviews">
<i class="fas fa-spinner fa-spin small"></i>
</em>
{{ site.data.locales[lang].post.pageview_measure }}
</span>
{% endif %}
<!-- read time --> <!-- read time -->
{% include read-time.html content=content prompt=true lang=lang %} {% include read-time.html content=content prompt=true lang=lang %}
</div> </div>
<!-- .d-flex -->
</div> </div>
<!-- .post-meta --> </div>
</header> </header>
<div class="content"> <div class="content">

View file

@ -1,5 +1,6 @@
--- ---
title: Text and Typography title: Text and Typography
description: Examples of text, typography, math equations, diagrams, flowcharts, pictures, videos, and more.
author: cotes author: cotes
date: 2019-08-08 11:33:00 +0800 date: 2019-08-08 11:33:00 +0800
categories: [Blogging, Demo] categories: [Blogging, Demo]
@ -13,10 +14,10 @@ image:
alt: Responsive rendering of Chirpy theme on multiple devices. alt: Responsive rendering of Chirpy theme on multiple devices.
--- ---
This post is to show Markdown syntax rendering on [**Chirpy**](https://github.com/cotes2020/jekyll-theme-chirpy/fork), you can also use it as an example of writing. Now, let's start looking at text and typography.
## Headings ## Headings
<!-- markdownlint-capture -->
<!-- markdownlint-disable -->
# H1 - heading # H1 - heading
{: .mt-4 .mb-0 } {: .mt-4 .mb-0 }
@ -28,6 +29,7 @@ This post is to show Markdown syntax rendering on [**Chirpy**](https://github.co
#### H4 - heading #### H4 - heading
{: data-toc-skip='' .mt-4 } {: data-toc-skip='' .mt-4 }
<!-- markdownlint-restore -->
## Paragraph ## Paragraph
@ -44,15 +46,15 @@ Quisque egestas convallis ipsum, ut sollicitudin risus tincidunt a. Maecenas int
### Unordered list ### Unordered list
- Chapter - Chapter
+ Section - Section
* Paragraph - Paragraph
### ToDo list ### ToDo list
- [ ] Job - [ ] Job
+ [x] Step 1 - [x] Step 1
+ [x] Step 2 - [x] Step 2
+ [ ] Step 3 - [ ] Step 3
### Description list ### Description list
@ -68,6 +70,8 @@ Moon
## Prompts ## Prompts
<!-- markdownlint-capture -->
<!-- markdownlint-disable -->
> An example showing the `tip` type prompt. > An example showing the `tip` type prompt.
{: .prompt-tip } {: .prompt-tip }
@ -79,11 +83,12 @@ Moon
> An example showing the `danger` type prompt. > An example showing the `danger` type prompt.
{: .prompt-danger } {: .prompt-danger }
<!-- markdownlint-restore -->
## Tables ## Tables
| Company | Contact | Country | | Company | Contact | Country |
|:-----------------------------|:-----------------|--------:| | :--------------------------- | :--------------- | ------: |
| Alfreds Futterkiste | Maria Anders | Germany | | Alfreds Futterkiste | Maria Anders | Germany |
| Island Trading | Helen Bennett | UK | | Island Trading | Helen Bennett | UK |
| Magazzini Alimentari Riuniti | Giovanni Rovelli | Italy | | Magazzini Alimentari Riuniti | Giovanni Rovelli | Italy |
@ -108,7 +113,7 @@ Here is the `/path/to/the/file.extend`{: .filepath}.
### Common ### Common
``` ```text
This is a common code snippet, without syntax highlight and line number. This is a common code snippet, without syntax highlight and line number.
``` ```

View file

@ -58,7 +58,6 @@ Adding author information in `_data/authors.yml` (If your website doesn't have t
``` ```
{: file="_data/authors.yml" } {: file="_data/authors.yml" }
And then use `author` to specify a single entry or `authors` to specify multiple entries: And then use `author` to specify a single entry or `authors` to specify multiple entries:
```yaml ```yaml
@ -74,6 +73,18 @@ Having said that, the key `author` can also identify multiple entries.
> The benefit of reading the author information from the file `_data/authors.yml`{: .filepath } is that the page will have the meta tag `twitter:creator`, which enriches the [Twitter Cards](https://developer.twitter.com/en/docs/twitter-for-websites/cards/guides/getting-started#card-and-content-attribution) and is good for SEO. > The benefit of reading the author information from the file `_data/authors.yml`{: .filepath } is that the page will have the meta tag `twitter:creator`, which enriches the [Twitter Cards](https://developer.twitter.com/en/docs/twitter-for-websites/cards/guides/getting-started#card-and-content-attribution) and is good for SEO.
{: .prompt-info } {: .prompt-info }
### Post Description
By default, the first words of the post are used to display on the home page for a list of posts, in the _Further Reading_ section, and in the XML of the RSS feed. If you don't want to display the auto-generated description for the post, you can customize it using the `description` field in the _Front Matter_ as follows:
```yaml
---
description: Short summary of the post.
---
```
Additionally, the `description` text will also be displayed under the post title on the post's page.
## Table of Contents ## Table of Contents
By default, the **T**able **o**f **C**ontents (TOC) is displayed on the right panel of the post. If you want to turn it off globally, go to `_config.yml`{: .filepath} and set the value of variable `toc` to `false`. If you want to turn off TOC for a specific post, add the following to the post's [Front Matter](https://jekyllrb.com/docs/front-matter/): By default, the **T**able **o**f **C**ontents (TOC) is displayed on the right panel of the post. If you want to turn it off globally, go to `_config.yml`{: .filepath} and set the value of variable `toc` to `false`. If you want to turn off TOC for a specific post, add the following to the post's [Front Matter](https://jekyllrb.com/docs/front-matter/):
@ -98,7 +109,9 @@ comments: false
## Mathematics ## Mathematics
For website performance reasons, the mathematical feature won't be loaded by default. But it can be enabled by: We use [**MathJax**][mathjax] to generate mathematics. For website performance reasons, the mathematical feature won't be loaded by default. But it can be enabled by:
[mathjax]: https://www.mathjax.org/
```yaml ```yaml
--- ---
@ -143,6 +156,12 @@ Can be referenced as \eqref{eq:label_name}.
3. \$$ LaTeX_math_expression $$ 3. \$$ LaTeX_math_expression $$
``` ```
> Starting with `v7.0.0`, configuration options for **MathJax** have been moved to file `assets/js/data/mathjax.js`{: .filepath }, and you can change the options as needed, such as adding [extensions][mathjax-exts].
> If you are building the site via `chirpy-starter`, copy that file from the gem installation directory (check with command `bundle info --path jekyll-theme-chirpy`) to the same directory in your repository.
{: .prompt-tip }
[mathjax-exts]: https://docs.mathjax.org/en/latest/input/tex/extensions/index.html
## Mermaid ## Mermaid
[**Mermaid**](https://github.com/mermaid-js/mermaid) is a great diagram generation tool. To enable it on your post, add the following to the YAML block: [**Mermaid**](https://github.com/mermaid-js/mermaid) is a great diagram generation tool. To enable it on your post, add the following to the YAML block:
@ -236,14 +255,14 @@ The screenshots of the program window can be considered to show the shadow effec
### CDN URL ### CDN URL
If you host the images on the CDN, you can save the time of repeatedly writing the CDN URL by assigning the variable `img_cdn` of `_config.yml`{: .filepath} file: If you host the media resources on the CDN, you can save the time of repeatedly writing the CDN URL by assigning the variable `cdn` of `_config.yml`{: .filepath} file:
```yaml ```yaml
img_cdn: https://cdn.com cdn: https://cdn.com
``` ```
{: file='_config.yml' .nolineno} {: file='_config.yml' .nolineno}
Once `img_cdn` is assigned, the CDN URL will be added to the path of all images (images of site avatar and posts) starting with `/`. Once `cdn` is assigned, the CDN URL will be added to the path of all media resources (site avatar, posts' images, audio and video files) starting with `/`.
For instance, when using images: For instance, when using images:
@ -259,13 +278,13 @@ The parsing result will automatically add the CDN prefix `https://cdn.com` befor
``` ```
{: .nolineno } {: .nolineno }
### Image Path ### Media Subpath
When a post contains many images, it will be a time-consuming task to repeatedly define the path of the images. To solve this, we can define this path in the YAML block of the post: When a post contains many images, it will be a time-consuming task to repeatedly define the path of the media resources. To solve this, we can define this path in the YAML block of the post:
```yml ```yml
--- ---
img_path: /img/path/ media_subpath: /img/path/
--- ---
``` ```
@ -297,7 +316,7 @@ image:
--- ---
``` ```
Note that the [`img_path`](#image-path) can also be passed to the preview image, that is, when it has been set, the attribute `path` only needs the image file name. Note that the [`media_subpath`](#media-subpath) can also be passed to the preview image, that is, when it has been set, the attribute `path` only needs the image file name.
For simple use, you can also just use `image` to define the path. For simple use, you can also just use `image` to define the path.
@ -318,8 +337,7 @@ image:
--- ---
``` ```
> You can observe LQIP in the preview image of post [_Text and Typography_](/posts/text-and-typography/). > You can observe LQIP in the preview image of post \"[Text and Typography](../text-and-typography/)\".
For normal images: For normal images:
@ -427,6 +445,8 @@ Or adding `render_with_liquid: false` (Requires Jekyll 4.0 or higher) to the pos
## Videos ## Videos
### Video Sharing Platform
You can embed a video with the following syntax: You can embed a video with the following syntax:
```liquid ```liquid
@ -443,6 +463,76 @@ The following table shows how to get the two parameters we need in a given video
| [https://www.**twitch**.tv/videos/**1634779211**](https://www.twitch.tv/videos/1634779211) | `twitch` | `1634779211` | | [https://www.**twitch**.tv/videos/**1634779211**](https://www.twitch.tv/videos/1634779211) | `twitch` | `1634779211` |
| [https://www.**bilibili**.com/video/**BV1Q44y1B7Wf**](https://www.bilibili.com/video/BV1Q44y1B7Wf) | `bilibili` | `BV1Q44y1B7Wf` | | [https://www.**bilibili**.com/video/**BV1Q44y1B7Wf**](https://www.bilibili.com/video/BV1Q44y1B7Wf) | `bilibili` | `BV1Q44y1B7Wf` |
### Video File
If you want to embed a video file directly, use the following syntax:
```liquid
{% include embed/video.html src='{URL}' %}
```
Where `URL` is an URL to a video file e.g. `/assets/img/sample/video.mp4`.
You can also specify additional attributes for the embedded video file. Here is a full list of attributes allowed.
- `poster='/path/to/poster.png'` - poster image for a video that is shown while video is downloading
- `title='Text'` - title for a video that appears below the video and looks same as for images
- `autoplay=true` - video automatically begins to play back as soon as it can
- `loop=true` - automatically seek back to the start upon reaching the end of the video
- `muted=true` - audio will be initially silenced
- `types` - specify the extensions of additional video formats separated by `|`. Ensure these files exist in the same directory as your primary video file.
Consider an example utilizing all of the above:
```liquid
{%
include embed/video.html
src='/path/to/video/video.mp4'
types='ogg|mov'
poster='poster.png'
title='Demo video'
autoplay=true
loop=true
muted=true
%}
```
> It's not recommended to host video files in `assets` folder as they cannot be cached by PWA and may cause issues.
> Instead, use CDN to host video files. Alternatively, use a separate folder that is excluded from PWA (see `pwa.deny_paths` setting in `_config.yml`).
{: .prompt-warning }
## Audios
### Audio File
If you want to embed an audio file directly, use the following syntax:
```liquid
{% include embed/audio.html src='{URL}' %}
```
Where `URL` is an URL to an audio file e.g. `/assets/img/sample/audio.mp3`.
You can also specify additional attributes for the embedded audio file. Here is a full list of attributes allowed.
- `title='Text'` - title for an audio that appears below the audio and looks same as for images
- `types` - specify the extensions of additional audio formats separated by `|`. Ensure these files exist in the same directory as your primary audio file.
Consider an example utilizing all of the above:
```liquid
{%
include embed/audio.html
src='/path/to/audio/audio.mp3'
types='ogg|wav|aac'
title='Demo audio'
%}
```
> It's not recommended to host audio files in `assets` folder as they cannot be cached by PWA and may cause issues.
> Instead, use CDN to host audio files. Alternatively, use a separate folder that is excluded from PWA (see `pwa.deny_paths` setting in `_config.yml`).
{: .prompt-warning }
## Learn More ## Learn More
For more knowledge about Jekyll posts, visit the [Jekyll Docs: Posts](https://jekyllrb.com/docs/posts/). For more knowledge about Jekyll posts, visit the [Jekyll Docs: Posts](https://jekyllrb.com/docs/posts/).

View file

@ -1,11 +1,14 @@
--- ---
title: Getting Started title: Getting Started
description: >-
Get started with Chirpy basics in this comprehensive overview.
You will learn how to install, configure, and use your first Chirpy-based website, as well as deploy it to a web server.
author: cotes author: cotes
date: 2019-08-09 20:55:00 +0800 date: 2019-08-09 20:55:00 +0800
categories: [Blogging, Tutorial] categories: [Blogging, Tutorial]
tags: [getting started] tags: [getting started]
pin: true pin: true
img_path: '/posts/20180809' media_subpath: '/posts/20180809'
--- ---
## Prerequisites ## Prerequisites
@ -18,8 +21,8 @@ Follow the instructions in the [Jekyll Docs](https://jekyllrb.com/docs/installat
There are two ways to create a new repository for this theme: There are two ways to create a new repository for this theme:
- [**Using the Chirpy Starter**](#option-1-using-the-chirpy-starter) - Easy to upgrade, isolates irrelevant project files so you can focus on writing. - [**Using the Chirpy Starter**](#option-1-using-the-chirpy-starter) Easy to upgrade, isolates irrelevant project files so you can focus on writing.
- [**GitHub Fork**](#option-2-github-fork) - Convenient for custom development, but difficult to upgrade. Unless you are familiar with Jekyll and are determined to tweak or contribute to this project, this approach is not recommended. - [**GitHub Fork**](#option-2-github-fork) Convenient for custom development, but difficult to upgrade. Unless you are familiar with Jekyll and are determined to tweak or contribute to this project, this approach is not recommended.
#### Option 1. Using the Chirpy Starter #### Option 1. Using the Chirpy Starter
@ -29,7 +32,7 @@ Sign in to GitHub and browse to [**Chirpy Starter**][starter], click the button
Sign in to GitHub to [fork **Chirpy**](https://github.com/cotes2020/jekyll-theme-chirpy/fork), and then rename it to `USERNAME.github.io` (`USERNAME` means your username). Sign in to GitHub to [fork **Chirpy**](https://github.com/cotes2020/jekyll-theme-chirpy/fork), and then rename it to `USERNAME.github.io` (`USERNAME` means your username).
Next, clone your site to local machine. In order to build JavaScript files later, we need to install [Node.js][nodejs], and then run the tool: Next, clone the repository to your local machine, make sure it has [Node.js][nodejs] installed, then go to the root directory of the repo and run the following command:
```console ```console
$ bash tools/init $ bash tools/init
@ -42,7 +45,7 @@ The above command will:
1. Check out the code to the [latest tag][latest-tag] (to ensure the stability of your site: as the code for the default branch is under development). 1. Check out the code to the [latest tag][latest-tag] (to ensure the stability of your site: as the code for the default branch is under development).
2. Remove non-essential sample files and take care of GitHub-related files. 2. Remove non-essential sample files and take care of GitHub-related files.
3. Build JavaScript files and export to `assets/js/dist/`{: .filepath }, then make them tracked by Git. 3. Build CSS/JS assets files and then make them tracked by Git.
4. Automatically create a new commit to save the changes above. 4. Automatically create a new commit to save the changes above.
### Installing Dependencies ### Installing Dependencies

View file

@ -30,6 +30,7 @@ The following table will help you understand the changes to the favicon files:
| `*.PNG` | ✓ | ✗ | | `*.PNG` | ✓ | ✗ |
| `*.ICO` | ✓ | ✗ | | `*.ICO` | ✓ | ✗ |
<!-- markdownlint-disable-next-line -->
> ✓ means keep, ✗ means delete. > ✓ means keep, ✗ means delete.
{: .prompt-info } {: .prompt-info }

View file

@ -129,6 +129,11 @@ kbd {
box-shadow: inset 0 -2px 0 var(--kbd-wrap-color); box-shadow: inset 0 -2px 0 var(--kbd-wrap-color);
} }
hr {
border-color: var(--main-border-color);
opacity: 1;
}
footer { footer {
background-color: var(--main-bg); background-color: var(--main-bg);
height: $footer-height; height: $footer-height;
@ -364,7 +369,6 @@ main {
h1 { h1 {
margin-top: 2rem; margin-top: 2rem;
margin-bottom: 1.5rem;
} }
p { p {
@ -550,17 +554,32 @@ main {
width: 100%; width: 100%;
height: 100%; height: 100%;
margin-bottom: 1rem; margin-bottom: 1rem;
aspect-ratio: 16 / 9;
@extend %rounded; @extend %rounded;
&.youtube,
&.bilibili {
aspect-ratio: 16 / 9;
}
&.twitch { &.twitch {
aspect-ratio: 310 / 189; aspect-ratio: 310 / 189;
} }
&.file {
display: block;
width: auto;
height: auto;
max-width: 100%;
max-height: 100%;
margin: auto;
margin-bottom: 0;
}
@extend %img-caption;
}
.embed-audio {
width: 100%;
display: block;
@extend %img-caption;
} }
/* --- buttons --- */ /* --- buttons --- */
@ -577,26 +596,6 @@ main {
/* --- Effects classes --- */ /* --- Effects classes --- */
.loaded {
display: block !important;
@at-root .d-flex#{&} {
display: flex !important;
}
}
.unloaded {
display: none !important;
}
.visible {
visibility: visible !important;
}
.hidden {
visibility: hidden !important;
}
.flex-grow-1 { .flex-grow-1 {
flex-grow: 1 !important; flex-grow: 1 !important;
} }
@ -653,18 +652,6 @@ main {
/* --- Overriding --- */ /* --- Overriding --- */
/* magnific-popup */
figure .mfp-title {
text-align: center;
padding-right: 0;
margin-top: 0.5rem;
}
.mfp-img {
transition: none;
}
/* mermaid */ /* mermaid */
.mermaid { .mermaid {
text-align: center; text-align: center;
@ -836,7 +823,10 @@ $btn-mb: 0.5rem;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
&:not(:focus-visible) {
box-shadow: var(--sidebar-border-color) 0 0 0 1px; box-shadow: var(--sidebar-border-color) 0 0 0 1px;
}
&:hover { &:hover {
background-color: var(--sidebar-hover-bg); background-color: var(--sidebar-hover-bg);
@ -857,10 +847,7 @@ $btn-mb: 0.5rem;
line-height: $btn-size; line-height: $btn-size;
} }
.mode-toggle { #mode-toggle {
padding: 0;
border: 0;
@extend %button; @extend %button;
@extend %sidebar-links; @extend %sidebar-links;
@extend %sidebar-link-hover; @extend %sidebar-link-hover;
@ -1139,7 +1126,8 @@ search {
/* --- button back-to-top --- */ /* --- button back-to-top --- */
#back-to-top { #back-to-top {
display: none; visibility: hidden;
opacity: 0;
z-index: 1; z-index: 1;
cursor: pointer; cursor: pointer;
position: fixed; position: fixed;
@ -1152,8 +1140,7 @@ search {
height: $back2top-size; height: $back2top-size;
border-radius: 50%; border-radius: 50%;
border: 1px solid var(--btn-backtotop-border-color); border: 1px solid var(--btn-backtotop-border-color);
transition: transform 0.2s ease-out; transition: opacity 0.5s ease-in-out, transform 0.2s ease-out;
-webkit-transition: transform 0.2s ease-out;
&:hover { &:hover {
transform: translate3d(0, -5px, 0); transform: translate3d(0, -5px, 0);
@ -1165,6 +1152,11 @@ search {
position: relative; position: relative;
bottom: 2px; bottom: 2px;
} }
&.show {
opacity: 1;
visibility: visible;
}
} }
#notification { #notification {

View file

@ -104,10 +104,6 @@
display: none; display: none;
} }
hr {
border-color: var(--main-border-color);
}
/* categories */ /* categories */
.categories.card, .categories.card,
.list-group-item { .list-group-item {

View file

@ -14,8 +14,16 @@
padding-right: $pr; padding-right: $pr;
} }
h1 + .post-meta { header {
> span + span::before { .post-desc {
@extend %heading;
font-size: 1.125rem;
line-height: 1.6;
}
.post-meta {
span + span::before {
@include dot; @include dot;
} }
@ -29,6 +37,11 @@ h1 + .post-meta {
color: inherit; color: inherit;
} }
} }
}
h1 + .post-meta {
margin-top: 1.5rem;
}
} }
.post-tail-wrapper { .post-tail-wrapper {

2
_sass/main.bundle.scss Normal file
View file

@ -0,0 +1,2 @@
@import 'dist/bootstrap';
@import 'main';

View file

@ -1,6 +1,10 @@
--- ---
--- ---
@import 'main'; @import 'main
{%- if jekyll.environment == 'production' -%}
.bundle
{%- endif -%}
';
/* append your custom style below */ /* append your custom style below */

View file

@ -45,14 +45,7 @@ permalink: /feed.xml
{% endfor %} {% endfor %}
{% endif %} {% endif %}
{% if post.summary %} <summary>{% include post-description.html max_length=400 %}</summary>
<summary>{{ post.summary | strip }}</summary>
{% else %}
<summary>
{% include no-linenos.html content=post.content %}
{{ content | strip_html | truncate: 400 }}
</summary>
{% endif %}
</entry> </entry>
{% endfor %} {% endfor %}

25
assets/js/data/mathjax.js Normal file
View file

@ -0,0 +1,25 @@
---
layout: compress
# WARNING: Don't use '//' to comment out code, use '{% comment %}' and '{% endcomment %}' instead.
---
{%- comment -%}
See: <https://docs.mathjax.org/en/latest/options/input/tex.html#tex-options>
{%- endcomment -%}
MathJax = {
tex: {
{%- comment -%} start/end delimiter pairs for in-line math {%- endcomment -%}
inlineMath: [
['$', '$'],
['\\(', '\\)']
],
{%- comment -%} start/end delimiter pairs for display math {%- endcomment -%}
displayMath: [
['$$', '$$'],
['\\[', '\\]']
],
{%- comment -%} equation numbering {%- endcomment -%}
tags: 'ams'
}
};

View file

@ -22,20 +22,6 @@ const swconf = {
{% endfor %} {% endfor %}
], ],
{%- comment -%} The request url with below domain will be cached. {%- endcomment -%}
allowHosts: [
{% if site.img_cdn and site.img_cdn contains '//' %}
'{{ site.img_cdn | split: '//' | last | split: '/' | first }}',
{% endif %}
{%- unless site.assets.self_host.enabled -%}
{% for cdn in site.data.origin["cors"].cdns %}
'{{ cdn.url | split: "//" | last }}'
{%- unless forloop.last -%},{%- endunless -%}
{% endfor %}
{% endunless %}
],
{%- comment -%} The request url with below path will not be cached. {%- endcomment -%} {%- comment -%} The request url with below path will not be cached. {%- endcomment -%}
denyPaths: [ denyPaths: [
{% for path in site.pwa.cache.deny_paths %} {% for path in site.pwa.cache.deny_paths %}

@ -1 +1 @@
Subproject commit 7bc0d86b6af83d7acfc63db50f29a5975cec2513 Subproject commit b9c58cf485a7dcbc833e698d67dd1850bdc93eb3

View file

@ -1,7 +1,5 @@
# Changelog # Changelog
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
## [6.5.5](https://github.com/cotes2020/jekyll-theme-chirpy/compare/v6.5.4...v6.5.5) (2024-03-23) ## [6.5.5](https://github.com/cotes2020/jekyll-theme-chirpy/compare/v6.5.4...v6.5.5) (2024-03-23)
### Bug Fixes ### Bug Fixes
@ -17,6 +15,8 @@ All notable changes to this project will be documented in this file. See [standa
## [6.5.3](https://github.com/cotes2020/jekyll-theme-chirpy/compare/v6.5.2...v6.5.3) (2024-03-07) ## [6.5.3](https://github.com/cotes2020/jekyll-theme-chirpy/compare/v6.5.2...v6.5.3) (2024-03-07)
### Changes
* replace `polyfill.io` with `cdnjs` hosted link ([#1598](https://github.com/cotes2020/jekyll-theme-chirpy/pull/1598)) ([75a3d73](https://github.com/cotes2020/jekyll-theme-chirpy/commit/75a3d7399b257256a09d602cbe01062fe1cdf68d)) * replace `polyfill.io` with `cdnjs` hosted link ([#1598](https://github.com/cotes2020/jekyll-theme-chirpy/pull/1598)) ([75a3d73](https://github.com/cotes2020/jekyll-theme-chirpy/commit/75a3d7399b257256a09d602cbe01062fe1cdf68d))
## [6.5.2](https://github.com/cotes2020/jekyll-theme-chirpy/compare/v6.5.1...v6.5.2) (2024-02-29) ## [6.5.2](https://github.com/cotes2020/jekyll-theme-chirpy/compare/v6.5.1...v6.5.2) (2024-02-29)

View file

@ -3,9 +3,9 @@
## Supported Versions ## Supported Versions
| Version | Supported | | Version | Supported |
|:----------|:---------:| | :--------- | :-------: |
| `6.x` | ✓ | | >= `7.0.0` | ✓ |
| < `6.0.0` | ✗ | | <= `6.0.0` | ✗ |
## Reporting a Vulnerability ## Reporting a Vulnerability

View file

@ -13,27 +13,40 @@
}, },
"homepage": "https://github.com/cotes2020/jekyll-theme-chirpy/", "homepage": "https://github.com/cotes2020/jekyll-theme-chirpy/",
"scripts": { "scripts": {
"prebuild": "npx rimraf assets/js/dist", "build": "concurrently npm:build:*",
"build": "NODE_ENV=production npx rollup -c --bundleConfigAsCjs", "build:css": "purgecss -c purgecss.config.js",
"prewatch": "npx rimraf assets/js/dist", "build:js": "rollup -c --bundleConfigAsCjs --environment BUILD:production",
"watch": "npx rollup -c --bundleConfigAsCjs -w", "watch:js": "rollup -c --bundleConfigAsCjs -w",
"test": "npx stylelint _sass/**/*.scss", "lint:scss": "stylelint _sass/**/*.scss",
"fixlint": "npm run test -- --fix" "lint:fix:scss": "npm run lint:scss -- --fix",
"test": "npm run lint:scss"
},
"dependencies": {
"@popperjs/core": "^2.11.8",
"bootstrap": "^5.3.3"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.23.9", "@babel/core": "^7.24.5",
"@babel/plugin-proposal-class-properties": "^7.18.6", "@babel/plugin-transform-class-properties": "^7.24.1",
"@babel/preset-env": "^7.23.9", "@babel/preset-env": "^7.24.5",
"@commitlint/cli": "^18.6.1", "@commitlint/cli": "^19.3.0",
"@commitlint/config-conventional": "^18.6.2", "@commitlint/config-conventional": "^19.2.2",
"@rollup/plugin-babel": "^6.0.4", "@rollup/plugin-babel": "^6.0.4",
"@rollup/plugin-node-resolve": "^15.2.3",
"@rollup/plugin-terser": "^0.4.4", "@rollup/plugin-terser": "^0.4.4",
"@rollup/plugin-yaml": "^4.1.2",
"@semantic-release/changelog": "^6.0.3",
"@semantic-release/exec": "^6.0.3",
"@semantic-release/git": "^10.0.1",
"concurrently": "^8.2.2",
"conventional-changelog-conventionalcommits": "^7.0.2",
"husky": "^9.0.11", "husky": "^9.0.11",
"rimraf": "^5.0.5", "purgecss": "^6.0.0",
"rollup": "^4.10.0", "rollup": "^4.17.2",
"rollup-plugin-license": "^3.2.0", "rollup-plugin-license": "^3.3.1",
"stylelint": "^16.2.1", "semantic-release": "^23.1.1",
"stylelint-config-standard-scss": "^13.0.0" "stylelint": "^16.5.0",
"stylelint-config-standard-scss": "^13.1.0"
}, },
"prettier": { "prettier": {
"trailingComma": "none" "trailingComma": "none"
@ -89,11 +102,22 @@
"media-feature-range-notation": "prefix" "media-feature-range-notation": "prefix"
} }
}, },
"standard-version": { "release": {
"skip": { "branches": [
"commit": true, "production"
"tag": true ],
}, "plugins": [
[
"@semantic-release/commit-analyzer",
{
"preset": "conventionalcommits"
}
],
[
"@semantic-release/release-notes-generator",
{
"preset": "conventionalcommits",
"presetConfig": {
"types": [ "types": [
{ {
"type": "feat", "type": "feat",
@ -106,7 +130,48 @@
{ {
"type": "perf", "type": "perf",
"section": "Improvements" "section": "Improvements"
},
{
"type": "refactor",
"section": "Changes",
"hidden": true
} }
] ]
} }
}
],
[
"@semantic-release/changelog",
{
"changelogFile": "docs/CHANGELOG.md",
"changelogTitle": "# Changelog"
}
],
[
"@semantic-release/npm",
{
"npmPublish": false
}
],
[
"@semantic-release/exec",
{
"prepareCmd": "bash tools/release --prepare",
"publishCmd": "bash tools/release"
}
],
[
"@semantic-release/git",
{
"assets": [
"docs",
"package.json",
"*.gemspec"
],
"message": "chore(release): ${nextRelease.version}\n\n${nextRelease.notes}"
}
],
"@semantic-release/github"
]
}
} }

23
purgecss.config.js Normal file
View file

@ -0,0 +1,23 @@
const fs = require('fs');
const DIST_PATH = '_sass/dist';
fs.rm(DIST_PATH, { recursive: true, force: true }, (err) => {
if (err) {
throw err;
}
fs.mkdirSync(DIST_PATH);
});
module.exports = {
content: ['_includes/**/*.html', '_layouts/**/*.html', '_javascript/**/*.js'],
css: ['node_modules/bootstrap/dist/css/bootstrap.min.css'],
keyframes: true,
variables: true,
output: `${DIST_PATH}/bootstrap.css`,
// The `safelist` should be changed appropriately for future development
safelist: {
standard: [/^collaps/, /^w-/, 'shadow', 'border', 'kbd'],
greedy: [/^col-/, /tooltip/]
}
};

View file

@ -1,37 +1,59 @@
import babel from '@rollup/plugin-babel'; import babel from '@rollup/plugin-babel';
import terser from '@rollup/plugin-terser'; import terser from '@rollup/plugin-terser';
import license from 'rollup-plugin-license'; import license from 'rollup-plugin-license';
import { nodeResolve } from '@rollup/plugin-node-resolve';
import fs from 'fs';
import path from 'path'; import path from 'path';
import yaml from '@rollup/plugin-yaml';
const SRC_DEFAULT = '_javascript'; const SRC_DEFAULT = '_javascript';
const DIST_DEFAULT = 'assets/js/dist'; const DIST_DEFAULT = 'assets/js/dist';
const isProd = process.env.NODE_ENV === 'production'; const SRC_PWA = `${SRC_DEFAULT}/pwa`;
const isProd = process.env.BUILD === 'production';
if (fs.existsSync(DIST_DEFAULT)) {
fs.rm(DIST_DEFAULT, { recursive: true, force: true }, (err) => {
if (err) {
throw err;
}
});
}
function build(filename, opts = {}) {
const src = opts.src || SRC_DEFAULT;
const dist = opts.dist || DIST_DEFAULT;
const bannerUrl =
opts.bannerUrl || path.join(__dirname, SRC_DEFAULT, '_copyright');
const commentStyle = opts.commentStyle || 'ignored';
function build(filename) {
return { return {
input: [`${SRC_DEFAULT}/${filename}.js`], input: [`${src}/${filename}.js`],
output: { output: {
file: `${DIST_DEFAULT}/${filename}.min.js`, file: `${dist}/${filename}.min.js`,
format: 'iife', format: 'iife',
name: 'Chirpy', name: 'Chirpy',
sourcemap: !isProd sourcemap: !isProd
}, },
watch: { watch: {
include: `${SRC_DEFAULT}/**` include: `${src}/**`
}, },
plugins: [ plugins: [
babel({ babel({
babelHelpers: 'bundled', babelHelpers: 'bundled',
presets: ['@babel/env'], presets: ['@babel/env'],
plugins: ['@babel/plugin-proposal-class-properties'] plugins: ['@babel/plugin-transform-class-properties']
}), }),
nodeResolve(),
yaml(),
isProd && commentStyle === 'none' && terser(),
license({ license({
banner: { banner: {
commentStyle: 'ignored', commentStyle,
content: { file: path.join(__dirname, SRC_DEFAULT, '_copyright') } content: { file: bannerUrl }
} }
}), }),
isProd && terser() isProd && commentStyle !== 'none' && terser()
] ]
}; };
} }
@ -42,5 +64,11 @@ export default [
build('categories'), build('categories'),
build('page'), build('page'),
build('post'), build('post'),
build('misc') build('misc'),
build('app', { src: SRC_PWA }),
build('sw', {
src: SRC_PWA,
bannerUrl: path.join(__dirname, SRC_PWA, '_frontmatter'),
commentStyle: 'none'
})
]; ];

View file

@ -9,6 +9,8 @@ CLI=("git" "npm")
ACTIONS_WORKFLOW=pages-deploy.yml ACTIONS_WORKFLOW=pages-deploy.yml
RELEASE_HASH=$(git log --grep="chore(release):" -1 --pretty="%H")
# 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" TEMP_SUFFIX="to-delete"
@ -28,7 +30,7 @@ help() {
_sedi() { _sedi() {
regex=$1 regex=$1
file=$2 file=$2
sed -i.$TEMP_SUFFIX "$regex" "$file" sed -i.$TEMP_SUFFIX -E "$regex" "$file"
rm -f "$file".$TEMP_SUFFIX rm -f "$file".$TEMP_SUFFIX
} }
@ -50,22 +52,7 @@ _check_status() {
} }
_check_init() { _check_init() {
local _has_inited=false if [[ $(git rev-parse HEAD^1) == "$RELEASE_HASH" ]]; then
if [[ ! -d .github ]]; then # using option `--no-gh`
_has_inited=true
else
if [[ -f .github/workflows/$ACTIONS_WORKFLOW ]]; then
# on BSD, the `wc` could contains blank
local _count
_count=$(find .github/workflows/ -type f -name "*.yml" | wc -l)
if [[ ${_count//[[:blank:]]/} == 1 ]]; then
_has_inited=true
fi
fi
fi
if $_has_inited; then
echo "Already initialized." echo "Already initialized."
exit 0 exit 0
fi fi
@ -77,23 +64,25 @@ check_env() {
_check_init _check_init
} }
checkout_latest_release() { reset_latest() {
hash=$(git log --grep="chore(release):" -1 --pretty="%H") git reset --hard "$RELEASE_HASH"
git reset --hard "$hash" git clean -fd
git submodule update --init --recursive
} }
init_files() { init_files() {
if $_no_gh; then if $_no_gh; then
rm -rf .github rm -rf .github
else else
## Change the files of `.github` ## Change the files of `.github/`
mv .github/workflows/$ACTIONS_WORKFLOW.hook . temp="$(mktemp -d)"
rm -rf .github find .github/workflows -type f -name "*$ACTIONS_WORKFLOW*" -exec mv {} "$temp/$ACTIONS_WORKFLOW" \;
mkdir -p .github/workflows rm -rf .github && mkdir -p .github/workflows
mv ./${ACTIONS_WORKFLOW}.hook .github/workflows/${ACTIONS_WORKFLOW} mv "$temp/$ACTIONS_WORKFLOW" .github/workflows/"$ACTIONS_WORKFLOW"
rm -rf "$temp"
## Cleanup image settings in site config ## Cleanup image settings in site config
_sedi "s/^img_cdn:.*/img_cdn:/;s/^avatar:.*/avatar:/" _config.yml _sedi "s/(^timezone:).*/\1/;s/(^.*cdn:).*/\1/;s/(^avatar:).*/\1/" _config.yml
fi fi
# remove the other files # remove the other files
@ -102,19 +91,19 @@ init_files() {
# build assets # build assets
npm i && npm run build npm i && npm run build
# track the js output # track the CSS/JS output
_sedi "/^assets.*\/dist/d" .gitignore _sedi "/.*\/dist$/d" .gitignore
} }
commit() { commit() {
git add -A git add -A
git commit -m "chore: initialize the environment" -q git commit -m "chore: initialize the environment" -q
echo -e "\n[INFO] Initialization successful!\n" echo -e "\n> Initialization successful!\n"
} }
main() { main() {
check_env check_env
checkout_latest_release reset_latest
init_files init_files
commit commit
} }

View file

@ -1,56 +1,48 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# #
# Release a new version to the GitLab flow production branch.
#
# For a new major/minor version, bump version on the main branch, and then merge into the production branch.
#
# For a patch version, bump the version number on the patch branch, then merge that branch into the main branch
# and production branch.
#
#
# Usage: run on the default, release or the patch branch
#
# Requires: Git, NPM and RubyGems # Requires: Git, NPM and RubyGems
set -eu set -eu
opt_pre=false # preview mode option opt_pre=false # option for bump gem version
opt_pkg=false # option for building gem package
working_branch="$(git branch --show-current)" MAIN_BRANCH="master"
RELEASE_BRANCH="production"
DEFAULT_BRANCH="$(git symbolic-ref refs/remotes/origin/HEAD | sed 's@^refs/remotes/origin/@@')"
PROD_BRANCH="production"
GEM_SPEC="jekyll-theme-chirpy.gemspec" GEM_SPEC="jekyll-theme-chirpy.gemspec"
NODE_CONFIG="package.json" NODE_SPEC="package.json"
CHANGE_LOG="docs/CHANGELOG.md" CHANGELOG="docs/CHANGELOG.md"
CONFIG="_config.yml"
CSS_DIST="_sass/dist"
JS_DIST="assets/js/dist" JS_DIST="assets/js/dist"
BACKUP_PATH="$(mktemp -d)"
FILES=( FILES=(
"$GEM_SPEC" "$GEM_SPEC"
"$NODE_CONFIG" "$NODE_SPEC"
"$CHANGELOG"
"$CONFIG"
) )
TOOLS=( TOOLS=(
"git" "git"
"npm" "npm"
"standard-version"
"gem" "gem"
) )
help() { help() {
echo "A tool to release new version Chirpy gem" echo -e "A tool to release new version Chirpy gem.\nThis tool will:"
echo " 1. Build a new gem and publish it to RubyGems.org"
echo " 2. Merge the release branch into the default branch"
echo echo
echo "Usage:" echo "Usage:"
echo
echo " bash ./tools/release [options]" echo " bash ./tools/release [options]"
echo echo
echo "Options:" echo "Options:"
echo " -p, --preview Enable preview mode, only package, and will not modify the branches" echo " --prepare Preparation for release"
echo " -h, --help Print this information." echo " -p, --package Build a gem package only, for local packaging in case of auto-publishing failure"
echo " -h, --help Display this help message"
} }
_check_cli() { _check_cli() {
@ -64,17 +56,17 @@ _check_cli() {
} }
_check_git() { _check_git() {
$opt_pre || (
# ensure that changes have been committed # ensure that changes have been committed
if [[ -n $(git status . -s) ]]; then if [[ -n $(git status . -s) ]]; then
echo "> Abort: Commit the staged files first, and then run this tool again." echo "> Abort: Commit the staged files first, and then run this tool again."
exit 1 exit 1
fi fi
)
$opt_pre || ( $opt_pkg || (
if [[ $working_branch != "$DEFAULT_BRANCH" && if [[ "$(git branch --show-current)" != "$RELEASE_BRANCH" ]]; then
$working_branch != hotfix/* && echo "> Abort: Please run the tool in the '$RELEASE_BRANCH' branch."
$working_branch != "$PROD_BRANCH" ]]; then
echo "> Abort: Please run on the default, release or patch branch."
exit 1 exit 1
fi fi
) )
@ -90,121 +82,110 @@ _check_src() {
done done
} }
_check_node_packages() { init() {
if [[ ! -d node_modules || "$(du node_modules | awk '{print $1}')" == "0" ]]; then
npm i
fi
}
check() {
_check_cli _check_cli
_check_git _check_git
_check_src _check_src
_check_node_packages echo -e "> npm install\n"
npm i
} }
# Auto-generate a new version number to the file 'package.json' ## Bump new version to gem-spec file
bump_node() { _bump_version() {
bump="standard-version -i $CHANGE_LOG" _version="$(grep '"version":' "$NODE_SPEC" | sed 's/.*: "//;s/".*//')"
sed -i "s/[[:digit:]]\+\.[[:digit:]]\+\.[[:digit:]]\+/$_version/" "$GEM_SPEC"
echo "> Bump gem version to $_version"
}
if $opt_pre; then _improve_changelog() {
bump="$bump -p rc"
fi
eval "$bump"
# Change heading of Patch version to heading level 2 (a bug from `standard-version`)
sed -i "s/^### \[/## \[/g" "$CHANGE_LOG"
# Replace multiple empty lines with a single empty line # Replace multiple empty lines with a single empty line
sed -i "/^$/N;/^\n$/D" "$CHANGE_LOG" sed -i '/^$/N;/^\n$/D' "$CHANGELOG"
# Escape left angle brackets of HTML tag in the changelog as they break the markdown structure. e.g., '<hr>'
sed -i -E 's/\s(<[a-z])/ \\\1/g' "$CHANGELOG"
} }
## Bump new version to gem config file prepare() {
bump_gem() { _bump_version
_ver="$1" _improve_changelog
if $opt_pre; then
_ver="${1/-/.}"
fi
sed -i "s/[[:digit:]]\+\.[[:digit:]]\+\.[[:digit:]]\+/$_ver/" "$GEM_SPEC"
}
# Creates a new tag on the production branch with the given version number.
# Also commits the changes and merges the production branch into the default branch.
branch() {
_version="$1" # X.Y.Z
git add .
git commit -m "chore(release): $_version"
# Create a new tag on production branch
echo -e "> Create tag v$_version\n"
git tag "v$_version"
git checkout "$DEFAULT_BRANCH"
git merge --no-ff --no-edit "$PROD_BRANCH"
if [[ $working_branch == hotfix/* ]]; then
# delete the patch branch
git branch -D "$working_branch"
fi
} }
## Build a Gem package ## Build a Gem package
build_gem() { build_gem() {
git checkout "$PROD_BRANCH" if $opt_pkg; then
BACKUP_PATH="$(mktemp -d)"
mkdir -p "$BACKUP_PATH"/css "$BACKUP_PATH"/js
[[ -d $CSS_DIST ]] && cp "$CSS_DIST"/* "$BACKUP_PATH"/css
[[ -d $JS_DIST ]] && cp "$JS_DIST"/* "$BACKUP_PATH"/js
fi
# Remove unnecessary theme settings # Remove unnecessary theme settings
sed -i "s/^img_cdn:.*/img_cdn:/;s/^avatar:.*/avatar:/" _config.yml sed -i -E "s/(^timezone:).*/\1/;s/(^cdn:).*/\1/;s/(^avatar:).*/\1/" $CONFIG
rm -f ./*.gem rm -f ./*.gem
npm run build npm run build
git add "$JS_DIST" -f # add JS distribution files to gem # add CSS/JS distribution files to gem package
gem build "$GEM_SPEC" git add "$CSS_DIST" "$JS_DIST" -f
cp "$JS_DIST"/* "$BACKUP_PATH"
# Resume the settings echo -e "\n> gem build $GEM_SPEC\n"
gem build "$GEM_SPEC"
echo -e "\n> Resume file changes ...\n"
git reset git reset
git checkout . git checkout .
if $opt_pkg; then
# restore the dist files for future development # restore the dist files for future development
mkdir -p "$JS_DIST" && cp "$BACKUP_PATH"/* "$JS_DIST" mkdir -p "$CSS_DIST" "$JS_DIST"
cp "$BACKUP_PATH"/css/* "$CSS_DIST"
cp "$BACKUP_PATH"/js/* "$JS_DIST"
rm -rf "$BACKUP_PATH"
fi
}
# back to the default branch # Push the gem to RubyGems.org (using $GEM_HOST_API_KEY)
git checkout "$DEFAULT_BRANCH" push_gem() {
gem push ./*.gem
}
## Merge the release branch into the default branch
merge() {
git fetch origin "$MAIN_BRANCH"
git checkout -b "$MAIN_BRANCH" origin/"$MAIN_BRANCH"
git merge --no-ff --no-edit "$RELEASE_BRANCH" || (
git merge --abort
echo -e "\n> Conflict detected. Aborting merge.\n"
exit 0
)
git push origin "$MAIN_BRANCH"
} }
main() { main() {
check init
if [[ $opt_pre = false && $working_branch != "$PROD_BRANCH" ]]; then if $opt_pre; then
git checkout "$PROD_BRANCH" prepare
git merge --no-ff --no-edit "$working_branch" exit 0
fi fi
bump_node
_version="$(grep '"version":' "$NODE_CONFIG" | sed 's/.*: "//;s/".*//')"
bump_gem "$_version"
if [[ $opt_pre = false ]]; then
branch "$_version"
fi
echo -e "> Build the gem package for v$_version\n"
build_gem build_gem
$opt_pkg && exit 0
push_gem
merge
} }
while (($#)); do while (($#)); do
opt="$1" opt="$1"
case $opt in case $opt in
-p | --preview) --prepare)
opt_pre=true opt_pre=true
shift shift
;; ;;
-p | --package)
opt_pkg=true
shift
;;
-h | --help) -h | --help)
help help
exit 0 exit 0

View file

@ -2,4 +2,49 @@
# #
# Run jekyll serve and then launch the site # Run jekyll serve and then launch the site
bundle exec jekyll s -H 0.0.0.0 -l prod=false
command="bundle exec jekyll s -l"
host="127.0.0.1"
help() {
echo "Usage:"
echo
echo " bash /path/to/run [options]"
echo
echo "Options:"
echo " -H, --host [HOST] Host to bind to."
echo " -p, --production Run Jekyll in 'production' mode."
echo " -h, --help Print this help information."
}
while (($#)); do
opt="$1"
case $opt in
-H | --host)
host="$2"
shift 2
;;
-p | --production)
prod=true
shift
;;
-h | --help)
help
exit 0
;;
*)
echo -e "> Unknown option: '$opt'\n"
help
exit 1
;;
esac
done
command="$command -H $host"
if $prod; then
command="JEKYLL_ENV=production $command"
fi
echo -e "\n> $command\n"
eval "$command"

View file

@ -62,7 +62,7 @@ main() {
# test # test
bundle exec htmlproofer "$SITE_DIR" \ bundle exec htmlproofer "$SITE_DIR" \
--disable-external=true \ --disable-external \
--ignore-urls "/^http:\/\/127.0.0.1/,/^http:\/\/0.0.0.0/,/^http:\/\/localhost/" --ignore-urls "/^http:\/\/127.0.0.1/,/^http:\/\/0.0.0.0/,/^http:\/\/localhost/"
} }