Merge branch 'master' into production

This commit is contained in:
Cotes Chung 2024-02-15 00:18:35 +08:00
commit b9d053b3cd
No known key found for this signature in database
GPG key ID: 0D9E54843167A808
32 changed files with 304 additions and 184 deletions

2
.github/codeql/codeql-config.yml vendored Normal file
View file

@ -0,0 +1,2 @@
paths-ignore:
- "assets/js"

View file

@ -7,7 +7,7 @@ updates:
bundler: bundler:
dependency-type: "production" dependency-type: "production"
schedule: schedule:
interval: "monthly" interval: "weekly"
- package-ecosystem: "npm" - package-ecosystem: "npm"
directory: "/" directory: "/"
versioning-strategy: increase versioning-strategy: increase
@ -15,7 +15,7 @@ updates:
npm: npm:
dependency-type: "development" dependency-type: "development"
schedule: schedule:
interval: "monthly" interval: "weekly"
- package-ecosystem: "github-actions" - package-ecosystem: "github-actions"
directory: "/" directory: "/"
groups: groups:
@ -23,4 +23,4 @@ updates:
update-types: update-types:
- "major" - "major"
schedule: schedule:
interval: "monthly" interval: "weekly"

View file

@ -2,11 +2,9 @@ name: "CodeQL"
on: on:
push: push:
paths: ["**.js"] paths: ["_javascript/**/*.js"]
pull_request: pull_request:
paths: ["**.js"] paths: ["_javascript/**/*.js"]
schedule:
- cron: "0 0 * * 5"
jobs: jobs:
analyze: analyze:
@ -32,6 +30,7 @@ jobs:
uses: github/codeql-action/init@v3 uses: github/codeql-action/init@v3
with: with:
languages: "${{ matrix.language }}" languages: "${{ matrix.language }}"
config-file: .github/codeql/codeql-config.yml
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java). # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java).
# If this step fails, then you should remove it and run the build manually (see below) # If this step fails, then you should remove it and run the build manually (see below)

View file

@ -22,8 +22,8 @@ jobs:
steps: steps:
- uses: actions/stale@v9 - uses: actions/stale@v9
with: with:
days-before-stale: 30 # 60 days before marking issues/PRs stale
days-before-close: 1 days-before-close: -1 # does not close automatically
stale-issue-label: ${{ env.STALE_LABEL }} stale-issue-label: ${{ env.STALE_LABEL }}
exempt-issue-labels: ${{ env.EXEMPT_LABELS }} exempt-issue-labels: ${{ env.EXEMPT_LABELS }}
stale-issue-message: ${{ env.MESSAGE }} stale-issue-message: ${{ env.MESSAGE }}

1
.gitignore vendored
View file

@ -16,7 +16,6 @@ package-lock.json
# IDE configurations # IDE configurations
.idea .idea
.vscode
# Misc # Misc
assets/js/dist assets/js/dist

View file

@ -1,4 +1,4 @@
#!/bin/sh #!/bin/sh
. "$(dirname "$0")/_/husky.sh" . "$(dirname "$0")/_/husky.sh"
npx --no -- commitlint -x $(npm root -g)/@commitlint/config-conventional --edit npx --no -- commitlint --edit ${1}

12
.vscode/extensions.json vendored Normal file
View file

@ -0,0 +1,12 @@
{
"recommendations": [
// Liquid tags auto-complete
"killalau.vscode-liquid-snippets",
// Liquid syntax highlighting and formatting
"Shopify.theme-check-vscode",
// Common formatter
"esbenp.prettier-vscode",
"foxundermoon.shell-format",
"stylelint.vscode-stylelint"
]
}

24
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,24 @@
{
// Prettier
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"prettier.trailingComma": "none",
// Shopify Liquid
"files.associations": {
"*.html": "liquid"
},
// Formatter
"[html][liquid]": {
"editor.defaultFormatter": "Shopify.theme-check-vscode"
},
"[shellscript]": {
"editor.defaultFormatter": "foxundermoon.shell-format"
},
// Disable vscode built-in stylelint
"css.validate": false,
"scss.validate": false,
"less.validate": false,
// Stylint extension settings
"stylelint.snippet": ["css", "less", "postcss", "scss"],
"stylelint.validate": ["css", "less", "postcss", "scss"]
}

