Merge branch 'master' of https://github.com/CaiJimmy/hugo-theme-stack into CaiJimmy-master

This commit is contained in:
mapleafgo 2022-03-09 10:37:13 +08:00
commit c7bb15fbd1
92 changed files with 2018 additions and 651 deletions

31
.devcontainer/Dockerfile Normal file
View File

@ -0,0 +1,31 @@
# Update the NODE_VERSION arg in docker-compose.yml to pick a Node version: 10, 12, 14
ARG NODE_VERSION=14
FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:0-${NODE_VERSION}
# VARIANT can be either 'hugo' for the standard version or 'hugo_extended' for the extended version.
ARG VARIANT=hugo
# VERSION can be either 'latest' or a specific version number
ARG VERSION=latest
# Download Hugo
RUN apt-get update && apt-get install -y ca-certificates openssl git curl && \
rm -rf /var/lib/apt/lists/* && \
case ${VERSION} in \
latest) \
export VERSION=$(curl -s https://api.github.com/repos/gohugoio/hugo/releases/latest | grep "tag_name" | awk '{print substr($2, 3, length($2)-4)}') ;;\
esac && \
echo ${VERSION} && \
wget -O ${VERSION}.tar.gz https://github.com/gohugoio/hugo/releases/download/v${VERSION}/${VARIANT}_${VERSION}_Linux-64bit.tar.gz && \
tar xf ${VERSION}.tar.gz && \
mv hugo /usr/bin/hugo
# Hugo dev server port
EXPOSE 1313
# [Optional] Uncomment this section to install additional OS packages you may want.
#
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
# && apt-get -y install --no-install-recommends <your-package-list-here>
# [Optional] Uncomment if you want to install more global node packages
# RUN sudo -u node npm install -g <your-package-list-here>

View File

@ -0,0 +1,45 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
// https://github.com/microsoft/vscode-dev-containers/tree/v0.202.3/containers/hugo
{
"name": "Hugo (Community)",
"build": {
"dockerfile": "Dockerfile",
"args": {
// Update VARIANT to pick hugo variant.
// Example variants: hugo, hugo_extended
// Rebuild the container if it already exists to update.
"VARIANT": "hugo_extended",
// Update VERSION to pick a specific hugo version.
// Example versions: latest, 0.73.0, 0,71.1
// Rebuild the container if it already exists to update.
"VERSION": "latest",
// Update NODE_VERSION to pick the Node.js version: 12, 14
"NODE_VERSION": "14",
}
},
// Set *default* container specific settings.json values on container create.
"settings": {
"html.format.templating": true,
},
// Add the IDs of extensions you want installed when the container is created.
"extensions": [
"bungcip.better-toml",
"davidanson.vscode-markdownlint"
],
// Use 'forwardPorts' to make a list of ports inside the container available locally.
"forwardPorts": [
1313
],
// Use 'postCreateCommand' to run commands after the container is created.
// "postCreateCommand": "uname -a",
// Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
"remoteUser": "node",
"features": {
"golang": "latest"
}
}

1
.github/FUNDING.yml vendored
View File

@ -1 +1,2 @@
ko_fi: jimmycai ko_fi: jimmycai
github: CaiJimmy

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
public
resources
assets/jsconfig.json
.hugo_build.lock

View File

@ -3,9 +3,13 @@
> Card-style Hugo theme designed for bloggers. > Card-style Hugo theme designed for bloggers.
## Quickstart
Use this template: [CaiJimmy/hugo-theme-stack-starter](https://github.com/CaiJimmy/hugo-theme-stack-starter)
## Demo ## Demo
[Example Site](https://theme-stack.jimmycai.com/) [Example Site](https://demo.stack.jimmycai.com/)
[![Netlify Status](https://api.netlify.com/api/v1/badges/a2d2807a-a905-4bcb-97da-8da8d847da3d/deploy-status)](https://app.netlify.com/sites/hugo-theme-stack/deploys) [![Netlify Status](https://api.netlify.com/api/v1/badges/a2d2807a-a905-4bcb-97da-8da8d847da3d/deploy-status)](https://app.netlify.com/sites/hugo-theme-stack/deploys)
@ -28,6 +32,7 @@ Stack is a simple card-style Hugo theme designed for bloggers, some of its featu
- Properly cropped thumbnails - Properly cropped thumbnails
- Subsection support - Subsection support
- Table of contents - Table of contents
- Multilingual mode and RTL support
## Requirements ## Requirements
@ -35,7 +40,10 @@ It's necessary to use **Hugo Extended ≥ 0.87.0**.
## Installation ## Installation
Clone / Download this repository to `theme` folder, and edit your site config following `exampleSite/config.yaml`. * Route 1: Clone / Download this repository to `theme` folder
* Route 2: Turn your site into a hugo module and add this theme as a module dependency
Edit your site config following `exampleSite/config.yaml`.
*Note: Remove `config.toml` if there is one in the site folder.* *Note: Remove `config.toml` if there is one in the site folder.*

View File

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-brand-github" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<path d="M9 19c-4.3 1.4 -4.3 -2.5 -6 -3m12 5v-3.5c0 -1 .1 -1.4 -.5 -2c2.8 -.3 5.5 -1.4 5.5 -6a4.6 4.6 0 0 0 -1.3 -3.2a4.2 4.2 0 0 0 -.1 -3.2s-1.1 -.3 -3.5 1.3a12.3 12.3 0 0 0 -6.2 0c-2.4 -1.6 -3.5 -1.3 -3.5 -1.3a4.2 4.2 0 0 0 -.1 3.2a4.6 4.6 0 0 0 -1.3 3.2c0 4.6 2.7 5.7 5.5 6c-.6 .6 -.6 1.2 -.5 2v3.5" />
</svg>

After

Width:  |  Height:  |  Size: 603 B

View File

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-brand-twitter" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<path d="M22 4.01c-1 .49 -1.98 .689 -3 .99c-1.121 -1.265 -2.783 -1.335 -4.38 -.737s-2.643 2.06 -2.62 3.737v1c-3.245 .083 -6.135 -1.395 -8 -4c0 0 -4.182 7.433 4 11c-1.872 1.247 -3.739 2.088 -6 2c3.308 1.803 6.913 2.423 10.034 1.517c3.58 -1.04 6.522 -3.723 7.651 -7.742a13.84 13.84 0 0 0 .497 -3.753c-.002 -.249 1.51 -2.772 1.818 -4.013z" />
</svg>

After

Width:  |  Height:  |  Size: 638 B

View File

@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-hash" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z"/>
<line x1="5" y1="9" x2="19" y2="9" />
<line x1="5" y1="15" x2="19" y2="15" />
<line x1="11" y1="4" x2="7" y2="20" />
<line x1="17" y1="4" x2="13" y2="20" />
</svg>

After

Width:  |  Height:  |  Size: 440 B

10
assets/icons/language.svg Normal file
View File

@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-language" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<path d="M4 5h7" />
<path d="M9 3v2c0 4.418 -2.239 8 -5 8" />
<path d="M5 9c-.003 2.144 2.952 3.908 6.7 4" />
<path d="M12 20l4 -9l4 9" />
<path d="M19.1 18h-6.2" />
</svg>

After

Width:  |  Height:  |  Size: 467 B

View File

@ -6,5 +6,7 @@
"*" "*"
] ]
}, },
"lib": ["es2020", "dom"],
"jsx": "preserve"
} }
} }

View File

@ -11,7 +11,7 @@
/// Display right sidebar when min-width: lg /// Display right sidebar when min-width: lg
@include respond(lg) { @include respond(lg) {
display: block; display: flex;
} }
} }
@ -67,24 +67,6 @@
} }
} }
&.align-items--flex-start {
align-items: flex-start;
}
.grow {
flex-grow: 1;
}
.do-not-shrink {
flex-shrink: 0;
}
.do-not-overflow {
min-width: 0;
flex-shrink: 1;
max-width: 100%;
}
.full-width { .full-width {
width: 100%; width: 100%;
} }
@ -94,14 +76,21 @@ main.main {
min-width: 0; min-width: 0;
max-width: 100%; max-width: 100%;
flex-grow: 1; flex-grow: 1;
display: flex;
flex-direction: column;
gap: var(--section-separation);
@include respond(md) {
padding-top: var(--main-top-padding); padding-top: var(--main-top-padding);
}
} }
.main-container { .main-container {
min-height: 100vh; min-height: 100vh;
align-items: flex-start; align-items: flex-start;
padding: 0 15px; padding: 0 15px;
column-gap: var(--section-separation); gap: var(--section-separation);
padding-top: var(--main-top-padding);
@include respond(md) { @include respond(md) {
padding: 0 20px; padding: 0 20px;

View File

@ -2,6 +2,7 @@
.article-list { .article-list {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: var(--section-separation);
article { article {
display: flex; display: flex;
@ -17,10 +18,6 @@
box-shadow: var(--shadow-l2); box-shadow: var(--shadow-l2);
} }
&:not(:last-of-type) {
margin-bottom: var(--section-separation);
}
.article-image { .article-image {
img { img {
width: 100%; width: 100%;
@ -52,13 +49,13 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
padding: var(--card-padding); padding: var(--card-padding);
gap: 15px;
} }
.article-title { .article-title {
font-weight: 600; font-weight: 600;
margin: 10px 0; margin: 0;
color: var(--card-text-color-main); color: var(--card-text-color-main);
font-size: 2.2rem; font-size: 2.2rem;
@ -73,52 +70,58 @@
color: var(--card-text-color-main); color: var(--card-text-color-main);
} }
} }
& + .article-subtitle {
margin-top: 0;
}
} }
.article-subtitle { .article-subtitle {
font-weight: normal; font-weight: normal;
color: var(--card-text-color-secondary); color: var(--card-text-color-secondary);
margin: 5px 0;
line-height: 1.5; line-height: 1.5;
margin: 0;
font-size: 1.75rem; font-size: 1.75rem;
@include respond(xl) { @include respond(xl) {
font-size: 2rem; font-size: 2rem;
} }
} }
.article-time { .article-title-wrapper {
display: flex;
flex-direction: column;
gap: 8px;
}
.article-time,
.article-translations {
display: flex; display: flex;
align-items: center; align-items: center;
color: var(--card-text-color-tertiary); color: var(--card-text-color-tertiary);
gap: 15px; gap: 15px;
margin-top: 10px;
flex-wrap: wrap; flex-wrap: wrap;
svg { svg {
vertical-align: middle; vertical-align: middle;
margin-right: 15px;
width: 20px; width: 20px;
height: 20px; height: 20px;
stroke-width: 1.33; stroke-width: 1.33;
} }
time { time,
a {
font-size: 1.4rem; font-size: 1.4rem;
color: var(--card-text-color-tertiary);
} }
& > div { & > div {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
gap: 15px;
} }
} }
.article-category, .article-category,
.article-tags { .article-tags {
display: flex;
gap: 10px;
a { a {
color: var(--accent-color-text); color: var(--accent-color-text);
background-color: var(--accent-color); background-color: var(--accent-color);
@ -126,8 +129,6 @@
border-radius: var(--tag-border-radius); border-radius: var(--tag-border-radius);
display: inline-block; display: inline-block;
font-size: 1.4rem; font-size: 1.4rem;
margin-right: 10px;
margin-bottom: 10px;
transition: background-color 0.5s ease; transition: background-color 0.5s ease;
&:hover { &:hover {
@ -148,15 +149,12 @@
--image-size: 60px; --image-size: 60px;
} }
& + .pagination {
margin-top: var(--section-separation);
}
article { article {
& > a { & > a {
display: flex; display: flex;
align-items: center; align-items: center;
padding: var(--small-card-padding); padding: var(--small-card-padding);
gap: 15px;
} }
&:not(:last-of-type) { &:not(:last-of-type) {
@ -166,8 +164,8 @@
.article-details { .article-details {
flex-grow: 1; flex-grow: 1;
padding: 0; padding: 0;
padding-right: 15px;
min-height: var(--image-size); min-height: var(--image-size);
gap: 10px;
} }
.article-title { .article-title {

View File

@ -1,7 +1,6 @@
html { html {
font-size: 62.5%; font-size: 62.5%;
overflow-y: scroll; overflow-y: scroll;
scroll-behavior: smooth;
} }
* { * {

View File

@ -2,7 +2,6 @@ footer.site-footer {
padding: 20px 0 var(--section-separation) 0; padding: 20px 0 var(--section-separation) 0;
font-size: 1.4rem; font-size: 1.4rem;
line-height: 1.75; line-height: 1.75;
margin-top: var(--section-separation);
&:before { &:before {
content: ""; content: "";

View File

@ -10,7 +10,8 @@
} }
/* Other */ /* Other */
.chroma .x {} .chroma .x {
}
/* Error */ /* Error */
.chroma .err { .chroma .err {
@ -40,367 +41,369 @@
.chroma .hl { .chroma .hl {
display: block; display: block;
width: 100%; width: 100%;
background-color: #ffffcc background-color: #ffffcc;
} }
/* LineNumbersTable */ /* LineNumbersTable */
.chroma .lnt { .chroma .lnt {
margin-right: 0.4em; margin-right: 0.4em;
padding: 0 0.4em 0 0.4em; padding: 0 0.4em 0 0.4em;
color: #7f7f7f color: #7f7f7f;
} }
/* LineNumbers */ /* LineNumbers */
.chroma .ln { .chroma .ln {
margin-right: 0.4em; margin-right: 0.4em;
padding: 0 0.4em 0 0.4em; padding: 0 0.4em 0 0.4em;
color: #7f7f7f color: #7f7f7f;
} }
/* Keyword */ /* Keyword */
.chroma .k { .chroma .k {
color: #00a8c8 color: #00a8c8;
} }
/* KeywordConstant */ /* KeywordConstant */
.chroma .kc { .chroma .kc {
color: #00a8c8 color: #00a8c8;
} }
/* KeywordDeclaration */ /* KeywordDeclaration */
.chroma .kd { .chroma .kd {
color: #00a8c8 color: #00a8c8;
} }
/* KeywordNamespace */ /* KeywordNamespace */
.chroma .kn { .chroma .kn {
color: #f92672 color: #f92672;
} }
/* KeywordPseudo */ /* KeywordPseudo */
.chroma .kp { .chroma .kp {
color: #00a8c8 color: #00a8c8;
} }
/* KeywordReserved */ /* KeywordReserved */
.chroma .kr { .chroma .kr {
color: #00a8c8 color: #00a8c8;
} }
/* KeywordType */ /* KeywordType */
.chroma .kt { .chroma .kt {
color: #00a8c8 color: #00a8c8;
} }
/* Name */ /* Name */
.chroma .n { .chroma .n {
color: #111111 color: #111111;
} }
/* NameAttribute */ /* NameAttribute */
.chroma .na { .chroma .na {
color: #75af00 color: #75af00;
} }
/* NameBuiltin */ /* NameBuiltin */
.chroma .nb { .chroma .nb {
color: #111111 color: #111111;
} }
/* NameBuiltinPseudo */ /* NameBuiltinPseudo */
.chroma .bp { .chroma .bp {
color: #111111 color: #111111;
} }
/* NameClass */ /* NameClass */
.chroma .nc { .chroma .nc {
color: #75af00 color: #75af00;
} }
/* NameConstant */ /* NameConstant */
.chroma .no { .chroma .no {
color: #00a8c8 color: #00a8c8;
} }
/* NameDecorator */ /* NameDecorator */
.chroma .nd { .chroma .nd {
color: #75af00 color: #75af00;
} }
/* NameEntity */ /* NameEntity */
.chroma .ni { .chroma .ni {
color: #111111 color: #111111;
} }
/* NameException */ /* NameException */
.chroma .ne { .chroma .ne {
color: #75af00 color: #75af00;
} }
/* NameFunction */ /* NameFunction */
.chroma .nf { .chroma .nf {
color: #75af00 color: #75af00;
} }
/* NameFunctionMagic */ /* NameFunctionMagic */
.chroma .fm { .chroma .fm {
color: #111111 color: #111111;
} }
/* NameLabel */ /* NameLabel */
.chroma .nl { .chroma .nl {
color: #111111 color: #111111;
} }
/* NameNamespace */ /* NameNamespace */
.chroma .nn { .chroma .nn {
color: #111111 color: #111111;
} }
/* NameOther */ /* NameOther */
.chroma .nx { .chroma .nx {
color: #75af00 color: #75af00;
} }
/* NameProperty */ /* NameProperty */
.chroma .py { .chroma .py {
color: #111111 color: #111111;
} }
/* NameTag */ /* NameTag */
.chroma .nt { .chroma .nt {
color: #f92672 color: #f92672;
} }
/* NameVariable */ /* NameVariable */
.chroma .nv { .chroma .nv {
color: #111111 color: #111111;
} }
/* NameVariableClass */ /* NameVariableClass */
.chroma .vc { .chroma .vc {
color: #111111 color: #111111;
} }
/* NameVariableGlobal */ /* NameVariableGlobal */
.chroma .vg { .chroma .vg {
color: #111111 color: #111111;
} }
/* NameVariableInstance */ /* NameVariableInstance */
.chroma .vi { .chroma .vi {
color: #111111 color: #111111;
} }
/* NameVariableMagic */ /* NameVariableMagic */
.chroma .vm { .chroma .vm {
color: #111111 color: #111111;
} }
/* Literal */ /* Literal */
.chroma .l { .chroma .l {
color: #ae81ff color: #ae81ff;
} }
/* LiteralDate */ /* LiteralDate */
.chroma .ld { .chroma .ld {
color: #d88200 color: #d88200;
} }
/* LiteralString */ /* LiteralString */
.chroma .s { .chroma .s {
color: #d88200 color: #d88200;
} }
/* LiteralStringAffix */ /* LiteralStringAffix */
.chroma .sa { .chroma .sa {
color: #d88200 color: #d88200;
} }
/* LiteralStringBacktick */ /* LiteralStringBacktick */
.chroma .sb { .chroma .sb {
color: #d88200 color: #d88200;
} }
/* LiteralStringChar */ /* LiteralStringChar */
.chroma .sc { .chroma .sc {
color: #d88200 color: #d88200;
} }
/* LiteralStringDelimiter */ /* LiteralStringDelimiter */
.chroma .dl { .chroma .dl {
color: #d88200 color: #d88200;
} }
/* LiteralStringDoc */ /* LiteralStringDoc */
.chroma .sd { .chroma .sd {
color: #d88200 color: #d88200;
} }
/* LiteralStringDouble */ /* LiteralStringDouble */
.chroma .s2 { .chroma .s2 {
color: #d88200 color: #d88200;
} }
/* LiteralStringEscape */ /* LiteralStringEscape */
.chroma .se { .chroma .se {
color: #8045ff color: #8045ff;
} }
/* LiteralStringHeredoc */ /* LiteralStringHeredoc */
.chroma .sh { .chroma .sh {
color: #d88200 color: #d88200;
} }
/* LiteralStringInterpol */ /* LiteralStringInterpol */
.chroma .si { .chroma .si {
color: #d88200 color: #d88200;
} }
/* LiteralStringOther */ /* LiteralStringOther */
.chroma .sx { .chroma .sx {
color: #d88200 color: #d88200;
} }
/* LiteralStringRegex */ /* LiteralStringRegex */
.chroma .sr { .chroma .sr {
color: #d88200 color: #d88200;
} }
/* LiteralStringSingle */ /* LiteralStringSingle */
.chroma .s1 { .chroma .s1 {
color: #d88200 color: #d88200;
} }
/* LiteralStringSymbol */ /* LiteralStringSymbol */
.chroma .ss { .chroma .ss {
color: #d88200 color: #d88200;
} }
/* LiteralNumber */ /* LiteralNumber */
.chroma .m { .chroma .m {
color: #ae81ff color: #ae81ff;
} }
/* LiteralNumberBin */ /* LiteralNumberBin */
.chroma .mb { .chroma .mb {
color: #ae81ff color: #ae81ff;
} }
/* LiteralNumberFloat */ /* LiteralNumberFloat */
.chroma .mf { .chroma .mf {
color: #ae81ff color: #ae81ff;
} }
/* LiteralNumberHex */ /* LiteralNumberHex */
.chroma .mh { .chroma .mh {
color: #ae81ff color: #ae81ff;
} }
/* LiteralNumberInteger */ /* LiteralNumberInteger */
.chroma .mi { .chroma .mi {
color: #ae81ff color: #ae81ff;
} }
/* LiteralNumberIntegerLong */ /* LiteralNumberIntegerLong */
.chroma .il { .chroma .il {
color: #ae81ff color: #ae81ff;
} }
/* LiteralNumberOct */ /* LiteralNumberOct */
.chroma .mo { .chroma .mo {
color: #ae81ff color: #ae81ff;
} }
/* Operator */ /* Operator */
.chroma .o { .chroma .o {
color: #f92672 color: #f92672;
} }
/* OperatorWord */ /* OperatorWord */
.chroma .ow { .chroma .ow {
color: #f92672 color: #f92672;
} }
/* Punctuation */ /* Punctuation */
.chroma .p { .chroma .p {
color: #111111 color: #111111;
} }
/* Comment */ /* Comment */
.chroma .c { .chroma .c {
color: #75715e color: #75715e;
} }
/* CommentHashbang */ /* CommentHashbang */
.chroma .ch { .chroma .ch {
color: #75715e color: #75715e;
} }
/* CommentMultiline */ /* CommentMultiline */
.chroma .cm { .chroma .cm {
color: #75715e color: #75715e;
} }
/* CommentSingle */ /* CommentSingle */
.chroma .c1 { .chroma .c1 {
color: #75715e color: #75715e;
} }
/* CommentSpecial */ /* CommentSpecial */
.chroma .cs { .chroma .cs {
color: #75715e color: #75715e;
} }
/* CommentPreproc */ /* CommentPreproc */
.chroma .cp { .chroma .cp {
color: #75715e color: #75715e;
} }
/* CommentPreprocFile */ /* CommentPreprocFile */
.chroma .cpf { .chroma .cpf {
color: #75715e color: #75715e;
} }
/* Generic */ /* Generic */
.chroma .g {} .chroma .g {
}
/* GenericDeleted */ /* GenericDeleted */
.chroma .gd {} .chroma .gd {
color: #f92672;
}
/* GenericEmph */ /* GenericEmph */
.chroma .ge { .chroma .ge {
font-style: italic font-style: italic;
} }
/* GenericError */ /* GenericError */
.chroma .gr {} .chroma .gr {
}
/* GenericHeading */ /* GenericHeading */
.chroma .gh {} .chroma .gh {
}
/* GenericInserted */ /* GenericInserted */
.chroma .gi {} .chroma .gi {
color: #7ca727;
}
/* GenericOutput */ /* GenericOutput */
.chroma .go {} .chroma .go {
}
/* GenericPrompt */ /* GenericPrompt */
.chroma .gp {} .chroma .gp {
}
/* GenericStrong */ /* GenericStrong */
.chroma .gs { .chroma .gs {
font-weight: bold font-weight: bold;
} }
/* GenericSubheading */ /* GenericSubheading */
.chroma .gu {} .chroma .gu {
color: #75715e;
}
/* GenericTraceback */ /* GenericTraceback */
.chroma .gt {} .chroma .gt {
}
/* GenericUnderline */ /* GenericUnderline */
.chroma .gl {} .chroma .gl {
}
/* TextWhitespace */ /* TextWhitespace */
.chroma .w {} .chroma .w {
}

View File

@ -1,3 +0,0 @@
.archives-group {
margin-bottom: var(--section-separation);
}

View File

@ -13,10 +13,6 @@
box-shadow: var(--shadow-l1); box-shadow: var(--shadow-l1);
overflow: hidden; overflow: hidden;
&.main-article {
margin-bottom: var(--section-separation);
}
.article-header { .article-header {
.article-image { .article-image {
img { img {
@ -57,11 +53,11 @@
display: flex; display: flex;
align-items: center; align-items: center;
font-size: 1.4rem; font-size: 1.4rem;
gap: 15px;
svg { svg {
width: 20px; width: 20px;
height: 20px; height: 20px;
margin-right: 15px;
stroke-width: 1.33; stroke-width: 1.33;
} }
} }
@ -71,7 +67,8 @@
text-transform: unset; text-transform: unset;
} }
.article-copyright, .article-lastmod { .article-copyright,
.article-lastmod {
a { a {
color: var(--body-text-color); color: var(--body-text-color);
} }
@ -121,80 +118,6 @@
} }
} }
.article-page.has-toc {
scroll-behavior: smooth;
.left-sidebar {
display: none;
}
.right-sidebar {
width: 100%;
padding: 0;
display: none;
@include respond(xl) {
display: block;
top: var(--main-top-padding);
}
}
#article-toolbar {
display: block;
@include respond(md) {
padding: 0;
}
@include respond(xl) {
margin-top: 0;
position: sticky;
top: var(--main-top-padding);
flex-shrink: 1;
a {
background: transparent;
box-shadow: none;
border: 1px solid var(--body-text-color);
width: 100%;
margin-right: 0;
svg {
flex-shrink: 0;
}
}
}
}
.main-container {
align-items: start;
flex-direction: column;
@include respond(xl) {
flex-direction: row;
}
}
.main {
padding-top: 0;
@include respond(xl) {
padding-top: var(--main-top-padding);
}
}
}
.widget-head {
display: flex;
align-items: center;
.widget-title {
font-size: 1.6rem;
font-weight: bold;
color: var(--body-text-color);
}
}
.widget--toc { .widget--toc {
background-color: var(--card-background); background-color: var(--card-background);
border-radius: var(--card-border-radius); border-radius: var(--card-border-radius);
@ -204,6 +127,10 @@
color: var(--card-text-color-main); color: var(--card-text-color-main);
overflow: hidden; overflow: hidden;
::-webkit-scrollbar-thumb {
background-color: var(--card-separator-color);
}
#TableOfContents { #TableOfContents {
overflow-x: auto; overflow-x: auto;
max-height: 75vh; max-height: 75vh;
@ -218,7 +145,7 @@
list-style-type: none; list-style-type: none;
counter-reset: item; counter-reset: item;
li:before { li a::before {
counter-increment: item; counter-increment: item;
content: counters(item, ".") ". "; content: counters(item, ".") ". ";
font-weight: bold; font-weight: bold;
@ -231,8 +158,7 @@
} }
li { li {
list-style-type: none; margin: 15px 0 15px 20px;
margin: 15px 20px;
padding: 5px; padding: 5px;
& > ol, & > ol,
@ -246,11 +172,39 @@
} }
} }
} }
li.active-class > a {
border-left: var(--heading-border-size) solid var(--accent-color);
font-weight: bold;
} }
}
.related-contents--wrapper { ul li.active-class > a {
margin-bottom: var(--section-separation); display: block;
}
@function repeat($str, $n) {
$result: "";
@for $_ from 0 to $n {
$result: $result + $str;
}
@return $result;
}
// Support up to 6 levels of indentation for lists in ToCs
@for $i from 0 to 5 {
& > ul #{repeat("> li > ul", $i)} > li.active-class > a {
$n: 25 + $i * 35;
margin-left: calc(-#{$n}px - 1em);
padding-left: calc(#{$n}px + 1em - var(--heading-border-size));
}
& > ol #{repeat("> li > ol", $i)} > li.active-class > a {
$n: 9 + $i * 35;
margin-left: calc(-#{$n}px - 1em);
padding-left: calc(#{$n}px + 1em - var(--heading-border-size));
display: block;
}
}
}
} }
.related-contents { .related-contents {
@ -298,9 +252,9 @@
h4, h4,
h5, h5,
h6 { h6 {
margin-left: calc((var(--card-padding)) * -1); margin-inline-start: calc((var(--card-padding)) * -1);
padding-left: calc(var(--card-padding) - var(--heading-border-size)); padding-inline-start: calc(var(--card-padding) - var(--heading-border-size));
border-left: var(--heading-border-size) solid var(--accent-color); border-inline-start: var(--heading-border-size) solid var(--accent-color);
} }
figure { figure {
@ -315,7 +269,7 @@
blockquote { blockquote {
position: relative; position: relative;
margin: 1.5em 0; margin: 1.5em 0;
border-left: var(--blockquote-border-size) solid var(--card-separator-color); border-inline-start: var(--blockquote-border-size) solid var(--card-separator-color);
padding: 15px calc(var(--card-padding) - var(--blockquote-border-size)); padding: 15px calc(var(--card-padding) - var(--blockquote-border-size));
background-color: var(--blockquote-background-color); background-color: var(--blockquote-background-color);
} }
@ -343,13 +297,10 @@
flex-direction: row; flex-direction: row;
justify-content: center; justify-content: center;
margin: 1.5em 0; margin: 1.5em 0;
gap: 10px;
figure { figure {
margin: 0; margin: 0;
& + figure {
margin-left: 10px;
}
} }
} }
@ -362,7 +313,10 @@
line-height: 1.428571429; line-height: 1.428571429;
word-break: break-all; word-break: break-all;
padding: var(--card-padding); padding: var(--card-padding);
// keep Codeblocks LTR
[dir="rtl"] & {
direction: ltr;
}
code { code {
color: unset; color: unset;
border: none; border: none;
@ -371,6 +325,50 @@
} }
} }
.highlight {
background-color: var(--pre-background-color);
padding: var(--card-padding);
position: relative;
&:hover {
.copyCodeButton {
opacity: 1;
}
}
// keep Codeblocks LTR
[dir="rtl"] & {
direction: ltr;
}
pre {
margin: initial;
padding: 0;
margin: 0;
width: auto;
}
}
.copyCodeButton {
position: absolute;
top: calc(var(--card-padding));
right: calc(var(--card-padding));
background: var(--card-background);
border: none;
box-shadow: var(--shadow-l2);
border-radius: var(--tag-border-radius);
padding: 8px 16px;
color: var(--card-text-color-main);
cursor: pointer;
font-size: 14px;
opacity: 0;
transition: opacity 0.3s ease;
}
.table-wrapper {
padding: 0 var(--card-padding);
overflow-x: auto;
display: block;
}
table { table {
width: 100%; width: 100%;
border-collapse: collapse; border-collapse: collapse;
@ -419,38 +417,14 @@
/// Negative margins /// Negative margins
blockquote, blockquote,
figure, figure,
.gallery, .highlight,
pre, pre,
.gallery,
.video-wrapper, .video-wrapper,
.table-wrapper,
.s_video_simple { .s_video_simple {
margin-left: calc((var(--card-padding)) * -1); margin-left: calc((var(--card-padding)) * -1);
margin-right: calc((var(--card-padding)) * -1); margin-right: calc((var(--card-padding)) * -1);
width: calc(100% + var(--card-padding) * 2); width: calc(100% + var(--card-padding) * 2);
} }
.highlight {
position: relative;
&:hover {
.copyCodeButton {
opacity: 1;
}
}
}
.copyCodeButton {
position: absolute;
top: calc(var(--card-padding));
right: 0;
background: var(--card-background);
border: none;
box-shadow: var(--shadow-l2);
border-radius: var(--tag-border-radius);
padding: 8px 16px;
color: var(--card-text-color-main);
cursor: pointer;
font-size: 14px;
opacity: 0;
transition: opacity 0.3s ease;
}
} }

