diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000..fb615c8
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1 @@
+custom: ['https://www.buymeacoffee.com/jimmycai']
\ No newline at end of file
diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml
new file mode 100644
index 0000000..fcca4ea
--- /dev/null
+++ b/.github/release-drafter.yml
@@ -0,0 +1,31 @@
+name-template: 'v$RESOLVED_VERSION 🌈'
+tag-template: 'v$RESOLVED_VERSION'
+categories:
+ - title: '🚀 Features'
+ labels:
+ - 'feature'
+ - 'enhancement'
+ - title: '🐛 Bug Fixes'
+ labels:
+ - 'fix'
+ - 'bugfix'
+ - 'bug'
+ - title: '🧰 Maintenance'
+ label: 'chore'
+change-template: '- $TITLE @$AUTHOR (#$NUMBER)'
+change-title-escapes: '\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks.
+version-resolver:
+ major:
+ labels:
+ - 'major'
+ minor:
+ labels:
+ - 'minor'
+ patch:
+ labels:
+ - 'patch'
+ default: patch
+template: |
+ ## Changes
+
+ $CHANGES
diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml
new file mode 100644
index 0000000..2c30bcd
--- /dev/null
+++ b/.github/workflows/release-drafter.yml
@@ -0,0 +1,16 @@
+name: Release Drafter
+
+on:
+ push:
+ # branches to consider in the event; optional, defaults to all
+ branches:
+ - master
+
+jobs:
+ update_release_draft:
+ runs-on: ubuntu-latest
+ steps:
+ # Drafts your next Release notes as Pull Requests are merged into "master"
+ - uses: release-drafter/release-drafter@v5
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/README.md b/README.md
index 69bd657..e871020 100644
--- a/README.md
+++ b/README.md
@@ -11,7 +11,7 @@
## Documentation & more information
-[Documentation](https://www.notion.so/jimmycai/Hugo-Theme-Stack-511aec5b9ed845ce9b6e3ae0bf7fb6d4) | [中文文档](https://www.notion.so/jimmycai/Hugo-Theme-Stack-511aec5b9ed845ce9b6e3ae0bf7fb6d4)
+[Documentation](https://docs.stack.jimmycai.com/) | [中文文档](https://docs.stack.jimmycai.com/v/zh-cn/)
## Introduction
@@ -35,7 +35,12 @@ The only JavaScript library being used is [node-vibrant](https://github.com/Vibr
This theme uses SCSS and TypeScript. For that reason, it's necessary to use **Hugo ≥ 0.74.0**.
-**Note**: You'll need Hugo Extended version to edit SCSS files
+Use Hugo Extended version if you want to:
+
+* Use the latest feature/fix from `master` branch
+* Edit SCSS files
+
+**Compiled CSS are updated once per release.**
## Installation
@@ -51,6 +56,14 @@ Please do not remove the "*Theme Stack designed by Jimmy*" text and link.
If you want to port this theme to another blogging platform, please let me know🙏.
+## Sponsoring
+
+If you like this theme, consider supporting its development:
+
+
+
+Your support is greatly appreciated :)
+
## Thanks to
- [Vibrant-Colors/node-vibrant](https://github.com/Vibrant-Colors/node-vibrant)
@@ -62,4 +75,4 @@ If you want to port this theme to another blogging platform, please let me know
- [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/)
\ No newline at end of file
+ - Markdown gallery syntax from [Typlog](https://typlog.com/)
diff --git a/assets/icons/search.svg b/assets/icons/search.svg
new file mode 100644
index 0000000..a0b0ddc
--- /dev/null
+++ b/assets/icons/search.svg
@@ -0,0 +1,7 @@
+
+
+
diff --git a/assets/scss/custom.scss b/assets/scss/custom.scss
new file mode 100644
index 0000000..61fa80f
--- /dev/null
+++ b/assets/scss/custom.scss
@@ -0,0 +1 @@
+/* Place your custom SCSS in HUGO_SITE_FOLDER/assets/scss/custom.scss */
\ No newline at end of file
diff --git a/assets/scss/grid.scss b/assets/scss/grid.scss
new file mode 100644
index 0000000..73fcdfc
--- /dev/null
+++ b/assets/scss/grid.scss
@@ -0,0 +1,119 @@
+.container {
+ margin-left: auto;
+ margin-right: auto;
+
+ &.extended {
+ @media (min-width: $on-phone) {
+ max-width: 800px;
+
+ .left-sidebar {
+ width: 25%;
+ }
+ }
+
+ @media (min-width: $on-tablet) {
+ max-width: 972px;
+
+ .right-sidebar {
+ width: 25%;
+ }
+ }
+
+ @media (min-width: $on-desktop) {
+ max-width: 1200px;
+
+ .left-sidebar {
+ width: 20%;
+ }
+
+ .right-sidebar {
+ width: 25%;
+ }
+ }
+
+ @media (min-width: $on-desktop-large) {
+ max-width: 1536px;
+
+ .left-sidebar {
+ width: 15%;
+ }
+ }
+ }
+
+ &.compact {
+ @media (min-width: $on-phone) {
+ max-width: 800px;
+
+ .left-sidebar {
+ width: 25%;
+ }
+ }
+
+ @media (min-width: $on-tablet) {
+ max-width: 972px;
+ }
+
+ @media (min-width: $on-desktop) {
+ max-width: 1050px;
+
+ .left-sidebar {
+ width: 20%;
+ }
+ }
+
+ @media (min-width: $on-desktop-large) {
+ max-width: 1300px;
+ }
+ }
+}
+
+.flex {
+ display: flex;
+ flex-direction: row;
+
+ &.column {
+ flex-direction: column;
+ }
+
+ &.on-phone--column {
+ @media (max-width: $on-phone) {
+ flex-direction: column;
+ }
+ }
+
+ &.align-items--flex-start {
+ align-items: flex-start;
+ }
+
+ .grow {
+ flex-grow: 1;
+ }
+
+ .do-not-shrink {
+ flex-shrink: 0;
+ }
+
+ .do-not-overflow {
+ min-width: 0;
+ flex-shrink: 1;
+ max-width: 100%;
+ }
+
+ .full-width {
+ width: 100%;
+ }
+}
+
+main.main {
+ min-width: 0;
+ padding: 0 15px;
+ max-width: 100%;
+ flex-grow: 1;
+ padding-top: var(--main-top-padding);
+}
+
+.main-grid {
+ @media (max-width: $on-phone) {
+ flex-direction: column;
+ }
+}
diff --git a/assets/scss/partials/article.scss b/assets/scss/partials/article.scss
index bcc10c1..1f40673 100644
--- a/assets/scss/partials/article.scss
+++ b/assets/scss/partials/article.scss
@@ -11,8 +11,8 @@
border-radius: var(--card-border-radius);
overflow: hidden;
- transition: box-shadow .3s ease;
-
+ transition: box-shadow 0.3s ease;
+
&:hover {
box-shadow: var(--shadow-l2);
}
@@ -53,7 +53,7 @@
flex-direction: column;
justify-content: center;
- padding: var(--content-padding);
+ padding: var(--card-padding);
}
.article-category {
@@ -75,10 +75,14 @@
}
.article-title {
- font-size: 2.4rem;
font-weight: 600;
margin: 10px 0;
color: var(--card-text-color-main);
+ font-size: 2.2rem;
+
+ @media (min-width: $on-desktop-large) {
+ font-size: 2.4rem;
+ }
a {
color: var(--card-text-color-main);
@@ -88,14 +92,6 @@
}
}
- @media (min-width: $on-desktop-large) {
- font-size: 2.4rem;
- }
-
- @media (max-width: $on-desktop) {
- font-size: 2rem;
- }
-
& + .article-subtitle {
margin-top: 0;
}
@@ -103,18 +99,14 @@
.article-subtitle {
font-weight: normal;
- font-size: 1.8rem;
color: var(--card-text-color-secondary);
margin: 5px 0;
line-height: 1.5;
+ font-size: 1.75rem;
@media (min-width: $on-desktop-large) {
font-size: 2rem;
}
-
- @media (max-width: $on-desktop) {
- font-size: 1.6rem;
- }
}
.article-time {
@@ -125,9 +117,10 @@
svg {
vertical-align: middle;
- margin-right: 8px;
+ margin-right: 15px;
width: 20px;
height: 20px;
+ stroke-width: 1.33;
}
time {
@@ -159,24 +152,32 @@
border-radius: var(--card-border-radius);
box-shadow: var(--shadow-l1);
background-color: var(--card-background);
+ --image-size: 60px;
+
+ @media (max-width: $on-tablet) {
+ --image-size: 50px;
+ }
& + .pagination {
margin-top: var(--section-separation);
}
article {
- display: flex;
- align-items: center;
- padding: var(--small-card-padding);
+ & > a {
+ display: flex;
+ align-items: center;
+ padding: var(--small-card-padding);
+ }
&:not(:last-of-type) {
- border-bottom: 2px solid var(--card-separator-color);
+ border-bottom: 1.5px solid var(--card-separator-color);
}
.article-details {
flex-grow: 1;
padding: 0;
padding-right: 15px;
+ min-height: var(--image-size);
}
.article-title {
@@ -190,10 +191,21 @@
.article-image {
img {
- width: 60px;
- height: 60px;
+ width: var(--image-size);
+ height: var(--image-size);
}
}
+
+ .article-time {
+ font-size: 1.4rem;
+ }
+
+ .article-preview{
+ font-size: 1.4rem;
+ color: var(--card-text-color-tertiary);
+ margin-top: 10px;
+ line-height: 1.5;
+ }
}
}
diff --git a/assets/scss/partials/layout/404.scss b/assets/scss/partials/layout/404.scss
index c1c0db1..d9d8752 100644
--- a/assets/scss/partials/layout/404.scss
+++ b/assets/scss/partials/layout/404.scss
@@ -2,5 +2,5 @@
background-color: var(--card-background);
box-shadow: var(--shadow-l1);
border-radius: var(--card-border-radius);
- padding: var(--content-padding);
+ padding: var(--card-padding);
}
diff --git a/assets/scss/partials/layout/archives.scss b/assets/scss/partials/layout/archives.scss
index 569348e..2ac3836 100644
--- a/assets/scss/partials/layout/archives.scss
+++ b/assets/scss/partials/layout/archives.scss
@@ -1,15 +1,5 @@
.archives-group {
margin-bottom: var(--section-separation);
- .archives-date {
- text-transform: uppercase;
- margin-bottom: 10px;
- font-size: 1.6rem;
- font-weight: bold;
-
- a {
- color: var(--body-text-color);
- }
- }
}
.template-archives {
diff --git a/assets/scss/partials/layout/article.scss b/assets/scss/partials/layout/article.scss
index 76e0fad..c76fcce 100644
--- a/assets/scss/partials/layout/article.scss
+++ b/assets/scss/partials/layout/article.scss
@@ -43,7 +43,7 @@
}
}
- article {
+ .main-article {
background: var(--card-background);
border-radius: var(--card-border-radius);
box-shadow: var(--shadow-l1);
@@ -64,13 +64,13 @@
}
.article-details {
- padding: var(--content-padding);
+ padding: var(--card-padding);
padding-bottom: 0;
}
}
.article-content {
- margin: var(--content-padding) 0;
+ margin: var(--card-padding) 0;
color: var(--card-text-color-main);
img {
@@ -80,11 +80,11 @@
}
.article-footer {
- padding: var(--content-padding);
- padding-top: 0;
+ margin: var(--card-padding);
+ margin-top: 0;
section:not(:first-child) {
- margin-top: var(--content-padding);
+ margin-top: var(--card-padding);
}
section {
@@ -104,6 +104,7 @@
.article-tags {
flex-wrap: wrap;
+ text-transform: unset;
}
}
}
@@ -192,7 +193,7 @@
& > p {
margin: 1.5em 0;
- padding: 0 var(--content-padding);
+ padding: 0 var(--card-padding);
}
h1,
@@ -201,7 +202,7 @@
h4,
h5,
h6 {
- padding: 0 calc(var(--content-padding) - var(--heading-border-size));
+ padding: 0 calc(var(--card-padding) - var(--heading-border-size));
border-left: var(--heading-border-size) solid var(--accent-color);
}
@@ -219,13 +220,13 @@
position: relative;
margin: 10px 0;
border-left: var(--blockquote-border-size) solid var(--card-separator-color);
- padding: 15px calc(var(--content-padding) - var(--blockquote-border-size));
+ padding: 15px calc(var(--card-padding) - var(--blockquote-border-size));
background-color: var(--blockquote-background-color);
}
& > ul,
& > ol {
- margin: 1em var(--content-padding);
+ margin: 1em var(--card-padding);
}
hr {
@@ -270,7 +271,7 @@
font-family: var(--code-font-family);
line-height: 1.428571429;
word-break: break-all;
- padding: var(--content-padding);
+ padding: var(--card-padding);
code {
color: unset;
@@ -281,9 +282,9 @@
}
table {
- margin: 0 var(--content-padding);
+ margin: 0 var(--card-padding);
width: 100%;
- max-width: calc(100% - var(--content-padding) * 2);
+ max-width: calc(100% - var(--card-padding) * 2);
border-collapse: collapse;
border-spacing: 0;
margin-bottom: 1.5em;
diff --git a/assets/scss/partials/layout/search.scss b/assets/scss/partials/layout/search.scss
new file mode 100644
index 0000000..b390a7b
--- /dev/null
+++ b/assets/scss/partials/layout/search.scss
@@ -0,0 +1,82 @@
+.search-form {
+ margin-bottom: var(--section-separation);
+ position: relative;
+ --button-size: 80px;
+
+ &.widget {
+ --button-size: 60px;
+
+ label {
+ font-size: 1.3rem;
+ top: 10px;
+ }
+
+ input {
+ font-size: 1.5rem;
+ padding: 30px 20px 15px 20px;
+ }
+ }
+
+ p {
+ position: relative;
+ margin: 0;
+ }
+
+ label {
+ position: absolute;
+ top: 15px;
+ left: 20px;
+ font-size: 1.4rem;
+ color: var(--card-text-color-tertiary);
+ }
+
+ input {
+ padding: 40px 20px 20px;
+ border-radius: var(--card-border-radius);
+ background-color: var(--card-background);
+ box-shadow: var(--shadow-l1);
+ color: var(--card-text-color-main);
+ width: 100%;
+ border: 0;
+ -webkit-appearance: none;
+
+ transition: box-shadow 0.3s ease;
+
+ font-size: 1.8rem;
+
+ &:focus {
+ outline: 0;
+ box-shadow: var(--shadow-l2);
+ }
+ }
+
+ button {
+ position: absolute;
+ right: 0;
+ top: 0;
+ height: 100%;
+ width: var(--button-size);
+ cursor: pointer;
+ background-color: transparent;
+ border: 0;
+
+ padding: 0 10px;
+
+ &:focus {
+ outline: 0;
+
+ svg {
+ stroke-width: 2;
+ color: var(--accent-color);
+ }
+ }
+
+ svg {
+ color: var(--card-text-color-secondary);
+ stroke-width: 1.33;
+ transition: all 0.3s ease;
+ width: 20px;
+ height: 20px;
+ }
+ }
+}
\ No newline at end of file
diff --git a/assets/scss/partials/layout/taxonomy.scss b/assets/scss/partials/layout/taxonomy.scss
index d594b23..4575a20 100644
--- a/assets/scss/partials/layout/taxonomy.scss
+++ b/assets/scss/partials/layout/taxonomy.scss
@@ -1,11 +1,3 @@
-.taxonomy-type {
- text-transform: uppercase;
- color: var(--body-text-color);
- font-weight: bold;
- margin-bottom: 5px;
- font-size: 1.6rem;
-}
-
.taxonomy-card {
border-radius: var(--card-border-radius);
background-color: var(--card-background);
diff --git a/assets/scss/partials/menu.scss b/assets/scss/partials/menu.scss
index 7cb7e83..7aa5acd 100644
--- a/assets/scss/partials/menu.scss
+++ b/assets/scss/partials/menu.scss
@@ -126,9 +126,11 @@
list-style: none;
display: flex;
flex-direction: column;
- margin-top: 25px;
+ margin-top: var(--sidebar-element-separation);
+ margin-bottom: 0;
overflow-y: auto;
flex-grow: 1;
+ font-size: 1.5rem;
@media (min-width: $on-desktop-large) {
margin-top: 30px;
@@ -180,6 +182,11 @@
height: 25px;
stroke-width: 1.33;
margin-right: 40px;
+
+ @media (max-width: $on-desktop-large) {
+ width: 20px;
+ height: 20px;
+ }
}
a {
@@ -187,9 +194,8 @@
display: inline-flex;
align-items: center;
color: var(--body-text-color);
- font-size: 1.5rem;
- @media (max-width: $on-desktop) {
+ @media (max-width: $on-desktop-large) {
font-size: 1.4rem;
}
}
diff --git a/assets/scss/partials/sidebar.scss b/assets/scss/partials/sidebar.scss
index c1ddeb1..c967879 100644
--- a/assets/scss/partials/sidebar.scss
+++ b/assets/scss/partials/sidebar.scss
@@ -12,6 +12,14 @@
flex-direction: column;
flex-shrink: 0;
+ --sidebar-avatar-size: 150px;
+ --sidebar-element-separation: 25px;
+
+ @media (max-width: $on-desktop-large) {
+ --sidebar-avatar-size: 120px;
+ --sidebar-element-separation: 20px;
+ }
+
@media (max-width: $on-phone) {
width: 100%;
padding: 30px 0;
@@ -23,19 +31,10 @@
}
@media (min-width: $on-phone + 1) {
- width: 25%;
margin-right: 1%;
padding: var(--main-top-padding) 15px;
height: 100vh;
}
-
- @media (min-width: $on-desktop) {
- width: 20%;
- }
-
- @media (min-width: $on-desktop-large) {
- width: 15%;
- }
}
.right-sidebar {
@@ -50,19 +49,15 @@
}
@media (min-width: $on-tablet) {
- width: 25%;
margin-left: 1%;
- padding-top: 50px;
- }
-
- @media (min-width: $on-desktop + 1) {
- width: 25%;
+ padding-top: var(--main-top-padding);
}
}
.site-info {
z-index: 1;
transition: box-shadow 0.5s ease;
+
@media (max-width: $on-phone) {
padding: 15px 30px;
}
@@ -70,14 +65,10 @@
.site-avatar {
position: relative;
margin: 0;
- margin-bottom: 25px;
- width: 150px;
- height: 150px;
+ width: var(--sidebar-avatar-size);
+ height: var(--sidebar-avatar-size);
- @media (max-width: $on-desktop-large) {
- height: 120px;
- width: 120px;
- }
+ margin-bottom: var(--sidebar-element-separation);
.site-logo {
width: 100%;
@@ -131,16 +122,15 @@
.sidebar {
.widget {
- &:not(:last-of-type) {
- margin-bottom: var(--section-separation);
- &:after {
- content: "";
- width: 100px;
- height: 2px;
- background-color: var(--body-text-color);
- display: block;
- margin-top: var(--section-separation);
- }
+ margin-bottom: var(--section-separation);
+
+ &:not(:last-of-type):after {
+ content: "";
+ width: 100px;
+ height: 2px;
+ background-color: var(--body-text-color);
+ display: block;
+ margin-top: var(--section-separation);
}
}
}
diff --git a/assets/scss/partials/widgets.scss b/assets/scss/partials/widgets.scss
index c80c8ad..33a02dd 100644
--- a/assets/scss/partials/widgets.scss
+++ b/assets/scss/partials/widgets.scss
@@ -1,13 +1,4 @@
.widget {
- .widget-title {
- text-transform: uppercase;
- color: var(--body-text-color);
- font-weight: bold;
- margin: 0;
- margin-bottom: 10px;
- font-size: 1.6rem;
- }
-
.widget-icon {
svg {
width: 32px;
@@ -45,28 +36,26 @@
/* Archives widget */
.widget.archives {
+ .widget-archive--list {
+ border-radius: var(--card-border-radius);
+ box-shadow: var(--shadow-l1);
+ background-color: var(--card-background);
+ }
+
.archives-year {
- margin-bottom: 10px;
+ &:not(:last-of-type) {
+ border-bottom: 1.5px solid var(--card-separator-color);
+ }
+
a {
- background-color: var(--card-background);
- padding: 15px 25px;
- border-radius: var(--card-border-radius);
- box-shadow: var(--shadow-l1);
+ font-size: 1.4rem;
+ padding: 18px 25px;
display: flex;
- transition: box-shadow 0.3s ease;
-
- &:hover {
- box-shadow: var(--shadow-l2);
- }
-
- @media (max-width: $on-desktop-large) {
- padding: 12px 20px;
- font-size: 1.4rem;
- }
span.year {
flex: 1;
color: var(--card-text-color-main);
+ font-weight: bold;
}
span.count {
diff --git a/assets/scss/style.scss b/assets/scss/style.scss
index 908be5e..3e4b56a 100644
--- a/assets/scss/style.scss
+++ b/assets/scss/style.scss
@@ -8,6 +8,7 @@
@import "breakpoints.scss";
@import "variables.scss";
+@import "grid.scss";
@import "external/normalize.scss";
@@ -22,6 +23,9 @@
@import "partials/layout/article.scss";
@import "partials/layout/taxonomy.scss";
@import "partials/layout/404.scss";
+@import "partials/layout/search.scss";
+
+@import "custom.scss";
a {
text-decoration: none;
@@ -41,96 +45,16 @@ a {
}
}
-.container {
- margin-left: auto;
- margin-right: auto;
+.section-title {
+ text-transform: uppercase;
+ margin-top: 0;
+ margin-bottom: 10px;
+ display: block;
+ font-size: 1.6rem;
+ font-weight: bold;
+ color: var(--body-text-color);
- &.extended {
- @media (min-width: $on-phone) {
- max-width: 800px;
- }
-
- @media (min-width: $on-tablet) {
- max-width: 972px;
- }
-
- @media (min-width: $on-desktop) {
- max-width: 1200px;
- }
-
- @media (min-width: $on-desktop-large) {
- max-width: 1536px;
- }
- }
-}
-
-main.main {
- min-width: 0;
- padding: 0 15px;
- max-width: 100%;
- flex-grow: 1;
- padding-top: var(--main-top-padding);
-}
-
-.main-grid {
- @media (max-width: $on-phone) {
- flex-direction: column;
- }
-}
-
-.flex {
- display: flex;
- flex-direction: row;
-
- &.column {
- flex-direction: column;
- }
-
- &.on-phone--column {
- @media (max-width: $on-phone) {
- flex-direction: column;
- }
- }
-
- &.align-items--flex-start {
- align-items: flex-start;
- }
-
- .grow {
- flex-grow: 1;
- }
-
- .do-not-shrink {
- flex-shrink: 0;
- }
-
- .do-not-overflow {
- min-width: 0;
- flex-shrink: 1;
- max-width: 100%;
- }
-
- .full-width {
- width: 100%;
- }
-}
-
-.alert {
- position: fixed;
- right: 20px;
- bottom: 20px;
- z-index: 5;
- background: var(--card-background);
- max-width: 400px;
- padding: 15px 20px;
- border-radius: var(--card-border-radius);
- line-height: 1.75;
- color: var(--card-text-color-secondary);
- box-shadow: var(--shadow-l2);
-
- @media (max-width: $on-phone) {
- max-width: 100vw;
- width: calc(100% - 30px);
- left: 15px;
+ a {
+ color: var(--body-text-color);
}
}
diff --git a/assets/scss/variables.scss b/assets/scss/variables.scss
index 3e886fb..495ef1a 100644
--- a/assets/scss/variables.scss
+++ b/assets/scss/variables.scss
@@ -6,9 +6,14 @@ $defaultTagColors: #fff, #fff, #fff, #fff, #fff;
*/
:root {
@media (min-width: $on-phone + 1) {
+ --main-top-padding: 35px;
+ }
+
+ @media (min-width: $on-desktop-large) {
--main-top-padding: 50px;
}
+
--body-background: #f5f5fa;
--accent-color: #34495e;
@@ -54,15 +59,18 @@ $defaultTagColors: #fff, #fff, #fff, #fff, #fff;
--card-border-radius: 10px;
- --content-padding: 30px;
+ --card-padding: 30px;
@media (max-width: $on-desktop-large) {
- --content-padding: 25px;
+ --card-padding: 25px;
}
@media (max-width: $on-tablet) {
- --content-padding: 20px;
+ --card-padding: 20px;
}
--small-card-padding: 25px;
+ @media (max-width: $on-tablet) {
+ --small-card-padding: 25px 20px;
+ }
.theme-dark {
--card-background: #424242;
diff --git a/assets/ts/createElement.ts b/assets/ts/createElement.ts
new file mode 100644
index 0000000..3a1e85e
--- /dev/null
+++ b/assets/ts/createElement.ts
@@ -0,0 +1,34 @@
+/**
+ * createElement
+ * Edited from:
+ * @link https://stackoverflow.com/a/42405694
+ */
+function createElement(tag, attrs, children) {
+ var element = document.createElement(tag);
+
+ for (let name in attrs) {
+ if (name && attrs.hasOwnProperty(name)) {
+ let value = attrs[name];
+
+ if (name == "dangerouslySetInnerHTML") {
+ element.innerHTML = value.__html;
+ }
+ else if (value === true) {
+ element.setAttribute(name, name);
+ } else if (value !== false && value != null) {
+ element.setAttribute(name, value.toString());
+ }
+ }
+ }
+ for (let i = 2; i < arguments.length; i++) {
+ let child = arguments[i];
+ if (child) {
+ element.appendChild(
+ child.nodeType == null ?
+ document.createTextNode(child.toString()) : child);
+ }
+ }
+ return element;
+}
+
+export default createElement;
\ No newline at end of file
diff --git a/assets/ts/gallery.ts b/assets/ts/gallery.ts
index b9871ff..41fba8e 100644
--- a/assets/ts/gallery.ts
+++ b/assets/ts/gallery.ts
@@ -236,10 +236,10 @@ function wrap(gallery: HTMLElement[]) {
*/
function loadPhotoSwipe() {
const tasks = [
- loadScript("https://cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/photoswipe.min.js"),
- loadScript("https://cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/photoswipe-ui-default.min.js"),
- loadStyle("https://cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/photoswipe.min.css"),
- loadStyle("https://cdnjs.cloudflare.com/ajax/libs/photoswipe/4.1.3/default-skin/default-skin.min.css")
+ loadScript("https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe.min.js"),
+ loadScript("https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe-ui-default.min.js"),
+ loadStyle("https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe.min.css"),
+ loadStyle("https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/default-skin/default-skin.min.css")
];
return Promise.all(tasks);
diff --git a/assets/ts/main.ts b/assets/ts/main.ts
index 8d60b07..168f16b 100644
--- a/assets/ts/main.ts
+++ b/assets/ts/main.ts
@@ -10,6 +10,7 @@ import { createGallery } from "./gallery"
import { getColor } from './color';
import menu from './menu';
import darkmode from "./darkmode";
+import createElement from './createElement';
let Stack = {
init: () => {
@@ -68,29 +69,6 @@ let Stack = {
observer.observe(articleTile)
}
- },
- alert: (content, time = 5000, animationSpeed = 500) => {
- const alert = document.createElement('div');
- alert.innerHTML = content;
- alert.className = 'alert';
- alert.style.visibility = 'hidden';
- document.body.appendChild(alert);
-
- alert.style.transform = `translateY(${alert.clientHeight + 50}px)`;
- alert.style.transition = `transform ${animationSpeed / 1000}s ease`;
-
- setTimeout(() => {
- alert.style.removeProperty('visibility');
- alert.style.transform = `translateY(0)`;
- }, animationSpeed);
-
- setTimeout(() => {
- alert.style.transform = `translateY(${alert.clientHeight + 50}px)`;
- }, animationSpeed + time);
-
- setTimeout(() => {
- alert.remove();
- }, 2 * animationSpeed + time);
}
}
@@ -100,4 +78,12 @@ window.addEventListener('load', () => {
}, 0);
})
-window.Stack = Stack;
\ No newline at end of file
+declare global {
+ interface Window {
+ createElement: any;
+ Stack: any
+ }
+}
+
+window.Stack = Stack;
+window.createElement = createElement;
\ No newline at end of file
diff --git a/assets/ts/search.tsx b/assets/ts/search.tsx
new file mode 100644
index 0000000..8e4eb6f
--- /dev/null
+++ b/assets/ts/search.tsx
@@ -0,0 +1,263 @@
+interface pageData {
+ title: string,
+ date: string,
+ permalink: string,
+ content: string,
+ image?: string,
+ preview: string,
+ matchCount: number
+}
+
+/**
+ * Escape HTML tags as HTML entities
+ * Edited from:
+ * @link https://stackoverflow.com/a/5499821
+ */
+const tagsToReplace = {
+ '&': '&',
+ '<': '<',
+ '>': '>',
+ '"': '"',
+ '…': '…'
+};
+
+function replaceTag(tag) {
+ return tagsToReplace[tag] || tag;
+}
+
+function replaceHTMLEnt(str) {
+ return str.replace(/[&<>"]/g, replaceTag);
+}
+
+function escapeRegExp(string) {
+ return string.replace(/[.*+\-?^${}()|[\]\\]/g, '\\$&');
+}
+
+class Search {
+ private data: pageData[];
+ private form: HTMLFormElement;
+ private input: HTMLInputElement;
+ private list: HTMLDivElement;
+ private resultTitle: HTMLHeadElement;
+ private resultTitleTemplate: string;
+
+ constructor({ form, input, list, resultTitle, resultTitleTemplate }) {
+ this.form = form;
+ this.input = input;
+ this.list = list;
+ this.resultTitle = resultTitle;
+ this.resultTitleTemplate = resultTitleTemplate;
+
+ this.handleQueryString();
+ this.bindQueryStringChange();
+ 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
+ });
+
+ for (const item of rawData) {
+ 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)} `;
+ }
+ }
+ }
+
+ if (matched) {
+ result.preview += '[...]';
+ results.push(result);
+ }
+ }
+
+ /** Result with more matches appears first */
+ return results.sort((a, b) => {
+ return b.matchCount - a.matchCount;
+ });
+ }
+
+ public static marker(match) {
+ return '' + match + '';
+ }
+
+ private async doSearch(keywords: string[]) {
+ const startTime = performance.now();
+
+ const results = await this.searchKeywords(keywords);
+ this.clear();
+
+ for (const item of results) {
+ this.list.append(Search.render(item));
+ }
+
+ const endTime = performance.now();
+
+ this.resultTitle.innerText = this.generateResultTitle(results.length, ((endTime - startTime) / 1000).toPrecision(1));
+ }
+
+ private generateResultTitle(resultLen, time) {
+ return this.resultTitleTemplate.replace("#PAGES_COUNT", resultLen).replace("#TIME_SECONDS", time);
+ }
+
+ public async getData() {
+ if (!this.data) {
+ /// Not fetched yet
+ const jsonURL = this.form.dataset.json;
+ this.data = await fetch(jsonURL).then(res => res.json());
+ }
+
+ return this.data;
+ }
+
+ private bindSearchForm() {
+ let lastSearch = '';
+
+ const eventHandler = (e) => {
+ e.preventDefault();
+ const keywords = this.input.value;
+
+ Search.updateQueryString(keywords, true);
+
+ if (keywords === '') {
+ return this.clear();
+ }
+
+ if (lastSearch === keywords) return;
+ lastSearch = keywords;
+
+ this.doSearch(keywords.split(' '));
+ }
+
+ this.input.addEventListener('input', eventHandler);
+ this.input.addEventListener('compositionend', eventHandler);
+ }
+
+ private clear() {
+ this.list.innerHTML = '';
+ this.resultTitle.innerText = '';
+ }
+
+ private bindQueryStringChange() {
+ window.addEventListener('popstate', (e) => {
+ this.handleQueryString()
+ })
+ }
+
+ private handleQueryString() {
+ const pageURL = new URL(window.location.toString());
+ const keywords = pageURL.searchParams.get('keyword');
+ this.input.value = keywords;
+
+ if (keywords) {
+ this.doSearch(keywords.split(' '));
+ }
+ else {
+ this.clear()
+ }
+ }
+
+ private static updateQueryString(keywords: string, replaceState = false) {
+ const pageURL = new URL(window.location.toString());
+
+ if (keywords === '') {
+ pageURL.searchParams.delete('keyword')
+ }
+ else {
+ pageURL.searchParams.set('keyword', keywords);
+ }
+
+ if (replaceState) {
+ window.history.replaceState('', '', pageURL.toString());
+ }
+ else {
+ window.history.pushState('', '', pageURL.toString());
+ }
+ }
+
+ public static render(item: pageData) {
+ return
+
-
----
-
## YouTube Privacy Enhanced Shortcode
{{< youtube ZJthWmvUzzc >}}
diff --git a/i18n/en.toml b/i18n/en.toml
deleted file mode 100644
index f00f938..0000000
--- a/i18n/en.toml
+++ /dev/null
@@ -1,26 +0,0 @@
-[toggleMenu]
- other = "Toggle Menu"
-
-[relatedContents]
- other = "Related contents"
-
-[lastUpdatedOn]
- other ="Last updated on {{ .Count }}"
-
-[widgetArchivesTitle]
- other = "Archives"
-
-[widgetArchivesMore]
- other = "More"
-
-[widgetTagCloudTitle]
- other = "Tags"
-
-[notFoundTitle]
- other = "Not Found"
-
-[notFoundSubtitle]
- other = "This page does not exist."
-
-[darkModeToggle]
- other = "Dark Mode"
\ No newline at end of file
diff --git a/i18n/en.yaml b/i18n/en.yaml
new file mode 100644
index 0000000..1adc0d2
--- /dev/null
+++ b/i18n/en.yaml
@@ -0,0 +1,46 @@
+toggleMenu:
+ other: Toggle Menu
+
+darkMode:
+ toggle:
+ other: Dark Mode
+
+archives:
+ categories:
+ other: Categories
+
+article:
+ relatedContents:
+ other: Related contents
+ lastUpdatedOn:
+ other: Last updated on
+
+notFound:
+ title:
+ other: Not Found
+ subtitle:
+ other: This page does not exist.
+
+widget:
+ archives:
+ title:
+ other: Archives
+ more:
+ other: More
+ tagCloud:
+ title:
+ other: Tags
+
+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 }}
diff --git a/i18n/fr.yaml b/i18n/fr.yaml
new file mode 100644
index 0000000..59cc111
--- /dev/null
+++ b/i18n/fr.yaml
@@ -0,0 +1,38 @@
+toggleMenu:
+ other: Afficher le menu
+
+article:
+ relatedContents:
+ other: Contenus liés
+ lastUpdatedOn:
+ other: Dernière mise à jour le
+
+notFound:
+ title:
+ other: Page non trouvée
+ subtitle:
+ other: Cette page n'existe pas.
+
+widget:
+ archives:
+ title:
+ other: Archives
+ more:
+ other: Autres
+ tagCloud:
+ title:
+ other: Mots clés
+
+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 }}
diff --git a/i18n/id.yaml b/i18n/id.yaml
new file mode 100644
index 0000000..31cea39
--- /dev/null
+++ b/i18n/id.yaml
@@ -0,0 +1,38 @@
+toggleMenu:
+ other: Tampilkan Menu
+
+article:
+ relatedContents:
+ other: Konten terkait
+ lastUpdatedOn:
+ other: Terakhir diperbarui pada
+
+notFound:
+ title:
+ other: Not Found
+ subtitle:
+ other: Halaman ini tidak ada.
+
+widget:
+ archives:
+ title:
+ other: Arsip
+ more:
+ other: Lebih
+ tagCloud:
+ title:
+ other: Tag
+
+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 }}
diff --git a/i18n/ja.yaml b/i18n/ja.yaml
new file mode 100644
index 0000000..e982ce3
--- /dev/null
+++ b/i18n/ja.yaml
@@ -0,0 +1,24 @@
+toggleMenu:
+ other: メニューを開く・閉じる
+
+article:
+ relatedContents:
+ other: 関連するコンテンツ
+ lastUpdatedOn:
+ other: 最終更新
+
+notFound:
+ title:
+ other: 404 Not Found
+ subtitle:
+ other: 指定されたページは存在しません。
+
+widget:
+ archives:
+ title:
+ other: アーカイブ
+ more:
+ other: さらに見る
+ tagCloud:
+ title:
+ other: タグ
diff --git a/i18n/ko.yaml b/i18n/ko.yaml
new file mode 100644
index 0000000..d9916cd
--- /dev/null
+++ b/i18n/ko.yaml
@@ -0,0 +1,38 @@
+toggleMenu:
+ other: 메뉴 여닫기
+
+article:
+ relatedContents:
+ other: 관련 글
+ lastUpdatedOn:
+ other: "마지막 수정: "
+
+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: "{{ .DesignedBy }}의 {{ .Theme }} 테마 사용 중"
diff --git a/i18n/pt-BR.yaml b/i18n/pt-BR.yaml
new file mode 100644
index 0000000..5380c68
--- /dev/null
+++ b/i18n/pt-BR.yaml
@@ -0,0 +1,42 @@
+toggleMenu:
+ other: Alternar Menu
+
+archives:
+ categories:
+ other: Categorias
+
+article:
+ relatedContents:
+ other: Conteúdos Relacionados
+ lastUpdatedOn:
+ other: Última atualização em
+
+notFound:
+ title:
+ other: Não Encontrado
+ subtitle:
+ other: Esta página não existe.
+
+widget:
+ archives:
+ title:
+ other: Arquivos
+ more:
+ other: Mais
+ tagCloud:
+ title:
+ other: Tags
+
+search:
+ title:
+ other: Busca
+ placeholder:
+ other: Digite algo...
+ resultTitle:
+ other: "#PAGES_COUNT páginas (#TIME_SECONDS segundos)"
+
+footer:
+ builtWith:
+ other: Criado com {{ .Generator }}
+ designedBy:
+ other: Tema {{ .Theme }} desenvolvido por {{ .DesignedBy }}
diff --git a/i18n/zh-CN.toml b/i18n/zh-CN.toml
deleted file mode 100644
index a4e280c..0000000
--- a/i18n/zh-CN.toml
+++ /dev/null
@@ -1,26 +0,0 @@
-[toggleMenu]
- other = "切换菜单"
-
-[relatedContents]
- other = "相关文章"
-
-[lastUpdatedOn]
- other ="最后更新于 {{ .Count }}"
-
-[widgetArchivesTitle]
- other = "归档"
-
-[widgetArchivesMore]
- other = "更多"
-
-[widgetTagCloudTitle]
- other = "标签云"
-
-[notFoundTitle]
- other = "404 错误"
-
-[notFoundSubtitle]
- other = "页面不存在"
-
-[darkModeToggle]
- other = "暗色模式"
\ No newline at end of file
diff --git a/i18n/zh-CN.yaml b/i18n/zh-CN.yaml
new file mode 100644
index 0000000..44b7dc8
--- /dev/null
+++ b/i18n/zh-CN.yaml
@@ -0,0 +1,40 @@
+toggleMenu:
+ other: 切换菜单
+
+darkMode:
+ toggle:
+ other: 暗色模式
+
+archives:
+ categories:
+ other: 分类
+
+article:
+ relatedContents:
+ other: 相关文章
+ lastUpdatedOn:
+ other: 最后更新于
+
+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 秒)"
diff --git a/layouts/404.html b/layouts/404.html
index dd5e49d..b89d2b6 100644
--- a/layouts/404.html
+++ b/layouts/404.html
@@ -1,7 +1,7 @@
{{ define "main" }}