View file

@ -34,7 +34,7 @@
- Built-in Search - Built-in Search
- Atom Feeds - Atom Feeds
- PWA - PWA
- Google Analytics - Google Analytics / GoatCounter
- SEO & Performance Optimization - SEO & Performance Optimization
## Documentation ## Documentation

View file

@ -52,6 +52,9 @@ google_site_verification: # fill in to your verification string
google_analytics: google_analytics:
id: # fill in your Google Analytics ID id: # fill in your Google Analytics ID
goatcounter:
id: # fill in your Goatcounter ID
# Prefer color scheme setting. # Prefer color scheme setting.
# #
# Note: Keep empty will follow the system prefer color by default, # Note: Keep empty will follow the system prefer color by default,
@ -63,7 +66,7 @@ google_analytics:
# light - Use the light color scheme # light - Use the light color scheme
# dark - Use the dark color scheme # dark - Use the dark color scheme
# #
theme_mode: # [light|dark] theme_mode: # [light | dark]
# The CDN endpoint for images. # The CDN endpoint for images.
# Notice that once it is assigned, the CDN url # Notice that once it is assigned, the CDN url
@ -108,10 +111,17 @@ assets:
enabled: # boolean, keep empty means false enabled: # boolean, keep empty means false
# specify the Jekyll environment, empty means both # specify the Jekyll environment, empty means both
# only works if `assets.self_host.enabled` is 'true' # only works if `assets.self_host.enabled` is 'true'
env: # [development|production] env: # [development | production]
pwa: pwa:
enabled: true # the option for PWA feature enabled: true # the option for PWA feature (installable)
cache:
enabled: true # the option for PWA offline cache
# Paths defined here will be excluded from the PWA cache.
# Usually its value is the `baseurl` of another website that
# shares the same domain name as the current website.
deny_paths:
# - "/example" # URLs match `<SITE_URL>/example/*` will not be cached by the PWA
paginate: 10 paginate: 10
@ -157,10 +167,6 @@ defaults:
values: values:
layout: page layout: page
permalink: /:title/ permalink: /:title/
- scope:
path: assets/img/favicons
values:
swcache: true
- scope: - scope:
path: assets/js/dist path: assets/js/dist
values: values:

View file

@ -8,6 +8,8 @@ cdns:
- url: https://fonts.googleapis.com - url: https://fonts.googleapis.com
# jsDelivr CDN # jsDelivr CDN
- url: https://cdn.jsdelivr.net - url: https://cdn.jsdelivr.net
# polyfill.io for math
- url: https://polyfill.io
# fonts # fonts
@ -33,7 +35,7 @@ search:
js: https://cdn.jsdelivr.net/npm/simple-jekyll-search@1.10.0/dest/simple-jekyll-search.min.js js: https://cdn.jsdelivr.net/npm/simple-jekyll-search@1.10.0/dest/simple-jekyll-search.min.js
mermaid: mermaid:
js: https://cdn.jsdelivr.net/npm/mermaid@10.6.1/dist/mermaid.min.js js: https://cdn.jsdelivr.net/npm/mermaid@10.8.0/dist/mermaid.min.js
dayjs: dayjs:
js: js:

View file

@ -8,7 +8,9 @@
<link rel="apple-touch-icon" sizes="180x180" href="{{ favicon_path }}/apple-touch-icon.png"> <link rel="apple-touch-icon" sizes="180x180" href="{{ favicon_path }}/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="{{ favicon_path }}/favicon-32x32.png"> <link rel="icon" type="image/png" sizes="32x32" href="{{ favicon_path }}/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="{{ favicon_path }}/favicon-16x16.png"> <link rel="icon" type="image/png" sizes="16x16" href="{{ favicon_path }}/favicon-16x16.png">
<link rel="manifest" href="{{ favicon_path }}/site.webmanifest"> {% if site.pwa.enabled %}
<link rel="manifest" href="{{ favicon_path }}/site.webmanifest">
{% endif %}
<link rel="shortcut icon" href="{{ favicon_path }}/favicon.ico"> <link rel="shortcut icon" href="{{ favicon_path }}/favicon.ico">
<meta name="apple-mobile-web-app-title" content="{{ site.title }}"> <meta name="apple-mobile-web-app-title" content="{{ site.title }}">
<meta name="application-name" content="{{ site.title }}"> <meta name="application-name" content="{{ site.title }}">

