Merge branch 'CaiJimmy:master' into master

This commit is contained in:
fariszr 2022-02-23 00:56:44 +03:00 committed by GitHub
commit d119f08631
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
97 changed files with 3165 additions and 672 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"
}
}

View File

@ -7,32 +7,39 @@ assignees: ''
---
**Describe the bug**
<!--
Before creating this bug report, make sure you have read the theme documentation: https://docs.stack.jimmycai.com/
-->
## Describe the bug
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
## Expected behavior
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
## To Reproduce
Indicate the steps to reproduce this bug, if applicable.
## Screenshots
Add screenshots to help explain your problem.
## Environment
- **Hugo version** [e.g: 0.80.0]:
- **Hugo extended?**: Yes / No
## Additional context
Add any other context about the problem here.
If it's a UI issue, fill the following information:
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
## Content of config.yaml
```yaml
### Paste the content of the config file here
```
**Additional context**
Add any other context about the problem here.
## Link to the demo site and/or source repository

4
.gitignore vendored Normal file
View File

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

View File

@ -5,13 +5,13 @@
## 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)
## Documentation & more information
## Documentation
[Documentation](https://docs.stack.jimmycai.com/) | [中文文档](https://docs.stack.jimmycai.com/v/zh-cn/)
[Documentation](https://docs.stack.jimmycai.com/) | [中文文档](https://docs.stack.jimmycai.com/zh/)
## Introduction
@ -27,21 +27,20 @@ Stack is a simple card-style Hugo theme designed for bloggers, some of its featu
- No CSS framework, keep it simple and minimal
- Properly cropped thumbnails
- Subsection support
- Table of contents
## Requirements
It's necessary to use **Hugo ≥ 0.78.0**.
Use Hugo Extended version (recommended) if you want to:
* Use the latest feature/fix from `master` branch
* Edit SCSS files
**Compiled CSS are updated once per release.**
It's necessary to use **Hugo Extended ≥ 0.87.0**.
## Installation
Clone / Download this repository to `theme` folder, and edit your site config following `exampleSite/config.toml`.
* 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.*
Check [documentation](https://docs.stack.jimmycai.com/) for more details.
@ -63,13 +62,22 @@ Your support is greatly appreciated :)
## Thanks to
- [Vibrant-Colors/node-vibrant](https://github.com/Vibrant-Colors/node-vibrant)
- [Normalize.css](https://necolas.github.io/normalize.css/)
- [Tabler icons](https://tablericons.com/)
- [Pure CSS implementation of Google Photos / 500px image layout](https://github.com/xieranmaya/blog/issues/6)
- [jonsuh/hamburgers](https://github.com/jonsuh/hamburgers)
- [PhotoSwipe](https://photoswipe.com/)
- [artchen/hexo-theme-element](https://github.com/artchen/hexo-theme-element)
- [MunifTanjim/minimo](https://github.com/MunifTanjim/minimo)
- [lepture/yue.css](https://github.com/lepture/yue.css)
- Markdown gallery syntax from [Typlog](https://typlog.com/)
| Project | Description | Licence |
| ------- | ----------- | ------- |
| [PhotoSwipe](https://photoswipe.com/) | For the lightbox effect | [MIT](https://github.com/dimsemenov/PhotoSwipe/blob/master/LICENSE) |
| [Normalize.css](https://github.com/necolas/normalize.css) | - | [MIT](https://github.com/necolas/normalize.css/blob/master/LICENSE.md) |
| [Node Vibrant](https://github.com/Vibrant-Colors/node-vibrant) | To extract the color from images | [MIT](https://github.com/Vibrant-Colors/node-vibrant/blob/master/LICENSE.md)
| [Tabler icons](https://github.com/tabler/tabler-icons) | Default menu icons | [MIT](https://github.com/tabler/tabler-icons/blob/master/LICENSE) |
| [jonsuh/hamburgers](https://github.com/jonsuh/hamburgers) | Hamburger icon of menu | [MIT](https://github.com/jonsuh/hamburgers/blob/master/LICENSE) |
| [lepture/yue.css](https://github.com/lepture/yue.css) | Part of it is used for styling article content | MIT |
| [Typlog](https://typlog.com/) | Where the markdown gallery syntax is borrowed from | The author gave me the permission |
| [Pure CSS implementation of Google Photos / 500px image layout](https://github.com/xieranmaya/blog/issues/6) | Used for image gallery | - |
### References
Some references that I took while building this theme:
| Project | Licence|
| ------- | ------|
| [artchen/hexo-theme-element](https://github.com/artchen/hexo-theme-element) | [MIT](https://github.com/artchen/hexo-theme-element/blob/master/LICENSE) |
| [MunifTanjim/minimo](https://github.com/MunifTanjim/minimo) | [MIT](https://github.com/MunifTanjim/minimo/blob/master/LICENSE) |

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

9
assets/icons/date.svg Normal file
View File

@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-calendar-time" width="56" height="56" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z"/>
<path d="M11.795 21h-6.795a2 2 0 0 1 -2 -2v-12a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v4" />
<circle cx="18" cy="18" r="4" />
<path d="M15 3v4" />
<path d="M7 3v4" />
<path d="M3 11h16" />
<path d="M18 16.496v1.504l1 1" />
</svg>

After

Width:  |  Height:  |  Size: 508 B

View File

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

View File

@ -1,4 +1,17 @@
$on-phone: 812px;
$on-tablet: 1024px;
$on-desktop: 1519px;
$on-desktop-large: 1920px;
$breakpoints: (
sm: 640px,
md: 768px,
lg: 1024px,
xl: 1280px,
2xl: 1536px,
);
@mixin respond($breakpoint) {
@if not map-has-key($breakpoints, $breakpoint) {
@warn "'#{$breakpoint}' is not a valid breakpoint";
} @else {
@media (min-width: map-get($breakpoints, $breakpoint)) {
@content;
}
}
}

View File

@ -2,67 +2,52 @@
margin-left: auto;
margin-right: auto;
.left-sidebar {
max-width: var(--left-sidebar-max-width);
}
.right-sidebar {
max-width: var(--right-sidebar-max-width);
/// Display right sidebar when min-width: lg
@include respond(lg) {
display: block;
}
}
&.extended {
@media (min-width: $on-phone) {
max-width: 800px;
.left-sidebar {
width: 25%;
}
@include respond(md) {
max-width: 1024px;
--left-sidebar-max-width: 25%;
--right-sidebar-max-width: 30%;
}
@media (min-width: $on-tablet) {
max-width: 972px;
.right-sidebar {
width: 25%;
}
@include respond(lg) {
max-width: 1280px;
--left-sidebar-max-width: 20%;
--right-sidebar-max-width: 30%;
}
@media (min-width: $on-desktop) {
max-width: 1200px;
.left-sidebar {
width: 20%;
}
.right-sidebar {
width: 25%;
}
}
@media (min-width: $on-desktop-large) {
@include respond(xl) {
max-width: 1536px;
.left-sidebar {
width: 15%;
}
--left-sidebar-max-width: 15%;
--right-sidebar-max-width: 25%;
}
}
&.compact {
@media (min-width: $on-phone) {
max-width: 800px;
.left-sidebar {
width: 25%;
}
@include respond(md) {
--left-sidebar-max-width: 25%;
max-width: 768px;
}
@media (min-width: $on-tablet) {
max-width: 972px;
@include respond(lg) {
max-width: 1024px;
--left-sidebar-max-width: 20%;
}
@media (min-width: $on-desktop) {
max-width: 1050px;
.left-sidebar {
width: 20%;
}
}
@media (min-width: $on-desktop-large) {
max-width: 1300px;
@include respond(xl) {
max-width: 1280px;
}
}
}
@ -76,8 +61,9 @@
}
&.on-phone--column {
@media (max-width: $on-phone) {
flex-direction: column;
flex-direction: column;
@include respond(md) {
flex-direction: unset;
}
}
@ -106,7 +92,6 @@
main.main {
min-width: 0;
padding: 0 15px;
max-width: 100%;
flex-grow: 1;
padding-top: var(--main-top-padding);
@ -114,4 +99,11 @@ main.main {
.main-container {
min-height: 100vh;
}
align-items: flex-start;
padding: 0 15px;
column-gap: var(--section-separation);
@include respond(md) {
padding: 0 20px;
}
}

View File

@ -24,14 +24,14 @@
.article-image {
img {
width: 100%;
height: 200px;
height: 150px;
object-fit: cover;
@media (max-width: $on-tablet) {
height: 150px;
@include respond(md) {
height: 200px;
}
@media (min-width: $on-desktop-large) {
@include respond(xl) {
height: 250px;
}
}
@ -62,7 +62,7 @@
color: var(--card-text-color-main);
font-size: 2.2rem;
@media (min-width: $on-desktop-large) {
@include respond(xl) {
font-size: 2.4rem;
}
@ -86,7 +86,7 @@
line-height: 1.5;
font-size: 1.75rem;
@media (min-width: $on-desktop-large) {
@include respond(xl) {
font-size: 2rem;
}
}
@ -95,7 +95,9 @@
display: flex;
align-items: center;
color: var(--card-text-color-tertiary);
gap: 15px;
margin-top: 10px;
flex-wrap: wrap;
svg {
vertical-align: middle;
@ -108,6 +110,11 @@
time {
font-size: 1.4rem;
}
& > div {
display: inline-flex;
align-items: center;
}
}
.article-category,
@ -135,10 +142,10 @@
border-radius: var(--card-border-radius);
box-shadow: var(--shadow-l1);
background-color: var(--card-background);
--image-size: 60px;
--image-size: 50px;
@media (max-width: $on-tablet) {
--image-size: 50px;
@include respond(md) {
--image-size: 60px;
}
& + .pagination {
@ -165,10 +172,10 @@
.article-title {
margin: 0;
font-size: 1.8rem;
font-size: 1.6rem;
@media (max-width: $on-tablet) {
font-size: 1.6rem;
@include respond(md) {
font-size: 1.8rem;
}
}
@ -242,20 +249,20 @@
flex-direction: column;
justify-content: flex-end;
z-index: 2;
padding: 20px;
padding: 15px;
@media (max-width: $on-phone) {
padding: 15px;
@include respond(sm) {
padding: 20px;
}
}
.article-title {
font-size: 2.2rem;
font-size: 2rem;
font-weight: 500;
color: var(--card-text-color-main);
@media (max-width: $on-phone) {
font-size: 2rem;
@include respond(sm) {
font-size: 2.2rem;
}
}
}

View File

@ -15,3 +15,24 @@ body {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* scrollbar styles for Firefox */
* {
scrollbar-width: auto;
scrollbar-color: var(--scrollbar-thumb) transparent;
}
/**/
/* scrollbar styles for Chromium */
::-webkit-scrollbar {
height: auto;
}
::-webkit-scrollbar-thumb {
background-color: var(--scrollbar-thumb);
}
::-webkit-scrollbar-track {
background-color: transparent;
}
/**/

View File

@ -0,0 +1,394 @@
.disqus-container {
background-color: var(--card-background);
border-radius: var(--card-border-radius);
box-shadow: var(--shadow-l1);
padding: var(--card-padding);
}
#dsqjs * {
margin: 0;
padding: 0
}
#dsqjs a {
text-decoration: none;
color: #076dd0
}
#dsqjs .dsqjs-hide {
display: none!important
}
#dsqjs .dsqjs-disabled {
cursor: not-allowed;
opacity: .5
}
#dsqjs #dsqjs-msg {
text-align: center;
margin-top: 4px;
margin-bottom: 4px;
font-size: 14px
}
#dsqjs #dsqjs-msg .dsqjs-msg-btn {
cursor: pointer
}
#dsqjs .dsqjs-bullet {
line-height: 1.4;
margin: 0 2px
}
#dsqjs .dsqjs-bullet:after {
color: #c2c6cc;
content: "·";
font-weight: 700
}
#dsqjs .dsqjs-clearfix:after,#dsqjs .dsqjs-clearfix:before {
display: table;
content: "";
line-height: 0;
clear: both
}
#dsqjs .dsqjs-nav {
position: relative;
margin: 0 0 20px;
border-bottom: 2px solid #e7e9ee
}
#dsqjs ol,#dsqjs ul {
list-style: none;
list-style-type: none
}
#dsqjs .dsqjs-no-comment {
text-align: center;
font-size: 16px;
line-height: 1.5;
word-wrap: break-word;
overflow: hidden;
color: #2a2e2e;
margin-bottom: 6px
}
#dsqjs .dsqjs-nav-tab {
float: left;
text-transform: capitalize;
font-size: 15px;
padding: 12px 8px;
color: #656c7a;
display: block;
margin: 0 15px 0 0;
font-weight: 700;
line-height: 1;
position: relative;
transition: all .2s ease-in-out
}
#dsqjs .dsqjs-nav-tab:last-child {
margin: 0
}
#dsqjs .dsqjs-tab-active {
color: #2a2e2e
}
#dsqjs .dsqjs-tab-active>span:after {
content: " ";
display: block;
height: 2px;
background-color: #076dd0!important;
position: absolute;
bottom: -5px;
left: 0;
right: 0
}
#dsqjs .dsqjs-post-list .dsqjs-post-item {
position: relative;
margin-bottom: 16px
}
#dsqjs .dsqjs-post-list .dsqjs-post-avatar {
float: left;
margin-right: 10px;
position: relative;
background: #dbdfe4;
padding: 0;
display: block;
border-radius: 4px
}
#dsqjs .dsqjs-post-list .dsqjs-post-avatar img {
width: 44px;
height: 44px;
display: block;
border-radius: 4px
}
#dsqjs .dsqjs-post-list .dsqjs-post-header {
line-height: 1;
font-size: 14px;
margin-bottom: 3px
}
#dsqjs .dsqjs-post-list .dsqjs-post-header .dsqjs-post-author {
color: #656c7a;
font-weight: 700
}
#dsqjs .dsqjs-post-list .dsqjs-post-header .dsqjs-admin-badge {
color: #fff;
background: #687a86;
padding: 1px 3px;
margin-left: 4px;
font-size: 12px;
line-height: 1;
font-weight: 700;
border-radius: 3px;
display: inline-block;
position: relative;
top: -1px;
left: 1px
}
#dsqjs .dsqjs-post-list .dsqjs-post-header .dsqjs-meta {
display: inline-block;
font-size: 12px;
color: #656c7a
}
#dsqjs .dsqjs-post-body {
font-size: 15px;
line-height: 1.5;
word-wrap: break-word;
overflow: hidden;
color: #2a2e2e
}
#dsqjs .dsqjs-post-body code {
padding: .2em .4em;
margin: 0;
font-size: 85%;
background: #f5f5f5;
color: inherit;
border-radius: 3px
}
#dsqjs .dsqjs-post-body pre {
padding: .5em;
overflow: auto;
font-size: 85%;
line-height: 1.45;
border-radius: 3px;
background: #f5f5f5;
margin: .5em 0
}
#dsqjs .dsqjs-post-body blockquote {
padding: 0 .8em;
margin: .5em 0;
color: #6a737d;
border-left: .25em solid #dfe2e5
}
#dsqjs .dsqjs-post-body p:last-child {
margin: 0
}
#dsqjs .dsqjs-post-list.dsqjs-children>li {
margin-left: 30px
}
#dsqjs .dsqjs-post-list.dsqjs-children .dsqjs-post-avatar img {
width: 38px;
height: 38px
}
#dsqjs .dsqjs-load-more {
font-size: 14px;
font-weight: 400;
display: block;
text-align: center;
padding: 11px 14px;
margin: 0 0 24px;
background: #687a86;
color: #fff;
cursor: pointer
}
#dsqjs .dsqjs-load-more:hover {
opacity: .8
}
#dsqjs footer {
text-align: right;
line-height: 1.5;
padding-top: 10px;
padding-right: 10px;
border-top: 2px solid #e7e9ee;
margin-top: 12px;
font-weight: 700;
font-size: 16px;
color: #555
}
#dsqjs .dsqjs-disqus-logo {
background-image: url(https://c.disquscdn.com/next/embed/assets/img/sprite.654110a9206fd22f08cca0798e34a65e.png);
background-repeat: no-repeat;
display: inline-block;
background-size: 86px 40.5px;
height: 16.5px;
width: 86px;
}
#dsqjs .dsqjs-order {
display: flex;
float: right;
align-items: center;
margin-top: 10px;
margin-bottom: 12px
}
#dsqjs .dsqjs-order-radio {
display: none
}
#dsqjs .dsqjs-order-radio:checked+.dsqjs-order-label {
color: #fff;
background-color: #888
}
#dsqjs .dsqjs-order-label {
display: block;
height: 20px;
line-height: 20px;
margin-right: 10px;
font-size: 12px;
border-radius: 2px;
padding: 0 5px;
background-color: #dcdcdc;
cursor: pointer
}
#dsqjs p.dsqjs-has-more {
margin-bottom: 24px;
margin-left: 48px;
font-size: 13px;
line-height: 15px
}
#dsqjs p.dsqjs-has-more a.dsqjs-has-more-btn {
color: #656c7a;
text-decoration: underline;
cursor: pointer
}
@media (min-width: 768px) {
#dsqjs .dsqjs-post-list.dsqjs-children>li {
margin-left:48px
}
#dsqjs .dsqjs-post-list .dsqjs-post-avatar {
margin-right: 12px
}
#dsqjs .dsqjs-post-list .dsqjs-post-item {
margin-bottom: 20px
}
}
@media (min-width: 1024px) {
#dsqjs .dsqjs-post-list.dsqjs-children>li {
margin-left:60px
}
}
:root[data-scheme="light"] {
#dsqjs .dsqjs-disqus-logo {
background-position: 0 -7px;
}
}
:root[data-scheme="dark"] {
#dsqjs {
--t-s: rgba(255,255,255,0.9);
--alt: #3e4b5e;
--link-hover: #47a2e0;
--hover-bg: #3e4b5e;
--tag: #3e4b5e;
--border: #435266;
--pre: #3c495b;
--c-bg: #2f3947;
--code: #c3c7cb;
--kbd: #4e5f77;
--hl: #abb2bf;
--hlc: #808895;
--hlk: #c678dd;
--hln: #e06c75;
--hll: #56b6c2;
--hls: #98c379;
--hlt: #e6c07b;
--hlv: #d19a66;
--bg: #181c27;
--main: #252d38;
--t: rgba(255,255,255,0.86);
--t-l: rgba(255,255,255,0.66);
--logo: #fff;
--link: #38a3fd;
--title: rgba(255,255,255,0.92);
--fab: #364151;
--shadow: none;
}
#disqus_thread {
color: var(--body-text-color)
}
#dsqjs #dsqjs-msg {
color: var(--t)
}
#dsqjs a {
color:var(--link)
}
#dsqjs a:focus,#dsqjs a:hover {
color: var(--link-hover)
}
#dsqjs .dsqjs-disqus-logo {
background-position: 0 -24px;
}
#dsqjs .dsqjs-nav,#dsqjs footer {
border-color: var(--hlc)
}
#dsqjs .dsqjs-load-more,#dsqjs .dsqjs-load-more:hover,#dsqjs .dsqjs-nav-tab,#dsqjs .dsqjs-no-comment,#dsqjs .dsqjs-post-content {
color: var(--t)
}
#dsqjs .dsqjs-order-label {
background-color: var(--hlc)
}
#dsqjs .dsqjs-order-radio:checked+.dsqjs-order-label {
background-color: var(--kbd)
}
#dsqjs .dsqjs-tab-active>span:after {
background-color: #2e9fff
}
#dsqjs .dsqjs-footer,#dsqjs .dsqjs-meta {
color: var(--t-l)
}
#dsqjs .dsqjs-post-body blockquote {
border-color: var(--border)
}
}

