Compare commits

...

10 commits

Author SHA1 Message Date
Cotes Chung
d51345e297
ci: reduce unnecessary pr-filter runs (#2033)
- Checking the repository of the PR is more effective than checking the label to identify bot-initiated PRs
- This change also allows more flexible PR body definitions for developers with write access to the repository
2024-11-08 22:35:18 +08:00
Cotes Chung
2f844978aa
chore: change stale label to inactive 2024-11-08 22:15:31 +08:00
Supreeth Mysore Venkatesh
42dea8ee29
build(deps): update wdm gem version for compatibility (#2028) 2024-11-04 00:45:59 +08:00
Alexander Fuks
86b13c917f
chore: improve feed interoperability (#2024) 2024-11-01 14:39:03 +04:00
Cotes Chung
4ef3cd8efc
ci: improve workflow triggers (#2017)
- Unchain commit-lint and CI
  - Even if a commit does not meet the CI path filter, it still needs to lint the commit message.
- Unchain PR filter and CI
  - The CI workflow needs to be triggered when the commits in a pull request are modified.
- Allow manual publishing
  - Sometimes `semantic-release` will error out due to commit messages referencing discussions, but this does not affect the final RubyGems/GitHub Release. In such cases, manual triggering of the publish process is needed to complete the remaining publishing steps.
2024-10-29 22:56:32 +08:00
Cotes Chung
c7f967529c
ci: skip test for invalid PRs (#2013) 2024-10-26 16:58:07 +08:00
Cotes Chung
74ed06321c
ci: block invalid pull requests (#2010) 2024-10-25 19:48:11 +08:00
Cotes Chung
d4f7f39ece
refactor: simplify sidebar animation 2024-10-22 11:13:06 +08:00
Cotes Chung
c1bd9eb9ee
refactor: reduce duplicate scss 2024-10-20 13:52:42 +08:00
Cotes Chung
6f461132c0
refactor: improve toc popup module 2024-10-19 21:15:31 +08:00
21 changed files with 197 additions and 113 deletions

View file

@ -2,13 +2,12 @@ name: CD
on: on:
push: push:
branches: branches: [production]
- production tags-ignore: ["**"]
tags-ignore:
- "**"
jobs: jobs:
release: release:
if: ${{ ! startsWith(github.event.head_commit.message, 'chore(release)') }}
permissions: permissions:
contents: write contents: write
issues: write issues: write

View file

@ -1,17 +1,25 @@
name: "CI" name: CI
on: on:
push: push:
branches: branches:
- "master" - master
- "hotfix/**" - "hotfix/*"
paths-ignore: paths-ignore:
- ".github/**" - ".github/**"
- "!.github/workflows/ci.yml" - "!.github/workflows/ci.yml"
- ".gitignore" - .gitignore
- "docs/**" - "docs/**"
- "README.md" - README.md
- "LICENSE" - LICENSE
pull_request: pull_request:
paths-ignore:
- ".github/**"
- "!.github/workflows/ci.yml"
- .gitignore
- "docs/**"
- README.md
- LICENSE
jobs: jobs:
build: build:

View file

@ -1,5 +1,11 @@
name: Lint Commit Messages name: Lint Commit Messages
on: pull_request
on:
push:
branches:
- master
- "hotfix/*"
pull_request:
jobs: jobs:
commitlint: commitlint:

25
.github/workflows/pr-filter.yml vendored Normal file
View file

@ -0,0 +1,25 @@
name: PR Filter
on:
pull_request_target:
types: [opened, reopened]
jobs:
check-template:
if: github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Check PR Content
id: intercept
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const script = require('.github/workflows/scripts/pr-filter.js');
await script({ github, context, core });

View file

@ -10,6 +10,7 @@ on:
required: true required: true
BUILDER: BUILDER:
required: true required: true
workflow_dispatch:
jobs: jobs:
launch: launch:

36
.github/workflows/scripts/pr-filter.js vendored Normal file
View file

@ -0,0 +1,36 @@
function hasTypes(markdown) {
return /## Type of change/.test(markdown) && /-\s\[x\]/i.test(markdown);
}
function hasDescription(markdown) {
return (
/## Description/.test(markdown) &&
!/## Description\s*\n\s*(##|\s*$)/.test(markdown)
);
}
module.exports = async ({ github, context, core }) => {
const pr = context.payload.pull_request;
const body = pr.body === null ? '' : pr.body;
const markdown = body.replace(/<!--[\s\S]*?-->/g, '');
const action = context.payload.action;
const isValid =
markdown !== '' && hasTypes(markdown) && hasDescription(markdown);
if (!isValid) {
await github.rest.pulls.update({
...context.repo,
pull_number: pr.number,
state: 'closed'
});
await github.rest.issues.createComment({
...context.repo,
issue_number: pr.number,
body: `Oops, it seems you've ${action} an invalid pull request. No worries, we'll close it for you.`
});
core.setFailed('PR content does not meet template requirements.');
}
};

View file

@ -9,7 +9,7 @@ permissions:
pull-requests: write pull-requests: write
env: env:
STALE_LABEL: stale STALE_LABEL: inactive
EXEMPT_LABELS: "pending,planning,in progress" EXEMPT_LABELS: "pending,planning,in progress"
MESSAGE: > MESSAGE: >
This conversation has been automatically marked as stale because it has not had recent activity. This conversation has been automatically marked as stale because it has not had recent activity.

View file

@ -1,8 +1,10 @@
name: "Style Lint" name: Style Lint
on: on:
push: push:
branches: ["master", "hotfix/**"] branches:
- master
- "hotfix/*"
paths: ["_sass/**/*.scss"] paths: ["_sass/**/*.scss"]
pull_request: pull_request:
paths: ["_sass/**/*.scss"] paths: ["_sass/**/*.scss"]

View file

@ -11,4 +11,4 @@ platforms :mingw, :x64_mingw, :mswin, :jruby do
gem "tzinfo-data" gem "tzinfo-data"
end end
gem "wdm", "~> 0.1.1", :platforms => [:mingw, :x64_mingw, :mswin] gem "wdm", "~> 0.2.0", :platforms => [:mingw, :x64_mingw, :mswin]

View file

@ -2,26 +2,21 @@
* Expand or close the sidebar in mobile screens. * Expand or close the sidebar in mobile screens.
*/ */
const ATTR_DISPLAY = 'sidebar-display'; const $sidebar = document.getElementById('sidebar');
const $trigger = document.getElementById('sidebar-trigger');
const $mask = document.getElementById('mask');
class SidebarUtil { class SidebarUtil {
static isExpanded = false; static #isExpanded = false;
static toggle() { static toggle() {
if (SidebarUtil.isExpanded === false) { this.#isExpanded = !this.#isExpanded;
document.body.setAttribute(ATTR_DISPLAY, ''); document.body.toggleAttribute('sidebar-display', this.#isExpanded);
} else { $sidebar.classList.toggle('z-2', this.#isExpanded);
document.body.removeAttribute(ATTR_DISPLAY); $mask.classList.toggle('d-none', !this.#isExpanded);
}
SidebarUtil.isExpanded = !SidebarUtil.isExpanded;
} }
} }
export function sidebarExpand() { export function sidebarExpand() {
document $trigger.onclick = $mask.onclick = () => SidebarUtil.toggle();
.getElementById('sidebar-trigger')
.addEventListener('click', SidebarUtil.toggle);
document.getElementById('mask').addEventListener('click', SidebarUtil.toggle);
} }

View file

@ -5,7 +5,10 @@ const desktopMode = matchMedia('(min-width: 1200px)');
function refresh(e) { function refresh(e) {
if (e.matches) { if (e.matches) {
if (mobile.popupOpened) {
mobile.hidePopup(); mobile.hidePopup();
}
desktop.refresh(); desktop.refresh();
} else { } else {
mobile.refresh(); mobile.refresh();

View file

@ -12,8 +12,8 @@ const SCROLL_LOCK = 'overflow-hidden';
const CLOSING = 'closing'; const CLOSING = 'closing';
export class TocMobile { export class TocMobile {
static invisible = true; static #invisible = true;
static barHeight = 16 * 3; // 3rem static #barHeight = 16 * 3; // 3rem
static options = { static options = {
tocSelector: '#toc-popup-content', tocSelector: '#toc-popup-content',
@ -23,7 +23,7 @@ export class TocMobile {
orderedList: false, orderedList: false,
scrollSmooth: false, scrollSmooth: false,
collapseDepth: 4, collapseDepth: 4,
headingsOffset: this.barHeight headingsOffset: this.#barHeight
}; };
static initBar() { static initBar() {
@ -33,44 +33,40 @@ export class TocMobile {
$tocBar.classList.toggle('invisible', entry.isIntersecting); $tocBar.classList.toggle('invisible', entry.isIntersecting);
}); });
}, },
{ rootMargin: `-${this.barHeight}px 0px 0px 0px` } { rootMargin: `-${this.#barHeight}px 0px 0px 0px` }
); );
observer.observe($soloTrigger); observer.observe($soloTrigger);
this.invisible = false; this.#invisible = false;
} }
static listenAnchors() { static listenAnchors() {
const $anchors = document.getElementsByClassName('toc-link'); const $anchors = document.getElementsByClassName('toc-link');
[...$anchors].forEach((anchor) => { [...$anchors].forEach((anchor) => {
anchor.onclick = this.hidePopup; anchor.onclick = () => this.hidePopup();
}); });
} }
static refresh() { static refresh() {
if (this.invisible) { if (this.#invisible) {
this.initComponents(); this.initComponents();
} }
tocbot.refresh(this.options); tocbot.refresh(this.options);
this.listenAnchors(); this.listenAnchors();
} }
static get popupOpened() {
return $popup.open;
}
static showPopup() { static showPopup() {
TocMobile.lockScroll(true); this.lockScroll(true);
$popup.showModal(); $popup.showModal();
const activeItem = $popup.querySelector('li.is-active-li'); const activeItem = $popup.querySelector('li.is-active-li');
activeItem.scrollIntoView({ block: 'center' }); activeItem.scrollIntoView({ block: 'center' });
} }
static hidePopup(event) { static hidePopup() {
if (event?.type === 'cancel') {
event.preventDefault();
}
if (!$popup.open) {
return;
}
$popup.toggleAttribute(CLOSING); $popup.toggleAttribute(CLOSING);
$popup.addEventListener( $popup.addEventListener(
@ -82,7 +78,7 @@ export class TocMobile {
{ once: true } { once: true }
); );
TocMobile.lockScroll(false); this.lockScroll(false);
} }
static lockScroll(enable) { static lockScroll(enable) {
@ -91,6 +87,10 @@ export class TocMobile {
} }
static clickBackdrop(event) { static clickBackdrop(event) {
if ($popup.hasAttribute(CLOSING)) {
return;
}
const rect = event.target.getBoundingClientRect(); const rect = event.target.getBoundingClientRect();
if ( if (
event.clientX < rect.left || event.clientX < rect.left ||
@ -98,7 +98,7 @@ export class TocMobile {
event.clientY < rect.top || event.clientY < rect.top ||
event.clientY > rect.bottom event.clientY > rect.bottom
) { ) {
TocMobile.hidePopup(); this.hidePopup();
} }
} }
@ -106,11 +106,15 @@ export class TocMobile {
this.initBar(); this.initBar();
[...$triggers].forEach((trigger) => { [...$triggers].forEach((trigger) => {
trigger.onclick = this.showPopup; trigger.onclick = () => this.showPopup();
}); });
$popup.onclick = this.clickBackdrop; $popup.onclick = (e) => this.clickBackdrop(e);
$btnClose.onclick = $popup.oncancel = this.hidePopup; $btnClose.onclick = () => this.hidePopup();
$popup.oncancel = (e) => {
e.preventDefault();
this.hidePopup();
};
} }
static init() { static init() {

View file

@ -68,7 +68,7 @@ layout: compress
</aside> </aside>
</div> </div>
<div id="mask"></div> <div id="mask" class="d-none position-fixed w-100 h-100 z-1"></div>
{% if site.pwa.enabled %} {% if site.pwa.enabled %}
{% include_cached notification.html lang=lang %} {% include_cached notification.html lang=lang %}

View file

@ -100,7 +100,7 @@ tail_includes:
{% if enable_toc %} {% if enable_toc %}
<div id="toc-bar" class="d-flex align-items-center justify-content-between invisible"> <div id="toc-bar" class="d-flex align-items-center justify-content-between invisible">
<span class="label text-truncate">{{ page.title }}</span> <span class="label text-truncate">{{ page.title }}</span>
<button type="button" class="toc-trigger btn btn-link me-1"> <button type="button" class="toc-trigger btn me-1">
<i class="fa-solid fa-list-ul fa-fw"></i> <i class="fa-solid fa-list-ul fa-fw"></i>
</button> </button>
</div> </div>
@ -113,8 +113,8 @@ tail_includes:
<dialog id="toc-popup" class="p-0"> <dialog id="toc-popup" class="p-0">
<div class="header d-flex flex-row align-items-center justify-content-between"> <div class="header d-flex flex-row align-items-center justify-content-between">
<div class="label text-truncate py-2 ms-4">{{- page.title -}}</div> <div class="label text-truncate py-2 ms-4">{{- page.title -}}</div>
<button id="toc-popup-close" type="button" class="btn btn-link"> <button id="toc-popup-close" type="button" class="btn mx-1 my-1 opacity-75">
<i class="fas fa-close fa-fw"></i> <i class="fas fa-close"></i>
</button> </button>
</div> </div>
<div id="toc-popup-content" class="px-4 py-3 pb-4"></div> <div id="toc-popup-content" class="px-4 py-3 pb-4"></div>

View file

@ -251,8 +251,8 @@ i {
> p { > p {
margin-left: 0.25em; margin-left: 0.25em;
margin-top: 0;
margin-bottom: 0; @include mt-mb(0);
} }
} }
} }
@ -688,7 +688,6 @@ $btn-mb: 0.5rem;
height: 100%; height: 100%;
overflow-y: auto; overflow-y: auto;
width: $sidebar-width; width: $sidebar-width;
z-index: 99;
background: var(--sidebar-bg); background: var(--sidebar-bg);
border-right: 1px solid var(--sidebar-border-color); border-right: 1px solid var(--sidebar-border-color);
@ -769,8 +768,8 @@ $btn-mb: 0.5rem;
li.nav-item { li.nav-item {
opacity: 0.9; opacity: 0.9;
width: 100%; width: 100%;
padding-left: 1.5rem;
padding-right: 1.5rem; @include pl-pr(1.5rem);
a.nav-link { a.nav-link {
@include pt-pb(0.6rem); @include pt-pb(0.6rem);
@ -1043,7 +1042,7 @@ search {
a { a {
font-size: 1.4rem; font-size: 1.4rem;
line-height: 2.5rem; line-height: 1.5rem;
&:hover { &:hover {
@extend %link-hover; @extend %link-hover;
@ -1069,8 +1068,9 @@ search {
} }
> p { > p {
overflow: hidden; @extend %text-ellipsis;
text-overflow: ellipsis;
white-space: break-spaces;
display: -webkit-box; display: -webkit-box;
-webkit-line-clamp: 3; -webkit-line-clamp: 3;
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
@ -1086,23 +1086,11 @@ search {
color: var(--topbar-text-color); color: var(--topbar-text-color);
text-align: center; text-align: center;
width: 70%; width: 70%;
overflow: hidden;
text-overflow: ellipsis;
word-break: keep-all; word-break: keep-all;
white-space: nowrap;
} }
#mask { #mask {
display: none;
position: fixed;
inset: 0 0 0 0; inset: 0 0 0 0;
height: 100%;
width: 100%;
z-index: 1;
@at-root [#{$sidebar-display}] & {
display: block !important;
}
} }
/* --- basic wrappers --- */ /* --- basic wrappers --- */
@ -1492,8 +1480,8 @@ search {
#main-wrapper > .container { #main-wrapper > .container {
max-width: $main-content-max-width; max-width: $main-content-max-width;
padding-left: 1.75rem !important;
padding-right: 1.75rem !important; @include pl-pr(1.75rem, true);
} }
main.col-12, main.col-12,

View file

@ -112,6 +112,16 @@
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
} }
@mixin text-ellipsis {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
%text-ellipsis {
@include text-ellipsis;
}
%text-highlight { %text-highlight {
color: var(--text-muted-highlight-color); color: var(--text-muted-highlight-color);
font-weight: 600; font-weight: 600;
@ -158,10 +168,15 @@
padding-bottom: $val; padding-bottom: $val;
} }
@mixin pl-pr($val) { @mixin pl-pr($val, $important: false) {
@if $important {
padding-left: $val !important;
padding-right: $val !important;
} @else {
padding-left: $val; padding-left: $val;
padding-right: $val; padding-right: $val;
} }
}
@mixin placeholder { @mixin placeholder {
color: var(--text-muted-color) !important; color: var(--text-muted-color) !important;

View file

@ -58,9 +58,8 @@
li { li {
font-size: 1.1rem; font-size: 1.1rem;
line-height: 3rem; line-height: 3rem;
white-space: nowrap;
overflow: hidden; @extend %text-ellipsis;
text-overflow: ellipsis;
&:nth-child(odd) { &:nth-child(odd) {
background-color: var(--main-bg, #ffffff); background-color: var(--main-bg, #ffffff);

View file

@ -63,9 +63,7 @@
} }
> a { > a {
white-space: nowrap; @include text-ellipsis;
overflow: hidden;
text-overflow: ellipsis;
} }
} }
} }

View file

@ -74,9 +74,8 @@
> div:first-child { > div:first-child {
display: block; display: block;
white-space: nowrap;
overflow: hidden; @extend %text-ellipsis;
text-overflow: ellipsis;
} }
} }
} }

View file

@ -1,5 +1,5 @@
/* /**
Post-specific style * Post-specific styles
*/ */
%btn-post-nav { %btn-post-nav {
@ -97,7 +97,7 @@ header {
&:hover { &:hover {
i { i {
@extend %btn-share-hovor; @extend %btn-share-hover;
} }
} }
} }
@ -258,9 +258,8 @@ header {
.toc-link { .toc-link {
display: block; display: block;
white-space: nowrap;
overflow: hidden; @extend %text-ellipsis;
text-overflow: ellipsis;
&:hover { &:hover {
color: var(--toc-highlight); color: var(--toc-highlight);
@ -380,12 +379,13 @@ header {
$slide-in: slide-in 0.3s ease-out; $slide-in: slide-in 0.3s ease-out;
$slide-out: slide-out 0.3s ease-out; $slide-out: slide-out 0.3s ease-out;
$curtain-height: 2rem; $curtain-height: 2rem;
$backdrop: blur(5px);
border-color: var(--toc-popup-border-color); border-color: var(--toc-popup-border-color);
border-width: 1px; border-width: 1px;
border-radius: $radius-lg; border-radius: $radius-lg;
color: var(--text-color); color: var(--text-color);
background: var(--main-bg); background: var(--card-bg);
margin-top: $topbar-height; margin-top: $topbar-height;
min-width: 20rem; min-width: 20rem;
font-size: 1.05rem; font-size: 1.05rem;
@ -422,9 +422,16 @@ header {
} }
} }
button:focus-visible { button {
> i {
font-size: 1.25rem;
vertical-align: middle;
}
&:focus-visible {
box-shadow: none; box-shadow: none;
} }
}
ul { ul {
list-style-type: none; list-style-type: none;
@ -461,20 +468,20 @@ header {
} }
&::-webkit-backdrop { &::-webkit-backdrop {
-webkit-backdrop-filter: blur(5px); -webkit-backdrop-filter: $backdrop;
backdrop-filter: blur(5px); backdrop-filter: $backdrop;
} }
&::backdrop { &::backdrop {
-webkit-backdrop-filter: blur(5px); -webkit-backdrop-filter: $backdrop;
backdrop-filter: blur(5px); backdrop-filter: $backdrop;
} }
&::after { &::after {
display: flex; display: flex;
content: ''; content: '';
position: relative; position: relative;
background: linear-gradient(transparent, var(--main-bg) 70%); background: linear-gradient(transparent, var(--card-bg) 70%);
height: $curtain-height; height: $curtain-height;
} }
@ -501,10 +508,11 @@ header {
} }
p { p {
@extend %text-ellipsis;
font-size: 0.9rem; font-size: 0.9rem;
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
overflow: hidden; white-space: break-spaces;
text-overflow: ellipsis;
display: -webkit-box; display: -webkit-box;
-webkit-line-clamp: 2; -webkit-line-clamp: 2;
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
@ -526,7 +534,7 @@ header {
max-width: 100%; max-width: 100%;
} }
%btn-share-hovor { %btn-share-hover {
color: var(--btn-share-hover-color) !important; color: var(--btn-share-hover-color) !important;
} }
@ -558,10 +566,8 @@ header {
/* Hide SideBar and TOC */ /* Hide SideBar and TOC */
@media all and (max-width: 849px) { @media all and (max-width: 849px) {
.post-navigation { .post-navigation {
padding-left: 0; @include pl-pr(0);
padding-right: 0; @include ml-mr(-0.5rem);
margin-left: -0.5rem;
margin-right: -0.5rem;
} }
} }

View file

@ -34,7 +34,7 @@ permalink: /feed.xml
<updated>{{ post.date | date_to_xmlschema }}</updated> <updated>{{ post.date | date_to_xmlschema }}</updated>
{% endif %} {% endif %}
<id>{{ post_absolute_url }}</id> <id>{{ post_absolute_url }}</id>
<content src="{{ post_absolute_url }}" /> <content type="text/html" src="{{ post_absolute_url }}" />
<author> <author>
<name>{{ post.author | default: site.social.name }}</name> <name>{{ post.author | default: site.social.name }}</name>
</author> </author>