View file

@ -8,9 +8,15 @@
" "
> >
<p> <p>
{{ '©' }} {{- '©' }}
<time>{{ 'now' | date: '%Y' }}</time> <time>{{ 'now' | date: '%Y' }}</time>
<a href="{{ site.social.links[0] }}">{{ site.social.name }}</a>.
{% if site.social.links %}
<a href="{{ site.social.links[0] }}">{{ site.social.name }}</a>.
{% else %}
<em class="fst-normal">{{ site.social.name }}</em>.
{% endif %}
{% if site.data.locales[include.lang].copyright.brief %} {% if site.data.locales[include.lang].copyright.brief %}
<span <span
data-bs-toggle="tooltip" data-bs-toggle="tooltip"

View file

@ -0,0 +1,8 @@
<!-- GoatCounter -->
<script
data-goatcounter="https://{{ site.goatcounter.id }}.goatcounter.com/count"
async
src="https://gc.zgo.at/count.js"
></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 %} {% include img-url.html src=src img_path=page.img_path absolute=true %}
{%- endcapture -%} {%- endcapture -%}
{%- capture old_url -%}{{ src | absolute_url }}{%- endcapture -%} {%- capture old_url -%}{{ src | absolute_url }}{%- endcapture -%}
@ -31,15 +31,20 @@
{% 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 %} {% include img-url.html src=site.social_preview_image absolute=true %}
{%- endcapture -%} {%- endcapture -%}
{%- capture og_image -%} {%- capture og_image -%}
<meta property="og:image" content="{{ img_url }}" /> <meta property="og:image" content="{{ img_url }}" />
{%- endcapture -%} {%- endcapture -%}
{% assign old_meta_clip = '<meta name="twitter:card"' %} {%- capture twitter_image -%}
{% assign new_meta_clip = og_image | append: old_meta_clip %} <meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:image" content="{{ img_url }}" />
{%- endcapture -%}
{% assign old_meta_clip = '<meta name="twitter:card" content="summary" />' %}
{% assign new_meta_clip = og_image | append: twitter_image %}
{% assign seo_tags = seo_tags | replace: old_meta_clip, new_meta_clip %} {% assign seo_tags = seo_tags | replace: old_meta_clip, new_meta_clip %}
{% endif %} {% endif %}
@ -76,7 +81,7 @@
{% endif %} {% endif %}
<!-- Bootstrap --> <!-- Bootstrap -->
<link rel="stylesheet" href="{{ site.data.origin[type].bootstrap.css | relative_url}}"> <link rel="stylesheet" href="{{ site.data.origin[type].bootstrap.css | relative_url }}">
<!-- Font Awesome --> <!-- Font Awesome -->
<link rel="stylesheet" href="{{ site.data.origin[type].fontawesome.css | relative_url }}"> <link rel="stylesheet" href="{{ site.data.origin[type].fontawesome.css | relative_url }}">

View file

@ -2,8 +2,9 @@
Generate image final URL based on `site.img_cdn`, `page.img_path` Generate image final URL based on `site.img_cdn`, `page.img_path`
Arguments: Arguments:
src - basic image path, required src - required, basic image path
img_path - relative path of image, optional img_path - optional, relative path of image
absolute - optional, boolean, if true, generate absolute URL
Return: Return:
image URL image URL
@ -14,7 +15,7 @@
{%- if url -%} {%- if url -%}
{% unless url contains ':' %} {% unless url contains ':' %}
{%- comment -%} CND URL {%- endcomment -%} {%- comment -%} CND URL {%- endcomment -%}
{% assign prefix = site.img_cdn | default: '' | relative_url %} {% assign prefix = site.img_cdn | default: '' %}
{%- comment -%} Add page image path prefix {%- endcomment -%} {%- comment -%} Add page image path prefix {%- endcomment -%}
{% assign url = include.img_path | default: '' | append: '/' | append: url %} {% assign url = include.img_path | default: '' | append: '/' | append: url %}
@ -26,6 +27,12 @@
| replace: '//', '/' | replace: '//', '/'
| replace: ':', ':/' | replace: ':', ':/'
%} %}
{% if include.absolute %}
{% assign url = url | absolute_url %}
{% else %}
{% assign url = url | relative_url %}
{% endif %}
{% endunless %} {% endunless %}
{%- endif -%} {%- endif -%}