View File

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

View File

@ -1,43 +1,9 @@
.keep-sidebar {
@media (min-width: $on-phone) and (max-width: $on-tablet) {
--main-top-padding: 50px;
}
}
.article-page {
&.with-toolbar {
@media (max-width: $on-tablet) {
--main-top-padding: 0;
}
}
&.hide-sidebar-sm .left-sidebar {
display: none;
&:not(.keep-sidebar) .left-sidebar {
@media (max-width: $on-tablet) {
display: none;
}
}
.article-sidebar {
position: sticky;
top: 50px;
flex-shrink: 0;
@media (max-width: $on-tablet) {
display: none;
}
@media (min-width: $on-tablet) {
padding-left: 15px;
margin-left: 1%;
}
@media (min-width: $on-tablet) {
width: 25%;
margin-right: 1%;
}
@media (min-width: $on-desktop) {
width: 30%;
@include respond(md) {
display: inherit;
}
}
@ -104,6 +70,13 @@
flex-wrap: wrap;
text-transform: unset;
}
.article-copyright,
.article-lastmod {
a {
color: var(--body-text-color);
}
}
}
}
}
@ -111,12 +84,9 @@
#article-toolbar {
display: flex;
align-items: center;
margin: 20px 0;
@media (max-width: $on-tablet) {
margin: 20px 0;
}
@media (min-width: $on-tablet) {
@include respond(md) {
display: none;
}
@ -147,6 +117,157 @@
span {
font-weight: 500;
white-space: nowrap;
}
}
}
.article-page.has-toc {
.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--toc {
background-color: var(--card-background);
border-radius: var(--card-border-radius);
box-shadow: var(--shadow-l1);
display: flex;
flex-direction: column;
color: var(--card-text-color-main);
overflow: hidden;
::-webkit-scrollbar-thumb {
background-color: var(--card-separator-color);
}
#TableOfContents {
overflow-x: auto;
max-height: 75vh;
ol,
ul {
margin: 0;
padding: 0;
}
ol {
list-style-type: none;
counter-reset: item;
li a::before {
counter-increment: item;
content: counters(item, ".") ". ";
font-weight: bold;
margin-right: 5px;
}
}
& > ul {
padding: 0 1em;
}
li {
margin: 15px 0 15px 20px;
padding: 5px;
& > ol,
& > ul {
margin-top: 10px;
padding-left: 10px;
margin-bottom: -5px;
& > li:last-child {
margin-bottom: 0;
}
}
}
li.active-class > a {
border-left: var(--heading-border-size) solid var(--accent-color);
font-weight: bold;
}
ul li.active-class > a {
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;
}
}
}
}
@ -187,11 +308,11 @@
.article-content {
font-family: var(--article-font-family);
font-size: var(--article-font-size);
padding: 0 var(--card-padding);
line-height: var(--article-line-height);
& > p {
margin: 1.5em 0;
padding: 0 var(--card-padding);
}
h1,
@ -200,12 +321,12 @@
h4,
h5,
h6 {
padding: 0 calc(var(--card-padding) - var(--heading-border-size));
margin-left: calc((var(--card-padding)) * -1);
padding-left: calc(var(--card-padding) - var(--heading-border-size));
border-left: var(--heading-border-size) solid var(--accent-color);
}
figure {
margin: 0 auto;
text-align: center;
figcaption {
@ -216,17 +337,12 @@
blockquote {
position: relative;
margin: 10px 0;
margin: 1.5em 0;
border-left: var(--blockquote-border-size) solid var(--card-separator-color);
padding: 15px calc(var(--card-padding) - var(--blockquote-border-size));
background-color: var(--blockquote-background-color);
}
& > ul,
& > ol {
margin: 1em var(--card-padding);
}
hr {
width: 100px;
margin: 40px auto;
@ -249,7 +365,7 @@
display: flex;
flex-direction: row;
justify-content: center;
margin: 20px 0;
margin: 1.5em 0;
figure {
margin: 0;
@ -278,10 +394,49 @@
}
}
.highlight {
background-color: var(--pre-background-color);
padding: var(--card-padding);
position: relative;
&:hover {
.copyCodeButton {
opacity: 1;
}
}
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 {
margin: 0 var(--card-padding);
width: 100%;
max-width: calc(100% - var(--card-padding) * 2);
border-collapse: collapse;
border-spacing: 0;
margin-bottom: 1.5em;
@ -306,4 +461,36 @@
.twitter-tweet {
color: var(--card-text-color-main);
}
.video-wrapper {
position: relative;
width: 100%;
height: 0;
padding-bottom: 56.25%;
overflow: hidden;
& > iframe,
& > video {
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
border: 0;
}
}
/// Negative margins
blockquote,
figure,
.highlight,
pre,
.gallery,
.video-wrapper,
.table-wrapper,
.s_video_simple {
margin-left: calc((var(--card-padding)) * -1);
margin-right: calc((var(--card-padding)) * -1);
width: calc(100% + var(--card-padding) * 2);
}
}

View File

@ -106,9 +106,10 @@
z-index: 2;
cursor: pointer;
@media (min-width: $on-phone + 1) {
@include respond(md) {
display: none;
}
outline: none;
&.is-active {
@ -126,67 +127,59 @@
list-style: none;
display: flex;
flex-direction: column;
margin-top: var(--sidebar-element-separation);
margin-bottom: 0;
overflow-y: auto;
flex-grow: 1;
font-size: 1.5rem;
font-size: 1.4rem;
@media (min-width: $on-desktop-large) {
margin-top: 30px;
background-color: var(--card-background);
padding: 15px 0;
box-shadow: var(--shadow-l1);
display: none;
margin: 0 -15px;
&.show {
display: block;
}
@media (max-width: $on-phone) {
background-color: var(--card-background);
margin-top: 0;
padding: 15px 0;
box-shadow: var(--shadow-l1);
display: none;
&.show {
display: block;
}
}
@media (min-width: $on-phone + 1) {
@include respond(md) {
align-items: flex-end;
display: flex;
background-color: transparent;
padding: 0;
box-shadow: none;
margin: 0;
margin-top: var(--sidebar-element-separation);
}
@include respond(xl) {
margin-top: 30px;
}
li {
position: relative;
vertical-align: middle;
padding: 10px 0;
padding: 10px 30px;
&:not(:last-of-type) {
margin-bottom: 15px;
@media (min-width: $on-desktop-large) {
@include respond(xl) {
margin-bottom: 20px;
}
}
@media (max-width: $on-phone) {
padding: 10px 30px;
}
@media (min-width: $on-phone + 1) and (max-width: ($on-desktop - 1)) {
@include respond(md) {
width: 100%;
padding: 10px 0;
}
@media (min-width: $on-phone + 1) {
width: 100%;
}
svg {
width: 25px;
height: 25px;
stroke-width: 1.33;
margin-right: 40px;
@media (max-width: $on-desktop-large) {
width: 20px;
height: 20px;
}
width: 20px;
height: 20px;
}
a {
@ -194,10 +187,6 @@
display: inline-flex;
align-items: center;
color: var(--body-text-color);
@media (max-width: $on-desktop-large) {
font-size: 1.4rem;
}
}
span {
@ -212,3 +201,18 @@
}
}
}
.social-menu {
list-style: none;
padding: 0%;
display: flex;
flex-direction: row;
gap: 10px;
svg {
width: 24px;
height: 24px;
stroke: var(--body-text-color);
stroke-width: 1.33;
}
}

View File

@ -1,7 +1,6 @@
.sidebar {
padding: 0 15px;
&.sticky {
@media (min-width: ($on-phone + 1)) {
@include respond(md) {
position: sticky;
}
}
@ -11,45 +10,41 @@
display: flex;
flex-direction: column;
flex-shrink: 0;
align-self: stretch;
--sidebar-avatar-size: 150px;
--sidebar-element-separation: 25px;
width: 100%;
padding: 30px 0 15px 0;
max-width: none;
@media (max-width: $on-desktop-large) {
--sidebar-avatar-size: 120px;
--sidebar-element-separation: 20px;
--sidebar-avatar-size: 120px;
--sidebar-element-separation: 20px;
@include respond(md) {
width: auto;
padding-top: var(--main-top-padding);
padding-bottom: var(--main-top-padding);
max-height: 100vh;
}
@media (max-width: $on-phone) {
width: 100%;
padding: 30px 0;
max-width: none;
@include respond(2xl) {
--sidebar-avatar-size: 140px;
--sidebar-element-separation: 25px;
}
&.sticky {
top: 0;
}
@media (min-width: $on-phone + 1) {
margin-right: 1%;
padding: var(--main-top-padding) 15px;
max-height: 100vh;
}
}
.right-sidebar {
flex-shrink: 0;
display: none;
&.sticky {
top: 0;
}
@media (max-width: $on-desktop - 1) {
display: none;
}
@media (min-width: $on-tablet) {
margin-left: 1%;
@include respond(lg) {
padding-top: var(--main-top-padding);
}
}
@ -58,8 +53,10 @@
z-index: 1;
transition: box-shadow 0.5s ease;
@media (max-width: $on-phone) {
padding: 15px 30px;
padding: 15px;
@include respond(md) {
padding: 0;
}
.site-avatar {
@ -79,9 +76,9 @@
.emoji {
position: absolute;
width: 50px;
height: 50px;
line-height: 50px;
width: 40px;
height: 40px;
line-height: 40px;
border-radius: 100%;
bottom: 0;
right: 0;
@ -90,20 +87,20 @@
background-color: var(--card-background);
box-shadow: var(--shadow-l2);
@media (max-width: $on-desktop-large) {
width: 40px;
height: 40px;
line-height: 40px;
@include respond(2xl) {
width: 50px;
height: 50px;
line-height: 50px;
}
}
}
.site-name {
color: var(--accent-color);
font-size: 2.4rem;
margin: 0;
font-size: 1.8rem;
@media (max-width: $on-desktop-large) {
@include respond(2xl) {
font-size: 2rem;
}
}
@ -112,10 +109,10 @@
color: var(--body-text-color);
font-weight: normal;
margin: 10px 0;
font-size: 1.8rem;
font-size: 1.6rem;
@media (max-width: $on-desktop-large) {
font-size: 1.6rem;
@include respond(2xl) {
font-size: 1.8rem;
}
}
}

View File

@ -1,27 +1,15 @@
$defaultTagBackgrounds: #8ea885, #df7988, #0177b8, #ffb900, #6b69d6;
$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
*/
:root {
@media (min-width: $on-phone + 1) {
@include respond(md) {
--main-top-padding: 35px;
}
@media (min-width: $on-desktop-large) {
@include respond(xl) {
--main-top-padding: 50px;
}
@ -36,12 +24,17 @@ $defaultTagColors: #fff, #fff, #fff, #fff, #fff;
--section-separation: 40px;
[data-scheme="dark"] {
--scrollbar-thumb: hsl(0, 0%, 85%);
--scrollbar-track: var(--body-background);
&[data-scheme="dark"] {
--body-background: #303030;
--accent-color: #ecf0f1;
--accent-color-darker: #bdc3c7;
--accent-color-text: #000;
--body-text-color: rgba(255, 255, 255, 0.7);
--scrollbar-thumb: hsl(0, 0%, 40%);
--scrollbar-track: var(--body-background);
}
}
@ -70,20 +63,23 @@ $defaultTagColors: #fff, #fff, #fff, #fff, #fff;
--card-border-radius: 10px;
--card-padding: 30px;
@media (max-width: $on-desktop-large) {
--card-padding: 20px;
@include respond(md) {
--card-padding: 25px;
}
@media (max-width: $on-tablet) {
--card-padding: 20px;
@include respond(xl) {
--card-padding: 30px;
}
--small-card-padding: 25px;
@media (max-width: $on-tablet) {
--small-card-padding: 25px 20px;
--small-card-padding: 25px 20px;
@include respond(md) {
--small-card-padding: 25px;
}
[data-scheme="dark"] {
&[data-scheme="dark"] {
--card-background: #424242;
--card-background-selected: rgba(255, 255, 255, 0.16);
--card-text-color-main: rgba(255, 255, 255, 0.9);
@ -98,10 +94,12 @@ $defaultTagColors: #fff, #fff, #fff, #fff, #fff;
*/
:root {
--article-font-family: var(--base-font-family);
--article-font-size: 1.7rem;
@media (max-width: $on-tablet) {
--article-font-size: 1.6rem;
--article-font-size: 1.6rem;
@include respond(md) {
--article-font-size: 1.7rem;
}
--article-line-height: 1.85;
}
@ -127,7 +125,7 @@ $defaultTagColors: #fff, #fff, #fff, #fff, #fff;
--table-border-color: #dadada;
--tr-even-background-color: #efefee;
[data-scheme="dark"] {
&[data-scheme="dark"] {
--code-background-color: #272822;
--code-text-color: rgba(255, 255, 255, 0.9);
@ -149,3 +147,15 @@ $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),
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";
}

View File

@ -9,7 +9,7 @@ class StackColorScheme {
this.bindMatchMedia();
this.currentScheme = this.getSavedScheme();
this.dispatchEvent(document.body.dataset.scheme as colorScheme);
this.dispatchEvent(document.documentElement.dataset.scheme as colorScheme);
if (toggleEl)
this.bindClick(toggleEl);
@ -22,7 +22,7 @@ class StackColorScheme {
localStorage.setItem(this.localStorageKey, this.currentScheme);
}
private bindClick(toggleEl) {
private bindClick(toggleEl: HTMLElement) {
toggleEl.addEventListener('click', (e) => {
if (this.isDark()) {
/// Disable dark mode
@ -56,13 +56,13 @@ class StackColorScheme {
private setBodyClass() {
if (this.isDark()) {
document.body.dataset.scheme = 'dark';
document.documentElement.dataset.scheme = 'dark';
}
else {
document.body.dataset.scheme = 'light';
document.documentElement.dataset.scheme = 'light';
}
this.dispatchEvent(document.body.dataset.scheme as colorScheme);
this.dispatchEvent(document.documentElement.dataset.scheme as colorScheme);
}
private getSavedScheme(): colorScheme {
@ -85,4 +85,4 @@ class StackColorScheme {
}
}
export default StackColorScheme;
export default StackColorScheme;

View File

@ -34,7 +34,7 @@ class StackGallery {
private loadItems(container: HTMLElement) {
this.items = [];
const figures = container.querySelectorAll('figure');
const figures = container.querySelectorAll('figure.gallery-image');
for (const el of figures) {
const figcaption = el.querySelector('figcaption'),
@ -57,7 +57,61 @@ class StackGallery {
}
public static createGallery(container: HTMLElement) {
const figuresEl = container.querySelectorAll('figure');
/// 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');
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');
let currentGallery = [];

View File

@ -10,6 +10,8 @@ import { getColor } from 'ts/color';
import menu from 'ts/menu';
import createElement from 'ts/createElement';
import StackColorScheme from 'ts/colorScheme';
import { setupScrollspy } from 'ts/scrollspy';
import { setupSmoothAnchors } from "ts/smoothAnchors";
let Stack = {
init: () => {
@ -21,6 +23,8 @@ let Stack = {
const articleContent = document.querySelector('.article-content') as HTMLElement;
if (articleContent) {
new StackGallery(articleContent);
setupSmoothAnchors();
setupScrollspy();
}
/**
@ -54,6 +58,39 @@ let Stack = {
observer.observe(articleTile)
}
/**
* Add copy button to code block
*/
const highlights = document.querySelectorAll('.article-content div.highlight');
const copyText = `Copy`,
copiedText = `Copied!`;
highlights.forEach(highlight => {
const copyButton = document.createElement('button');
copyButton.innerHTML = copyText;
copyButton.classList.add('copyCodeButton');
highlight.appendChild(copyButton);
const codeBlock = highlight.querySelector('code[data-lang]');
if (!codeBlock) return;
copyButton.addEventListener('click', () => {
navigator.clipboard.writeText(codeBlock.textContent)
.then(() => {
copyButton.textContent = copiedText;
setTimeout(() => {
copyButton.textContent = copyText;
}, 1000);
})
.catch(err => {
alert(err)
console.log('Something went wrong', err);
});
});
});
new StackColorScheme(document.getElementById('dark-mode-toggle'));
}
}

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
}
interface match {
start: number,
end: number
}
/**
* Escape HTML tags as HTML entities
* Edited from:
@ -53,79 +58,131 @@ class Search {
this.bindSearchForm();
}
private async searchKeywords(keywords: string[]) {
const rawData = await this.getData();
let results: pageData[] = [];
/// Sort keywords by their length
keywords.sort((a, b) => {
return b.length - a.length
/**
* Processes search matches
* @param str original text
* @param matches array of matches
* @param ellipsis whether to add ellipsis to the end of each match
* @param charLimit max length of preview string
* @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) {
const titleMatches: match[] = [],
contentMatches: match[] = [];
let result = {
...item,
preview: '',
matchCount: 0
}
let matched = false;
for (const keyword of keywords) {
if (keyword === '') continue;
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)} `;
}
}
const contentMatchAll = item.content.matchAll(regex);
for (const match of Array.from(contentMatchAll)) {
contentMatches.push({
start: match.index,
end: match.index + match[0].length
});
}
if (matched) {
result.preview += '[...]';
results.push(result);
const titleMatchAll = item.title.matchAll(regex);
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 */
/// Result with more matches appears first
return results.sort((a, b) => {
return b.matchCount - a.matchCount;
});
}
public static marker(match) {
return '<mark>' + match + '</mark>';
}
private async doSearch(keywords: string[]) {
const startTime = performance.now();
@ -150,6 +207,11 @@ class Search {
/// Not fetched yet
const jsonURL = this.form.dataset.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;
@ -160,7 +222,7 @@ class Search {
const eventHandler = (e) => {
e.preventDefault();
const keywords = this.input.value;
const keywords = this.input.value.trim();
Search.updateQueryString(keywords, true);
@ -225,7 +287,7 @@ class Search {
<a href={item.permalink}>
<div class="article-details">
<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>
{item.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 };

147
config.yaml Normal file
View File

@ -0,0 +1,147 @@
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:
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
gitalk:
owner:
admin:
repo:
clientID:
clientSecret:
cusdis:
host:
id:
widgets:
enabled:
- search
- archives
- tag-cloud
archives:
limit: 5
tagCloud:
limit: 10
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

@ -23,16 +23,24 @@ PhotoSwipe:
type: style
KaTeX:
- src: https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css
integrity: sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X
- src: https://cdn.jsdelivr.net/npm/katex@0.13.13/dist/katex.min.css
integrity: sha384-RZU/ijkSsFbcmivfdRBQDtwuwVqK7GMOw6IMvKyeWL2K5UAlyp6WonmB8m7Jd0Hn
type: style
- src: https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.js
integrity: sha384-g7c+Jr9ZivxKLnZTDUhnkOnsh30B4H0rpLUpJ4jAIKs4fnJI+sEnkvrMWph2EDg4
- src: https://cdn.jsdelivr.net/npm/katex@0.13.13/dist/katex.min.js
integrity: sha384-pK1WpvzWVBQiP0/GjnvRxV4mOb0oxFuyRxJlk6vVw146n3egcN5C925NCP7a7BY8
type: script
defer: true
- src: https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/contrib/auto-render.min.js
integrity: sha384-mll67QQFJfxn0IYznZYonOWZ644AWYC+Pt2cHqMaRhXVrursRwvLnLaebdGIlYNa
- src: https://cdn.jsdelivr.net/npm/katex@0.13.13/dist/contrib/auto-render.min.js
integrity: sha384-vZTG03m+2yp6N6BNi5iM4rW4oIwk5DfcNdFfxkk9ZWpDriOkXX8voJBFrAO7MpVl
type: script
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
*.test
/public
/themes
public
themes
resources
assets/jsconfig.json
.DS_Store

View File

@ -11,9 +11,13 @@ disqusShortname: hugo-theme-stack
googleAnalytics:
# Theme i18n support
# Available values: en, fr, id, ja, ko, pt-br, zh-cn
# Available values: en, fr, id, ja, ko, pt-br, zh-cn, zh-tw, es, de, nl, it, th, el, uk
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:
post: /p/:slug/
page: /:slug/
@ -37,11 +41,14 @@ params:
emoji: 🍥
subtitle: Lorem ipsum dolor sit amet, consectetur adipiscing elit.
avatar:
enabled: true
local: true
src: img/avatar.png
article:
math: false
toc: true
readingTime: true
license:
enabled: true
default: Licensed under CC BY-NC-SA 4.0
@ -50,6 +57,13 @@ params:
enabled: true
provider: disqus
disqusjs:
shortname:
apiUrl:
apiKey:
admin:
adminLabel:
utterances:
repo:
issueTerm: pathname
@ -60,6 +74,63 @@ params:
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:
# 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:
repo:
repoID:
category:
categoryID:
mapping:
lightTheme:
darkTheme:
reactionsEnabled: 1
emitMetadata: 0
gitalk:
owner:
admin:
repo:
clientID:
clientSecret:
cusdis:
host:
id:
widgets:
enabled:
- search
@ -99,31 +170,32 @@ params:
content:
enabled: true
### Custom menu
### 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
menu:
main:
- identifier: home
name: Home
url: /
weight: -100
pre: home
params:
### For demonstration purpose, the home link will be open in a new tab
newTab: true
icon: home
- identifier: about
name: About
url: about
weight: -90
pre: user
social:
- identifier: github
name: GitHub
url: https://github.com/CaiJimmy/hugo-theme-stack
params:
icon: brand-github
- identifier: archives
name: Archives
url: archives
weight: -70
pre: archives
- identifier: search
name: Search
url: search
weight: -60
pre: search
- identifier: twitter
name: Twitter
url: https://twitter.com
params:
icon: brand-twitter
related:
includeNewer: true
@ -137,5 +209,19 @@ related:
weight: 200
markup:
goldmark:
renderer:
## Set to true if you have HTML content inside Markdown
unsafe: false
tableOfContents:
endLevel: 4
ordered: true
startLevel: 2
highlight:
noClasses: false
codeFences: true
guessSyntax: true
lineNoStart: 1
lineNos: true
lineNumbersInTable: true
tabWidth: 4

View File

@ -1,9 +1,9 @@
---
title: "Test"
description: "This is a example category"
description: "This is an example category"
slug: "test"
image: "hutomo-abrianto-l2jk-uxb1BY-unsplash.jpg"
style:
background: "#2a9d8f"
color: "#fff"
---
---

View File

@ -1,12 +1,19 @@
+++
title = "About"
description = "Hugo, the world's fastest framework for building websites"
date = "2019-02-28"
aliases = ["about-us", "about-hugo", "contact"]
author = "Hugo Authors"
license = "CC BY-NC-ND"
lastmod = "2020-10-09"
+++
---
title: About
description: Hugo, the world's fastest framework for building websites
date: '2019-02-28'
aliases:
- about-us
- about-hugo
- contact
license: CC BY-NC-ND
lastmod: '2020-10-09'
menu:
main:
weight: -90
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.

View File

@ -1,6 +0,0 @@
---
title: "Archives"
date: 2019-05-28
layout: "archives"
slug: "archives"
---

View File

@ -0,0 +1,11 @@
---
title: "Archives"
date: 2019-05-28
layout: "archives"
slug: "archives"
menu:
main:
weight: -70
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

@ -5,4 +5,9 @@ layout: "search"
outputs:
- html
- json
menu:
main:
weight: -60
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` |
| 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 block with backticks
@ -113,6 +117,16 @@ Tables aren't part of the core Markdown spec, but Hugo supports supports them ou
</html>
{{< /highlight >}}
#### Diff code block
```diff
[dependencies.bevy]
git = "https://github.com/bevyengine/bevy"
rev = "11f52b8c72fc3a568e8bb4a4cd1f3eb025ac2e13"
- features = ["dynamic"]
+ features = ["jpeg", "dynamic"]
```
## List Types
#### 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.
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

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

View File

@ -1,27 +0,0 @@
> Original Repo: https://github.com/cdeleeuwe/netlify-plugin-hugo-cache-resources
>
# Netlify Build Plugin: Persist Hugo resources Between Builds
Persist [Hugo](https://gohugo.io/) resources folder between Netlify builds for huge build speed improvements! ⚡️
This plugin caches the `resources` folder after build. If you are processing many images, this would improve build duration significantly.
Note: Restoring cache only comes from the production branch. So once cache is saved on the production branch, the next preview build would use the cache. For example, when pushing to the same preview build, the latest preview build will not get the cache from the previous preview build, but will get it from master.
## Usage
To install, add the following lines to your `netlify.toml` file:
```toml
[build]
publish = "public"
[[plugins]]
package = "netlify-plugin-hugo-cache-resources"
[plugins.inputs]
# If it should show more verbose logs (optional, default = true)
debug = true
```
Note: The `[[plugins]]` line is required for each plugin, even if you have other plugins in your `netlify.toml` file already.

View File

@ -1,39 +0,0 @@
const getResourcesDir = () => {
return 'exampleSite/resources';
}
const printList = (items) => {
console.log('---');
items.forEach((item, index) => {
console.log(`${index + 1}. ${item}`);
});
}
module.exports = {
async onPreBuild({ utils, inputs }) {
const path = getResourcesDir();
const success = await utils.cache.restore(path);
if (success) {
const cachedFiles = await utils.cache.list(path);
console.log(`Restored cached resources folder. Total files: ${cachedFiles.length}`);
if (inputs.debug) printList(cachedFiles);
} else {
console.log(`No cache found for resources folder`);
}
},
async onPostBuild({ utils, inputs }) {
const path = getResourcesDir();
const success = await utils.cache.save(path);
if (success) {
const cachedFiles = await utils.cache.list(path);
console.log(`Saved resources folder to cache. Total files: ${cachedFiles.length}`);
if (inputs.debug) printList(cachedFiles);
} else {
console.log(`No resources folder cached`);
}
}
};

View File

@ -1,5 +0,0 @@
name: netlify-plugin-hugo-cache
inputs:
- name: debug
description: Show more verbose logs
default: true

3
go.mod Normal file
View File

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

70
i18n/de.yaml Normal file
View File

@ -0,0 +1,70 @@
toggleMenu:
other: Menü umschalten
darkMode:
other: Dunkler Modus
list:
page:
one: "{{ .Count }} Seite"
other: "{{ .Count }} Seiten"
section:
other: Abschnitt
subsection:
one: Unterabschnitt
other: Unterabschnitte
article:
back:
other: Zurück
tableOfContents:
other: Inhaltsverzeichnis
relatedContents:
other: Verwandte Inhalte
lastUpdatedOn:
other: Zuletzt aktualisiert am
readingTime:
one: "{{ .Count }} Minute Lesezeit"
other: "{{ .Count }} Minuten Lesezeit"
notFound:
title:
other: Seite nicht gefunden
subtitle:
other: Diese Seite existiert nicht
widget:
archives:
title:
other: Archiv
more:
other: Weitere
tagCloud:
title:
other: Schlagwörter
search:
title:
other: Suche
placeholder:
other: Etwas tippen...
resultTitle:
other: "#PAGES_COUNT Seiten (#TIME_SECONDS Sekunden)"
footer:
builtWith:
other: Erstellt mit {{ .Generator }}
designedBy:
other: Theme {{ .Theme }} gestaltet von {{ .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

@ -17,23 +17,37 @@ list:
other: Subsections
article:
back:
other: Back
tableOfContents:
other: Table of contents
relatedContents:
other: Related contents
lastUpdatedOn:
other: Last updated on
readingTime:
one: "{{ .Count }} minute read"
other: "{{ .Count }} minute read"
notFound:
title:
other: Not Found
subtitle:
other: This page does not exist.
other: This page does not exist
widget:
archives:
title:
other: Archives
more:
other: More
tagCloud:
title:
other: Tags
@ -41,13 +55,16 @@ widget:
search:
title:
other: Search
placeholder:
other: Type something...
resultTitle:
other: "#PAGES_COUNT pages (#TIME_SECONDS seconds)"
footer:
builtWith:
other: Built with {{ .Generator }}
designedBy:
other: Theme {{ .Theme }} designed by {{ .DesignedBy }}

70
i18n/es.yaml Normal file
View File

@ -0,0 +1,70 @@
toggleMenu:
other: Toggle Menu
darkMode:
other: Modo oscuro
list:
page:
one: "{{ .Count }} página"
other: "{{ .Count }} páginas"
section:
other: Sección
subsection:
one: Subsección
other: Subsecciones
article:
back:
other: Volver
tableOfContents:
other: Tabla de contenido
relatedContents:
other: Contenidos relacionados
lastUpdatedOn:
other: Última vez actualizado
readingTime:
one: "{{ .Count }} minuto a leer"
other: "{{ .Count }} minutos a leer"
notFound:
title:
other: No Encontrado
subtitle:
other: Esta página no existe
widget:
archives:
title:
other: Archivo
more:
other: Más
tagCloud:
title:
other: Etiquetas
search:
title:
other: Búsqueda
placeholder:
other: Teclea algo...
resultTitle:
other: "#PAGES_COUNT páginas en (#TIME_SECONDS segundos)"
footer:
builtWith:
other: Creado con {{ .Generator }}
designedBy:
other: Tema {{ .Theme }} diseñado por {{ .DesignedBy }}

View File

@ -1,12 +1,38 @@
toggleMenu:
other: Afficher le menu
darkMode:
other: Mode sombre
list:
page:
one: "{{ .Count }} page"
other: "{{ .Count }} pages"
section:
other: Section
subsection:
one: Sous-section
other: Sous-sections
article:
back:
other: Retour
tableOfContents:
other: Table des matières
relatedContents:
other: Contenus liés
lastUpdatedOn:
other: Dernière mise à jour le
readingTime:
one: "{{ .Count }} minute de lecture"
other: "{{ .Count }} minutes de lecture"
notFound:
title:
other: Page non trouvée
@ -17,8 +43,10 @@ widget:
archives:
title:
other: Archives
more:
other: Autres
tagCloud:
title:
other: Mots clés
@ -26,13 +54,16 @@ widget:
search:
title:
other: Rechercher
placeholder:
other: Cherchez un article, une publication, etc.
resultTitle:
other: "#PAGES_COUNT pages (#TIME_SECONDS secondes)"
footer:
builtWith:
other: Généré avec {{ .Generator }}
designedBy:
other: Thème {{ .Theme }} conçu par {{ .DesignedBy }}

View File

@ -1,24 +1,52 @@
toggleMenu:
other: Tampilkan Menu
darkMode:
other: Mode Gelap
list:
page:
one: "{{ .Count }} halaman"
other: "{{ .Count }} halaman"
section:
other: Bagian
subsection:
one: Subbagian
other: Subbagian
article:
back:
other: Kembali
tableOfContents:
other: Daftar Isi
relatedContents:
other: Konten terkait
lastUpdatedOn:
other: Terakhir diperbarui pada
readingTime:
one: "Waktu Membaca: {{ .Count }} menit"
other: "Waktu Membaca: {{ .Count }} menit"
notFound:
title:
other: Not Found
other: Tidak ditemukan
subtitle:
other: Halaman ini tidak ada.
other: Halaman yang Anda akses tidak ditemukan.
widget:
archives:
title:
other: Arsip
more:
other: Lebih
tagCloud:
title:
other: Tag
@ -26,13 +54,16 @@ widget:
search:
title:
other: Cari
placeholder:
other: Ketik sesuatu...
resultTitle:
other: "#PAGES_COUNT halaman (#TIME_SECONDS detik)"
footer:
builtWith:
other: Dibangun dengan {{ .Generator }}
designedBy:
other: Tema {{ .Theme }} dirancang oleh {{ .DesignedBy }}

69
i18n/it.yaml Normal file
View File

@ -0,0 +1,69 @@
toggleMenu:
other: Toggle Menu
darkMode:
other: Dark Mode
list:
page:
one: "{{ .Count }} pagina"
other: "{{ .Count }} pagine"
section:
other: Sezione
subsection:
one: Sottosezione
other: Sottosezioni
article:
back:
other: Indietro
tableOfContents:
other: Indice
relatedContents:
other: Contenuti correlati
lastUpdatedOn:
other: Aggiornato il
readingTime:
one: "{{ .Count }} min per leggere"
other: "{{ .Count }} min per leggere"
notFound:
title:
other: Non trovato
subtitle:
other: Questa pagina non esiste.
widget:
archives:
title:
other: Archivi
more:
other: Di più
tagCloud:
title:
other: Tags
search:
title:
other: Cerca
placeholder:
other: Scrivi qualcosa...
resultTitle:
other: "#PAGES_COUNT pagine (#TIME_SECONDS secondi)"
footer:
builtWith:
other: Realizzato con {{ .Generator }}
designedBy:
other: Tema {{ .Theme }} realizzato da {{ .DesignedBy }}

View File

@ -5,11 +5,21 @@ darkMode:
other: ダークモード
article:
back:
other: 前のページ
tableOfContents:
other: 目次
relatedContents:
other: 関連するコンテンツ
lastUpdatedOn:
other: 最終更新
readingTime:
other: "読了時間: {{ .Count }}分"
notFound:
title:
other: 404 Not Found
@ -20,16 +30,20 @@ widget:
archives:
title:
other: アーカイブ
more:
other: さらに見る
tagCloud:
title:
other: タグ
search:
title:
other: サーチ
other: 検索
placeholder:
other: 入力...
resultTitle:
other: "#PAGES_COUNT 件 #TIME_SECONDS 秒)"

View File

@ -1,15 +1,42 @@
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: 페이지를 찾을 수 없습니다.
@ -19,6 +46,7 @@ widget:
other: 보관함
more:
other: 더보기
tagCloud:
title:
other: 태그
@ -26,8 +54,10 @@ widget:
search:
title:
other: 검색
placeholder:
other: 검색어를 입력하세요...
resultTitle:
other: "#PAGES_COUNT 페이지 (#TIME_SECONDS 초)"

53
i18n/nl.yaml Normal file
View File

@ -0,0 +1,53 @@
toggleMenu:
other: Open Menu
darkMode:
other: Donkere modus
list:
page:
one: "{{ .Count }} pagina"
other: "{{ .Count }} pagina's"
section:
other: Sectie
subsection:
one: Subsectie
other: Subsecties
article:
relatedContents:
other: Gerelateerde inhoud
lastUpdatedOn:
other: Laatst bijgewerkt op
notFound:
title:
other: Niet gevonden
subtitle:
other: Deze pagina bestaat niet.
widget:
archives:
title:
other: Archief
more:
other: Meer
tagCloud:
title:
other: Tags
search:
title:
other: Zoeken
placeholder:
other: Typ iets
resultTitle:
other: "#PAGES_COUNT pagina's (#TIME_SECONDS seconden)"
footer:
builtWith:
other: Gemaakt met {{ .Generator }}
designedBy:
other: Theme {{ .Theme }} ontworpen door {{ .DesignedBy }}

69
i18n/pl.yaml Normal file
View File

@ -0,0 +1,69 @@
toggleMenu:
other: Przełącz Menu
darkMode:
other: Tryb ciemny
list:
page:
one: "{{ .Count }} strona"
other: "{{ .Count }} stron"
section:
other: Sekcja
subsection:
one: Podsekcja
other: Podsekcje
article:
back:
other: Wróć
tableOfContents:
other: Spis treści
relatedContents:
other: Powiązane artykuły
lastUpdatedOn:
other: Ostatnio zaktualizowany
readingTime:
one: "Przeczytasz w {{ .Count }} minutę"
other: "Przeczytasz w {{ .Count }} minut"
notFound:
title:
other: Nie znaleziono
subtitle:
other: Ta strona nie istnieje
widget:
archives:
title:
other: Archiwum
more:
other: Więcej
tagCloud:
title:
other: Tagi
search:
title:
other: Szukaj
placeholder:
other: Wpisz coś...
resultTitle:
other: "#PAGES_COUNT stron (#TIME_SECONDS sekund)"
footer:
builtWith:
other: Zbudowano z {{ .Generator }}
designedBy:
other: Motyw {{ .Theme }} zaprojektowany przez {{ .DesignedBy }}

View File

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

View File

@ -21,10 +21,16 @@ list:
other: Подразделы
article:
back:
other: Назад
relatedContents:
other: Также рекомендуем
lastUpdatedOn:
other: Обновлено
tableOfContents:
other: Содержание
readingTime:
other: "Время чтения: {{ .Count }} мин."
notFound:
title:

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

@ -5,11 +5,21 @@ darkMode:
other: 暗色模式
article:
back:
other: 返回
tableOfContents:
other: 目录
relatedContents:
other: 相关文章
lastUpdatedOn:
other: 最后更新于
readingTime:
other: "阅读时长: {{ .Count }} 分钟"
notFound:
title:
other: 404 错误
@ -20,8 +30,10 @@ widget:
archives:
title:
other: 归档
more:
other: 更多
tagCloud:
title:
other: 标签云
@ -29,7 +41,9 @@ widget:
search:
title:
other: 搜索
placeholder:
other: 输入关键词...
resultTitle:
other: "#PAGES_COUNT 个结果 (用时 #TIME_SECONDS 秒)"

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

@ -1,30 +1,41 @@
{{- $image := .Page.Resources.GetMatch (printf "%s" (.Destination | safeURL)) -}}
{{- if and $image (ne (path.Ext .Destination) ".svg") -}}
{{- $alt := .PlainText | safeHTML -}}
<figure style="flex-grow: {{ div (mul $image.Width 100) $image.Height }}; flex-basis: {{ div (mul $image.Width 240) $image.Height }}px">
<a href="{{ $image.RelPermalink }}" data-size="{{ $image.Width }}x{{ $image.Height }}">
{{- $Permalink := $image.RelPermalink -}}
{{- $Width := $image.Width -}}
{{- $Height := $image.Height -}}
{{- $Srcset := "" -}}
{{- $Permalink := .Destination | relURL | safeURL -}}
{{- $alt := .PlainText | safeHTML -}}
{{- $Width := 0 -}}
{{- $Height := 0 -}}
{{- $Srcset := "" -}}
{{- if (default true .Page.Site.Params.imageProcessing.content.enabled) -}}
{{- $small := $image.Resize "480x" -}}
{{- $big := $image.Resize "1024x" -}}
{{- $Srcset = printf "%s 480w, %s 1024w" $small.RelPermalink $big.RelPermalink -}}
{{- end -}}
<img src="{{ $Permalink }}"
{{ with $Srcset }}srcset="{{ . }}"{{ end }}
width="{{ $Width }}"
height="{{ $Height }}"
loading="lazy"
{{ with $alt }}alt="{{ . }}"{{ end }}>
</a>
{{ with $alt }}
<figcaption>{{ . | markdownify }}</figcaption>
{{ end }}
</figure>
{{- else -}}
<img src="{{ .Destination | safeURL }}" alt="{{ .Text }}" {{ with .Title }} title="{{ . }}"{{ end }} />
{{- end -}}
{{/* SVG and external images won't work with gallery layout, because their width and height attributes are unknown */}}
{{- $galleryImage := false -}}
{{- if $image -}}
{{- $notSVG := ne (path.Ext .Destination) ".svg" -}}
{{- $Permalink = $image.RelPermalink -}}
{{- if $notSVG -}}
{{- $Width = $image.Width -}}
{{- $Height = $image.Height -}}
{{- $galleryImage = true -}}
{{- if (default true .Page.Site.Params.imageProcessing.content.enabled) -}}
{{- $small := $image.Resize `480x` -}}
{{- $big := $image.Resize `1024x` -}}
{{- $Srcset = printf `%s 480w, %s 1024w` $small.RelPermalink $big.RelPermalink -}}
{{- end -}}
{{- end -}}
{{- end -}}
<img src="{{ $Permalink }}"
{{ with $Width }}width="{{ . }}"{{ end }}
{{ with $Height }}height="{{ . }}"{{ end }}
{{ with $Srcset }}srcset="{{ . }}"{{ end }}
loading="lazy"
{{ with $alt }}
alt="{{ . }}"
{{ end }}
{{ 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

@ -6,8 +6,10 @@
</head>
<body class="{{ block `body-class` . }}{{ end }}">
{{- partial "head/colorScheme" . -}}
<div class="container main-container flex on-phone--column {{ if .Site.Params.widgets.enabled }}extended{{ else }}compact{{ end }} {{ block `container-class` . }}{{end}}">
{{ partial "sidebar/left.html" . }}
<div class="container main-container flex {{ block `container-class` . }}on-phone--column {{ if .Site.Params.widgets.enabled }}extended{{ else }}compact{{ end }}{{ end }}">
{{- block "left-sidebar" . -}}
{{ partial "sidebar/left.html" . }}
{{- end -}}
<main class="main full-width">
{{- block "main" . }}{{- end }}
</main>

View File

@ -1,12 +1,61 @@
{{ define "body-class" }}article-page keep-sidebar{{ end }}
{{ define "body-class" }}
{{ $TOCEnabled := default (default false .Site.Params.article.toc) .Params.toc }}
{{- .Scratch.Set "hasTOC" (and (ge (len .TableOfContents) 100) $TOCEnabled) -}}
article-page {{ if (.Scratch.Get "hasTOC") }}has-toc{{ end }}
{{ end }}
{{ define "container-class" }}
{{ if (.Scratch.Get "hasTOC") }}
extended
{{ else }}
on-phone--column {{ if .Site.Params.widgets.enabled }}extended{{ else }}compact{{ end }}
{{ end }}
{{ end }}
{{ define "main" }}
{{ partial "article/article.html" . }}
{{ if or (not (isset .Params "comments")) (eq .Params.comments "true")}}
{{ if .Params.links }}
{{ partial "article/components/links" . }}
{{ end }}
{{ partial "article/components/related-contents" . }}
{{ if not (eq .Params.comments false) }}
{{ partial "comments/include" . }}
{{ end }}
{{ partialCached "footer/footer" . }}
{{ partialCached "article/components/photoswipe" . }}
{{ end }}
{{ end }}
{{ define "left-sidebar" }}
{{ if (.Scratch.Get "hasTOC") }}
<div id="article-toolbar">
<a href="{{ .Site.BaseURL | relLangURL }}" 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" }}
{{ if (.Scratch.Get "hasTOC") }}
<aside class="sidebar right-sidebar sticky">
<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">
{{ .TableOfContents }}
</div>
</section>
</aside>
{{ end }}
{{ end }}

View File

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

View File

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

View File

@ -1,3 +1,5 @@
<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>

View File

@ -21,12 +21,25 @@
</h3>
{{ end }}
{{- if not .Date.IsZero -}}
{{ if or (not .Date.IsZero) (.Site.Params.article.readingTime) }}
<footer class="article-time">
{{ partial "helper/icon" "clock" }}
<time class="article-time--published">
{{- .Date.Format (or .Site.Params.dateFormat.published "Jan 02, 2006") -}}
</time>
{{ if not .Date.IsZero }}
<div>
{{ partial "helper/icon" "date" }}
<time class="article-time--published">
{{- .Date.Format (or .Site.Params.dateFormat.published "Jan 02, 2006") -}}
</time>
</div>
{{ end }}
{{ if .Site.Params.article.readingTime }}
<div>
{{ partial "helper/icon" "clock" }}
<time class="article-time--reading">
{{ T "article.readingTime" .ReadingTime }}
</time>
</div>
{{ end }}
</footer>
{{- end -}}
{{ end }}
</div>

View File

@ -4,16 +4,16 @@
{{ if and (.Site.Params.article.license.enabled) (not (eq .Params.license false)) }}
<section class="article-copyright">
{{ partial "helper/icon" "copyright" }}
<span>{{ default .Site.Params.article.license.default .Params.license }}</span>
<span>{{ default .Site.Params.article.license.default .Params.license | markdownify }}</span>
</section>
{{ end }}
{{- if ne .Lastmod .Date -}}
<section class="article-time">
<section class="article-lastmod">
{{ partial "helper/icon" "clock" }}
<span class="article-time--modified">
<span>
{{ T "article.lastUpdatedOn" }} {{ .Lastmod.Format ( or .Site.Params.dateFormat.lastUpdated "Jan 02, 2006 15:04 MST" ) }}
</span>
</section>
{{- end -}}
</footer>
</footer>

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

@ -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

@ -0,0 +1,61 @@
{{- $pc := .Site.Config.Privacy.Disqus -}}
{{- $disqusjs := .Site.Params.Comments.disqusjs -}}
{{- if and (not $pc.Disable) (and $disqusjs.Shortname $disqusjs.ApiKey) -}}
{{- $style := resources.Get "scss/partials/comments/disqusjs.scss" | resources.ToCSS | minify -}}
<link rel="stylesheet" href="{{ $style.RelPermalink }}">
<div class="disqus-container">
<div id="disqus_thread"></div>
<script type="application/javascript">
let disqusjs;
function loadDisqusJS() {
disqusjs = new DisqusJS({
shortname: {{ $disqusjs.Shortname }},
siteName: {{ .Site.Title }},
apikey: {{ $disqusjs.ApiKey }},
{{ with $disqusjs.ApiUrl }}api: {{ . }},{{ end }}
{{ with $disqusjs.Admin }}admin: {{ . }},{{ end }}
{{ with $disqusjs.AdminLabel }}adminLabel: {{ . }}{{ end }}
});
}
function lazyLoadDisqusJS() {
if (["localhost", "127.0.0.1"].indexOf(window.location.hostname) != -1) {
document.getElementById('disqus_thread').innerHTML = 'Disqus comments not available by default when the website is previewed locally.';
return;
}
let d = document.createElement('script');
d.src = 'https://cdn.jsdelivr.net/npm/disqusjs@1.3/dist/disqus.js';
d.async = false;
document.body.appendChild(d);
d.onload = () => {
loadDisqusJS();
window.addEventListener('onColorSchemeChange', (e) => {
if (disqusjs) {
loadDisqusJS();
}
})
}
}
let runningOnBrowser = typeof window !== "undefined";
let isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent);
let supportsIntersectionObserver = runningOnBrowser && "IntersectionObserver" in window;
if (!isBot && supportsIntersectionObserver) {
let disqus_observer = new IntersectionObserver(function(entries) {
if (entries[0].isIntersecting) {
lazyLoadDisqusJS();
disqus_observer.disconnect();
}
});
disqus_observer.observe(document.getElementById('disqus_thread'));
} else {
lazyLoadDisqusJS();
}
</script>
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>
</div>
{{- end -}}

View File

@ -0,0 +1,49 @@
{{- with .Site.Params.comments.giscus -}}
<script
src="https://giscus.app/client.js"
data-repo="{{- .repo -}}"
data-repo-id="{{- .repoID -}}"
data-category="{{- .category -}}"
data-category-id="{{- .categoryID -}}"
data-mapping="{{- default `title` .mapping -}}"
data-reactions-enabled="{{- default 1 .reactionsEnabled -}}"
data-emit-metadata="{{- default 0 .emitMetadata -}}"
data-theme="{{- default `light` .lightTheme -}}"
crossorigin="anonymous"
async
></script>
<script>
function setGiscusTheme(theme) {
let giscus = document.querySelector('iframe.giscus-frame');
if (giscus) {
giscus.contentWindow.postMessage(
{
giscus: {
setConfig: {
theme: theme
}
}
},
"https://giscus.app"
);
};
};
(function(){
addEventListener('message', (e) => {
if (event.origin !== 'https://giscus.app') return;
handler()
});
window.addEventListener('onColorSchemeChange', handler);
function handler() {
if (document.documentElement.dataset.scheme === "light") {
setGiscusTheme('{{- default "light" .lightTheme -}}');
} else {
setGiscusTheme('{{- default "dark_dimmed" .darkTheme -}}');
};
};
}());
</script>
{{- 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

@ -7,7 +7,7 @@
components: ['embed'],
url: "{{ $.Permalink }}",
max_shown_comments: {{ default 15 .max_shown_comments }},
theme: document.body.dataset.scheme,
theme: document.documentElement.dataset.scheme,
page_title: '{{ $.Title }}',
locale: '{{ default "en" .locale }}',
show_email_subscription: {{ default true .show_email_subscription }}
@ -26,4 +26,4 @@
window.REMARK42.changeTheme(e.detail);
})
</script>
{{- end -}}
{{- end -}}

View File

@ -0,0 +1,49 @@
<script src="//cdn.jsdelivr.net/npm/twikoo@1.4.15/dist/twikoo.all.min.js"></script>
<div id="tcomment"></div>
<style>
.twikoo {
background-color: var(--card-background);
border-radius: var(--card-border-radius);
box-shadow: var(--shadow-l1);
padding: var(--card-padding);
}
:root[data-scheme="dark"] {
--twikoo-body-text-color-main: rgba(255, 255, 255, 0.9);
--twikoo-body-text-color: rgba(255, 255, 255, 0.7);
}
.twikoo .el-input-group__prepend,
.twikoo .tk-action-icon,
.twikoo .tk-time,
.twikoo .tk-comments-count {
color: var(--twikoo-body-text-color);
}
.twikoo .el-input__inner,
.twikoo .el-textarea__inner,
.twikoo .tk-preview-container,
.twikoo .tk-content,
.twikoo .tk-nick,
.twikoo .tk-send {
color: var(--twikoo-body-text-color-main);
}
.twikoo .el-button{
color: var(--twikoo-body-text-color)!important;
}
</style>
{{- with .Site.Params.comments.twikoo -}}
<script>
twikoo.init({
envId: '{{- .envId -}}',
el: '#tcomment',
{{- with .region -}}
region: '{{- . -}}',
{{- end -}}
{{- with .path -}}
path: '{{- . -}}',
{{- end -}}
{{- with .lang -}}
lang: '{{- . -}}',
{{- end -}}
})
</script>
{{- end -}}

View File

@ -31,10 +31,10 @@
addEventListener('message', event => {
if (event.origin !== 'https://utteranc.es') return;
setUtterancesTheme(document.body.dataset.scheme)
setUtterancesTheme(document.documentElement.dataset.scheme)
});
window.addEventListener('onColorSchemeChange', (e) => {
setUtterancesTheme(e.detail)
})
</script>
</script>

View File

@ -0,0 +1,27 @@
{{- with .Site.Params.comments.vssue -}}
<link rel="stylesheet" href="https://unpkg.com/vssue/dist/vssue.min.css" />
<div id="vssue"></div>
<script src="https://unpkg.com/vue/dist/vue.runtime.min.js"></script>
<script src="https://unpkg.com/vssue/dist/vssue.{{ .platform }}.min.js"></script>
<script>
new Vue({
el: "#vssue",
render: (h) =>
h("Vssue", {
props: {
title: "{{ $.Title }}",
options: {
autoCreateIssue: {{ default false .autoCreateIssue }},
owner: "{{ .owner }}",
repo: "{{ .repo }}",
clientId: "{{ .clientId }}",
clientSecret: "{{ .clientSecret }}",
},
},
}),
});
</script>
{{- end -}}

View File

@ -0,0 +1,32 @@
<script src='//cdn.jsdelivr.net/npm/@waline/client/dist/Waline.min.js'></script>
<div id="waline" class="waline-container"></div>
<style>
.waline-container {
background-color: var(--card-background);
border-radius: var(--card-border-radius);
box-shadow: var(--shadow-l1);
padding: var(--card-padding);
}
.waline-container .vcount {
color: var(--card-text-color-main);
}
</style>
{{- with .Site.Params.comments.waline -}}
{{- $config := dict "el" "#waline" "dark" `html[data-scheme="dark"]` -}}
{{- $replaceKeys := dict "serverurl" "serverURL" "requiredmeta" "requiredMeta" "wordlimit" "wordLimit" "pagesize" "pageSize" "avatarcdn" "avatarCDN" "avatarforce" "avatarForce" -}}
{{- range $key, $val := . -}}
{{- if $val -}}
{{- $replaceKey := index $replaceKeys $key -}}
{{- $k := default $key $replaceKey -}}
{{- $config = merge $config (dict $k $val) -}}
{{- end -}}
{{- end -}}
<script>
/// Waline client configuration see: https://waline.js.org/en/reference/client.html
new Waline({{ $config | jsonify | safeJS }});
</script>
{{- end -}}

View File

@ -3,4 +3,10 @@
{{- $opts := dict "minify" hugo.IsProduction -}}
{{- $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 := "2.2.0" -}}
{{- $ThemeVersion := "3.8.0" -}}
<footer class="site-footer">
<section class="copyright">
&copy;
@ -20,4 +20,4 @@
{{ T "footer.builtWith" (dict "Generator" $Generator) | safeHTML }} <br />
{{ T "footer.designedBy" (dict "Theme" $Theme "DesignedBy" $DesignedBy) | safeHTML }}
</section>
</footer>
</footer>

View File

@ -31,9 +31,9 @@
* 1. If dark mode is set already (in local storage)
* 2. Auto mode & prefere color scheme is dark
*/
document.body.dataset.scheme = 'dark';
document.documentElement.dataset.scheme = 'dark';
} else {
document.body.dataset.scheme = 'light';
document.documentElement.dataset.scheme = 'light';
}
})();
</script>
</script>

View File

@ -21,5 +21,5 @@
<link rel="shortcut icon" href="{{ . }}" />
{{ end }}
{{- template "_internal/google_analytics_async.html" . -}}
{{- template "_internal/google_analytics.html" . -}}
{{- partial "head/custom.html" . -}}

View File

@ -1,5 +1,6 @@
{{- with .Site.Params.opengraph.twitter.site -}}
<meta name="twitter:site" content="{{ . }}">
<meta name="twitter:site" content="@{{ . }}">
<meta name="twitter:creator" content="@{{ . }}">
{{- end -}}
{{- $title := partialCached "data/title" . .RelPermalink -}}

View File

@ -1,3 +1,3 @@
{{ $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 }}">

View File

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

View File

@ -8,7 +8,7 @@
{{ $url := urls.Parse $imageValue }}
{{ if or (eq $url.Scheme "http") (eq $url.Scheme "https") }}
<!-- Is a external image -->
<!-- Is an external image -->
{{ $result = merge $result (dict "permalink" $imageValue) }}
{{ else }}
{{ $pageResourceImage := .Context.Resources.GetMatch (printf "%s" ($imageValue | safeURL)) }}
@ -23,7 +23,7 @@
{{ end }}
{{ else }}
<!-- Can not find the image under page bundle. Could be a relative linked image -->
{{ $result = merge $result (dict "permalink" $imageValue) }}
{{ $result = merge $result (dict "permalink" (relURL $imageValue)) }}
{{ end }}
{{ end }}
@ -51,11 +51,11 @@
{{ else }}
<!-- External image -->
{{ $result = merge $result (dict "permalink" $defaultImageSetting.src) }}
{{ $result = merge $result (dict "permalink" (relURL $defaultImageSetting.src)) }}
{{ end }}
{{ end }}
{{ end }}
{{ return $result }}
{{ return $result }}

View File

@ -7,7 +7,9 @@
<header class="site-info">
{{ with .Site.Params.sidebar.avatar }}
{{ if (default true .enabled) }}
<figure class="site-avatar">
<a href="{{ .Site.BaseURL | relLangURL }}">
{{ if not .local }}
<img src="{{ .src }}" width="300" height="300" class="site-logo" loading="lazy" alt="Avatar">
{{ else }}
@ -21,14 +23,35 @@
{{ errorf "Failed loading avatar from %q" . }}
{{ end }}
{{ end }}
</a>
{{ with $.Site.Params.sidebar.emoji }}
<span class="emoji">{{ . }}</span>
{{ end }}
</figure>
{{ end }}
{{ end }}
<h1 class="site-name"><a href="{{ .Site.BaseURL }}">{{ .Site.Title }}</a></h1>
<h1 class="site-name"><a href="{{ .Site.BaseURL | relLangURL }}">{{ .Site.Title }}</a></h1>
<h2 class="site-description">{{ .Site.Params.sidebar.subtitle }}</h2>
{{- 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 -}}
</header>
<ol class="menu" id="main-menu">
@ -37,9 +60,13 @@
{{ $active := or (eq $currentPage.Title .Name) (or ($currentPage.HasMenuCurrent "main" .) ($currentPage.IsMenuCurrent "main" .)) }}
<li {{ if $active }} class='current' {{ end }}>
<a href='{{ .URL | relURL }}'>
<a href='{{ .URL | relLangURL }}' {{ if eq .Params.newTab true }}target="_blank"{{ end }}>
{{ $icon := default .Pre .Params.Icon }}
{{ 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 }}
<span>{{- .Name -}}</span>
</a>

View File

@ -1,21 +0,0 @@
{{ define "container-class" }}article-page with-toolbar{{ end }}
{{ define "main" }}
<div id="article-toolbar">
<a href="{{ .Site.BaseURL }}" class="back-home">
{{ (resources.Get "icons/back.svg").Content | safeHTML }}
<span>Back</span>
</a>
</div>
{{ partial "article/article.html" . }}
{{ partial "article/components/related-contents" . }}
{{ if or (not (isset .Params "comments")) (eq .Params.comments "true")}}
{{ partial "comments/include" . }}
{{ end }}
{{ partialCached "footer/footer" . }}
{{- partialCached "article/components/photoswipe.html" . -}}
{{ end }}

View File

@ -12,13 +12,12 @@
<p>当前视频av或BV号{{ $vid }}视频分P{{ $videopage }}</p>
{{ end }}
<div style="position: relative; width: 100%; height: 0; padding-bottom: 56.25%;">
<iframe src="//player.bilibili.com/player.html?{{ $basicQuery | safeURL }}&{{ $videoQuery | safeURL }}"
<div class="video-wrapper">
<iframe src="https://player.bilibili.com/player.html?{{ $basicQuery | safeURL }}&{{ $videoQuery | safeURL }}"
scrolling="no"
frameborder="no"
framespacing="0"
allowfullscreen="true"
style="position: absolute; width: 100%; height: 100%; left: 0; top: 0;"
>
</iframe>
</div>

View File

@ -1,10 +1,10 @@
{{ $vid := .Get 0 }}
<div style="position: relative; width: 100%; height: 0; padding-bottom: 56.25%;">
<iframe src="http://v.qq.com/txp/iframe/player.html?vid={{ $vid }}&auto=0"
<div class="video-wrapper">
<iframe src="https://v.qq.com/txp/iframe/player.html?vid={{ $vid }}&auto=0"
scrolling="no"
frameborder="no"
framespacing="0"
allowfullscreen="true"
style="position: absolute; width: 100%; height: 100%;">
allowfullscreen="true"
>
</iframe>
</div>

View File

@ -0,0 +1,14 @@
{{- $src := .Get "src" | default (.Get 0) -}}
<div class="video-wrapper">
<video
controls
src="{{- $src -}}"
{{ with .Get "poster" }}poster="{{- . -}}"{{ end }}
{{ with .Get "autoplay" }}autoplay{{ end }}
>
<p>
Your browser doesn't support HTML5 video. Here is a
<a href="{{- $src -}}">link to the video</a> instead.
</p>
</video>
</div>

View File

@ -2,8 +2,12 @@
{{- if not $pc.Disable -}}
{{- $ytHost := cond $pc.PrivacyEnhanced "www.youtube-nocookie.com" "www.youtube.com" -}}
{{- $id := .Get "id" | default (.Get 0) -}}
{{- $class := .Get "class" | default (.Get 1) }}
<div {{ with $class }}class="{{ . }}"{{ else }}style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"{{ end }}>
<iframe loading="lazy" src="https://{{ $ytHost }}/embed/{{ $id }}{{ with .Get "autoplay" }}{{ if eq . "true" }}?autoplay=1{{ end }}{{ end }}" {{ if not $class }}style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" {{ end }}allowfullscreen title="YouTube Video"></iframe>
<div class="video-wrapper">
<iframe loading="lazy"
src="https://{{ $ytHost }}/embed/{{ $id }}{{ with .Get "autoplay" }}{{ if eq . "true" }}?autoplay=1{{ end }}{{ end }}"
allowfullscreen
title="YouTube Video"
>
</iframe>
</div>
{{ end -}}

View File

@ -2,7 +2,7 @@
publish = "exampleSite/public"
[build.environment]
HUGO_VERSION = "0.79.0"
HUGO_VERSION = "0.87.0"
HUGO_THEME = "repo"
[context.production]
@ -17,4 +17,10 @@
command = "cd exampleSite && hugo --gc --themesDir ../.. -b ${DEPLOY_PRIME_URL}"
[[plugins]]
package = "/exampleSite/plugins/netlify-plugin-hugo-cache-resources"
package = "netlify-plugin-hugo-cache-resources"
[plugins.inputs]
# If it should show more verbose logs (optional, default = true)
debug = true
# Relative path to source directory in case you use Hugo's "--s" option
srcdir = "exampleSite"

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
{"Target":"scss/style.min.css","MediaType":"text/css","Data":{}}

View File

@ -5,23 +5,23 @@ name = "Stack"
license = "GPL-3.0-only"
licenselink = "https://github.com/CaiJimmy/hugo-theme-stack/blob/master/LICENSE"
description = "Card-style Hugo theme designed for bloggers"
homepage = "https://theme-stack.jimmycai.com"
tags = [
"blog",
"responsive",
"clean",
"light",
"dark",
"personal"
]
homepage = "https://github.com/CaiJimmy/hugo-theme-stack"
demosite = "https://demo.stack.jimmycai.com"
tags = ["blog", "responsive", "clean", "light", "dark", "personal"]
features = [
"disqus",
"photoswipe",
"opengraph",
"widgets"
"widgets",
"darkmode",
"table of contents",
"search",
]
min_version = "0.78.0"
min_version = "0.87.0"
[author]
name = "Jimmy Cai"
homepage = "https://jimmycai.com"
name = "Jimmy Cai"
homepage = "https://jimmycai.com"