View File

@ -3,21 +3,16 @@
background-color: var(--card-background); background-color: var(--card-background);
padding: var(--small-card-padding); padding: var(--small-card-padding);
box-shadow: var(--shadow-l1); box-shadow: var(--shadow-l1);
margin-bottom: var(--section-separation);
display: flex; display: flex;
align-items: center; align-items: center;
gap: 20px;
--separation: 15px; --separation: 15px;
.section-term { .section-term {
font-size: 2.2rem; font-size: 2.2rem;
margin: 0; margin: 0;
margin-top: calc(var(--separation) / 2);
color: var(--card-text-color-main); color: var(--card-text-color-main);
& + .section-description {
margin-top: var(--separation);
}
} }
.section-description { .section-description {
@ -29,7 +24,9 @@
.section-details { .section-details {
flex-grow: 1; flex-grow: 1;
margin-right: 20px; display: flex;
flex-direction: column;
gap: 8px;
} }
.section-image { .section-image {
@ -49,7 +46,6 @@
} }
.subsection-list { .subsection-list {
margin-bottom: var(--section-separation);
overflow-x: auto; overflow-x: auto;
.article-list--tile { .article-list--tile {

View File

@ -1,5 +1,4 @@
.search-form { .search-form {
margin-bottom: var(--section-separation);
position: relative; position: relative;
--button-size: 80px; --button-size: 80px;
@ -25,7 +24,7 @@
label { label {
position: absolute; position: absolute;
top: 15px; top: 15px;
left: 20px; inset-inline-start: 20px;
font-size: 1.4rem; font-size: 1.4rem;
color: var(--card-text-color-tertiary); color: var(--card-text-color-tertiary);
} }
@ -52,7 +51,7 @@
button { button {
position: absolute; position: absolute;
right: 0; inset-inline-end: 0;
top: 0; top: 0;
height: 100%; height: 100%;
width: var(--button-size); width: var(--button-size);
@ -79,4 +78,5 @@
height: 20px; height: 20px;
} }
} }
} }

View File

@ -101,11 +101,16 @@
background: none; background: none;
border: none; border: none;
position: absolute; position: absolute;
right: 30px; right: 0;
top: 30px; top: 0;
z-index: 2; z-index: 2;
cursor: pointer; cursor: pointer;
[dir="rtl"] & {
left: 0;
right: auto;
}
@include respond(md) { @include respond(md) {
display: none; display: none;
} }
@ -125,21 +130,31 @@
.menu { .menu {
padding-left: 0; padding-left: 0;
list-style: none; list-style: none;
display: flex;
flex-direction: column; flex-direction: column;
overflow-y: auto; overflow-y: auto;
flex-grow: 1; flex-grow: 1;
font-size: 1.4rem; font-size: 1.4rem;
background-color: var(--card-background); background-color: var(--card-background);
padding: 15px 0;
box-shadow: var(--shadow-l1); box-shadow: var(--shadow-l1);
display: none; display: none;
margin: 0 calc(var(--container-padding) * -1);
margin: 0 -15px; padding: 30px 30px;
@include respond(xl) {
padding: 15px 0;
}
&,
.menu-bottom-section {
gap: 30px;
@include respond(xl) {
gap: 25px;
}
}
&.show { &.show {
display: block; display: flex;
} }
@include respond(md) { @include respond(md) {
@ -149,34 +164,19 @@
padding: 0; padding: 0;
box-shadow: none; box-shadow: none;
margin: 0; margin: 0;
margin-top: var(--sidebar-element-separation);
}
@include respond(xl) {
margin-top: 30px;
} }
li { li {
position: relative; position: relative;
vertical-align: middle; vertical-align: middle;
padding: 10px 30px; padding: 0;
&:not(:last-of-type) {
margin-bottom: 15px;
@include respond(xl) {
margin-bottom: 20px;
}
}
@include respond(md) { @include respond(md) {
width: 100%; width: 100%;
padding: 10px 0;
} }
svg { svg {
stroke-width: 1.33; stroke-width: 1.33;
margin-right: 40px;
width: 20px; width: 20px;
height: 20px; height: 20px;
@ -187,6 +187,7 @@
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
color: var(--body-text-color); color: var(--body-text-color);
gap: var(--menu-icon-separation);
} }
span { span {
@ -200,4 +201,27 @@
} }
} }
} }
.menu-bottom-section {
margin-top: auto;
display: flex;
flex-direction: column;
width: 100%;
}
}
.social-menu {
list-style: none;
padding: 0;
margin: 0;
display: flex;
flex-direction: row;
gap: 10px;
svg {
width: 24px;
height: 24px;
stroke: var(--body-text-color);
stroke-width: 1.33;
}
} }

View File

@ -5,7 +5,6 @@
border-radius: var(--card-border-radius); border-radius: var(--card-border-radius);
overflow: hidden; overflow: hidden;
flex-wrap: wrap; flex-wrap: wrap;
margin: var(--section-separation) 0;
.page-link { .page-link {
padding: 16px 32px; padding: 16px 32px;

View File

@ -11,13 +11,15 @@
flex-direction: column; flex-direction: column;
flex-shrink: 0; flex-shrink: 0;
align-self: stretch; align-self: stretch;
gap: var(--sidebar-element-separation);
width: 100%;
padding: 30px 0 15px 0;
max-width: none; max-width: none;
width: 100%;
position: relative;
--sidebar-avatar-size: 120px; --sidebar-avatar-size: 100px;
--sidebar-element-separation: 20px; --sidebar-element-separation: 20px;
--emoji-size: 40px;
--emoji-font-size: 20px;
@include respond(md) { @include respond(md) {
width: auto; width: auto;
@ -27,18 +29,49 @@
} }
@include respond(2xl) { @include respond(2xl) {
--sidebar-avatar-size: 140px; --sidebar-avatar-size: 120px;
--sidebar-element-separation: 25px; --sidebar-element-separation: 25px;
--emoji-size: 40px;
} }
&.sticky { &.sticky {
top: 0; top: 0;
} }
&.compact {
--sidebar-avatar-size: 80px;
--emoji-size: 30px;
--emoji-font-size: 15px;
header {
@include respond(lg) {
flex-direction: row;
}
.site-meta {
gap: 5px;
}
.site-name {
font-size: 1.4rem;
@include respond(2xl) {
font-size: 1.75rem;
}
}
.site-description {
font-size: 1.4rem;
}
}
}
} }
.right-sidebar { .right-sidebar {
flex-shrink: 0; width: 100%;
display: none; display: none;
flex-direction: column;
gap: var(--widget-separation);
&.sticky { &.sticky {
top: 0; top: 0;
@ -49,11 +82,12 @@
} }
} }
.site-info { .sidebar header {
z-index: 1; z-index: 1;
transition: box-shadow 0.5s ease; transition: box-shadow 0.5s ease;
display: flex;
padding: 15px; flex-direction: column;
gap: var(--sidebar-element-separation);
@include respond(md) { @include respond(md) {
padding: 0; padding: 0;
@ -64,8 +98,7 @@
margin: 0; margin: 0;
width: var(--sidebar-avatar-size); width: var(--sidebar-avatar-size);
height: var(--sidebar-avatar-size); height: var(--sidebar-avatar-size);
flex-shrink: 0;
margin-bottom: var(--sidebar-element-separation);
.site-logo { .site-logo {
width: 100%; width: 100%;
@ -76,58 +109,44 @@
.emoji { .emoji {
position: absolute; position: absolute;
width: 40px; width: var(--emoji-size);
height: 40px; height: var(--emoji-size);
line-height: 40px; line-height: var(--emoji-size);
border-radius: 100%; border-radius: 100%;
bottom: 0; bottom: 0;
right: 0; right: 0;
text-align: center; text-align: center;
font-size: 20px; font-size: var(--emoji-font-size);
background-color: var(--card-background); background-color: var(--card-background);
box-shadow: var(--shadow-l2); box-shadow: var(--shadow-l2);
}
}
@include respond(2xl) { .site-meta {
width: 50px; display: flex;
height: 50px; flex-direction: column;
line-height: 50px; gap: 10px;
} justify-content: center;
}
} }
.site-name { .site-name {
color: var(--accent-color); color: var(--accent-color);
margin: 0; margin: 0;
font-size: 1.8rem;
@include respond(2xl) {
font-size: 2rem;
}
}
.site-description {
color: var(--body-text-color);
font-weight: normal;
margin: 10px 0;
font-size: 1.6rem; font-size: 1.6rem;
@include respond(2xl) { @include respond(2xl) {
font-size: 1.8rem; font-size: 1.8rem;
} }
} }
}
.sidebar { .site-description {
.widget { color: var(--body-text-color);
margin-bottom: var(--section-separation); font-weight: normal;
margin: 0;
font-size: 1.4rem;
&:not(:last-of-type):after { @include respond(2xl) {
content: ""; font-size: 1.6rem;
width: 100px;
height: 2px;
background-color: var(--body-text-color);
display: block;
margin-top: var(--section-separation);
} }
} }
} }
@ -153,8 +172,27 @@
display: flex; display: flex;
align-items: center; align-items: center;
cursor: pointer; cursor: pointer;
gap: var(--menu-icon-separation);
.icon-tabler-toggle-right { .icon-tabler-toggle-right {
display: none; display: none;
} }
} }
#i18n-switch {
color: var(--body-text-color);
display: inline-flex;
align-content: center;
gap: var(--menu-icon-separation);
select {
border: 0;
background-color: transparent;
color: var(--body-text-color);
option {
color: var(--card-text-color-main);
background-color: var(--card-background);
}
}
}

View File

@ -1,4 +1,7 @@
.widget { .widget {
display: flex;
flex-direction: column;
.widget-icon { .widget-icon {
svg { svg {
width: 32px; width: 32px;
@ -14,16 +17,14 @@
.tagCloud-tags { .tagCloud-tags {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: 10px;
a { a {
background: var(--card-background); background: var(--card-background);
box-shadow: var(--shadow-l1); box-shadow: var(--shadow-l1);
border-radius: var(--tag-border-radius); border-radius: var(--tag-border-radius);
padding: 8px 20px; padding: 8px 20px;
color: var(--card-text-color-main); color: var(--card-text-color-main);
margin-bottom: 10px;
margin-right: 5px;
font-size: 1.4rem; font-size: 1.4rem;
transition: box-shadow 0.3s ease; transition: box-shadow 0.3s ease;

View File

@ -19,7 +19,6 @@
@import "partials/pagination.scss"; @import "partials/pagination.scss";
@import "partials/sidebar.scss"; @import "partials/sidebar.scss";
@import "partials/base.scss"; @import "partials/base.scss";
@import "partials/layout/archives.scss";
@import "partials/layout/article.scss"; @import "partials/layout/article.scss";
@import "partials/layout/list.scss"; @import "partials/layout/list.scss";
@import "partials/layout/404.scss"; @import "partials/layout/404.scss";

View File

@ -1,25 +1,11 @@
$defaultTagBackgrounds: #8ea885, #df7988, #0177b8, #ffb900, #6b69d6; $defaultTagBackgrounds: #8ea885, #df7988, #0177b8, #ffb900, #6b69d6;
$defaultTagColors: #fff, #fff, #fff, #fff, #fff; $defaultTagColors: #fff, #fff, #fff, #fff, #fff;
[data-scheme="light"] {
--pre-text-color: #272822;
--pre-background-color: #fafafa;
@import "partials/highlight/light.scss";
}
[data-scheme="dark"] {
--pre-text-color: #f8f8f2;
--pre-background-color: #272822;
@import "partials/highlight/dark.scss";
}
/* /*
* Global style * Global style
*/ */
:root { :root {
@include respond(md) {
--main-top-padding: 35px; --main-top-padding: 35px;
}
@include respond(xl) { @include respond(xl) {
--main-top-padding: 50px; --main-top-padding: 50px;
@ -45,7 +31,7 @@ $defaultTagColors: #fff, #fff, #fff, #fff, #fff;
--accent-color-darker: #bdc3c7; --accent-color-darker: #bdc3c7;
--accent-color-text: #000; --accent-color-text: #000;
--body-text-color: rgba(255, 255, 255, 0.7); --body-text-color: rgba(255, 255, 255, 0.7);
--scrollbar-thumb: #424242; --scrollbar-thumb: hsl(0, 0%, 40%);
--scrollbar-track: var(--body-background); --scrollbar-track: var(--body-background);
} }
} }
@ -159,3 +145,21 @@ $defaultTagColors: #fff, #fff, #fff, #fff, #fff;
--shadow-l4: 0px 24px 32px rgba(0, 0, 0, 0.04), 0px 16px 24px rgba(0, 0, 0, 0.04), 0px 4px 8px rgba(0, 0, 0, 0.04), --shadow-l4: 0px 24px 32px rgba(0, 0, 0, 0.04), 0px 16px 24px rgba(0, 0, 0, 0.04), 0px 4px 8px rgba(0, 0, 0, 0.04),
0px 0px 1px rgba(0, 0, 0, 0.04); 0px 0px 1px rgba(0, 0, 0, 0.04);
} }
[data-scheme="light"] {
--pre-text-color: #272822;
--pre-background-color: #fafafa;
@import "partials/highlight/light.scss";
}
[data-scheme="dark"] {
--pre-text-color: #f8f8f2;
--pre-background-color: #272822;
@import "partials/highlight/dark.scss";
}
:root {
--menu-icon-separation: 40px;
--container-padding: 15px;
--widget-separation: var(--section-separation);
}

View File

@ -57,6 +57,60 @@ class StackGallery {
} }
public static createGallery(container: HTMLElement) { public static createGallery(container: HTMLElement) {
/// The process of wrapping image with figure tag is done using JavaScript instead of only Hugo markdown render hook
/// because it can not detect whether image is being wrapped by a link or not
/// and it lead to a invalid HTML construction (<a><figure><img></figure></a>)
const images = container.querySelectorAll('img.gallery-image');
for (const img of Array.from(images)) {
/// Images are wrapped with figure tag if the paragraph has only images without texts
/// This is done to allow inline images within paragraphs
const paragraph = img.closest('p');
if (!paragraph || !container.contains(paragraph)) continue;
if (paragraph.textContent.trim() == '') {
/// Once we insert figcaption, this check no longer works
/// So we add a class to paragraph to mark it
paragraph.classList.add('no-text');
}
let isNewLineImage = paragraph.classList.contains('no-text');
if (!isNewLineImage) continue;
const hasLink = img.parentElement.tagName == 'A';
let el: HTMLElement = img;
/// Wrap image with figure tag, with flex-grow and flex-basis values extracted from img's data attributes
const figure = document.createElement('figure');
figure.style.setProperty('flex-grow', img.getAttribute('data-flex-grow') || '1');
figure.style.setProperty('flex-basis', img.getAttribute('data-flex-basis') || '0');
if (hasLink) {
/// Wrap <a> if it exists
el = img.parentElement;
}
el.parentElement.insertBefore(figure, el);
figure.appendChild(el);
/// Add figcaption if it exists
if (img.hasAttribute('alt')) {
const figcaption = document.createElement('figcaption');
figcaption.innerText = img.getAttribute('alt');
figure.appendChild(figcaption);
}
/// Wrap img tag with <a> tag if image was not wrapped by <a> tag
if (!hasLink) {
figure.className = 'gallery-image';
const a = document.createElement('a');
a.href = img.src;
a.setAttribute('target', '_blank');
img.parentNode.insertBefore(a, img);
a.appendChild(img);
}
}
const figuresEl = container.querySelectorAll('figure.gallery-image'); const figuresEl = container.querySelectorAll('figure.gallery-image');
let currentGallery = []; let currentGallery = [];

View File

@ -10,6 +10,8 @@ import { getColor } from 'ts/color';
import menu from 'ts/menu'; import menu from 'ts/menu';
import createElement from 'ts/createElement'; import createElement from 'ts/createElement';
import StackColorScheme from 'ts/colorScheme'; import StackColorScheme from 'ts/colorScheme';
import { setupScrollspy } from 'ts/scrollspy';
import { setupSmoothAnchors } from "ts/smoothAnchors";
let Stack = { let Stack = {
init: () => { init: () => {
@ -21,6 +23,8 @@ let Stack = {
const articleContent = document.querySelector('.article-content') as HTMLElement; const articleContent = document.querySelector('.article-content') as HTMLElement;
if (articleContent) { if (articleContent) {
new StackGallery(articleContent); new StackGallery(articleContent);
setupSmoothAnchors();
setupScrollspy();
} }
/** /**
@ -58,20 +62,21 @@ let Stack = {
/** /**
* Add copy button to code block * Add copy button to code block
*/ */
const codeBlocks = document.querySelectorAll('.article-content .highlight'); const highlights = document.querySelectorAll('.article-content div.highlight');
const copyText = `Copy`, const copyText = `Copy`,
copiedText = `Copied!`; copiedText = `Copied!`;
codeBlocks.forEach(codeBlock => {
highlights.forEach(highlight => {
const copyButton = document.createElement('button'); const copyButton = document.createElement('button');
copyButton.innerHTML = copyText; copyButton.innerHTML = copyText;
copyButton.classList.add('copyCodeButton'); copyButton.classList.add('copyCodeButton');
codeBlock.appendChild(copyButton); highlight.appendChild(copyButton);
const pre = codeBlock.getElementsByTagName('pre'); const codeBlock = highlight.querySelector('code[data-lang]');
const code = pre[0].textContent; if (!codeBlock) return;
copyButton.addEventListener('click', () => { copyButton.addEventListener('click', () => {
navigator.clipboard.writeText(code) navigator.clipboard.writeText(codeBlock.textContent)
.then(() => { .then(() => {
copyButton.textContent = copiedText; copyButton.textContent = copiedText;

131
assets/ts/scrollspy.ts Normal file
View File

@ -0,0 +1,131 @@
// Implements a scroll spy system for the ToC, displaying the current section with an indicator and scrolling to it when needed.
// Inspired from https://gomakethings.com/debouncing-your-javascript-events/
function debounced(func: Function) {
let timeout;
return () => {
if (timeout) {
window.cancelAnimationFrame(timeout);
}
timeout = window.requestAnimationFrame(() => func());
}
}
const headersQuery = ".article-content h1[id], .article-content h2[id], .article-content h3[id], .article-content h4[id], .article-content h5[id], .article-content h6[id]";
const tocQuery = "#TableOfContents";
const navigationQuery = "#TableOfContents li";
const activeClass = "active-class";
function scrollToTocElement(tocElement: HTMLElement, scrollableNavigation: HTMLElement) {
let textHeight = tocElement.querySelector("a").offsetHeight;
let scrollTop = tocElement.offsetTop - scrollableNavigation.offsetHeight / 2 + textHeight / 2 - scrollableNavigation.offsetTop;
if (scrollTop < 0) {
scrollTop = 0;
}
scrollableNavigation.scrollTo({ top: scrollTop, behavior: "smooth" });
}
type IdToElementMap = { [key: string]: HTMLElement };
function buildIdToNavigationElementMap(navigation: NodeListOf<Element>): IdToElementMap {
const sectionLinkRef: IdToElementMap = {};
navigation.forEach((navigationElement: HTMLElement) => {
const link = navigationElement.querySelector("a");
const href = link.getAttribute("href");
if (href.startsWith("#")) {
sectionLinkRef[href.slice(1)] = navigationElement;
}
});
return sectionLinkRef;
}
function computeOffsets(headers: NodeListOf<Element>) {
let sectionsOffsets = [];
headers.forEach((header: HTMLElement) => { sectionsOffsets.push({ id: header.id, offset: header.offsetTop }) });
sectionsOffsets.sort((a, b) => a.offset - b.offset);
return sectionsOffsets;
}
function setupScrollspy() {
let headers = document.querySelectorAll(headersQuery);
if (!headers) {
console.warn("No header matched query", headers);
return;
}
let scrollableNavigation = document.querySelector(tocQuery) as HTMLElement | undefined;
if (!scrollableNavigation) {
console.warn("No toc matched query", tocQuery);
return;
}
let navigation = document.querySelectorAll(navigationQuery);
if (!navigation) {
console.warn("No navigation matched query", navigationQuery);
return;
}
let sectionsOffsets = computeOffsets(headers);
// We need to avoid scrolling when the user is actively interacting with the ToC. Otherwise, if the user clicks on a link in the ToC,
// we would scroll their view, which is not optimal usability-wise.
let tocHovered: boolean = false;
scrollableNavigation.addEventListener("mouseenter", debounced(() => tocHovered = true));
scrollableNavigation.addEventListener("mouseleave", debounced(() => tocHovered = false));
let activeSectionLink: Element;
let idToNavigationElement: IdToElementMap = buildIdToNavigationElementMap(navigation);
function scrollHandler() {
let scrollPosition = document.documentElement.scrollTop || document.body.scrollTop;
let newActiveSection: HTMLElement | undefined;
// Find the section that is currently active.
// It is possible for no section to be active, so newActiveSection may be undefined.
sectionsOffsets.forEach((section) => {
if (scrollPosition >= section.offset - 20) {
newActiveSection = document.getElementById(section.id);
}
});
// Find the link for the active section. Once again, there are a few edge cases:
// - No active section = no link => undefined
// - No active section but the link does not exist in toc (e.g. because it is outside of the applicable ToC levels) => undefined
let newActiveSectionLink: HTMLElement | undefined
if (newActiveSection) {
newActiveSectionLink = idToNavigationElement[newActiveSection.id];
}
if (newActiveSection && !newActiveSectionLink) {
// The active section does not have a link in the ToC, so we can't scroll to it.
console.debug("No link found for section", newActiveSection);
} else if (newActiveSectionLink !== activeSectionLink) {
if (activeSectionLink)
activeSectionLink.classList.remove(activeClass);
if (newActiveSectionLink) {
newActiveSectionLink.classList.add(activeClass);
if (!tocHovered) {
// Scroll so that newActiveSectionLink is in the middle of scrollableNavigation, except when it's from a manual click (hence the tocHovered check)
scrollToTocElement(newActiveSectionLink, scrollableNavigation);
}
}
activeSectionLink = newActiveSectionLink;
}
}
window.addEventListener("scroll", debounced(scrollHandler));
// Resizing may cause the offset values to change: recompute them.
function resizeHandler() {
sectionsOffsets = computeOffsets(headers);
scrollHandler();
}
window.addEventListener("resize", debounced(resizeHandler));
}
export { setupScrollspy };

View File

@ -8,6 +8,11 @@ interface pageData {
matchCount: number matchCount: number
} }
interface match {
start: number,
end: number
}
/** /**
* Escape HTML tags as HTML entities * Escape HTML tags as HTML entities
* Edited from: * Edited from:
@ -53,77 +58,129 @@ class Search {
this.bindSearchForm(); this.bindSearchForm();
} }
private async searchKeywords(keywords: string[]) { /**
const rawData = await this.getData(); * Processes search matches
let results: pageData[] = []; * @param str original text
* @param matches array of matches
/// Sort keywords by their length * @param ellipsis whether to add ellipsis to the end of each match
keywords.sort((a, b) => { * @param charLimit max length of preview string
return b.length - a.length * @param offset how many characters before and after the match to include in preview
* @returns preview string
*/
private static processMatches(str: string, matches: match[], ellipsis: boolean = true, charLimit = 140, offset = 20): string {
matches.sort((a, b) => {
return a.start - b.start;
}); });
let i = 0,
lastIndex = 0,
charCount = 0;
const resultArray: string[] = [];
while (i < matches.length) {
const item = matches[i];
/// item.start >= lastIndex (equal only for the first iteration)
/// because of the while loop that comes after, iterating over variable j
if (ellipsis && item.start - offset > lastIndex) {
resultArray.push(`${replaceHTMLEnt(str.substring(lastIndex, lastIndex + offset))} [...] `);
resultArray.push(`${replaceHTMLEnt(str.substring(item.start - offset, item.start))}`);
charCount += offset * 2;
}
else {
/// If the match is too close to the end of last match, don't add ellipsis
resultArray.push(replaceHTMLEnt(str.substring(lastIndex, item.start)));
charCount += item.start - lastIndex;
}
let j = i + 1,
end = item.end;
/// Include as many matches as possible
/// [item.start, end] is the range of the match
while (j < matches.length && matches[j].start <= end) {
end = Math.max(matches[j].end, end);
++j;
}
resultArray.push(`<mark>${replaceHTMLEnt(str.substring(item.start, end))}</mark>`);
charCount += end - item.start;
i = j;
lastIndex = end;
if (ellipsis && charCount > charLimit) break;
}
/// Add the rest of the string
if (lastIndex < str.length) {
let end = str.length;
if (ellipsis) end = Math.min(end, lastIndex + offset);
resultArray.push(`${replaceHTMLEnt(str.substring(lastIndex, end))}`);
if (ellipsis && end != str.length) {
resultArray.push(` [...]`);
}
}
return resultArray.join('');
}
private async searchKeywords(keywords: string[]) {
const rawData = await this.getData();
const results: pageData[] = [];
const regex = new RegExp(keywords.filter((v, index, arr) => {
arr[index] = escapeRegExp(v);
return v.trim() !== '';
}).join('|'), 'gi');
for (const item of rawData) { for (const item of rawData) {
const titleMatches: match[] = [],
contentMatches: match[] = [];
let result = { let result = {
...item, ...item,
preview: '', preview: '',
matchCount: 0 matchCount: 0
} }
let matched = false; const contentMatchAll = item.content.matchAll(regex);
for (const match of Array.from(contentMatchAll)) {
for (const keyword of keywords) { contentMatches.push({
if (keyword === '') continue; start: match.index,
end: match.index + match[0].length
const regex = new RegExp(escapeRegExp(replaceHTMLEnt(keyword)), 'gi');
const contentMatch = regex.exec(result.content);
regex.lastIndex = 0; /// Reset regex
const titleMatch = regex.exec(result.title);
regex.lastIndex = 0; /// Reset regex
if (titleMatch) {
result.title = result.title.replace(regex, Search.marker);
}
if (titleMatch || contentMatch) {
matched = true;
++result.matchCount;
let start = 0,
end = 100;
if (contentMatch) {
start = contentMatch.index - 20;
end = contentMatch.index + 80
if (start < 0) start = 0;
}
if (result.preview.indexOf(keyword) !== -1) {
result.preview = result.preview.replace(regex, Search.marker);
}
else {
if (start !== 0) result.preview += `[...] `;
result.preview += `${result.content.slice(start, end).replace(regex, Search.marker)} `;
}
}
}
if (matched) {
result.preview += '[...]';
results.push(result);
}
}
/** Result with more matches appears first */
return results.sort((a, b) => {
return b.matchCount - a.matchCount;
}); });
} }
public static marker(match) { const titleMatchAll = item.title.matchAll(regex);
return '<mark>' + match + '</mark>'; for (const match of Array.from(titleMatchAll)) {
titleMatches.push({
start: match.index,
end: match.index + match[0].length
});
}
if (titleMatches.length > 0) result.title = Search.processMatches(result.title, titleMatches, false);
if (contentMatches.length > 0) {
result.preview = Search.processMatches(result.content, contentMatches);
}
else {
/// If there are no matches in the content, use the first 140 characters as preview
result.preview = replaceHTMLEnt(result.content.substring(0, 140));
}
result.matchCount = titleMatches.length + contentMatches.length;
if (result.matchCount > 0) results.push(result);
}
/// Result with more matches appears first
return results.sort((a, b) => {
return b.matchCount - a.matchCount;
});
} }
private async doSearch(keywords: string[]) { private async doSearch(keywords: string[]) {
@ -150,6 +207,11 @@ class Search {
/// Not fetched yet /// Not fetched yet
const jsonURL = this.form.dataset.json; const jsonURL = this.form.dataset.json;
this.data = await fetch(jsonURL).then(res => res.json()); this.data = await fetch(jsonURL).then(res => res.json());
const parser = new DOMParser();
for (const item of this.data) {
item.content = parser.parseFromString(item.content, 'text/html').body.innerText;
}
} }
return this.data; return this.data;
@ -160,7 +222,7 @@ class Search {
const eventHandler = (e) => { const eventHandler = (e) => {
e.preventDefault(); e.preventDefault();
const keywords = this.input.value; const keywords = this.input.value.trim();
Search.updateQueryString(keywords, true); Search.updateQueryString(keywords, true);
@ -225,7 +287,7 @@ class Search {
<a href={item.permalink}> <a href={item.permalink}>
<div class="article-details"> <div class="article-details">
<h2 class="article-title" dangerouslySetInnerHTML={{ __html: item.title }}></h2> <h2 class="article-title" dangerouslySetInnerHTML={{ __html: item.title }}></h2>
<secion class="article-preview" dangerouslySetInnerHTML={{ __html: item.preview }}></secion> <section class="article-preview" dangerouslySetInnerHTML={{ __html: item.preview }}></section>
</div> </div>
{item.image && {item.image &&
<div class="article-image"> <div class="article-image">

View File

@ -0,0 +1,34 @@
// Implements smooth scrolling when clicking on an anchor link.
// This is required instead of using modern CSS because Chromium does not currently support scrolling
// one element with scrollTo while another element is scrolled because of a click on a link. This would
// thus not work with the ToC scrollspy and e.g. footnotes.
// Here are additional links about this issue:
// - https://stackoverflow.com/questions/49318497/google-chrome-simultaneously-smooth-scrollintoview-with-more-elements-doesn
// - https://stackoverflow.com/questions/57214373/scrollintoview-using-smooth-function-on-multiple-elements-in-chrome
// - https://bugs.chromium.org/p/chromium/issues/detail?id=833617
// - https://bugs.chromium.org/p/chromium/issues/detail?id=1043933
// - https://bugs.chromium.org/p/chromium/issues/detail?id=1121151
const anchorLinksQuery = "a[href]";
function setupSmoothAnchors() {
document.querySelectorAll(anchorLinksQuery).forEach(aElement => {
let href = aElement.getAttribute("href");
if (!href.startsWith("#")) {
return;
}
aElement.addEventListener("click", clickEvent => {
clickEvent.preventDefault();
let targetId = aElement.getAttribute("href").substring(1);
// The replace done on ':' is here for footnotes, as this character would otherwise interfere when used as a CSS selector.
let target = document.querySelector(`#${targetId.replace(":", "\\:")}`) as HTMLElement;
window.history.pushState({}, "", aElement.getAttribute("href"));
scrollTo({ top: target.offsetTop, behavior: "smooth" });
});
});
}
export { setupSmoothAnchors };

141
config.yaml Normal file
View File

@ -0,0 +1,141 @@
module:
hugoVersion:
extended: true
min: "0.87.0"
params:
mainSections:
- post
featuredImageField: image
rssFullContent: true
favicon:
footer:
since:
customText:
dateFormat:
published: Jan 02, 2006
lastUpdated: Jan 02, 2006 15:04 MST
sidebar:
compact: false
emoji:
subtitle:
avatar:
enabled: true
local: true
src: img/avatar.png
article:
math: false
toc: true
readingTime: true
license:
enabled: false
default: Licensed under CC BY-NC-SA 4.0
comments:
enabled: false
provider: disqus
disqusjs:
shortname:
apiUrl:
apiKey:
admin:
adminLabel:
utterances:
repo:
issueTerm: pathname
label:
remark42:
host:
site:
locale:
vssue:
platform:
owner:
repo:
clientId:
clientSecret:
autoCreateIssue: false
# Waline client configuration see: https://waline.js.org/en/reference/client.html
waline:
serverURL:
lang:
visitor:
avatar:
emoji:
- https://cdn.jsdelivr.net/gh/walinejs/emojis/weibo
requiredMeta:
- name
- email
- url
placeholder:
locale:
admin: Admin
twikoo:
envId:
region:
path:
lang:
giscus:
repo:
repoID:
category:
categoryID:
mapping:
lightTheme:
darkTheme:
reactionsEnabled: 1
emitMetadata: 0
lang:
gitalk:
owner:
admin:
repo:
clientID:
clientSecret:
cusdis:
host:
id:
widgets:
homepage: []
page: []
opengraph:
twitter:
# Your Twitter username
site:
# Available values: summary, summary_large_image
card: summary_large_image
defaultImage:
opengraph:
enabled: false
local: false
src:
colorScheme:
# Display toggle
toggle: true
# Available values: auto, light, dark
default: auto
imageProcessing:
cover:
enabled: true
content:
enabled: true

View File

@ -36,3 +36,11 @@ KaTeX:
integrity: sha384-vZTG03m+2yp6N6BNi5iM4rW4oIwk5DfcNdFfxkk9ZWpDriOkXX8voJBFrAO7MpVl integrity: sha384-vZTG03m+2yp6N6BNi5iM4rW4oIwk5DfcNdFfxkk9ZWpDriOkXX8voJBFrAO7MpVl
type: script type: script
defer: true defer: true
Cactus:
- src: https://latest.cactus.chat/cactus.js
integrity:
type: script
- src: https://latest.cactus.chat/style.css
integrity:
type: style

1
debug.sh Executable file
View File

@ -0,0 +1 @@
cd exampleSite && hugo server --gc --themesDir=../..

View File

@ -22,6 +22,9 @@ _testmain.go
*.exe *.exe
*.test *.test
/public public
/themes themes
resources
assets/jsconfig.json
.DS_Store .DS_Store

View File

@ -4,6 +4,21 @@ theme: hugo-theme-stack
paginate: 5 paginate: 5
title: Example Site title: Example Site
languages:
en:
languageName: English
title: Example Site
weight: 1
cn:
languageName: 中文
title: 演示站点
weight: 2
ar:
languageName: عربي
languagedirection: rtl
title: موقع تجريبي
weight: 3
# Change it to your Disqus shortname before using # Change it to your Disqus shortname before using
disqusShortname: hugo-theme-stack disqusShortname: hugo-theme-stack
@ -11,9 +26,13 @@ disqusShortname: hugo-theme-stack
googleAnalytics: googleAnalytics:
# Theme i18n support # Theme i18n support
# Available values: en, fr, id, ja, ko, pt-br, zh-cn, es, de, nl, it # Available values: en, fr, id, ja, ko, pt-br, zh-cn, zh-tw, es, de, nl, it, th, el, uk, ar
DefaultContentLanguage: en DefaultContentLanguage: en
# Set hasCJKLanguage to true if DefaultContentLanguage is in [zh-cn ja ko]
# This will make .Summary and .WordCount behave correctly for CJK languages.
hasCJKLanguage: false
permalinks: permalinks:
post: /p/:slug/ post: /p/:slug/
page: /:slug/ page: /:slug/
@ -100,6 +119,12 @@ params:
path: path:
lang: lang:
# See https://cactus.chat/docs/reference/web-client/#configuration for description of the various options
cactus:
defaultHomeserverUrl: "https://matrix.cactus.chat:8448"
serverName: "cactus.chat"
siteName: "" # You must insert a unique identifier here matching the one you registered (See https://cactus.chat/docs/getting-started/quick-start/#register-your-site)
giscus: giscus:
repo: repo:
repoID: repoID:
@ -111,17 +136,30 @@ params:
reactionsEnabled: 1 reactionsEnabled: 1
emitMetadata: 0 emitMetadata: 0
gitalk:
owner:
admin:
repo:
clientID:
clientSecret:
cusdis:
host:
id:
widgets: widgets:
enabled: homepage:
- search - type: search
- archives - type: archives
- tag-cloud params:
archives:
limit: 5 limit: 5
- type: categories
tagCloud: params:
limit: 10 limit: 10
- type: tag-cloud
params:
limit: 10
page:
- type: toc
opengraph: opengraph:
twitter: twitter:
@ -154,15 +192,20 @@ params:
### See https://docs.stack.jimmycai.com/configuration/custom-menu.html ### See https://docs.stack.jimmycai.com/configuration/custom-menu.html
### To remove about, archive and search page menu item, remove `menu` field from their FrontMatter ### To remove about, archive and search page menu item, remove `menu` field from their FrontMatter
menu: menu:
main: main: []
- identifier: home
name: Home social:
url: / - identifier: github
weight: -100 name: GitHub
pre: home url: https://github.com/CaiJimmy/hugo-theme-stack
params: params:
### For demonstration purpose, the home link will be open in a new tab icon: brand-github
newTab: true
- identifier: twitter
name: Twitter
url: https://twitter.com
params:
icon: brand-twitter
related: related:
includeNewer: true includeNewer: true
@ -176,9 +219,19 @@ related:
weight: 200 weight: 200
markup: markup:
goldmark:
renderer:
## Set to true if you have HTML content inside Markdown
unsafe: false
tableOfContents: tableOfContents:
endLevel: 4 endLevel: 4
ordered: true ordered: true
startLevel: 2 startLevel: 2
highlight: highlight:
noClasses: false noClasses: false
codeFences: true
guessSyntax: true
lineNoStart: 1
lineNos: true
lineNumbersInTable: true
tabWidth: 4

View File

@ -0,0 +1,8 @@
---
menu:
main:
name: 主页
weight: -100
params:
icon: home
---

View File

@ -0,0 +1,8 @@
---
menu:
main:
name: Home
weight: -100
params:
icon: home
---

View File

@ -0,0 +1,5 @@
---
title: 关于
---
This is a test page for i18n support.

View File

@ -11,7 +11,8 @@ lastmod: '2020-10-09'
menu: menu:
main: main:
weight: -90 weight: -90
pre: user params:
icon: user
--- ---
Written in Go, Hugo is an open source static site generator available under the [Apache Licence 2.0.](https://github.com/gohugoio/hugo/blob/master/LICENSE) Hugo supports TOML, YAML and JSON data file types, Markdown and HTML content files and uses shortcodes to add rich content. Other notable features are taxonomies, multilingual mode, image processing, custom output formats, HTML/CSS/JS minification and support for Sass SCSS workflows. Written in Go, Hugo is an open source static site generator available under the [Apache Licence 2.0.](https://github.com/gohugoio/hugo/blob/master/LICENSE) Hugo supports TOML, YAML and JSON data file types, Markdown and HTML content files and uses shortcodes to add rich content. Other notable features are taxonomies, multilingual mode, image processing, custom output formats, HTML/CSS/JS minification and support for Sass SCSS workflows.

View File

@ -6,5 +6,6 @@ slug: "archives"
menu: menu:
main: main:
weight: -70 weight: -70
pre: archives params:
icon: archives
--- ---

View File

@ -0,0 +1,37 @@
---
title: Links
links:
- title: GitHub
description: GitHub is the world's largest software development platform.
website: https://github.com
image: https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png
- title: TypeScript
description: TypeScript is a typed superset of JavaScript that compiles to plain JavaScript.
website: https://www.typescriptlang.org
image: ts-logo-128.jpg
menu:
main:
weight: -50
params:
icon: link
comments: false
---
To use this feature, add `links` section to frontmatter.
This page's frontmatter:
```yaml
links:
- title: GitHub
description: GitHub is the world's largest software development platform.
website: https://github.com
image: https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png
- title: TypeScript
description: TypeScript is a typed superset of JavaScript that compiles to plain JavaScript.
website: https://www.typescriptlang.org
image: ts-logo-128.jpg
```
`image` field accepts both local and external images.

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -8,5 +8,6 @@ outputs:
menu: menu:
main: main:
weight: -60 weight: -60
pre: search params:
icon: search
--- ---

View File

@ -69,6 +69,10 @@ Tables aren't part of the core Markdown spec, but Hugo supports supports them ou
| -------- | -------- | ------ | | -------- | -------- | ------ |
| *italics* | **bold** | `code` | | *italics* | **bold** | `code` |
| A | B | C | D | E | F |
|----------------------------------------------------------|---------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------|------------------------------------------------------------|----------------------------------------------------------------------|
| Lorem ipsum dolor sit amet, consectetur adipiscing elit. | Phasellus ultricies, sapien non euismod aliquam, dui ligula tincidunt odio, at accumsan nulla sapien eget ex. | Proin eleifend dictum ipsum, non euismod ipsum pulvinar et. Vivamus sollicitudin, quam in pulvinar aliquam, metus elit pretium purus | Proin sit amet velit nec enim imperdiet vehicula. | Ut bibendum vestibulum quam, eu egestas turpis gravida nec | Sed scelerisque nec turpis vel viverra. Vivamus vitae pretium sapien |
## Code Blocks ## Code Blocks
#### Code block with backticks #### Code block with backticks
@ -113,6 +117,16 @@ Tables aren't part of the core Markdown spec, but Hugo supports supports them ou
</html> </html>
{{< /highlight >}} {{< /highlight >}}
#### Diff code block
```diff
[dependencies.bevy]
git = "https://github.com/bevyengine/bevy"
rev = "11f52b8c72fc3a568e8bb4a4cd1f3eb025ac2e13"
- features = ["dynamic"]
+ features = ["jpeg", "dynamic"]
```
## List Types ## List Types
#### Ordered List #### Ordered List
@ -148,3 +162,7 @@ X<sup>n</sup> + Y<sup>n</sup> = Z<sup>n</sup>
Press <kbd><kbd>CTRL</kbd>+<kbd>ALT</kbd>+<kbd>Delete</kbd></kbd> to end the session. Press <kbd><kbd>CTRL</kbd>+<kbd>ALT</kbd>+<kbd>Delete</kbd></kbd> to end the session.
Most <mark>salamanders</mark> are nocturnal, and hunt for insects, worms, and other small creatures. Most <mark>salamanders</mark> are nocturnal, and hunt for insects, worms, and other small creatures.
## Hyperlinked image
[![Google](https://www.google.com/images/branding/googlelogo/1x/googlelogo_light_color_272x92dp.png)](https://google.com)

View File

@ -0,0 +1,32 @@
+++
author = "Hugo Authors"
title = "مثال نص"
date = "2019-03-09"
description = "هذا النص هو مثال لنص يمكن أن يستبدل في نفس المساحة"
categories = [
"تجربة",
"تجربة مع فراغات"
]
tags = [
"ماركداون",
"نص",
"وسم مع فراغات"
]
image = "matt-le-SJSpo9hQf7s-unsplash.jpg"
+++
## فقرة 1
هذا النص هو مثال لنص يمكن أن يستبدل في نفس المساحة، لقد تم توليد هذا النص من [مولد النص العربى](https://colorslab.com/textgator/)، حيث يمكنك أن تولد مثل هذا النص أو العديد من النصوص الأخرى إضافة إلى زيادة عدد الحروف التى يولدها التطبيق.
إذا كنت تحتاج إلى عدد أكبر من الفقرات يتيح لك مولد النص العربى زيادة عدد الفقرات كما تريد، النص لن يبدو مقسما ولا يحوي أخطاء لغوية، مولد النص العربى مفيد لمصممي المواقع على وجه الخصوص، حيث يحتاج العميل فى كثير من الأحيان أن يطلع على صورة حقيقية لتصميم الموقع.
ومن هنا وجب على المصمم أن يضع نصوصا مؤقتة على التصميم ليظهر للعميل الشكل كاملاً،دور مولد النص العربى أن يوفر على المصمم عناء البحث عن نص بديل لا علاقة له بالموضوع الذى يتحدث عنه التصميم فيظهر بشكل لا يليق.
هذا النص يمكن أن يتم تركيبه على أي تصميم دون مشكلة فلن يبدو وكأنه نص منسوخ، غير منظم، غير منسق، أو حتى غير مفهوم. لأنه مازال نصاً بديلاً ومؤقتاً.
## فقرة 2
هذا النص هو مثال لنص يمكن أن يستبدل في نفس المساحة، لقد تم توليد هذا النص من [مولد النص العربى](https://colorslab.com/textgator/)، حيث يمكنك أن تولد مثل هذا النص أو العديد من النصوص الأخرى إضافة إلى زيادة عدد الحروف التى يولدها التطبيق.
إذا كنت تحتاج إلى عدد أكبر من الفقرات يتيح لك مولد النص العربى زيادة عدد الفقرات كما تريد، النص لن يبدو مقسما ولا يحوي أخطاء لغوية، مولد النص العربى مفيد لمصممي المواقع على وجه الخصوص، حيث يحتاج العميل فى كثير من الأحيان أن يطلع على صورة حقيقية لتصميم الموقع.
ومن هنا وجب على المصمم أن يضع نصوصا مؤقتة على التصميم ليظهر للعميل الشكل كاملاً،دور مولد النص العربى أن يوفر على المصمم عناء البحث عن نص بديل لا علاقة له بالموضوع الذى يتحدث عنه التصميم فيظهر بشكل لا يليق.
هذا النص يمكن أن يتم تركيبه على أي تصميم دون مشكلة فلن يبدو وكأنه نص منسوخ، غير منظم، غير منسق، أو حتى غير مفهوم. لأنه مازال نصاً بديلاً ومؤقتاً.
## تجربة RTL
كلمة 1 Text كلمة 2

View File

@ -32,3 +32,11 @@ Hugo ships with several [Built-in Shortcodes](https://gohugo.io/content-manageme
## Vimeo Simple Shortcode ## Vimeo Simple Shortcode
{{< vimeo_simple 48912912 >}} {{< vimeo_simple 48912912 >}}
## bilibilibi Shortcode
{{< bilibili av498363026 >}}
## Gist Shortcode
{{< gist spf13 7896402 >}}

3
go.mod Normal file
View File

@ -0,0 +1,3 @@
module github.com/CaiJimmy/hugo-theme-stack/v3
go 1.17

70
i18n/ar.yaml Normal file
View File

@ -0,0 +1,70 @@
toggleMenu:
other: اخفي القائمة
darkMode:
other: الوضع الداكن
list:
page:
one: "{{ .Count }} صفحه"
other: "{{ .Count }} صفحات"
section:
other: قسم
subsection:
one: قسم فرعي
other: اقسام فرعية
article:
back:
other: خلف
tableOfContents:
other: جدول المحتويات
relatedContents:
other: محتوى مشابهه
lastUpdatedOn:
other: التعديل الاخير
readingTime:
one: "قرائة {{ .Count }} دقيقة"
other: "قرائة {{ .Count }} دقائق"
notFound:
title:
other: غير موجود
subtitle:
other: تعذر العثور على الصفحة المطلوبة.
widget:
archives:
title:
other: الارشيفات
more:
other: اكثر
tagCloud:
title:
other: وسوم
search:
title:
other: بحث
placeholder:
other: اكتب...
resultTitle:
other: "#PAGES_COUNT نتيجة (#TIME_SECONDS ثواني)"
footer:
builtWith:
other: "مبني بستخدام {{ .Generator }}"
designedBy:
other: "قالب {{ .Theme }} مصمم من {{ .DesignedBy }}"

70
i18n/el.yaml Normal file
View File

@ -0,0 +1,70 @@
toggleMenu:
other: Εναλλαγή Μενού
darkMode:
other: Σκοτεινό θέμα
list:
page:
one: "{{ .Count }} σελιδα"
other: "{{ .Count }} σελιδες"
section:
other: Ενότητα
subsection:
one: Υποενότητα
other: Υποενότητες
article:
back:
other: Πισω
tableOfContents:
other: Πινακας περιεχομενων
relatedContents:
other: Σχετικο περιεχομενο
lastUpdatedOn:
other: Τελευταια τροποποιηση στις
readingTime:
one: "{{ .Count }} λεπτό ανάγνωσης"
### Seems that there's no need to add 's' even if it's plural in English
other: "{{ .Count }} λεπτά ανάγνωσης"
notFound:
title:
other: Δε βρέθηκε
subtitle:
other: Η σελίδα δε βρέθηκε.
widget:
archives:
title:
other: Αρχειο
more:
other: Περισσότερα
tagCloud:
title:
other: Tags
search:
title:
other: Αναζήτηση
placeholder:
other: Πληκτρολογήστε κάτι...
resultTitle:
other: "#PAGES_COUNT σελιδες (#TIME_SECONDS δευτερολεπτα)"
footer:
builtWith:
other: Δημιουργήθηκε με τη χρήση {{ .Generator }}
designedBy:
other: Το θέμα {{ .Theme }} σχεδιάστηκε από το {{ .DesignedBy }}

View File

@ -51,6 +51,9 @@ widget:
tagCloud: tagCloud:
title: title:
other: Tags other: Tags
categoriesCloud:
title:
other: Categories
search: search:
title: title:

View File

@ -29,6 +29,10 @@ article:
lastUpdatedOn: lastUpdatedOn:
other: Dernière mise à jour le other: Dernière mise à jour le
readingTime:
one: "{{ .Count }} minute de lecture"
other: "{{ .Count }} minutes de lecture"
notFound: notFound:
title: title:
other: Page non trouvée other: Page non trouvée

View File

@ -38,6 +38,10 @@ widget:
title: title:
other: タグ other: タグ
categoriesCloud:
title:
other: カテゴリ
search: search:
title: title:
other: 検索 other: 検索
@ -47,3 +51,10 @@ search:
resultTitle: resultTitle:
other: "#PAGES_COUNT 件 #TIME_SECONDS 秒)" other: "#PAGES_COUNT 件 #TIME_SECONDS 秒)"
footer:
builtWith:
other: Built with {{ .Generator }}
designedBy:
other: テーマ {{ .Theme }} は {{ .DesignedBy }} によって設計されています。

View File

@ -1,15 +1,42 @@
toggleMenu: toggleMenu:
other: 메뉴 여닫기 other: 메뉴 여닫기
darkMode:
other: 다크 모드
list:
page:
one: "{{ .Count }} 페이지"
other: "{{ .Count }} 페이지"
section:
other: 섹션
subsection:
one: 서브섹션
other: 서브섹션
article: article:
back:
other: 뒤로가기
tableOfContents:
other: 목차
relatedContents: relatedContents:
other: 관련 글 other: 관련 글
lastUpdatedOn: lastUpdatedOn:
other: "마지막 수정: " other: "마지막 수정: "
readingTime:
one: "{{ .Count }} 분 정도"
other: "{{ .Count }} 분 정도"
notFound: notFound:
title: title:
other: 찾을 수 없음 other: 찾을 수 없음
subtitle: subtitle:
other: 페이지를 찾을 수 없습니다. other: 페이지를 찾을 수 없습니다.
@ -19,6 +46,7 @@ widget:
other: 보관함 other: 보관함
more: more:
other: 더보기 other: 더보기
tagCloud: tagCloud:
title: title:
other: 태그 other: 태그
@ -26,8 +54,10 @@ widget:
search: search:
title: title:
other: 검색 other: 검색
placeholder: placeholder:
other: 검색어를 입력하세요... other: 검색어를 입력하세요...
resultTitle: resultTitle:
other: "#PAGES_COUNT 페이지 (#TIME_SECONDS 초)" other: "#PAGES_COUNT 페이지 (#TIME_SECONDS 초)"

View File

@ -17,11 +17,22 @@ list:
other: Subseções other: Subseções
article: article:
back:
other: Voltar
tableOfContents:
other: Índice
relatedContents: relatedContents:
other: Conteúdos Relacionados other: Conteúdo relacionado
lastUpdatedOn: lastUpdatedOn:
other: Última atualização em other: Última atualização em
readingTime:
one: "{{ .Count }} minuto de leitura"
other: "{{ .Count }} minutos de leitura"
notFound: notFound:
title: title:
other: Não Encontrado other: Não Encontrado

70
i18n/th.yaml Normal file
View File

@ -0,0 +1,70 @@
toggleMenu:
other: สลับเมนู
darkMode:
other: ธีมมืด
list:
page:
one: "{{ .Count }} หน้า"
other: "{{ .Count }} หน้า"
section:
other: หมวดหมู่
subsection:
one: หมวดหมู่ย่อย
other: หมวดหมู่ย่อยอื่นๆ
article:
back:
other: กลับไป
tableOfContents:
other: สารบัญ
relatedContents:
other: เนื้อหาคล้ายคลึงกัน
lastUpdatedOn:
other: อัปเดตล่าสุดเมื่อ
readingTime:
one: "น่าจะใช้เวลา {{ .Count }} นาทีในการอ่าน"
other: "น่าจะใช้เวลา {{ .Count }} นาทีในการอ่าน"
notFound:
title:
other: ไม่พบหัวข้อ
subtitle:
other: ไม่พบหน้านี้ในระบบ
widget:
archives:
title:
other: เนื้อหาที่เก็บถาวรแล้ว
more:
other: อื่นๆ นอกจากนี้
tagCloud:
title:
other: แท็ก
search:
title:
other: ค้นหา
placeholder:
other: พิมพ์เพื่อค้นหา ...
resultTitle:
other: "#PAGES_COUNT pages (#TIME_SECONDS seconds)"
footer:
builtWith:
other: ถูกสร้างด้วย {{ .Generator }}
designedBy:
other: ธีม {{ .Theme }} ออกแบบโดย {{ .DesignedBy }}

71
i18n/uk.yaml Normal file
View File

@ -0,0 +1,71 @@
toggleMenu:
other: Показати меню
darkMode:
other: Темна тема
list:
page:
one: "{{ .Count }} сторінка"
few: "{{ .Count }} сторінки"
other: "{{ .Count }} сторінок"
section:
other: Секція
subsection:
one: Підсекція
other: Підсекції
article:
back:
other: Назад
tableOfContents:
other: Зміст
relatedContents:
other: Схожі матеріали
lastUpdatedOn:
other: Востаннє оновлено
readingTime:
one: "Час читання: {{ .Count }} хв"
other: "Час читання: {{ .Count }} хв"
notFound:
title:
other: Не знайдено
subtitle:
other: Ця сторінка не існує
widget:
archives:
title:
other: Архіви
more:
other: Більше
tagCloud:
title:
other: Теґи
search:
title:
other: Пошук
placeholder:
other: Напишіть що-небудь...
resultTitle:
other: "#PAGES_COUNT сторінок (#TIME_SECONDS секунд)"
footer:
builtWith:
other: Створено з {{ .Generator }}
designedBy:
other: Тема {{ .Theme }}, дизайн {{ .DesignedBy }}

View File

@ -38,6 +38,10 @@ widget:
title: title:
other: 标签云 other: 标签云
categoriesCloud:
title:
other: 分类
search: search:
title: title:
other: 搜索 other: 搜索
@ -47,3 +51,10 @@ search:
resultTitle: resultTitle:
other: "#PAGES_COUNT 个结果 (用时 #TIME_SECONDS 秒)" other: "#PAGES_COUNT 个结果 (用时 #TIME_SECONDS 秒)"
footer:
builtWith:
other: Built with {{ .Generator }}
designedBy:
other: 主题 {{ .Theme }} 由 {{ .DesignedBy }} 设计

49
i18n/zh-TW.yaml Normal file
View File

@ -0,0 +1,49 @@
toggleMenu:
other: 切換選單
darkMode:
other: 夜晚模式
article:
back:
other: 返回
tableOfContents:
other: 目錄
relatedContents:
other: 相關文章
lastUpdatedOn:
other: 最後更新
readingTime:
other: "閱讀時間: {{ .Count }} 分鐘"
notFound:
title:
other: 404 錯誤
subtitle:
other: 頁面不存在
widget:
archives:
title:
other: 紀錄
more:
other: 更多
tagCloud:
title:
other: 標籤雲
search:
title:
other: 搜尋
placeholder:
other: 輸入關鍵字...
resultTitle:
other: "#PAGES_COUNT 個結果 (用時 #TIME_SECONDS 秒)"

View File

@ -25,22 +25,17 @@
{{- end -}} {{- end -}}
{{- end -}} {{- end -}}
<figure <img src="{{ $Permalink }}"
{{ if $galleryImage }}
class="gallery-image"
style="
flex-grow: {{ div (mul $image.Width 100) $image.Height }};
flex-basis: {{ div (mul $image.Width 240) $image.Height }}px"
{{ end }}>
<a href="{{ $Permalink }}" {{ if $galleryImage }}data-size="{{ $image.Width }}x{{ $image.Height }}"{{ end }}>
<img src="{{ $Permalink }}"
{{ with $Width }}width="{{ . }}"{{ end }} {{ with $Width }}width="{{ . }}"{{ end }}
{{ with $Height }}height="{{ . }}"{{ end }} {{ with $Height }}height="{{ . }}"{{ end }}
{{ with $Srcset }}srcset="{{ . }}"{{ end }} {{ with $Srcset }}srcset="{{ . }}"{{ end }}
loading="lazy" loading="lazy"
{{ with $alt }}alt="{{ . }}"{{ end }}>
</a>
{{ with $alt }} {{ with $alt }}
<figcaption>{{ . | markdownify }}</figcaption> alt="{{ . }}"
{{ end }} {{ end }}
</figure> {{ if $galleryImage }}
class="gallery-image"
data-flex-grow="{{ div (mul $image.Width 100) $image.Height }}"
data-flex-basis="{{ div (mul $image.Width 240) $image.Height }}px"
{{ end }}
>

View File

@ -1,5 +1,6 @@
{{ define "body-class" }}template-archives{{ end }} {{ define "body-class" }}template-archives{{ end }}
{{ define "main" }} {{ define "main" }}
<header>
{{- $taxonomy := $.Site.GetPage "taxonomyTerm" "categories" -}} {{- $taxonomy := $.Site.GetPage "taxonomyTerm" "categories" -}}
{{- $terms := $taxonomy.Pages -}} {{- $terms := $taxonomy.Pages -}}
{{ if $terms }} {{ if $terms }}
@ -12,6 +13,7 @@
</div> </div>
</div> </div>
{{ end }} {{ end }}
</header>
{{ $pages := where .Site.RegularPages "Type" "in" .Site.Params.mainSections }} {{ $pages := where .Site.RegularPages "Type" "in" .Site.Params.mainSections }}
{{ $notHidden := where .Site.RegularPages "Params.hidden" "!=" true }} {{ $notHidden := where .Site.RegularPages "Params.hidden" "!=" true }}

View File

@ -1,12 +1,20 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="{{ .Site.LanguageCode }}"> <html lang="{{ .Site.LanguageCode }}" dir="{{ default `ltr` .Language.LanguageDirection }}">
<head> <head>
{{- partial "head/head.html" . -}} {{- partial "head/head.html" . -}}
{{- block "head" . -}}{{ end }} {{- block "head" . -}}{{ end }}
</head> </head>
<body class="{{ block `body-class` . }}{{ end }}"> <body class="{{ block `body-class` . }}{{ end }}">
{{- partial "head/colorScheme" . -}} {{- partial "head/colorScheme" . -}}
<div class="container main-container flex {{ block `container-class` . }}on-phone--column {{ if .Site.Params.widgets.enabled }}extended{{ else }}compact{{ end }}{{ end }}">
{{/* The container is wider when there's any activated widget */}}
{{- $hasWidget := false -}}
{{- range .Site.Params.widgets -}}
{{- if gt (len .) 0 -}}
{{- $hasWidget = true -}}
{{- end -}}
{{- end -}}
<div class="container main-container flex on-phone--column {{ if $hasWidget }}extended{{ else }}compact{{ end }}">
{{- block "left-sidebar" . -}} {{- block "left-sidebar" . -}}
{{ partial "sidebar/left.html" . }} {{ partial "sidebar/left.html" . }}
{{- end -}} {{- end -}}

View File

@ -1,4 +1,5 @@
{{ define "main" }} {{ define "main" }}
<header>
<h3 class="section-title"> <h3 class="section-title">
{{ if eq .Parent (.GetPage "/") }} {{ if eq .Parent (.GetPage "/") }}
{{ T "list.section" }} {{ T "list.section" }}
@ -41,6 +42,7 @@
</div> </div>
{{ end }} {{ end }}
</div> </div>
</header>
{{- $subsections := .Sections -}} {{- $subsections := .Sections -}}
{{- $pages := .Pages | complement $subsections -}} {{- $pages := .Pages | complement $subsections -}}
@ -53,6 +55,7 @@
{{- end -}} {{- end -}}
{{- with $subsections -}} {{- with $subsections -}}
<aside>
<h2 class="section-title">{{ T "list.subsection" (len $subsections) }}</h2> <h2 class="section-title">{{ T "list.subsection" (len $subsections) }}</h2>
<div class="subsection-list"> <div class="subsection-list">
<div class="article-list--tile"> <div class="article-list--tile">
@ -61,6 +64,7 @@
{{ end }} {{ end }}
</div> </div>
</div> </div>
</aside>
{{- end -}} {{- end -}}
{{/* List only pages that are not a subsection */}} {{/* List only pages that are not a subsection */}}
@ -77,5 +81,5 @@
{{ end }} {{ end }}
{{ define "right-sidebar" }} {{ define "right-sidebar" }}
{{ partialCached "sidebar/right.html" . }} {{ partial "sidebar/right.html" (dict "Context" . "Scope" "homepage") }}
{{ end }} {{ end }}

View File

@ -1,9 +1,15 @@
{{- $pages := where .Site.RegularPages "Type" "in" .Site.Params.mainSections -}} {{- $pctx := . -}}
{{- $notHidden := where .Site.RegularPages "Params.hidden" "!=" true -}} {{- if .IsHome -}}{{ $pctx = .Site }}{{- end -}}
{{- $filtered := ($pages | intersect $notHidden) -}} {{- $pages := slice -}}
{{- if or $.IsHome $.IsSection -}}
{{- $pages = $pctx.RegularPages -}}
{{- else -}}
{{- $pages = $pctx.Pages -}}
{{- end -}}
{{- $pages := where $pages "Params.hidden" "!=" true -}}
{{- $limit := .Site.Config.Services.RSS.Limit -}} {{- $limit := .Site.Config.Services.RSS.Limit -}}
{{- if ge $limit 1 -}} {{- if ge $limit 1 -}}
{{- $filtered = $filtered | first $limit -}} {{- $pages = $pages | first $limit -}}
{{- end -}} {{- end -}}
{{- printf "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>" | safeHTML }} {{- printf "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>" | safeHTML }}
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"> <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
@ -20,7 +26,7 @@
{{- with .OutputFormats.Get "RSS" -}} {{- with .OutputFormats.Get "RSS" -}}
{{ printf "<atom:link href=%q rel=\"self\" type=%q />" .Permalink .MediaType | safeHTML }} {{ printf "<atom:link href=%q rel=\"self\" type=%q />" .Permalink .MediaType | safeHTML }}
{{- end -}} {{- end -}}
{{ range $filtered }} {{ range $pages }}
{{- $content := safeHTML (.Summary | html) -}} {{- $content := safeHTML (.Summary | html) -}}
{{- if .Site.Params.rssFullContent -}} {{- if .Site.Params.rssFullContent -}}
{{- $content = safeHTML (.Content | html) -}} {{- $content = safeHTML (.Content | html) -}}

View File

@ -1,23 +1,38 @@
{{ define "body-class" }} {{ define "body-class" }}
{{ $TOCEnabled := default (default false .Site.Params.article.toc) .Params.toc }} article-page
{{- .Scratch.Set "hasTOC" (and (ge (len .TableOfContents) 100) $TOCEnabled) -}} {{/*
article-page {{ if (.Scratch.Get "hasTOC") }}has-toc{{ end }} Enable the right sidebar if
{{ end }} - Widget different from 'TOC' is enabled
- TOC is enabled and not empty
*/}}
{{- $HasWidgetNotTOC := false -}}
{{- $TOCWidgetEnabled := false -}}
{{- range .Site.Params.widgets.page -}}
{{- if ne .type "toc" -}}
{{ $HasWidgetNotTOC = true -}}
{{- else -}}
{{ $TOCWidgetEnabled = true -}}
{{- end -}}
{{- end -}}
{{ define "container-class" }} {{- $TOCManuallyDisabled := eq .Params.toc false -}}
{{ if (.Scratch.Get "hasTOC") }} {{- $TOCEnabled := and (not $TOCManuallyDisabled) $TOCWidgetEnabled -}}
extended {{- $hasTOC := ge (len .TableOfContents) 100 -}}
{{ else }} {{- .Scratch.Set "TOCEnabled" (and $TOCEnabled $hasTOC) -}}
on-phone--column {{ if .Site.Params.widgets.enabled }}extended{{ else }}compact{{ end }}
{{ end }} {{- .Scratch.Set "hasWidget" (or $HasWidgetNotTOC (and $TOCEnabled $hasTOC)) -}}
{{ end }} {{ end }}
{{ define "main" }} {{ define "main" }}
{{ partial "article/article.html" . }} {{ partial "article/article.html" . }}
{{ if .Params.links }}
{{ partial "article/components/links" . }}
{{ end }}
{{ partial "article/components/related-contents" . }} {{ partial "article/components/related-contents" . }}
{{ if or (not (isset .Params "comments")) (eq .Params.comments "true")}} {{ if not (eq .Params.comments false) }}
{{ partial "comments/include" . }} {{ partial "comments/include" . }}
{{ end }} {{ end }}
@ -26,34 +41,6 @@
{{ partialCached "article/components/photoswipe" . }} {{ partialCached "article/components/photoswipe" . }}
{{ end }} {{ end }}
{{ define "left-sidebar" }}
{{ if (.Scratch.Get "hasTOC") }}
<div id="article-toolbar">
<a href="{{ .Site.BaseURL }}" class="back-home">
{{ (resources.Get "icons/back.svg").Content | safeHTML }}
<span>{{ T "article.back" }}</span>
</a>
</div>
{{ else }}
{{ partial "sidebar/left.html" . }}
{{ end }}
{{ end }}
{{ define "right-sidebar" }} {{ define "right-sidebar" }}
{{ if (.Scratch.Get "hasTOC") }} {{ if .Scratch.Get "hasWidget" }}{{ partial "sidebar/right.html" (dict "Context" . "Scope" "page") }}{{ end}}
<aside class="sidebar right-sidebar sticky">
<section class="widget archives">
<div class="widget-head">
<div class="widget-icon">
{{ partial "helper/icon" "hash" }}
</div>
<h2 class="widget-title">{{ T "article.tableOfContents" }}</h2>
</div>
<div class="widget--toc">
{{ .TableOfContents }}
</div>
</section>
</aside>
{{ end }}
{{ end }} {{ end }}

View File

@ -15,5 +15,5 @@
{{ end }} {{ end }}
{{ define "right-sidebar" }} {{ define "right-sidebar" }}
{{ partial "sidebar/right.html" . }} {{ partial "sidebar/right.html" (dict "Context" . "Scope" "homepage") }}
{{ end }} {{ end }}

View File

@ -16,8 +16,10 @@
</button> </button>
</form> </form>
<h3 class="search-result--title section-title"></h3> <div class="search-result">
<div class="search-result--list article-list--compact"></div> <h3 class="search-result--title section-title"></h3>
<div class="search-result--list article-list--compact"></div>
</div>
<script> <script>
window.searchResultTitleTemplate = "{{ T `search.resultTitle` }}" window.searchResultTitleTemplate = "{{ T `search.resultTitle` }}"

View File

@ -29,6 +29,7 @@
<img src="{{ $Permalink }}" <img src="{{ $Permalink }}"
width="{{ $Width }}" width="{{ $Width }}"
height="{{ $Height }}" height="{{ $Height }}"
alt="{{ .Title }}"
loading="lazy"> loading="lazy">
{{ else }} {{ else }}
<img src="{{ $image.permalink }}" loading="lazy" alt="Featured image of post {{ .Title }}" /> <img src="{{ $image.permalink }}" loading="lazy" alt="Featured image of post {{ .Title }}" />

View File

@ -21,7 +21,8 @@
width="{{ $Width }}" width="{{ $Width }}"
height="{{ $Height }}" height="{{ $Height }}"
loading="lazy" loading="lazy"
data-key="{{ .context.Slug }}" alt="Featured image of post {{ .context.Title }}"
{{ with .context.Slug }}data-key="{{ . }}" {{ end }}
data-hash="{{ $imageRaw.Data.Integrity }}"> data-hash="{{ $imageRaw.Data.Integrity }}">
{{ else }} {{ else }}
<img src="{{ $image.permalink }}" loading="lazy" data-key="{{ .context.Slug }}" data-hash="{{ $image.permalink }}"/> <img src="{{ $image.permalink }}" loading="lazy" data-key="{{ .context.Slug }}" data-hash="{{ $image.permalink }}"/>

View File

@ -1,3 +1,5 @@
<section class="article-content"> <section class="article-content">
{{ .Content }} <!-- Refer to https://discourse.gohugo.io/t/responsive-tables-in-markdown/10639/5 -->
{{ $wrappedTable := printf "<div class=\"table-wrapper\">${1}</div>" }}
{{ .Content | replaceRE "(<table>(?:.|\n)+?</table>)" $wrappedTable | safeHTML }}
</section> </section>

View File

@ -9,6 +9,7 @@
</header> </header>
{{ end }} {{ end }}
<div class="article-title-wrapper">
<h2 class="article-title"> <h2 class="article-title">
<a href="{{ .RelPermalink }}"> <a href="{{ .RelPermalink }}">
{{- .Title -}} {{- .Title -}}
@ -20,6 +21,7 @@
{{ . }} {{ . }}
</h3> </h3>
{{ end }} {{ end }}
</div>
{{ if or (not .Date.IsZero) (.Site.Params.article.readingTime) }} {{ if or (not .Date.IsZero) (.Site.Params.article.readingTime) }}
<footer class="article-time"> <footer class="article-time">
@ -42,4 +44,15 @@
{{ end }} {{ end }}
</footer> </footer>
{{ end }} {{ end }}
{{ if .IsTranslated }}
<footer class="article-translations">
{{ partial "helper/icon" "language" }}
<div>
{{ range .Translations }}
<a href="{{ .Permalink }}" class="link">{{ .Language.LanguageName }}</a>
{{ end }}
</div>
</footer>
{{ end }}
</div> </div>

View File

@ -0,0 +1,26 @@
<div class="article-list--compact links">
{{ range $i, $link := .Params.links }}
<article>
<a href="{{ $link.website }}" target="_blank" rel="noopener">
<div class="article-details">
<h2 class="article-title">
{{- $link.title -}}
</h2>
<footer class="article-time">
{{ with $link.description }}
{{ . }}
{{ else }}
{{ $link.website }}
{{ end }}
</footer>
</div>
{{ with $link.image }}
<div class="article-image">
<img src="{{ . }}" loading="lazy">
</div>
{{ end }}
</a>
</article>
{{ end }}
</div>

View File

@ -1,6 +1,6 @@
{{ $related := (where (.Site.RegularPages.Related .) "Params.hidden" "!=" true) | first 5 }}
{{ with $related }}
<aside class="related-contents--wrapper"> <aside class="related-contents--wrapper">
{{ $related := (where (.Site.RegularPages.Related .) "Params.hidden" "!=" true) | first 5 }}
{{ with $related }}
<h2 class="section-title">{{ T "article.relatedContents" }}</h2> <h2 class="section-title">{{ T "article.relatedContents" }}</h2>
<div class="related-contents"> <div class="related-contents">
<div class="flex article-list--tile"> <div class="flex article-list--tile">
@ -9,5 +9,5 @@
{{ end }} {{ end }}
</div> </div>
</div> </div>
{{ end }}
</aside> </aside>
{{ end }}

View File

@ -0,0 +1,29 @@
{{- with .Site.Params.comments.cactus -}}
{{- partial "helper/external" (dict "Context" $ "Namespace" "Cactus") -}}
<style>
.cactus-editor-textarea {
color: var(--body-text-color);
}
.cactus-comment-header {
color: var(--card-text-color-main);
}
.cactus-message-text > p {
color: var(--body-text-color);
}
</style>
<div id="comment-section"></div>
<script>
initComments({
node: document.getElementById("comment-section"),
defaultHomeserverUrl: "{{ .defaultHomeserverUrl | safeJS }}",
serverName: "{{ .serverName }}",
siteName: "{{ .siteName }}",
commentSectionId: "{{ $.File.UniqueID }}"
})
</script>
{{- end -}}

View File

@ -0,0 +1,21 @@
{{- $host := default "https://cusdis.com" .Site.Params.comments.cusdis.host -}}
<div id="cusdis_thread"
data-host="{{ $host }}"
data-app-id="{{ .Site.Params.comments.cusdis.id }}"
data-page-id="{{ .File.UniqueID }}"
data-page-url="{{ .Permalink }}"
data-page-title="{{ .Title }}"></div>
<script async defer src="{{ $host }}/js/cusdis.es.js"></script>
<script>
function setCusdisTheme(theme) {
let cusdis = document.querySelector('#cusdis_thread iframe');
if (cusdis) {
window.CUSDIS.setTheme(theme)
}
}
window.addEventListener('onColorSchemeChange', (e) => {
setCusdisTheme(e.detail)
})
</script>

View File

@ -9,41 +9,41 @@
data-reactions-enabled="{{- default 1 .reactionsEnabled -}}" data-reactions-enabled="{{- default 1 .reactionsEnabled -}}"
data-emit-metadata="{{- default 0 .emitMetadata -}}" data-emit-metadata="{{- default 0 .emitMetadata -}}"
data-theme="{{- default `light` .lightTheme -}}" data-theme="{{- default `light` .lightTheme -}}"
data-lang="{{- default `en` .lang -}}"
crossorigin="anonymous" crossorigin="anonymous"
async async
></script> ></script>
<script> <script>
function setGiscusTheme(theme) { function setGiscusTheme(theme) {
let giscus = document.querySelector('iframe.giscus-frame'); let giscus = document.querySelector("iframe.giscus-frame");
if (giscus) { if (giscus) {
giscus.contentWindow.postMessage( giscus.contentWindow.postMessage(
{ {
giscus: { giscus: {
setConfig: { setConfig: {
theme: theme theme: theme,
} },
} },
}, },
"https://giscus.app" "https://giscus.app"
); );
}; }
}; }
(function(){ (function () {
addEventListener('message', (e) => { addEventListener("message", (e) => {
if (event.origin !== 'https://giscus.app') return; if (event.origin !== "https://giscus.app") return;
handler() handler();
}); });
window.addEventListener('onColorSchemeChange', handler); window.addEventListener("onColorSchemeChange", handler);
function handler() { function handler() {
if (document.documentElement.dataset.scheme === "light") { if (document.documentElement.dataset.scheme === "light") {
setGiscusTheme('{{- default "light" .lightTheme -}}'); setGiscusTheme('{{- default "light" .lightTheme -}}');
} else { } else {
setGiscusTheme('{{- default "dark_dimmed" .darkTheme -}}'); setGiscusTheme('{{- default "dark_dimmed" .darkTheme -}}');
}; }
}; }
}()); })();
</script> </script>
{{- end -}} {{- end -}}

View File

@ -0,0 +1,30 @@
{{- with .Site.Params.comments.gitalk -}}
<div id="gitalk-container"></div>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/gitalk@1.7.2/dist/gitalk.css"
/>
<script src="https://cdn.jsdelivr.net/npm/gitalk@1.7.2/dist/gitalk.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/blueimp-md5@2.18.0/js/md5.min.js"></script>
<script>
const gitalk = new Gitalk({
clientID: "{{- .clientID -}}",
clientSecret: "{{- .clientSecret -}}",
repo: "{{- .repo -}}",
owner: "{{- .owner -}}",
admin: ["{{- .admin -}}"],
distractionFreeMode: false, // Facebook-like distraction free mode
id: md5(location.pathname), // Max Location.pathname Legth:75 https://github.com/gitalk/gitalk/issues/102
});
(function () {
if (
["localhost", "127.0.0.1"].indexOf(window.location.hostname) != -1
) {
document.getElementById("gitalk-container").innerHTML =
"Gitalk comments not available by default when the website is previewed locally.";
return;
}
gitalk.render("gitalk-container");
})();
</script>
{{ end }}

View File

@ -1,4 +1,4 @@
<script src="//cdn.jsdelivr.net/npm/twikoo@1.4.3/dist/twikoo.all.min.js"></script> <script src="//cdn.jsdelivr.net/npm/twikoo@1.4.18/dist/twikoo.all.min.js"></script>
<div id="tcomment"></div> <div id="tcomment"></div>
<style> <style>
.twikoo { .twikoo {

View File

@ -4,3 +4,9 @@
{{- $script := resources.Get "ts/main.ts" | js.Build $opts -}} {{- $script := resources.Get "ts/main.ts" | js.Build $opts -}}
<script type="text/javascript" src="{{ $script.RelPermalink }}" defer></script> <script type="text/javascript" src="{{ $script.RelPermalink }}" defer></script>
{{- with resources.Get "ts/custom.ts" -}}
{{/* Place your custom script in HUGO_SITE_FOLDER/assets/ts/custom.ts */}}
{{- $customScript := . | js.Build $opts -}}
<script type="text/javascript" src="{{ $customScript.RelPermalink }}" defer></script>
{{- end -}}

View File

@ -1,4 +1,4 @@
{{- $ThemeVersion := "3.2.0" -}} {{- $ThemeVersion := "3.10.0" -}}
<footer class="site-footer"> <footer class="site-footer">
<section class="copyright"> <section class="copyright">
&copy; &copy;

View File

@ -1,3 +1,3 @@
{{ $sass := resources.Get "scss/style.scss" }} {{ $sass := resources.Get "scss/style.scss" }}
{{ $style := $sass | resources.ToCSS | minify }} {{ $style := $sass | resources.ToCSS | minify | resources.Fingerprint "sha256" }}
<link rel="stylesheet" href="{{ $style.RelPermalink }}"> <link rel="stylesheet" href="{{ $style.RelPermalink }}">

View File

@ -8,7 +8,7 @@
integrity="{{ . }}" integrity="{{ . }}"
{{- end -}} {{- end -}}
crossorigin="anonymous" crossorigin="anonymous"
defer="{{ default false .defer }}" {{ if .defer }}defer{{ end }}
> >
</script> </script>
{{- else if eq .type "style" -}} {{- else if eq .type "style" -}}

View File

@ -1,14 +1,15 @@
<aside class="sidebar left-sidebar sticky"> <aside class="sidebar left-sidebar sticky {{ if .Site.Params.sidebar.compact }}compact{{ end }}">
<button class="hamburger hamburger--spin" type="button" id="toggle-menu" aria-label="{{ T `toggleMenu` }}"> <button class="hamburger hamburger--spin" type="button" id="toggle-menu" aria-label="{{ T `toggleMenu` }}">
<span class="hamburger-box"> <span class="hamburger-box">
<span class="hamburger-inner"></span> <span class="hamburger-inner"></span>
</span> </span>
</button> </button>
<header class="site-info"> <header>
{{ with .Site.Params.sidebar.avatar }} {{ with .Site.Params.sidebar.avatar }}
{{ if (default true .enabled) }} {{ if (default true .enabled) }}
<figure class="site-avatar"> <figure class="site-avatar">
<a href="{{ .Site.BaseURL | relLangURL }}">
{{ if not .local }} {{ if not .local }}
<img src="{{ .src }}" width="300" height="300" class="site-logo" loading="lazy" alt="Avatar"> <img src="{{ .src }}" width="300" height="300" class="site-logo" loading="lazy" alt="Avatar">
{{ else }} {{ else }}
@ -22,32 +23,71 @@
{{ errorf "Failed loading avatar from %q" . }} {{ errorf "Failed loading avatar from %q" . }}
{{ end }} {{ end }}
{{ end }} {{ end }}
</a>
{{ with $.Site.Params.sidebar.emoji }} {{ with $.Site.Params.sidebar.emoji }}
<span class="emoji">{{ . }}</span> <span class="emoji">{{ . }}</span>
{{ end }} {{ end }}
</figure> </figure>
{{ end }} {{ end }}
{{ end }} {{ end }}
<h1 class="site-name"><a href="{{ .Site.BaseURL }}">{{ .Site.Title }}</a></h1>
<div class="site-meta">
<h1 class="site-name"><a href="{{ .Site.BaseURL | relLangURL }}">{{ .Site.Title }}</a></h1>
<h2 class="site-description">{{ .Site.Params.sidebar.subtitle }}</h2> <h2 class="site-description">{{ .Site.Params.sidebar.subtitle }}</h2>
</div>
</header> </header>
{{- with .Site.Menus.social -}}
<ol class="social-menu">
{{ range . }}
<li>
<a
href='{{ .URL }}'
{{ if eq (default true .Params.newTab) true }}target="_blank"{{ end }}
{{ with .Name }}title="{{ . }}"{{ end }}
>
{{ $icon := default "link" .Params.Icon }}
{{ with $icon }}
{{ partial "helper/icon" . }}
{{ end }}
</a>
</li>
{{ end }}
</ol>
{{- end -}}
<ol class="menu" id="main-menu"> <ol class="menu" id="main-menu">
{{ $currentPage := . }} {{ $currentPage := . }}
{{ range .Site.Menus.main }} {{ range .Site.Menus.main }}
{{ $active := or (eq $currentPage.Title .Name) (or ($currentPage.HasMenuCurrent "main" .) ($currentPage.IsMenuCurrent "main" .)) }} {{ $active := or (eq $currentPage.Title .Name) (or ($currentPage.HasMenuCurrent "main" .) ($currentPage.IsMenuCurrent "main" .)) }}
<li {{ if $active }} class='current' {{ end }}> <li {{ if $active }} class='current' {{ end }}>
<a href='{{ .URL }}' {{ if eq .Params.newTab true }}target="_blank"{{ end }}> <a href='{{ .URL | relLangURL }}' {{ if eq .Params.newTab true }}target="_blank"{{ end }}>
{{ $icon := default .Pre .Params.Icon }}
{{ if .Pre }} {{ if .Pre }}
{{ partial "helper/icon" .Pre }} {{ warnf "Menu item [%s] is using [pre] field to set icon, please use [params.icon] instead.\nMore information: https://docs.stack.jimmycai.com/configuration/custom-menu.html" .URL }}
{{ end }}
{{ with $icon }}
{{ partial "helper/icon" . }}
{{ end }} {{ end }}
<span>{{- .Name -}}</span> <span>{{- .Name -}}</span>
</a> </a>
</li> </li>
{{ end }} {{ end }}
<div class="menu-bottom-section">
{{- $currentLanguageCode := .Language.Lang -}}
{{ with .Site.Home.AllTranslations }}
<li id="i18n-switch">
{{ partial "helper/icon" "language" }}
<select name="language" onchange="window.location.href = this.selectedOptions[0].value">
{{ range . }}
<option value="{{ .Permalink }}" {{ if eq .Language.Lang $currentLanguageCode }}selected{{ end }}>{{ .Language.LanguageName }}</option>
{{ end }}
</select>
</li>
{{ end }}
{{ if (default false .Site.Params.colorScheme.toggle) }} {{ if (default false .Site.Params.colorScheme.toggle) }}
<li id="dark-mode-toggle"> <li id="dark-mode-toggle">
{{ partial "helper/icon" "toggle-left" }} {{ partial "helper/icon" "toggle-left" }}
@ -55,5 +95,6 @@
<span>{{ T "darkMode" }}</span> <span>{{ T "darkMode" }}</span>
</li> </li>
{{ end }} {{ end }}
</div>
</ol> </ol>
</aside> </aside>

View File

@ -1,8 +1,13 @@
{{ if .Site.Params.widgets.enabled }} {{- $scope := default "homepage" .Scope -}}
{{ $context := . }} {{- $context := .Context -}}
{{- with (index .Context.Site.Params.widgets $scope) -}}
<aside class="sidebar right-sidebar sticky"> <aside class="sidebar right-sidebar sticky">
{{ range $widget := .Site.Params.widgets.enabled }} {{ range $widget := . }}
{{ partial (printf "widget/%s" $widget) $context }} {{ if templates.Exists (printf "partials/widget/%s.html" .type) }}
{{ partial (printf "widget/%s" .type) (dict "Context" $context "Params" .params) }}
{{ else }}
{{ warnf "Widget %s not found" .type }}
{{ end }}
{{ end }} {{ end }}
</aside> </aside>
{{ end }} {{ end }}

View File

@ -1,4 +1,6 @@
{{- $query := first 1 (where .Site.Pages "Layout" "==" "archives") -}} {{- $query := first 1 (where .Context.Site.Pages "Layout" "==" "archives") -}}
{{- $context := .Context -}}
{{- $limit := default 5 .Params.limit -}}
{{- if $query -}} {{- if $query -}}
{{- $archivesPage := index $query 0 -}} {{- $archivesPage := index $query 0 -}}
<section class="widget archives"> <section class="widget archives">
@ -7,17 +9,17 @@
</div> </div>
<h2 class="widget-title section-title">{{ T "widget.archives.title" }}</h2> <h2 class="widget-title section-title">{{ T "widget.archives.title" }}</h2>
{{ $pages := where .Site.RegularPages "Type" "in" .Site.Params.mainSections }} {{ $pages := where $context.Site.RegularPages "Type" "in" $context.Site.Params.mainSections }}
{{ $notHidden := where .Site.RegularPages "Params.hidden" "!=" true }} {{ $notHidden := where $context.Site.RegularPages "Params.hidden" "!=" true }}
{{ $filtered := ($pages | intersect $notHidden) }} {{ $filtered := ($pages | intersect $notHidden) }}
{{ $archives := $filtered.GroupByDate "2006" }} {{ $archives := $filtered.GroupByDate "2006" }}
<div class="widget-archive--list"> <div class="widget-archive--list">
{{ range $index, $item := first (add .Site.Params.widgets.archives.limit 1) ($archives) }} {{ range $index, $item := first (add $limit 1) ($archives) }}
{{- $id := lower (replace $item.Key " " "-") -}} {{- $id := lower (replace $item.Key " " "-") -}}
<div class="archives-year"> <div class="archives-year">
<a href="{{ $archivesPage.RelPermalink }}#{{ $id }}"> <a href="{{ $archivesPage.RelPermalink }}#{{ $id }}">
{{ if eq $index $.Site.Params.widgets.archives.limit }} {{ if eq $index $limit }}
<span class="year">{{ T "widget.archives.more" }}</span> <span class="year">{{ T "widget.archives.more" }}</span>
{{ else }} {{ else }}
<span class="year">{{ .Key }}</span> <span class="year">{{ .Key }}</span>

View File

@ -0,0 +1,16 @@
{{- $context := .Context -}}
{{- $limit := default 10 .Params.limit -}}
<section class="widget tagCloud">
<div class="widget-icon">
{{ partial "helper/icon" "categories" }}
</div>
<h2 class="widget-title section-title">{{ T "widget.categoriesCloud.title" }}</h2>
<div class="tagCloud-tags">
{{ range first $limit $context.Site.Taxonomies.categories.ByCount }}
<a href="{{ .Page.RelPermalink }}" class="font_size_{{ .Count }}">
{{ .Page.Title }}
</a>
{{ end }}
</div>
</section>

View File

@ -1,4 +1,4 @@
{{- $query := first 1 (where .Site.Pages "Layout" "==" "search") -}} {{- $query := first 1 (where .Context.Site.Pages "Layout" "==" "search") -}}
{{- if $query -}} {{- if $query -}}
{{- $searchPage := index $query 0 -}} {{- $searchPage := index $query 0 -}}
<form action="{{ $searchPage.RelPermalink }}" class="search-form widget" {{ with .OutputFormats.Get "json" -}}data-json="{{ .Permalink }}" {{- end }}> <form action="{{ $searchPage.RelPermalink }}" class="search-form widget" {{ with .OutputFormats.Get "json" -}}data-json="{{ .Permalink }}" {{- end }}>

View File

@ -1,3 +1,5 @@
{{- $context := .Context -}}
{{- $limit := default 10 .Params.limit -}}
<section class="widget tagCloud"> <section class="widget tagCloud">
<div class="widget-icon"> <div class="widget-icon">
{{ partial "helper/icon" "tag" }} {{ partial "helper/icon" "tag" }}
@ -5,7 +7,7 @@
<h2 class="widget-title section-title">{{ T "widget.tagCloud.title" }}</h2> <h2 class="widget-title section-title">{{ T "widget.tagCloud.title" }}</h2>
<div class="tagCloud-tags"> <div class="tagCloud-tags">
{{ range first .Site.Params.widgets.tagCloud.limit .Site.Taxonomies.tags.ByCount }} {{ range first $limit $context.Site.Taxonomies.tags.ByCount }}
<a href="{{ .Page.RelPermalink }}" class="font_size_{{ .Count }}"> <a href="{{ .Page.RelPermalink }}" class="font_size_{{ .Count }}">
{{ .Page.Title }} {{ .Page.Title }}
</a> </a>

View File

@ -0,0 +1,12 @@
{{ if (.Context.Scratch.Get "TOCEnabled") }}
<section class="widget archives">
<div class="widget-icon">
{{ partial "helper/icon" "hash" }}
</div>
<h2 class="widget-title section-title">{{ T "article.tableOfContents" }}</h2>
<div class="widget--toc">
{{ .Context.TableOfContents }}
</div>
</section>
{{ end }}

View File

@ -13,7 +13,7 @@
{{ end }} {{ end }}
<div class="video-wrapper"> <div class="video-wrapper">
<iframe src="//player.bilibili.com/player.html?{{ $basicQuery | safeURL }}&{{ $videoQuery | safeURL }}" <iframe src="https://player.bilibili.com/player.html?{{ $basicQuery | safeURL }}&{{ $videoQuery | safeURL }}"
scrolling="no" scrolling="no"
frameborder="no" frameborder="no"
framespacing="0" framespacing="0"

View File

@ -1,6 +1,6 @@
{{ $vid := .Get 0 }} {{ $vid := .Get 0 }}
<div class="video-wrapper"> <div class="video-wrapper">
<iframe src="http://v.qq.com/txp/iframe/player.html?vid={{ $vid }}&auto=0" <iframe src="https://v.qq.com/txp/iframe/player.html?vid={{ $vid }}&auto=0"
scrolling="no" scrolling="no"
frameborder="no" frameborder="no"
framespacing="0" framespacing="0"

View File

@ -20,7 +20,7 @@ features = [
"search", "search",
] ]
min_version = "0.78.0" min_version = "0.87.0"
[author] [author]
name = "Jimmy Cai" name = "Jimmy Cai"