View file

@ -11,6 +11,8 @@
<!-- layout specified --> <!-- layout specified -->
{% assign js_dist = '/assets/js/dist/' %}
{% if page.layout == 'post' or page.layout == 'page' or page.layout == 'home' %} {% if page.layout == 'post' or page.layout == 'page' or page.layout == 'home' %}
{% assign urls = urls | append: ',' | append: site.data.origin[type]['lazy-polyfill'].js %} {% assign urls = urls | append: ',' | append: site.data.origin[type]['lazy-polyfill'].js %}
@ -65,7 +67,7 @@
{% assign js = 'commons' %} {% assign js = 'commons' %}
{% endcase %} {% endcase %}
{% capture script %}/assets/js/dist/{{ js }}.min.js{% endcapture %} {% capture script %}{{ js_dist }}{{ js }}.min.js{% endcapture %}
<script defer src="{{ script | relative_url }}"></script> <script defer src="{{ script | relative_url }}"></script>
{% if page.math %} {% if page.math %}
@ -83,7 +85,9 @@
displayMath: [ displayMath: [
['$$', '$$'], ['$$', '$$'],
['\\[', '\\]'] ['\\[', '\\]']
] ],
/* equation numbering */
tags: 'ams'
} }
}; };
</script> </script>
@ -94,13 +98,16 @@
{% if jekyll.environment == 'production' %} {% if jekyll.environment == 'production' %}
<!-- PWA --> <!-- PWA -->
{% if site.pwa.enabled %} {% if site.pwa.enabled %}
<script defer src="{{ '/app.js' | relative_url }}"></script> <script defer src="{{ 'app.min.js' | prepend: js_dist | relative_url }}"></script>
{% else %}
<script defer src="{{ '/unregister.js' | relative_url }}"></script>
{% endif %} {% endif %}
<!-- GA --> <!-- GA -->
{% if site.google_analytics.id != empty and site.google_analytics.id %} {% if site.google_analytics.id != empty and site.google_analytics.id %}
{% include google-analytics.html %} {% include google-analytics.html %}
{% endif %} {% endif %}
<!-- GoatCounter -->
{% if site.goatcounter.id != empty and site.goatcounter.id %}
{% include goatcounter.html %}
{% endif %}
{% endif %} {% endif %}

View file

@ -17,7 +17,7 @@
{% if forloop.first %} {% if forloop.first %}
<span> <span>
<a href="{{ '/' | relative_url }}"> <a href="{{ '/' | relative_url }}">
{{ site.data.locales[include.lang].tabs.home | capitalize }} {{- site.data.locales[include.lang].tabs.home | capitalize -}}
</a> </a>
</span> </span>
@ -30,8 +30,8 @@
{% elsif page.layout == 'category' or page.layout == 'tag' %} {% elsif page.layout == 'category' or page.layout == 'tag' %}
<span> <span>
<a href="{{ item | relative_url }}"> <a href="{{ item | append: '/' | relative_url }}">
{{ site.data.locales[include.lang].tabs[item] | default: page.title }} {{- site.data.locales[include.lang].tabs[item] | default: page.title -}}
</a> </a>
</span> </span>
{% endif %} {% endif %}

View file

