mirror of
https://github.com/CaiJimmy/hugo-theme-stack.git
synced 2025-06-18 20:13:31 +08:00
Merge branch 'master' into MTRNord/cactus
This commit is contained in:
commit
d6ab5ed3c3
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,3 +1,4 @@
|
|||||||
public
|
public
|
||||||
resources
|
resources
|
||||||
assets/jsconfig.json
|
assets/jsconfig.json
|
||||||
|
.hugo_build.lock
|
@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
## Demo
|
## Demo
|
||||||
|
|
||||||
[Example Site](https://theme-stack.jimmycai.com/)
|
[Example Site](https://demo.stack.jimmycai.com/)
|
||||||
|
|
||||||
[](https://app.netlify.com/sites/hugo-theme-stack/deploys)
|
[](https://app.netlify.com/sites/hugo-theme-stack/deploys)
|
||||||
|
|
||||||
@ -35,7 +35,10 @@ It's necessary to use **Hugo Extended ≥ 0.87.0**.
|
|||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
Clone / Download this repository to `theme` folder, and edit your site config following `exampleSite/config.yaml`.
|
* Route 1: Clone / Download this repository to `theme` folder
|
||||||
|
* Route 2: Turn your site into a hugo module and add this theme as a module dependency
|
||||||
|
|
||||||
|
Edit your site config following `exampleSite/config.yaml`.
|
||||||
|
|
||||||
*Note: Remove `config.toml` if there is one in the site folder.*
|
*Note: Remove `config.toml` if there is one in the site folder.*
|
||||||
|
|
||||||
@ -77,4 +80,4 @@ Some references that I took while building this theme:
|
|||||||
| Project | Licence|
|
| Project | Licence|
|
||||||
| ------- | ------|
|
| ------- | ------|
|
||||||
| [artchen/hexo-theme-element](https://github.com/artchen/hexo-theme-element) | [MIT](https://github.com/artchen/hexo-theme-element/blob/master/LICENSE) |
|
| [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) |
|
| [MunifTanjim/minimo](https://github.com/MunifTanjim/minimo) | [MIT](https://github.com/MunifTanjim/minimo/blob/master/LICENSE) |
|
||||||
|
@ -6,5 +6,7 @@
|
|||||||
"*"
|
"*"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"lib": ["es2020", "dom"],
|
||||||
|
"jsx": "preserve"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,7 +1,6 @@
|
|||||||
html {
|
html {
|
||||||
font-size: 62.5%;
|
font-size: 62.5%;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
scroll-behavior: smooth;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
@ -23,17 +22,3 @@ body {
|
|||||||
scrollbar-color: var(--scrollbar-thumb) transparent;
|
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;
|
|
||||||
}
|
|
||||||
/**/
|
|
||||||
|
@ -10,7 +10,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Other */
|
/* Other */
|
||||||
.chroma .x {}
|
.chroma .x {
|
||||||
|
}
|
||||||
|
|
||||||
/* Error */
|
/* Error */
|
||||||
.chroma .err {
|
.chroma .err {
|
||||||
@ -40,367 +41,369 @@
|
|||||||
.chroma .hl {
|
.chroma .hl {
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background-color: #ffffcc
|
background-color: #ffffcc;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* LineNumbersTable */
|
/* LineNumbersTable */
|
||||||
.chroma .lnt {
|
.chroma .lnt {
|
||||||
margin-right: 0.4em;
|
margin-right: 0.4em;
|
||||||
padding: 0 0.4em 0 0.4em;
|
padding: 0 0.4em 0 0.4em;
|
||||||
color: #7f7f7f
|
color: #7f7f7f;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* LineNumbers */
|
/* LineNumbers */
|
||||||
.chroma .ln {
|
.chroma .ln {
|
||||||
margin-right: 0.4em;
|
margin-right: 0.4em;
|
||||||
padding: 0 0.4em 0 0.4em;
|
padding: 0 0.4em 0 0.4em;
|
||||||
color: #7f7f7f
|
color: #7f7f7f;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Keyword */
|
/* Keyword */
|
||||||
.chroma .k {
|
.chroma .k {
|
||||||
color: #00a8c8
|
color: #00a8c8;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* KeywordConstant */
|
/* KeywordConstant */
|
||||||
.chroma .kc {
|
.chroma .kc {
|
||||||
color: #00a8c8
|
color: #00a8c8;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* KeywordDeclaration */
|
/* KeywordDeclaration */
|
||||||
.chroma .kd {
|
.chroma .kd {
|
||||||
color: #00a8c8
|
color: #00a8c8;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* KeywordNamespace */
|
/* KeywordNamespace */
|
||||||
.chroma .kn {
|
.chroma .kn {
|
||||||
color: #f92672
|
color: #f92672;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* KeywordPseudo */
|
/* KeywordPseudo */
|
||||||
.chroma .kp {
|
.chroma .kp {
|
||||||
color: #00a8c8
|
color: #00a8c8;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* KeywordReserved */
|
/* KeywordReserved */
|
||||||
.chroma .kr {
|
.chroma .kr {
|
||||||
color: #00a8c8
|
color: #00a8c8;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* KeywordType */
|
/* KeywordType */
|
||||||
.chroma .kt {
|
.chroma .kt {
|
||||||
color: #00a8c8
|
color: #00a8c8;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Name */
|
/* Name */
|
||||||
.chroma .n {
|
.chroma .n {
|
||||||
color: #111111
|
color: #111111;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* NameAttribute */
|
/* NameAttribute */
|
||||||
.chroma .na {
|
.chroma .na {
|
||||||
color: #75af00
|
color: #75af00;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* NameBuiltin */
|
/* NameBuiltin */
|
||||||
.chroma .nb {
|
.chroma .nb {
|
||||||
color: #111111
|
color: #111111;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* NameBuiltinPseudo */
|
/* NameBuiltinPseudo */
|
||||||
.chroma .bp {
|
.chroma .bp {
|
||||||
color: #111111
|
color: #111111;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* NameClass */
|
/* NameClass */
|
||||||
.chroma .nc {
|
.chroma .nc {
|
||||||
color: #75af00
|
color: #75af00;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* NameConstant */
|
/* NameConstant */
|
||||||
.chroma .no {
|
.chroma .no {
|
||||||
color: #00a8c8
|
color: #00a8c8;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* NameDecorator */
|
/* NameDecorator */
|
||||||
.chroma .nd {
|
.chroma .nd {
|
||||||
color: #75af00
|
color: #75af00;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* NameEntity */
|
/* NameEntity */
|
||||||
.chroma .ni {
|
.chroma .ni {
|
||||||
color: #111111
|
color: #111111;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* NameException */
|
/* NameException */
|
||||||
.chroma .ne {
|
.chroma .ne {
|
||||||
color: #75af00
|
color: #75af00;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* NameFunction */
|
/* NameFunction */
|
||||||
.chroma .nf {
|
.chroma .nf {
|
||||||
color: #75af00
|
color: #75af00;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* NameFunctionMagic */
|
/* NameFunctionMagic */
|
||||||
.chroma .fm {
|
.chroma .fm {
|
||||||
color: #111111
|
color: #111111;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* NameLabel */
|
/* NameLabel */
|
||||||
.chroma .nl {
|
.chroma .nl {
|
||||||
color: #111111
|
color: #111111;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* NameNamespace */
|
/* NameNamespace */
|
||||||
.chroma .nn {
|
.chroma .nn {
|
||||||
color: #111111
|
color: #111111;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* NameOther */
|
/* NameOther */
|
||||||
.chroma .nx {
|
.chroma .nx {
|
||||||
color: #75af00
|
color: #75af00;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* NameProperty */
|
/* NameProperty */
|
||||||
.chroma .py {
|
.chroma .py {
|
||||||
color: #111111
|
color: #111111;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* NameTag */
|
/* NameTag */
|
||||||
.chroma .nt {
|
.chroma .nt {
|
||||||
color: #f92672
|
color: #f92672;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* NameVariable */
|
/* NameVariable */
|
||||||
.chroma .nv {
|
.chroma .nv {
|
||||||
color: #111111
|
color: #111111;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* NameVariableClass */
|
/* NameVariableClass */
|
||||||
.chroma .vc {
|
.chroma .vc {
|
||||||
color: #111111
|
color: #111111;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* NameVariableGlobal */
|
/* NameVariableGlobal */
|
||||||
.chroma .vg {
|
.chroma .vg {
|
||||||
color: #111111
|
color: #111111;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* NameVariableInstance */
|
/* NameVariableInstance */
|
||||||
.chroma .vi {
|
.chroma .vi {
|
||||||
color: #111111
|
color: #111111;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* NameVariableMagic */
|
/* NameVariableMagic */
|
||||||
.chroma .vm {
|
.chroma .vm {
|
||||||
color: #111111
|
color: #111111;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Literal */
|
/* Literal */
|
||||||
.chroma .l {
|
.chroma .l {
|
||||||
color: #ae81ff
|
color: #ae81ff;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* LiteralDate */
|
/* LiteralDate */
|
||||||
.chroma .ld {
|
.chroma .ld {
|
||||||
color: #d88200
|
color: #d88200;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* LiteralString */
|
/* LiteralString */
|
||||||
.chroma .s {
|
.chroma .s {
|
||||||
color: #d88200
|
color: #d88200;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* LiteralStringAffix */
|
/* LiteralStringAffix */
|
||||||
.chroma .sa {
|
.chroma .sa {
|
||||||
color: #d88200
|
color: #d88200;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* LiteralStringBacktick */
|
/* LiteralStringBacktick */
|
||||||
.chroma .sb {
|
.chroma .sb {
|
||||||
color: #d88200
|
color: #d88200;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* LiteralStringChar */
|
/* LiteralStringChar */
|
||||||
.chroma .sc {
|
.chroma .sc {
|
||||||
color: #d88200
|
color: #d88200;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* LiteralStringDelimiter */
|
/* LiteralStringDelimiter */
|
||||||
.chroma .dl {
|
.chroma .dl {
|
||||||
color: #d88200
|
color: #d88200;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* LiteralStringDoc */
|
/* LiteralStringDoc */
|
||||||
.chroma .sd {
|
.chroma .sd {
|
||||||
color: #d88200
|
color: #d88200;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* LiteralStringDouble */
|
/* LiteralStringDouble */
|
||||||
.chroma .s2 {
|
.chroma .s2 {
|
||||||
color: #d88200
|
color: #d88200;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* LiteralStringEscape */
|
/* LiteralStringEscape */
|
||||||
.chroma .se {
|
.chroma .se {
|
||||||
color: #8045ff
|
color: #8045ff;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* LiteralStringHeredoc */
|
/* LiteralStringHeredoc */
|
||||||
.chroma .sh {
|
.chroma .sh {
|
||||||
color: #d88200
|
color: #d88200;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* LiteralStringInterpol */
|
/* LiteralStringInterpol */
|
||||||
.chroma .si {
|
.chroma .si {
|
||||||
color: #d88200
|
color: #d88200;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* LiteralStringOther */
|
/* LiteralStringOther */
|
||||||
.chroma .sx {
|
.chroma .sx {
|
||||||
color: #d88200
|
color: #d88200;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* LiteralStringRegex */
|
/* LiteralStringRegex */
|
||||||
.chroma .sr {
|
.chroma .sr {
|
||||||
color: #d88200
|
color: #d88200;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* LiteralStringSingle */
|
/* LiteralStringSingle */
|
||||||
.chroma .s1 {
|
.chroma .s1 {
|
||||||
color: #d88200
|
color: #d88200;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* LiteralStringSymbol */
|
/* LiteralStringSymbol */
|
||||||
.chroma .ss {
|
.chroma .ss {
|
||||||
color: #d88200
|
color: #d88200;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* LiteralNumber */
|
/* LiteralNumber */
|
||||||
.chroma .m {
|
.chroma .m {
|
||||||
color: #ae81ff
|
color: #ae81ff;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* LiteralNumberBin */
|
/* LiteralNumberBin */
|
||||||
.chroma .mb {
|
.chroma .mb {
|
||||||
color: #ae81ff
|
color: #ae81ff;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* LiteralNumberFloat */
|
/* LiteralNumberFloat */
|
||||||
.chroma .mf {
|
.chroma .mf {
|
||||||
color: #ae81ff
|
color: #ae81ff;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* LiteralNumberHex */
|
/* LiteralNumberHex */
|
||||||
.chroma .mh {
|
.chroma .mh {
|
||||||
color: #ae81ff
|
color: #ae81ff;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* LiteralNumberInteger */
|
/* LiteralNumberInteger */
|
||||||
.chroma .mi {
|
.chroma .mi {
|
||||||
color: #ae81ff
|
color: #ae81ff;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* LiteralNumberIntegerLong */
|
/* LiteralNumberIntegerLong */
|
||||||
.chroma .il {
|
.chroma .il {
|
||||||
color: #ae81ff
|
color: #ae81ff;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* LiteralNumberOct */
|
/* LiteralNumberOct */
|
||||||
.chroma .mo {
|
.chroma .mo {
|
||||||
color: #ae81ff
|
color: #ae81ff;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Operator */
|
/* Operator */
|
||||||
.chroma .o {
|
.chroma .o {
|
||||||
color: #f92672
|
color: #f92672;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* OperatorWord */
|
/* OperatorWord */
|
||||||
.chroma .ow {
|
.chroma .ow {
|
||||||
color: #f92672
|
color: #f92672;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Punctuation */
|
/* Punctuation */
|
||||||
.chroma .p {
|
.chroma .p {
|
||||||
color: #111111
|
color: #111111;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Comment */
|
/* Comment */
|
||||||
.chroma .c {
|
.chroma .c {
|
||||||
color: #75715e
|
color: #75715e;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* CommentHashbang */
|
/* CommentHashbang */
|
||||||
.chroma .ch {
|
.chroma .ch {
|
||||||
color: #75715e
|
color: #75715e;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* CommentMultiline */
|
/* CommentMultiline */
|
||||||
.chroma .cm {
|
.chroma .cm {
|
||||||
color: #75715e
|
color: #75715e;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* CommentSingle */
|
/* CommentSingle */
|
||||||
.chroma .c1 {
|
.chroma .c1 {
|
||||||
color: #75715e
|
color: #75715e;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* CommentSpecial */
|
/* CommentSpecial */
|
||||||
.chroma .cs {
|
.chroma .cs {
|
||||||
color: #75715e
|
color: #75715e;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* CommentPreproc */
|
/* CommentPreproc */
|
||||||
.chroma .cp {
|
.chroma .cp {
|
||||||
color: #75715e
|
color: #75715e;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* CommentPreprocFile */
|
/* CommentPreprocFile */
|
||||||
.chroma .cpf {
|
.chroma .cpf {
|
||||||
color: #75715e
|
color: #75715e;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Generic */
|
/* Generic */
|
||||||
.chroma .g {}
|
.chroma .g {
|
||||||
|
}
|
||||||
/* GenericDeleted */
|
/* GenericDeleted */
|
||||||
.chroma .gd {}
|
.chroma .gd {
|
||||||
|
color: #f92672;
|
||||||
|
}
|
||||||
/* GenericEmph */
|
/* GenericEmph */
|
||||||
.chroma .ge {
|
.chroma .ge {
|
||||||
font-style: italic
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* GenericError */
|
/* GenericError */
|
||||||
.chroma .gr {}
|
.chroma .gr {
|
||||||
|
}
|
||||||
/* GenericHeading */
|
/* GenericHeading */
|
||||||
.chroma .gh {}
|
.chroma .gh {
|
||||||
|
}
|
||||||
/* GenericInserted */
|
/* GenericInserted */
|
||||||
.chroma .gi {}
|
.chroma .gi {
|
||||||
|
color: #7ca727;
|
||||||
|
}
|
||||||
/* GenericOutput */
|
/* GenericOutput */
|
||||||
.chroma .go {}
|
.chroma .go {
|
||||||
|
}
|
||||||
/* GenericPrompt */
|
/* GenericPrompt */
|
||||||
.chroma .gp {}
|
.chroma .gp {
|
||||||
|
}
|
||||||
/* GenericStrong */
|
/* GenericStrong */
|
||||||
.chroma .gs {
|
.chroma .gs {
|
||||||
font-weight: bold
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* GenericSubheading */
|
/* GenericSubheading */
|
||||||
.chroma .gu {}
|
.chroma .gu {
|
||||||
|
color: #75715e;
|
||||||
|
}
|
||||||
/* GenericTraceback */
|
/* GenericTraceback */
|
||||||
.chroma .gt {}
|
.chroma .gt {
|
||||||
|
}
|
||||||
/* GenericUnderline */
|
/* GenericUnderline */
|
||||||
.chroma .gl {}
|
.chroma .gl {
|
||||||
|
}
|
||||||
/* TextWhitespace */
|
/* TextWhitespace */
|
||||||
.chroma .w {}
|
.chroma .w {
|
||||||
|
}
|
||||||
|
@ -71,7 +71,8 @@
|
|||||||
text-transform: unset;
|
text-transform: unset;
|
||||||
}
|
}
|
||||||
|
|
||||||
.article-copyright, .article-lastmod {
|
.article-copyright,
|
||||||
|
.article-lastmod {
|
||||||
a {
|
a {
|
||||||
color: var(--body-text-color);
|
color: var(--body-text-color);
|
||||||
}
|
}
|
||||||
@ -122,7 +123,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.article-page.has-toc {
|
.article-page.has-toc {
|
||||||
scroll-behavior: smooth;
|
|
||||||
|
|
||||||
.left-sidebar {
|
.left-sidebar {
|
||||||
display: none;
|
display: none;
|
||||||
@ -193,6 +193,10 @@
|
|||||||
color: var(--card-text-color-main);
|
color: var(--card-text-color-main);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background-color: var(--card-separator-color);
|
||||||
|
}
|
||||||
|
|
||||||
#TableOfContents {
|
#TableOfContents {
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
max-height: 75vh;
|
max-height: 75vh;
|
||||||
@ -207,7 +211,7 @@
|
|||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
counter-reset: item;
|
counter-reset: item;
|
||||||
|
|
||||||
li:before {
|
li a::before {
|
||||||
counter-increment: item;
|
counter-increment: item;
|
||||||
content: counters(item, ".") ". ";
|
content: counters(item, ".") ". ";
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
@ -220,7 +224,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
li {
|
li {
|
||||||
margin: 15px 20px;
|
margin: 15px 0 15px 20px;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
|
|
||||||
& > ol,
|
& > ol,
|
||||||
@ -234,6 +238,38 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -359,6 +395,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.table-wrapper {
|
||||||
|
padding: 0 var(--card-padding);
|
||||||
|
overflow-x: auto;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
table {
|
table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
@ -407,9 +449,10 @@
|
|||||||
/// Negative margins
|
/// Negative margins
|
||||||
blockquote,
|
blockquote,
|
||||||
figure,
|
figure,
|
||||||
.gallery,
|
|
||||||
pre,
|
pre,
|
||||||
|
.gallery,
|
||||||
.video-wrapper,
|
.video-wrapper,
|
||||||
|
.table-wrapper,
|
||||||
.s_video_simple {
|
.s_video_simple {
|
||||||
margin-left: calc((var(--card-padding)) * -1);
|
margin-left: calc((var(--card-padding)) * -1);
|
||||||
margin-right: calc((var(--card-padding)) * -1);
|
margin-right: calc((var(--card-padding)) * -1);
|
||||||
|
@ -8,6 +8,7 @@ $defaultTagColors: #fff, #fff, #fff, #fff, #fff;
|
|||||||
}
|
}
|
||||||
|
|
||||||
[data-scheme="dark"] {
|
[data-scheme="dark"] {
|
||||||
|
color-scheme: dark;
|
||||||
--pre-text-color: #f8f8f2;
|
--pre-text-color: #f8f8f2;
|
||||||
--pre-background-color: #272822;
|
--pre-background-color: #272822;
|
||||||
@import "partials/highlight/dark.scss";
|
@import "partials/highlight/dark.scss";
|
||||||
|
@ -10,6 +10,8 @@ import { getColor } from 'ts/color';
|
|||||||
import menu from 'ts/menu';
|
import menu from 'ts/menu';
|
||||||
import createElement from 'ts/createElement';
|
import createElement from 'ts/createElement';
|
||||||
import StackColorScheme from 'ts/colorScheme';
|
import StackColorScheme from 'ts/colorScheme';
|
||||||
|
import { setupScrollspy } from 'ts/scrollspy';
|
||||||
|
import { setupSmoothAnchors } from "ts/smoothAnchors";
|
||||||
|
|
||||||
let Stack = {
|
let Stack = {
|
||||||
init: () => {
|
init: () => {
|
||||||
@ -21,6 +23,8 @@ let Stack = {
|
|||||||
const articleContent = document.querySelector('.article-content') as HTMLElement;
|
const articleContent = document.querySelector('.article-content') as HTMLElement;
|
||||||
if (articleContent) {
|
if (articleContent) {
|
||||||
new StackGallery(articleContent);
|
new StackGallery(articleContent);
|
||||||
|
setupSmoothAnchors();
|
||||||
|
setupScrollspy();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -58,7 +62,7 @@ let Stack = {
|
|||||||
/**
|
/**
|
||||||
* Add copy button to code block
|
* Add copy button to code block
|
||||||
*/
|
*/
|
||||||
const codeBlocks = document.querySelectorAll('.article-content .highlight');
|
const codeBlocks = document.querySelectorAll('.article-content > div.highlight');
|
||||||
const copyText = `Copy`,
|
const copyText = `Copy`,
|
||||||
copiedText = `Copied!`;
|
copiedText = `Copied!`;
|
||||||
codeBlocks.forEach(codeBlock => {
|
codeBlocks.forEach(codeBlock => {
|
||||||
|
131
assets/ts/scrollspy.ts
Normal file
131
assets/ts/scrollspy.ts
Normal 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 };
|
@ -8,6 +8,11 @@ interface pageData {
|
|||||||
matchCount: number
|
matchCount: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface match {
|
||||||
|
start: number,
|
||||||
|
end: number
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Escape HTML tags as HTML entities
|
* Escape HTML tags as HTML entities
|
||||||
* Edited from:
|
* Edited from:
|
||||||
@ -53,79 +58,131 @@ class Search {
|
|||||||
this.bindSearchForm();
|
this.bindSearchForm();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async searchKeywords(keywords: string[]) {
|
/**
|
||||||
const rawData = await this.getData();
|
* Processes search matches
|
||||||
let results: pageData[] = [];
|
* @param str original text
|
||||||
|
* @param matches array of matches
|
||||||
/// Sort keywords by their length
|
* @param ellipsis whether to add ellipsis to the end of each match
|
||||||
keywords.sort((a, b) => {
|
* @param charLimit max length of preview string
|
||||||
return b.length - a.length
|
* @param offset how many characters before and after the match to include in preview
|
||||||
|
* @returns preview string
|
||||||
|
*/
|
||||||
|
private static processMatches(str: string, matches: match[], ellipsis: boolean = true, charLimit = 140, offset = 20): string {
|
||||||
|
matches.sort((a, b) => {
|
||||||
|
return a.start - b.start;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let i = 0,
|
||||||
|
lastIndex = 0,
|
||||||
|
charCount = 0;
|
||||||
|
|
||||||
|
const resultArray: string[] = [];
|
||||||
|
|
||||||
|
while (i < matches.length) {
|
||||||
|
const item = matches[i];
|
||||||
|
|
||||||
|
/// item.start >= lastIndex (equal only for the first iteration)
|
||||||
|
/// because of the while loop that comes after, iterating over variable j
|
||||||
|
|
||||||
|
if (ellipsis && item.start - offset > lastIndex) {
|
||||||
|
resultArray.push(`${replaceHTMLEnt(str.substring(lastIndex, lastIndex + offset))} [...] `);
|
||||||
|
resultArray.push(`${replaceHTMLEnt(str.substring(item.start - offset, item.start))}`);
|
||||||
|
charCount += offset * 2;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
/// If the match is too close to the end of last match, don't add ellipsis
|
||||||
|
resultArray.push(replaceHTMLEnt(str.substring(lastIndex, item.start)));
|
||||||
|
charCount += item.start - lastIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
let j = i + 1,
|
||||||
|
end = item.end;
|
||||||
|
|
||||||
|
/// Include as many matches as possible
|
||||||
|
/// [item.start, end] is the range of the match
|
||||||
|
while (j < matches.length && matches[j].start <= end) {
|
||||||
|
end = Math.max(matches[j].end, end);
|
||||||
|
++j;
|
||||||
|
}
|
||||||
|
|
||||||
|
resultArray.push(`<mark>${replaceHTMLEnt(str.substring(item.start, end))}</mark>`);
|
||||||
|
charCount += end - item.start;
|
||||||
|
|
||||||
|
i = j;
|
||||||
|
lastIndex = end;
|
||||||
|
|
||||||
|
if (ellipsis && charCount > charLimit) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add the rest of the string
|
||||||
|
if (lastIndex < str.length) {
|
||||||
|
let end = str.length;
|
||||||
|
if (ellipsis) end = Math.min(end, lastIndex + offset);
|
||||||
|
|
||||||
|
resultArray.push(`${replaceHTMLEnt(str.substring(lastIndex, end))}`);
|
||||||
|
|
||||||
|
if (ellipsis && end != str.length) {
|
||||||
|
resultArray.push(` [...]`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resultArray.join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
private async searchKeywords(keywords: string[]) {
|
||||||
|
const rawData = await this.getData();
|
||||||
|
const results: pageData[] = [];
|
||||||
|
|
||||||
|
const regex = new RegExp(keywords.filter((v, index, arr) => {
|
||||||
|
arr[index] = escapeRegExp(v);
|
||||||
|
return v.trim() !== '';
|
||||||
|
}).join('|'), 'gi');
|
||||||
|
|
||||||
for (const item of rawData) {
|
for (const item of rawData) {
|
||||||
|
const titleMatches: match[] = [],
|
||||||
|
contentMatches: match[] = [];
|
||||||
|
|
||||||
let result = {
|
let result = {
|
||||||
...item,
|
...item,
|
||||||
preview: '',
|
preview: '',
|
||||||
matchCount: 0
|
matchCount: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
let matched = false;
|
const contentMatchAll = item.content.matchAll(regex);
|
||||||
|
for (const match of Array.from(contentMatchAll)) {
|
||||||
for (const keyword of keywords) {
|
contentMatches.push({
|
||||||
if (keyword === '') continue;
|
start: match.index,
|
||||||
|
end: match.index + match[0].length
|
||||||
const regex = new RegExp(escapeRegExp(replaceHTMLEnt(keyword)), 'gi');
|
});
|
||||||
|
|
||||||
const contentMatch = regex.exec(result.content);
|
|
||||||
regex.lastIndex = 0; /// Reset regex
|
|
||||||
|
|
||||||
const titleMatch = regex.exec(result.title);
|
|
||||||
regex.lastIndex = 0; /// Reset regex
|
|
||||||
|
|
||||||
if (titleMatch) {
|
|
||||||
result.title = result.title.replace(regex, Search.marker);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (titleMatch || contentMatch) {
|
|
||||||
matched = true;
|
|
||||||
++result.matchCount;
|
|
||||||
|
|
||||||
let start = 0,
|
|
||||||
end = 100;
|
|
||||||
|
|
||||||
if (contentMatch) {
|
|
||||||
start = contentMatch.index - 20;
|
|
||||||
end = contentMatch.index + 80
|
|
||||||
|
|
||||||
if (start < 0) start = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.preview.indexOf(keyword) !== -1) {
|
|
||||||
result.preview = result.preview.replace(regex, Search.marker);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (start !== 0) result.preview += `[...] `;
|
|
||||||
result.preview += `${result.content.slice(start, end).replace(regex, Search.marker)} `;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (matched) {
|
const titleMatchAll = item.title.matchAll(regex);
|
||||||
result.preview += '[...]';
|
for (const match of Array.from(titleMatchAll)) {
|
||||||
results.push(result);
|
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 results.sort((a, b) => {
|
||||||
return b.matchCount - a.matchCount;
|
return b.matchCount - a.matchCount;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static marker(match) {
|
|
||||||
return '<mark>' + match + '</mark>';
|
|
||||||
}
|
|
||||||
|
|
||||||
private async doSearch(keywords: string[]) {
|
private async doSearch(keywords: string[]) {
|
||||||
const startTime = performance.now();
|
const startTime = performance.now();
|
||||||
|
|
||||||
@ -150,6 +207,11 @@ class Search {
|
|||||||
/// Not fetched yet
|
/// Not fetched yet
|
||||||
const jsonURL = this.form.dataset.json;
|
const jsonURL = this.form.dataset.json;
|
||||||
this.data = await fetch(jsonURL).then(res => res.json());
|
this.data = await fetch(jsonURL).then(res => res.json());
|
||||||
|
const parser = new DOMParser();
|
||||||
|
|
||||||
|
for (const item of this.data) {
|
||||||
|
item.content = parser.parseFromString(item.content, 'text/html').body.innerText;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.data;
|
return this.data;
|
||||||
@ -160,7 +222,7 @@ class Search {
|
|||||||
|
|
||||||
const eventHandler = (e) => {
|
const eventHandler = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const keywords = this.input.value;
|
const keywords = this.input.value.trim();
|
||||||
|
|
||||||
Search.updateQueryString(keywords, true);
|
Search.updateQueryString(keywords, true);
|
||||||
|
|
||||||
@ -225,7 +287,7 @@ class Search {
|
|||||||
<a href={item.permalink}>
|
<a href={item.permalink}>
|
||||||
<div class="article-details">
|
<div class="article-details">
|
||||||
<h2 class="article-title" dangerouslySetInnerHTML={{ __html: item.title }}></h2>
|
<h2 class="article-title" dangerouslySetInnerHTML={{ __html: item.title }}></h2>
|
||||||
<secion class="article-preview" dangerouslySetInnerHTML={{ __html: item.preview }}></secion>
|
<section class="article-preview" dangerouslySetInnerHTML={{ __html: item.preview }}></section>
|
||||||
</div>
|
</div>
|
||||||
{item.image &&
|
{item.image &&
|
||||||
<div class="article-image">
|
<div class="article-image">
|
||||||
|
34
assets/ts/smoothAnchors.ts
Normal file
34
assets/ts/smoothAnchors.ts
Normal 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 };
|
37
exampleSite/content/page/links/index.md
Normal file
37
exampleSite/content/page/links/index.md
Normal 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.
|
BIN
exampleSite/content/page/links/ts-logo-128.jpg
Normal file
BIN
exampleSite/content/page/links/ts-logo-128.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.0 KiB |
@ -69,6 +69,10 @@ Tables aren't part of the core Markdown spec, but Hugo supports supports them ou
|
|||||||
| -------- | -------- | ------ |
|
| -------- | -------- | ------ |
|
||||||
| *italics* | **bold** | `code` |
|
| *italics* | **bold** | `code` |
|
||||||
|
|
||||||
|
| A | B | C | D | E | F |
|
||||||
|
|----------------------------------------------------------|---------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------|------------------------------------------------------------|----------------------------------------------------------------------|
|
||||||
|
| Lorem ipsum dolor sit amet, consectetur adipiscing elit. | Phasellus ultricies, sapien non euismod aliquam, dui ligula tincidunt odio, at accumsan nulla sapien eget ex. | Proin eleifend dictum ipsum, non euismod ipsum pulvinar et. Vivamus sollicitudin, quam in pulvinar aliquam, metus elit pretium purus | Proin sit amet velit nec enim imperdiet vehicula. | Ut bibendum vestibulum quam, eu egestas turpis gravida nec | Sed scelerisque nec turpis vel viverra. Vivamus vitae pretium sapien |
|
||||||
|
|
||||||
## Code Blocks
|
## Code Blocks
|
||||||
|
|
||||||
#### Code block with backticks
|
#### Code block with backticks
|
||||||
@ -113,6 +117,16 @@ Tables aren't part of the core Markdown spec, but Hugo supports supports them ou
|
|||||||
</html>
|
</html>
|
||||||
{{< /highlight >}}
|
{{< /highlight >}}
|
||||||
|
|
||||||
|
#### Diff code block
|
||||||
|
|
||||||
|
```diff
|
||||||
|
[dependencies.bevy]
|
||||||
|
git = "https://github.com/bevyengine/bevy"
|
||||||
|
rev = "11f52b8c72fc3a568e8bb4a4cd1f3eb025ac2e13"
|
||||||
|
- features = ["dynamic"]
|
||||||
|
+ features = ["jpeg", "dynamic"]
|
||||||
|
```
|
||||||
|
|
||||||
## List Types
|
## List Types
|
||||||
|
|
||||||
#### Ordered List
|
#### Ordered List
|
||||||
|
@ -15,6 +15,10 @@
|
|||||||
{{ define "main" }}
|
{{ define "main" }}
|
||||||
{{ partial "article/article.html" . }}
|
{{ partial "article/article.html" . }}
|
||||||
|
|
||||||
|
{{ if .Params.links }}
|
||||||
|
{{ partial "article/components/links" . }}
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
{{ partial "article/components/related-contents" . }}
|
{{ partial "article/components/related-contents" . }}
|
||||||
|
|
||||||
{{ if not (eq .Params.comments false) }}
|
{{ if not (eq .Params.comments false) }}
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
<section class="article-content">
|
<section class="article-content">
|
||||||
{{ .Content }}
|
<!-- Refer to https://discourse.gohugo.io/t/responsive-tables-in-markdown/10639/5 -->
|
||||||
|
{{ $wrappedTable := printf "<div class=\"table-wrapper\">${1}</div>" }}
|
||||||
|
{{ .Content | replaceRE "(<table>(?:.|\n)+?</table>)" $wrappedTable | safeHTML }}
|
||||||
</section>
|
</section>
|
||||||
|
26
layouts/partials/article/components/links.html
Normal file
26
layouts/partials/article/components/links.html
Normal 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>
|
@ -1,4 +1,4 @@
|
|||||||
<script src="//cdn.jsdelivr.net/npm/twikoo@1.4.3/dist/twikoo.all.min.js"></script>
|
<script src="//cdn.jsdelivr.net/npm/twikoo@1.4.15/dist/twikoo.all.min.js"></script>
|
||||||
<div id="tcomment"></div>
|
<div id="tcomment"></div>
|
||||||
<style>
|
<style>
|
||||||
.twikoo {
|
.twikoo {
|
||||||
|
@ -3,4 +3,10 @@
|
|||||||
{{- $opts := dict "minify" hugo.IsProduction -}}
|
{{- $opts := dict "minify" hugo.IsProduction -}}
|
||||||
{{- $script := resources.Get "ts/main.ts" | js.Build $opts -}}
|
{{- $script := resources.Get "ts/main.ts" | js.Build $opts -}}
|
||||||
|
|
||||||
<script type="text/javascript" src="{{ $script.RelPermalink }}" defer></script>
|
<script type="text/javascript" src="{{ $script.RelPermalink }}" defer></script>
|
||||||
|
|
||||||
|
{{- with resources.Get "ts/custom.ts" -}}
|
||||||
|
{{/* Place your custom script in HUGO_SITE_FOLDER/assets/ts/custom.ts */}}
|
||||||
|
{{- $customScript := . | js.Build $opts -}}
|
||||||
|
<script type="text/javascript" src="{{ $customScript.RelPermalink }}" defer></script>
|
||||||
|
{{- end -}}
|
@ -1,4 +1,4 @@
|
|||||||
{{- $ThemeVersion := "3.5.0" -}}
|
{{- $ThemeVersion := "3.7.0" -}}
|
||||||
<footer class="site-footer">
|
<footer class="site-footer">
|
||||||
<section class="copyright">
|
<section class="copyright">
|
||||||
©
|
©
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
{{ $sass := resources.Get "scss/style.scss" }}
|
{{ $sass := resources.Get "scss/style.scss" }}
|
||||||
{{ $style := $sass | resources.ToCSS | minify }}
|
{{ $style := $sass | resources.ToCSS | minify | resources.Fingerprint "sha256" }}
|
||||||
<link rel="stylesheet" href="{{ $style.RelPermalink }}">
|
<link rel="stylesheet" href="{{ $style.RelPermalink }}">
|
@ -9,6 +9,7 @@
|
|||||||
{{ with .Site.Params.sidebar.avatar }}
|
{{ with .Site.Params.sidebar.avatar }}
|
||||||
{{ if (default true .enabled) }}
|
{{ if (default true .enabled) }}
|
||||||
<figure class="site-avatar">
|
<figure class="site-avatar">
|
||||||
|
<a href="{{ .Site.BaseURL | relLangURL }}">
|
||||||
{{ if not .local }}
|
{{ if not .local }}
|
||||||
<img src="{{ .src }}" width="300" height="300" class="site-logo" loading="lazy" alt="Avatar">
|
<img src="{{ .src }}" width="300" height="300" class="site-logo" loading="lazy" alt="Avatar">
|
||||||
{{ else }}
|
{{ else }}
|
||||||
@ -22,7 +23,7 @@
|
|||||||
{{ errorf "Failed loading avatar from %q" . }}
|
{{ errorf "Failed loading avatar from %q" . }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
</a>
|
||||||
{{ with $.Site.Params.sidebar.emoji }}
|
{{ with $.Site.Params.sidebar.emoji }}
|
||||||
<span class="emoji">{{ . }}</span>
|
<span class="emoji">{{ . }}</span>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
Loading…
Reference in New Issue
Block a user