Compare commits
No commits in common. "d51345e29754ba92a0c2fd6534bbd248ff298331" and "03e302cbf68cc502a2b6bef0726186a0b3ef321c" have entirely different histories.
d51345e297
...
03e302cbf6
21 changed files with 113 additions and 197 deletions
7
.github/workflows/cd.yml
vendored
7
.github/workflows/cd.yml
vendored
|
@ -2,12 +2,13 @@ name: CD
|
|||
|
||||
on:
|
||||
push:
|
||||
branches: [production]
|
||||
tags-ignore: ["**"]
|
||||
branches:
|
||||
- production
|
||||
tags-ignore:
|
||||
- "**"
|
||||
|
||||
jobs:
|
||||
release:
|
||||
if: ${{ ! startsWith(github.event.head_commit.message, 'chore(release)') }}
|
||||
permissions:
|
||||
contents: write
|
||||
issues: write
|
||||
|
|
20
.github/workflows/ci.yml
vendored
20
.github/workflows/ci.yml
vendored
|
@ -1,25 +1,17 @@
|
|||
name: CI
|
||||
|
||||
name: "CI"
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- "hotfix/*"
|
||||
- "master"
|
||||
- "hotfix/**"
|
||||
paths-ignore:
|
||||
- ".github/**"
|
||||
- "!.github/workflows/ci.yml"
|
||||
- .gitignore
|
||||
- ".gitignore"
|
||||
- "docs/**"
|
||||
- README.md
|
||||
- LICENSE
|
||||
- "README.md"
|
||||
- "LICENSE"
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- ".github/**"
|
||||
- "!.github/workflows/ci.yml"
|
||||
- .gitignore
|
||||
- "docs/**"
|
||||
- README.md
|
||||
- LICENSE
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
|
8
.github/workflows/commitlint.yml
vendored
8
.github/workflows/commitlint.yml
vendored
|
@ -1,11 +1,5 @@
|
|||
name: Lint Commit Messages
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- "hotfix/*"
|
||||
pull_request:
|
||||
on: pull_request
|
||||
|
||||
jobs:
|
||||
commitlint:
|
||||
|
|
25
.github/workflows/pr-filter.yml
vendored
25
.github/workflows/pr-filter.yml
vendored
|
@ -1,25 +0,0 @@
|
|||
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 });
|
1
.github/workflows/publish.yml
vendored
1
.github/workflows/publish.yml
vendored
|
@ -10,7 +10,6 @@ on:
|
|||
required: true
|
||||
BUILDER:
|
||||
required: true
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
launch:
|
||||
|
|
36
.github/workflows/scripts/pr-filter.js
vendored
36
.github/workflows/scripts/pr-filter.js
vendored
|
@ -1,36 +0,0 @@
|
|||
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.');
|
||||
}
|
||||
};
|
2
.github/workflows/stale.yml
vendored
2
.github/workflows/stale.yml
vendored
|
@ -9,7 +9,7 @@ permissions:
|
|||
pull-requests: write
|
||||
|
||||
env:
|
||||
STALE_LABEL: inactive
|
||||
STALE_LABEL: stale
|
||||
EXEMPT_LABELS: "pending,planning,in progress"
|
||||
MESSAGE: >
|
||||
This conversation has been automatically marked as stale because it has not had recent activity.
|
||||
|
|
6
.github/workflows/style-lint.yml
vendored
6
.github/workflows/style-lint.yml
vendored
|
@ -1,10 +1,8 @@
|
|||
name: Style Lint
|
||||
name: "Style Lint"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- "hotfix/*"
|
||||
branches: ["master", "hotfix/**"]
|
||||
paths: ["_sass/**/*.scss"]
|
||||
pull_request:
|
||||
paths: ["_sass/**/*.scss"]
|
||||
|
|
2
Gemfile
2
Gemfile
|
@ -11,4 +11,4 @@ platforms :mingw, :x64_mingw, :mswin, :jruby do
|
|||
gem "tzinfo-data"
|
||||
end
|
||||
|
||||
gem "wdm", "~> 0.2.0", :platforms => [:mingw, :x64_mingw, :mswin]
|
||||
gem "wdm", "~> 0.1.1", :platforms => [:mingw, :x64_mingw, :mswin]
|
||||
|
|
|
@ -2,21 +2,26 @@
|
|||
* Expand or close the sidebar in mobile screens.
|
||||
*/
|
||||
|
||||
const $sidebar = document.getElementById('sidebar');
|
||||
const $trigger = document.getElementById('sidebar-trigger');
|
||||
const $mask = document.getElementById('mask');
|
||||
const ATTR_DISPLAY = 'sidebar-display';
|
||||
|
||||
class SidebarUtil {
|
||||
static #isExpanded = false;
|
||||
static isExpanded = false;
|
||||
|
||||
static toggle() {
|
||||
this.#isExpanded = !this.#isExpanded;
|
||||
document.body.toggleAttribute('sidebar-display', this.#isExpanded);
|
||||
$sidebar.classList.toggle('z-2', this.#isExpanded);
|
||||
$mask.classList.toggle('d-none', !this.#isExpanded);
|
||||
if (SidebarUtil.isExpanded === false) {
|
||||
document.body.setAttribute(ATTR_DISPLAY, '');
|
||||
} else {
|
||||
document.body.removeAttribute(ATTR_DISPLAY);
|
||||
}
|
||||
|
||||
SidebarUtil.isExpanded = !SidebarUtil.isExpanded;
|
||||
}
|
||||
}
|
||||
|
||||
export function sidebarExpand() {
|
||||
$trigger.onclick = $mask.onclick = () => SidebarUtil.toggle();
|
||||
document
|
||||
.getElementById('sidebar-trigger')
|
||||
.addEventListener('click', SidebarUtil.toggle);
|
||||
|
||||
document.getElementById('mask').addEventListener('click', SidebarUtil.toggle);
|
||||
}
|
||||
|
|
|
@ -5,10 +5,7 @@ const desktopMode = matchMedia('(min-width: 1200px)');
|
|||
|
||||
function refresh(e) {
|
||||
if (e.matches) {
|
||||
if (mobile.popupOpened) {
|
||||
mobile.hidePopup();
|
||||
}
|
||||
|
||||
desktop.refresh();
|
||||
} else {
|
||||
mobile.refresh();
|
||||
|
|
|
@ -12,8 +12,8 @@ const SCROLL_LOCK = 'overflow-hidden';
|
|||
const CLOSING = 'closing';
|
||||
|
||||
export class TocMobile {
|
||||
static #invisible = true;
|
||||
static #barHeight = 16 * 3; // 3rem
|
||||
static invisible = true;
|
||||
static barHeight = 16 * 3; // 3rem
|
||||
|
||||
static options = {
|
||||
tocSelector: '#toc-popup-content',
|
||||
|
@ -23,7 +23,7 @@ export class TocMobile {
|
|||
orderedList: false,
|
||||
scrollSmooth: false,
|
||||
collapseDepth: 4,
|
||||
headingsOffset: this.#barHeight
|
||||
headingsOffset: this.barHeight
|
||||
};
|
||||
|
||||
static initBar() {
|
||||
|
@ -33,40 +33,44 @@ export class TocMobile {
|
|||
$tocBar.classList.toggle('invisible', entry.isIntersecting);
|
||||
});
|
||||
},
|
||||
{ rootMargin: `-${this.#barHeight}px 0px 0px 0px` }
|
||||
{ rootMargin: `-${this.barHeight}px 0px 0px 0px` }
|
||||
);
|
||||
|
||||
observer.observe($soloTrigger);
|
||||
this.#invisible = false;
|
||||
this.invisible = false;
|
||||
}
|
||||
|
||||
static listenAnchors() {
|
||||
const $anchors = document.getElementsByClassName('toc-link');
|
||||
[...$anchors].forEach((anchor) => {
|
||||
anchor.onclick = () => this.hidePopup();
|
||||
anchor.onclick = this.hidePopup;
|
||||
});
|
||||
}
|
||||
|
||||
static refresh() {
|
||||
if (this.#invisible) {
|
||||
if (this.invisible) {
|
||||
this.initComponents();
|
||||
}
|
||||
tocbot.refresh(this.options);
|
||||
this.listenAnchors();
|
||||
}
|
||||
|
||||
static get popupOpened() {
|
||||
return $popup.open;
|
||||
}
|
||||
|
||||
static showPopup() {
|
||||
this.lockScroll(true);
|
||||
TocMobile.lockScroll(true);
|
||||
$popup.showModal();
|
||||
const activeItem = $popup.querySelector('li.is-active-li');
|
||||
activeItem.scrollIntoView({ block: 'center' });
|
||||
}
|
||||
|
||||
static hidePopup() {
|
||||
static hidePopup(event) {
|
||||
if (event?.type === 'cancel') {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
if (!$popup.open) {
|
||||
return;
|
||||
}
|
||||
|
||||
$popup.toggleAttribute(CLOSING);
|
||||
|
||||
$popup.addEventListener(
|
||||
|
@ -78,7 +82,7 @@ export class TocMobile {
|
|||
{ once: true }
|
||||
);
|
||||
|
||||
this.lockScroll(false);
|
||||
TocMobile.lockScroll(false);
|
||||
}
|
||||
|
||||
static lockScroll(enable) {
|
||||
|
@ -87,10 +91,6 @@ export class TocMobile {
|
|||
}
|
||||
|
||||
static clickBackdrop(event) {
|
||||
if ($popup.hasAttribute(CLOSING)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const rect = event.target.getBoundingClientRect();
|
||||
if (
|
||||
event.clientX < rect.left ||
|
||||
|
@ -98,7 +98,7 @@ export class TocMobile {
|
|||
event.clientY < rect.top ||
|
||||
event.clientY > rect.bottom
|
||||
) {
|
||||
this.hidePopup();
|
||||
TocMobile.hidePopup();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -106,15 +106,11 @@ export class TocMobile {
|
|||
this.initBar();
|
||||
|
||||
[...$triggers].forEach((trigger) => {
|
||||
trigger.onclick = () => this.showPopup();
|
||||
trigger.onclick = this.showPopup;
|
||||
});
|
||||
|
||||
$popup.onclick = (e) => this.clickBackdrop(e);
|
||||
$btnClose.onclick = () => this.hidePopup();
|
||||
$popup.oncancel = (e) => {
|
||||
e.preventDefault();
|
||||
this.hidePopup();
|
||||
};
|
||||
$popup.onclick = this.clickBackdrop;
|
||||
$btnClose.onclick = $popup.oncancel = this.hidePopup;
|
||||
}
|
||||
|
||||
static init() {
|
||||
|
|
|
@ -68,7 +68,7 @@ layout: compress
|
|||
</aside>
|
||||
</div>
|
||||
|
||||
<div id="mask" class="d-none position-fixed w-100 h-100 z-1"></div>
|
||||
<div id="mask"></div>
|
||||
|
||||
{% if site.pwa.enabled %}
|
||||
{% include_cached notification.html lang=lang %}
|
||||
|
|
|
@ -100,7 +100,7 @@ tail_includes:
|
|||
{% if enable_toc %}
|
||||
<div id="toc-bar" class="d-flex align-items-center justify-content-between invisible">
|
||||
<span class="label text-truncate">{{ page.title }}</span>
|
||||
<button type="button" class="toc-trigger btn me-1">
|
||||
<button type="button" class="toc-trigger btn btn-link me-1">
|
||||
<i class="fa-solid fa-list-ul fa-fw"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
@ -113,8 +113,8 @@ tail_includes:
|
|||
<dialog id="toc-popup" class="p-0">
|
||||
<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>
|
||||
<button id="toc-popup-close" type="button" class="btn mx-1 my-1 opacity-75">
|
||||
<i class="fas fa-close"></i>
|
||||
<button id="toc-popup-close" type="button" class="btn btn-link">
|
||||
<i class="fas fa-close fa-fw"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div id="toc-popup-content" class="px-4 py-3 pb-4"></div>
|
||||
|
|
|
@ -251,8 +251,8 @@ i {
|
|||
|
||||
> p {
|
||||
margin-left: 0.25em;
|
||||
|
||||
@include mt-mb(0);
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -688,6 +688,7 @@ $btn-mb: 0.5rem;
|
|||
height: 100%;
|
||||
overflow-y: auto;
|
||||
width: $sidebar-width;
|
||||
z-index: 99;
|
||||
background: var(--sidebar-bg);
|
||||
border-right: 1px solid var(--sidebar-border-color);
|
||||
|
||||
|
@ -768,8 +769,8 @@ $btn-mb: 0.5rem;
|
|||
li.nav-item {
|
||||
opacity: 0.9;
|
||||
width: 100%;
|
||||
|
||||
@include pl-pr(1.5rem);
|
||||
padding-left: 1.5rem;
|
||||
padding-right: 1.5rem;
|
||||
|
||||
a.nav-link {
|
||||
@include pt-pb(0.6rem);
|
||||
|
@ -1042,7 +1043,7 @@ search {
|
|||
|
||||
a {
|
||||
font-size: 1.4rem;
|
||||
line-height: 1.5rem;
|
||||
line-height: 2.5rem;
|
||||
|
||||
&:hover {
|
||||
@extend %link-hover;
|
||||
|
@ -1068,9 +1069,8 @@ search {
|
|||
}
|
||||
|
||||
> p {
|
||||
@extend %text-ellipsis;
|
||||
|
||||
white-space: break-spaces;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
|
@ -1086,11 +1086,23 @@ search {
|
|||
color: var(--topbar-text-color);
|
||||
text-align: center;
|
||||
width: 70%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
word-break: keep-all;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#mask {
|
||||
display: none;
|
||||
position: fixed;
|
||||
inset: 0 0 0 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
z-index: 1;
|
||||
|
||||
@at-root [#{$sidebar-display}] & {
|
||||
display: block !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* --- basic wrappers --- */
|
||||
|
@ -1480,8 +1492,8 @@ search {
|
|||
|
||||
#main-wrapper > .container {
|
||||
max-width: $main-content-max-width;
|
||||
|
||||
@include pl-pr(1.75rem, true);
|
||||
padding-left: 1.75rem !important;
|
||||
padding-right: 1.75rem !important;
|
||||
}
|
||||
|
||||
main.col-12,
|
||||
|
|
|
@ -112,16 +112,6 @@
|
|||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
@mixin text-ellipsis {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
%text-ellipsis {
|
||||
@include text-ellipsis;
|
||||
}
|
||||
|
||||
%text-highlight {
|
||||
color: var(--text-muted-highlight-color);
|
||||
font-weight: 600;
|
||||
|
@ -168,15 +158,10 @@
|
|||
padding-bottom: $val;
|
||||
}
|
||||
|
||||
@mixin pl-pr($val, $important: false) {
|
||||
@if $important {
|
||||
padding-left: $val !important;
|
||||
padding-right: $val !important;
|
||||
} @else {
|
||||
@mixin pl-pr($val) {
|
||||
padding-left: $val;
|
||||
padding-right: $val;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin placeholder {
|
||||
color: var(--text-muted-color) !important;
|
||||
|
|
|
@ -58,8 +58,9 @@
|
|||
li {
|
||||
font-size: 1.1rem;
|
||||
line-height: 3rem;
|
||||
|
||||
@extend %text-ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
&:nth-child(odd) {
|
||||
background-color: var(--main-bg, #ffffff);
|
||||
|
|
|
@ -63,7 +63,9 @@
|
|||
}
|
||||
|
||||
> a {
|
||||
@include text-ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -74,8 +74,9 @@
|
|||
|
||||
> div:first-child {
|
||||
display: block;
|
||||
|
||||
@extend %text-ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* Post-specific styles
|
||||
/*
|
||||
Post-specific style
|
||||
*/
|
||||
|
||||
%btn-post-nav {
|
||||
|
@ -97,7 +97,7 @@ header {
|
|||
|
||||
&:hover {
|
||||
i {
|
||||
@extend %btn-share-hover;
|
||||
@extend %btn-share-hovor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -258,8 +258,9 @@ header {
|
|||
|
||||
.toc-link {
|
||||
display: block;
|
||||
|
||||
@extend %text-ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
&:hover {
|
||||
color: var(--toc-highlight);
|
||||
|
@ -379,13 +380,12 @@ header {
|
|||
$slide-in: slide-in 0.3s ease-out;
|
||||
$slide-out: slide-out 0.3s ease-out;
|
||||
$curtain-height: 2rem;
|
||||
$backdrop: blur(5px);
|
||||
|
||||
border-color: var(--toc-popup-border-color);
|
||||
border-width: 1px;
|
||||
border-radius: $radius-lg;
|
||||
color: var(--text-color);
|
||||
background: var(--card-bg);
|
||||
background: var(--main-bg);
|
||||
margin-top: $topbar-height;
|
||||
min-width: 20rem;
|
||||
font-size: 1.05rem;
|
||||
|
@ -422,16 +422,9 @@ header {
|
|||
}
|
||||
}
|
||||
|
||||
button {
|
||||
> i {
|
||||
font-size: 1.25rem;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
button:focus-visible {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style-type: none;
|
||||
|
@ -468,20 +461,20 @@ header {
|
|||
}
|
||||
|
||||
&::-webkit-backdrop {
|
||||
-webkit-backdrop-filter: $backdrop;
|
||||
backdrop-filter: $backdrop;
|
||||
-webkit-backdrop-filter: blur(5px);
|
||||
backdrop-filter: blur(5px);
|
||||
}
|
||||
|
||||
&::backdrop {
|
||||
-webkit-backdrop-filter: $backdrop;
|
||||
backdrop-filter: $backdrop;
|
||||
-webkit-backdrop-filter: blur(5px);
|
||||
backdrop-filter: blur(5px);
|
||||
}
|
||||
|
||||
&::after {
|
||||
display: flex;
|
||||
content: '';
|
||||
position: relative;
|
||||
background: linear-gradient(transparent, var(--card-bg) 70%);
|
||||
background: linear-gradient(transparent, var(--main-bg) 70%);
|
||||
height: $curtain-height;
|
||||
}
|
||||
|
||||
|
@ -508,11 +501,10 @@ header {
|
|||
}
|
||||
|
||||
p {
|
||||
@extend %text-ellipsis;
|
||||
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: 0.5rem;
|
||||
white-space: break-spaces;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
|
@ -534,7 +526,7 @@ header {
|
|||
max-width: 100%;
|
||||
}
|
||||
|
||||
%btn-share-hover {
|
||||
%btn-share-hovor {
|
||||
color: var(--btn-share-hover-color) !important;
|
||||
}
|
||||
|
||||
|
@ -566,8 +558,10 @@ header {
|
|||
/* Hide SideBar and TOC */
|
||||
@media all and (max-width: 849px) {
|
||||
.post-navigation {
|
||||
@include pl-pr(0);
|
||||
@include ml-mr(-0.5rem);
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
margin-left: -0.5rem;
|
||||
margin-right: -0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@ permalink: /feed.xml
|
|||
<updated>{{ post.date | date_to_xmlschema }}</updated>
|
||||
{% endif %}
|
||||
<id>{{ post_absolute_url }}</id>
|
||||
<content type="text/html" src="{{ post_absolute_url }}" />
|
||||
<content src="{{ post_absolute_url }}" />
|
||||
<author>
|
||||
<name>{{ post.author | default: site.social.name }}</name>
|
||||
</author>
|
||||
|
|
Loading…
Reference in a new issue