@ -1,5 +1,5 @@
export function toc() { export function toc() {
if (document.querySelector('main h2')) { if (document.querySelector('main h2, main h3')) {
// see: https://github.com/tscanlin/tocbot#usage // see: https://github.com/tscanlin/tocbot#usage
tocbot.init({ tocbot.init({
tocSelector: '#toc', tocSelector: '#toc',

View file

@ -134,7 +134,14 @@ fi;
The mathematics powered by [**MathJax**](https://www.mathjax.org/): The mathematics powered by [**MathJax**](https://www.mathjax.org/):
$$ \sum_{n=1}^\infty 1/n^2 = \frac{\pi^2}{6} $$ $$
\begin{equation}
\sum_{n=1}^\infty 1/n^2 = \frac{\pi^2}{6}
\label{eq:series}
\end{equation}
$$
We can reference the equation as \eqref{eq:series}.
When $a \ne 0$, there are two solutions to $ax^2 + bx + c = 0$ and they are When $a \ne 0$, there are two solutions to $ax^2 + bx + c = 0$ and they are

View file

@ -69,7 +69,6 @@ authors: [<author1_id>, <author2_id>] # for multiple entries
--- ---
``` ```
Having said that, the key `author` can also identify multiple entries. 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.
@ -107,9 +106,11 @@ math: true
--- ---
``` ```
After enabling the mathematical feature, you can add math equations with the following syntax: After enabling the mathematical feature, you can add math equations with the following syntax:
- **Block math** should be added with `$$ math $$` with **mandatory** blank lines before and after `$$` - **Block math** should be added with `$$ math $$` with **mandatory** blank lines before and after `$$`
- **Inserting equation numbering** should be added with `$$\begin{equation} math \end{equation}$$`
- **Referencing equation numbering** should be done with `\label{eq:label_name}` in the equation block and `\eqref{eq:label_name}` inline with text (see example below)
- **Inline math** (in lines) should be added with `$$ math $$` without any blank line before or after `$$` - **Inline math** (in lines) should be added with `$$ math $$` without any blank line before or after `$$`
- **Inline math** (in lists) should be added with `\$$ math $$` - **Inline math** (in lists) should be added with `\$$ math $$`
@ -120,6 +121,17 @@ $$
LaTeX_math_expression LaTeX_math_expression
$$ $$
<!-- Equation numbering, keep all blank lines -->
$$
\begin{equation}
LaTeX_math_expression
\label{eq:label_name}
\end{equation}
$$
Can be referenced as \eqref{eq:label_name}.
<!-- Inline math in lines, NO blank lines --> <!-- Inline math in lines, NO blank lines -->
"Lorem ipsum dolor sit amet, $$ LaTeX_math_expression $$ consectetur adipiscing elit." "Lorem ipsum dolor sit amet, $$ LaTeX_math_expression $$ consectetur adipiscing elit."
@ -243,7 +255,7 @@ For instance, when using images:
The parsing result will automatically add the CDN prefix `https://cdn.com` before the image path: The parsing result will automatically add the CDN prefix `https://cdn.com` before the image path:
```html ```html
<img src="https://cdn.com/path/to/flower.png" alt="The flower"> <img src="https://cdn.com/path/to/flower.png" alt="The flower" />
``` ```
{: .nolineno } {: .nolineno }
@ -267,7 +279,7 @@ And then, the image source of Markdown can write the file name directly:
The output will be: The output will be:
```html ```html
<img src="/img/path/flower.png" alt="The flower"> <img src="/img/path/flower.png" alt="The flower" />
``` ```
{: .nolineno } {: .nolineno }
@ -285,7 +297,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 [`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.
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.
@ -420,6 +432,7 @@ You can embed a video with the following syntax:
```liquid ```liquid
{% include embed/{Platform}.html id='{ID}' %} {% include embed/{Platform}.html id='{ID}' %}
``` ```
Where `Platform` is the lowercase of the platform name, and `ID` is the video ID. Where `Platform` is the lowercase of the platform name, and `ID` is the video ID.
The following table shows how to get the two parameters we need in a given video URL, and you can also know the currently supported video platforms. The following table shows how to get the two parameters we need in a given video URL, and you can also know the currently supported video platforms.

View file

@ -101,7 +101,7 @@ Now you can choose _ONE_ of the following methods to deploy your Jekyll site.
There are a few things to get ready for. There are a few things to get ready for.
- If you're on the GitHub Free plan, keep your site repository public. - If you're on the GitHub Free plan, keep your site repository public.
- If you have committed `Gemfile.lock`{: .filepath} to the repository, and your local machine is not running Linux, go the the root of your site and update the platform list of the lock-file: - If you have committed `Gemfile.lock`{: .filepath} to the repository, and your local machine is not running Linux, go to the root of your site and update the platform list of the lock-file:
```console ```console
$ bundle lock --add-platform x86_64-linux $ bundle lock --add-platform x86_64-linux

View file

@ -144,6 +144,10 @@ footer {
} }
} }
em {
@extend %text-highlight;
}
p { p {
text-align: center; text-align: center;
margin-bottom: 0; margin-bottom: 0;

View file

@ -1,49 +0,0 @@
---
layout: compress
# The list to be cached by PWA
---
const resource = [
/* --- CSS --- */
'{{ "/assets/css/:THEME.css" | replace: ':THEME', site.theme | relative_url }}',
/* --- PWA --- */
'{{ "/app.js" | relative_url }}',
'{{ "/sw.js" | relative_url }}',
/* --- HTML --- */
'{{ "/index.html" | relative_url }}',
'{{ "/404.html" | relative_url }}',
{% for tab in site.tabs %}
'{{ tab.url | relative_url }}',
{% endfor %}
/* --- Favicons & compressed JS --- */
{% assign cache_list = site.static_files | where: 'swcache', true %}
{% for file in cache_list %}
'{{ file.path | relative_url }}'{%- unless forloop.last -%},{%- endunless -%}
{% endfor %}
];
/* The request url with below domain will be cached */
const allowedDomains = [
{% if site.google_analytics.id != empty and site.google_analytics.id %}
'www.googletagmanager.com',
'www.google-analytics.com',
{% endif %}
'{{ site.url | split: "//" | last }}',
{% if site.img_cdn contains '//' and site.img_cdn %}
'{{ site.img_cdn | split: '//' | last | split: '/' | first }}',
{% endif %}
'fonts.gstatic.com',
'fonts.googleapis.com',
'cdn.jsdelivr.net',
'polyfill.io'
];
/* Requests that include the following path will be banned */
const denyUrls = [];

51
assets/js/data/swconf.js Normal file
View file

@ -0,0 +1,51 @@
---
layout: compress
permalink: '/:path/swconf.js'
# Note that this file will be fetched by the ServiceWorker, so it will not be cached.
---
const swconf = {
{% if site.pwa.cache.enabled %}
cacheName: 'chirpy-{{ "now" | date: "%s" }}',
{%- comment -%} Resources added to the cache during PWA installation. {%- endcomment -%}
resources: [
'{{ "/assets/css/:THEME.css" | replace: ':THEME', site.theme | relative_url }}',
'{{ "/" | relative_url }}',
{% for tab in site.tabs %}
'{{- tab.url | relative_url -}}',
{% endfor %}
{% assign cache_list = site.static_files | where: 'swcache', true %}
{% for file in cache_list %}
'{{ file.path | relative_url }}'{%- unless forloop.last -%},{%- endunless -%}
{% 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 -%}
denyPaths: [
{% for path in site.pwa.cache.deny_paths %}
{% unless path == empty %}
'{{ path | relative_url }}'{%- unless forloop.last -%},{%- endunless -%}
{% endunless %}
{% endfor %}
],
purge: false
{% else %}
purge: true
{% endif %}
};

View file

@ -1,47 +1,54 @@
--- ---
layout: compress layout: compress
permalink: '/app.js' permalink: /assets/js/dist/:basename.min.js
--- ---
const $notification = $('#notification');
const $btnRefresh = $('#notification .toast-body>button');
if ('serviceWorker' in navigator) { if ('serviceWorker' in navigator) {
/* Registering Service Worker */ const isEnabled = '{{ site.pwa.enabled }}' === 'true';
navigator.serviceWorker.register('{{ "/sw.js" | relative_url }}')
.then(registration => {
/* in case the user ignores the notification */ if (isEnabled) {
if (registration.waiting) { const swUrl = '{{ '/sw.min.js' | relative_url }}';
$notification.toast('show'); const $notification = $('#notification');
const $btnRefresh = $('#notification .toast-body>button');
navigator.serviceWorker.register(swUrl).then((registration) => {
{% comment %}In case the user ignores the notification{% endcomment %}
if (registration.waiting) {
$notification.toast('show');
}
registration.addEventListener('updatefound', () => {
registration.installing.addEventListener('statechange', () => {
if (registration.waiting) {
if (navigator.serviceWorker.controller) {
$notification.toast('show');
} }
}
registration.addEventListener('updatefound', () => {
registration.installing.addEventListener('statechange', () => {
if (registration.waiting) {
if (navigator.serviceWorker.controller) {
$notification.toast('show');
}
}
});
});
$btnRefresh.click(() => {
if (registration.waiting) {
registration.waiting.postMessage('SKIP_WAITING');
}
$notification.toast('hide');
});
}); });
});
$btnRefresh.on('click', () => {
if (registration.waiting) {
registration.waiting.postMessage('SKIP_WAITING');
}
$notification.toast('hide');
});
});
let refreshing = false; let refreshing = false;
/* Detect controller change and refresh all the opened tabs */ {% comment %}Detect controller change and refresh all the opened tabs{% endcomment %}
navigator.serviceWorker.addEventListener('controllerchange', () => { navigator.serviceWorker.addEventListener('controllerchange', () => {
if (!refreshing) { if (!refreshing) {
window.location.reload(); window.location.reload();
refreshing = true; refreshing = true;
} }
}); });
} else {
navigator.serviceWorker.getRegistrations().then(function (registrations) {
for (let registration of registrations) {
registration.unregister();
}
});
}
} }

View file

@ -1,37 +1,51 @@
--- ---
layout: compress layout: compress
permalink: '/sw.js' permalink: /:basename.min.js
# PWA service worker # PWA service worker
--- ---
self.importScripts('{{ "/assets/js/data/swcache.js" | relative_url }}'); const swconfUrl = '{{ '/assets/js/data/swconf.js' | relative_url }}';
const cacheName = 'chirpy-{{ "now" | date: "%s" }}'; importScripts(swconfUrl);
const purge = swconf.purge;
function verifyDomain(url) { function verifyHost(url) {
for (const domain of allowedDomains) { for (const host of swconf.allowHosts) {
const regex = RegExp(`^http(s)?:\/\/${domain}\/`); const regex = RegExp(`^http(s)?://${host}/`);
if (regex.test(url)) { if (regex.test(url)) {
return true; return true;
} }
} }
return false; return false;
} }
function isExcluded(url) { function verifyUrl(url) {
for (const item of denyUrls) { if (!verifyHost(url)) {
if (url === item) { return false;
return true; }
const requestPath = new URL(url).pathname;
for (const path of swconf.denyPaths) {
if (requestPath.startsWith(path)) {
return false;
} }
} }
return false; return true;
}
if (!purge) {
swconf.allowHosts.push(location.host);
} }
self.addEventListener('install', (event) => { self.addEventListener('install', (event) => {
if (purge) {
return;
}
event.waitUntil( event.waitUntil(
caches.open(cacheName).then((cache) => { caches.open(swconf.cacheName).then((cache) => {
return cache.addAll(resource); return cache.addAll(swconf.resources);
}) })
); );
}); });
@ -41,8 +55,12 @@ self.addEventListener('activate', (event) => {
caches.keys().then((keyList) => { caches.keys().then((keyList) => {
return Promise.all( return Promise.all(
keyList.map((key) => { keyList.map((key) => {
if (key !== cacheName) { if (purge) {
return caches.delete(key); return caches.delete(key);
} else {
if (key !== swconf.cacheName) {
return caches.delete(key);
}
} }
}) })
); );
@ -66,22 +84,16 @@ self.addEventListener('fetch', (event) => {
return fetch(event.request).then((response) => { return fetch(event.request).then((response) => {
const url = event.request.url; const url = event.request.url;
if ( if (purge || event.request.method !== 'GET' || !verifyUrl(url)) {
event.request.method !== 'GET' ||
!verifyDomain(url) ||
isExcluded(url)
) {
return response; return response;
} }
/* see: <https://developers.google.com/web/fundamentals/primers/service-workers#cache_and_return_requests> */ {% comment %}See: <https://developers.google.com/web/fundamentals/primers/service-workers#cache_and_return_requests>{% endcomment %}
let responseToCache = response.clone(); let responseToCache = response.clone();
caches.open(cacheName).then((cache) => { caches.open(swconf.cacheName).then((cache) => {
/* console.log('[sw] Caching new resource: ' + event.request.url); */
cache.put(event.request, responseToCache); cache.put(event.request, responseToCache);
}); });
return response; return response;
}); });
}) })

View file

@ -1,12 +0,0 @@
---
layout: compress
permalink: '/unregister.js'
---
if ('serviceWorker' in navigator) {
navigator.serviceWorker.getRegistrations().then((registrations) => {
for (let reg of registrations) {
reg.unregister();
}
});
}

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

View file

@ -21,16 +21,19 @@
"fixlint": "npm run test -- --fix" "fixlint": "npm run test -- --fix"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.23.7", "@babel/core": "^7.23.9",
"@babel/plugin-proposal-class-properties": "^7.18.6", "@babel/plugin-proposal-class-properties": "^7.18.6",
"@babel/preset-env": "^7.23.7", "@babel/preset-env": "^7.23.9",
"@commitlint/cli": "^18.6.1",
"@commitlint/config-conventional": "^18.6.2",
"@rollup/plugin-babel": "^6.0.4", "@rollup/plugin-babel": "^6.0.4",
"@rollup/plugin-terser": "^0.4.4", "@rollup/plugin-terser": "^0.4.4",
"husky": "^9.0.11",
"rimraf": "^5.0.5", "rimraf": "^5.0.5",
"rollup": "^4.9.2", "rollup": "^4.10.0",
"rollup-plugin-license": "^3.2.0", "rollup-plugin-license": "^3.2.0",
"stylelint": "^16.1.0", "stylelint": "^16.2.1",
"stylelint-config-standard-scss": "^12.0.0" "stylelint-config-standard-scss": "^13.0.0"
}, },
"prettier": { "prettier": {
"trailingComma": "none" "trailingComma": "none"
@ -41,6 +44,9 @@
"not dead" "not dead"
], ],
"commitlint": { "commitlint": {
"extends": [
"@commitlint/config-conventional"
],
"rules": { "rules": {
"body-max-line-length": [ "body-max-line-length": [
0, 0,

View file

@ -3,21 +3,21 @@ import terser from '@rollup/plugin-terser';
import license from 'rollup-plugin-license'; import license from 'rollup-plugin-license';
import path from 'path'; import path from 'path';
const JS_SRC = '_javascript'; const SRC_DEFAULT = '_javascript';
const JS_DIST = 'assets/js/dist'; const DIST_DEFAULT = 'assets/js/dist';
const isProd = process.env.NODE_ENV === 'production'; const isProd = process.env.NODE_ENV === 'production';
function build(filename) { function build(filename) {
return { return {
input: [`${JS_SRC}/${filename}.js`], input: [`${SRC_DEFAULT}/${filename}.js`],
output: { output: {
file: `${JS_DIST}/${filename}.min.js`, file: `${DIST_DEFAULT}/${filename}.min.js`,
format: 'iife', format: 'iife',
name: 'Chirpy', name: 'Chirpy',
sourcemap: !isProd sourcemap: !isProd
}, },
watch: { watch: {
include: `${JS_SRC}/**` include: `${SRC_DEFAULT}/**`
}, },
plugins: [ plugins: [
babel({ babel({
@ -28,7 +28,7 @@ function build(filename) {
license({ license({
banner: { banner: {
commentStyle: 'ignored', commentStyle: 'ignored',
content: { file: path.join(__dirname, JS_SRC, '_copyright') } content: { file: path.join(__dirname, SRC_DEFAULT, '_copyright') }
} }
}), }),
isProd && terser() isProd && terser()

View file

@ -70,12 +70,14 @@ _check_git() {
exit 1 exit 1
fi fi
if [[ $working_branch != "$DEFAULT_BRANCH" && $opt_pre || (
$working_branch != hotfix/* && if [[ $working_branch != "$DEFAULT_BRANCH" &&
$working_branch != "$PROD_BRANCH" ]]; then $working_branch != hotfix/* &&
echo "> Abort: Please run on the default, release or patch branch." $working_branch != "$PROD_BRANCH" ]]; then
exit 1 echo "> Abort: Please run on the default, release or patch branch."
fi exit 1
fi
)
} }
_check_src() { _check_src() {
@ -156,7 +158,7 @@ build_gem() {
rm -f ./*.gem rm -f ./*.gem
npm run build npm run build
git add "$JS_DIST" -f # add JS dist to gem git add "$JS_DIST" -f # add JS distribution files to gem
gem build "$GEM_SPEC" gem build "$GEM_SPEC"
cp "$JS_DIST"/* "$BACKUP_PATH" cp "$JS_DIST"/* "$BACKUP_PATH"