commit 285dc2e4dcac7b25a05ddb5f9b43f6230b000286 Author: moonlightwatch Date: Wed Mar 26 15:32:12 2025 +0800 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c0df5f3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +public/* +.hugo_build.lock +resources/* diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/archetypes/default.md b/archetypes/default.md new file mode 100644 index 0000000..25b6752 --- /dev/null +++ b/archetypes/default.md @@ -0,0 +1,5 @@ ++++ +date = '{{ .Date }}' +draft = true +title = '{{ replace .File.ContentBaseName "-" " " | title }}' ++++ diff --git a/assets/img/logo_white.png b/assets/img/logo_white.png new file mode 100644 index 0000000..3ad919f Binary files /dev/null and b/assets/img/logo_white.png differ diff --git a/assets/jsconfig.json b/assets/jsconfig.json new file mode 100644 index 0000000..d2c571d --- /dev/null +++ b/assets/jsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "paths": { + "*": [ + "../themes/stack/assets/*" + ] + } + } +} \ No newline at end of file diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..dc997c4 --- /dev/null +++ b/build.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +chmod u+x hugo + +rm -rf public + +./hugo build + +ssh root@116.204.65.205 "rm -rf /opt/1panel/apps/openresty/openresty/www/sites/blog.xn--ghqz22d40r52n.com/index/*" + +scp -r -i ./id_rsa ./public/* root@116.204.65.205:/opt/1panel/apps/openresty/openresty/www/sites/blog.xn--ghqz22d40r52n.com/index/ \ No newline at end of file diff --git a/content/page/links/index.md b/content/page/links/index.md new file mode 100644 index 0000000..e6ee80c --- /dev/null +++ b/content/page/links/index.md @@ -0,0 +1,15 @@ +--- +title: 链接 +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 + +comments: false +--- + diff --git a/content/post/default01/helena-hertz-wWZzXlDpMog-unsplash.jpg b/content/post/default01/helena-hertz-wWZzXlDpMog-unsplash.jpg new file mode 100644 index 0000000..0120706 Binary files /dev/null and b/content/post/default01/helena-hertz-wWZzXlDpMog-unsplash.jpg differ diff --git a/content/post/default01/index.md b/content/post/default01/index.md new file mode 100644 index 0000000..aa998fd --- /dev/null +++ b/content/post/default01/index.md @@ -0,0 +1,45 @@ +--- +title: Chinese Test +description: 这是一个副标题 +date: 2020-09-09 +slug: test-chinese +image: helena-hertz-wWZzXlDpMog-unsplash.jpg +draft: true +keywords: + - 测试页面 +categories: + - Test + - 测试 +tags: + - chinese + - hello +--- + +## 正文测试 + +而这些并不是完全重要,更加重要的问题是, 带着这些问题,我们来审视一下学生会退会。 既然如何, 对我个人而言,学生会退会不仅仅是一个重大的事件,还可能会改变我的人生。 我们不得不面对一个非常尴尬的事实,那就是, 可是,即使是这样,学生会退会的出现仍然代表了一定的意义。 学生会退会,发生了会如何,不发生又会如何。 经过上述讨论, 生活中,若学生会退会出现了,我们就不得不考虑它出现了的事实。 学生会退会,到底应该如何实现。 这样看来, 在这种困难的抉择下,本人思来想去,寝食难安。 对我个人而言,学生会退会不仅仅是一个重大的事件,还可能会改变我的人生。 就我个人来说,学生会退会对我的意义,不能不说非常重大。 莎士比亚曾经提到过,人的一生是短的,但如果卑劣地过这一生,就太长了。这似乎解答了我的疑惑。 莫扎特说过一句富有哲理的话,谁和我一样用功,谁就会和我一样成功。这启发了我, 对我个人而言,学生会退会不仅仅是一个重大的事件,还可能会改变我的人生。 学生会退会,到底应该如何实现。 一般来说, 从这个角度来看, 这种事实对本人来说意义重大,相信对这个世界也是有一定意义的。 在这种困难的抉择下,本人思来想去,寝食难安。 了解清楚学生会退会到底是一种怎么样的存在,是解决一切问题的关键。 一般来说, 生活中,若学生会退会出现了,我们就不得不考虑它出现了的事实。 问题的关键究竟为何? 而这些并不是完全重要,更加重要的问题是。 + +奥斯特洛夫斯基曾经说过,共同的事业,共同的斗争,可以使人们产生忍受一切的力量。 带着这句话,我们还要更加慎重的审视这个问题: 一般来讲,我们都必须务必慎重的考虑考虑。 既然如此, 这种事实对本人来说意义重大,相信对这个世界也是有一定意义的。 带着这些问题,我们来审视一下学生会退会。 我认为, 我认为, 在这种困难的抉择下,本人思来想去,寝食难安。 问题的关键究竟为何? 每个人都不得不面对这些问题。 在面对这种问题时, 要想清楚,学生会退会,到底是一种怎么样的存在。 我认为, 既然如此, 每个人都不得不面对这些问题。 在面对这种问题时, 那么, 我认为, 学生会退会因何而发生。 + +## 引用 + +> 思念是最暖的忧伤像一双翅膀 +> 让我停不了飞不远在过往游荡 +> 不告而别的你 就算为了我着想 +> 这么沉痛的呵护 我怎么能翱翔 +> +> *[最暖的憂傷 - 田馥甄](https://www.youtube.com/watch?v=3aypp_YlBzI)* + +## 图片 + +![Photo by Florian Klauer on Unsplash](florian-klauer-nptLmg6jqDo-unsplash.jpg) ![Photo by Luca Bravo on Unsplash](luca-bravo-alS7ewQ41M8-unsplash.jpg) + +![Photo by Helena Hertz on Unsplash](helena-hertz-wWZzXlDpMog-unsplash.jpg) ![Photo by Hudai Gayiran on Unsplash](hudai-gayiran-3Od_VKcDEAA-unsplash.jpg) + +```markdown +![Photo by Florian Klauer on Unsplash](florian-klauer-nptLmg6jqDo-unsplash.jpg) ![Photo by Luca Bravo on Unsplash](luca-bravo-alS7ewQ41M8-unsplash.jpg) + +![Photo by Helena Hertz on Unsplash](helena-hertz-wWZzXlDpMog-unsplash.jpg) ![Photo by Hudai Gayiran on Unsplash](hudai-gayiran-3Od_VKcDEAA-unsplash.jpg) +``` + +相册语法来自 [Typlog](https://typlog.com/) \ No newline at end of file diff --git a/hugo b/hugo new file mode 100755 index 0000000..653a930 Binary files /dev/null and b/hugo differ diff --git a/hugo.yaml b/hugo.yaml new file mode 100644 index 0000000..4d63690 --- /dev/null +++ b/hugo.yaml @@ -0,0 +1,108 @@ +baseurl: https://blog.轻聆月下.com/ +languageCode: zh-cn +theme: stack +title: 轻聆月下-博客 +copyright: 轻聆月下 + +DefaultContentLanguage: zh-cn + +pagination: + pagerSize: 3 + + +permalinks: + post: /p/:slug/ + page: /:slug/ + + +params: + mainSections: + - post + featuredImageField: image + rssFullContent: true + favicon: /img/logo_white.png + + footer: + since: 2020 + customText: footer text + + dateFormat: + published: 2006-01-02 + lastUpdated: 2006-01-02 15:04:05 + + sidebar: + emoji: ♑ + subtitle: 不负责任的信口胡说…… + avatar: + enabled: true + local: true + src: img/logo_white.png + + article: + math: false + toc: true + readingTime: true + license: + enabled: true + default: Licensed under CC BY-NC-SA 4.0 + + defaultImage: + opengraph: + enabled: false + local: false + src: + + colorScheme: + # Display toggle + toggle: false + + # Available values: auto, light, dark + default: dark + + imageProcessing: + cover: + enabled: true + content: + enabled: true + +menu: + main: + - identifier: home + name: 主页 + url: / + weight: -100 + params: + icon: home + newTab: true + - identifier: links + name: 链接 + url: /links + weight: -100 + params: + icon: link + newTab: true + + # social: + # - identifier: github + # name: GitHub + # url: https://github.com/CaiJimmy/hugo-theme-stack + # params: + # icon: brand-github + + # - identifier: twitter + # name: Twitter + # url: https://twitter.com + # params: + # icon: brand-twitter + +related: + includeNewer: true + threshold: 60 + toLower: false + indices: + - name: tags + weight: 100 + + - name: categories + weight: 200 + diff --git a/static/img/logo_white.png b/static/img/logo_white.png new file mode 100644 index 0000000..3ad919f Binary files /dev/null and b/static/img/logo_white.png differ diff --git a/themes/stack/README.md b/themes/stack/README.md new file mode 100644 index 0000000..b071761 --- /dev/null +++ b/themes/stack/README.md @@ -0,0 +1,28 @@ +![image](https://user-images.githubusercontent.com/5889006/190859441-141b5f81-8483-40d2-bd96-ebf85616a46d.png) + +# Hugo Theme Stack + +logo + +Card-style Hugo theme designed for bloggers. + +## Quickstart + +Use this template: [CaiJimmy/hugo-theme-stack-starter](https://github.com/CaiJimmy/hugo-theme-stack-starter) + +## Demo + +* Starter template demo: [demo.stack.jimmycai.com](https://demo.stack.jimmycai.com) +* Dev build: [dev.stack.jimmycai.com](https://dev.stack.jimmycai.com) + +## Documentation + +Visit [stack.jimmycai.com](https://stack.jimmycai.com) + +## Copyright + +**Licensed under the GNU General Public License v3.0** + +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🙏. diff --git a/themes/stack/archetypes/categories.md b/themes/stack/archetypes/categories.md new file mode 100644 index 0000000..d771b29 --- /dev/null +++ b/themes/stack/archetypes/categories.md @@ -0,0 +1,7 @@ +--- +title: "{{ replace .Name "-" " " | title }}" +image: +style: + background: "#2a9d8f" + color: "#fff" +--- \ No newline at end of file diff --git a/themes/stack/archetypes/default.md b/themes/stack/archetypes/default.md new file mode 100644 index 0000000..0556d3a --- /dev/null +++ b/themes/stack/archetypes/default.md @@ -0,0 +1,11 @@ +--- +title: "{{ replace .Name "-" " " | title }}" +description: +date: {{ .Date }} +image: +math: +license: +hidden: false +comments: true +draft: true +--- \ No newline at end of file diff --git a/themes/stack/archetypes/tags.md b/themes/stack/archetypes/tags.md new file mode 100644 index 0000000..2fd2fd7 --- /dev/null +++ b/themes/stack/archetypes/tags.md @@ -0,0 +1,5 @@ +--- +title: "{{ replace .Name "-" " " | title }}" +description: +image: +--- \ No newline at end of file diff --git a/themes/stack/assets/icons/archives.svg b/themes/stack/assets/icons/archives.svg new file mode 100644 index 0000000..cd96cbe --- /dev/null +++ b/themes/stack/assets/icons/archives.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/themes/stack/assets/icons/arrow-back.svg b/themes/stack/assets/icons/arrow-back.svg new file mode 100644 index 0000000..0f7c5f4 --- /dev/null +++ b/themes/stack/assets/icons/arrow-back.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/themes/stack/assets/icons/back.svg b/themes/stack/assets/icons/back.svg new file mode 100644 index 0000000..ee52db4 --- /dev/null +++ b/themes/stack/assets/icons/back.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/themes/stack/assets/icons/brand-github.svg b/themes/stack/assets/icons/brand-github.svg new file mode 100644 index 0000000..1fe7e0b --- /dev/null +++ b/themes/stack/assets/icons/brand-github.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/themes/stack/assets/icons/brand-twitter.svg b/themes/stack/assets/icons/brand-twitter.svg new file mode 100644 index 0000000..17ab1b1 --- /dev/null +++ b/themes/stack/assets/icons/brand-twitter.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/themes/stack/assets/icons/categories.svg b/themes/stack/assets/icons/categories.svg new file mode 100644 index 0000000..e00ab1d --- /dev/null +++ b/themes/stack/assets/icons/categories.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/themes/stack/assets/icons/clock.svg b/themes/stack/assets/icons/clock.svg new file mode 100644 index 0000000..a7a8be6 --- /dev/null +++ b/themes/stack/assets/icons/clock.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/themes/stack/assets/icons/copyright.svg b/themes/stack/assets/icons/copyright.svg new file mode 100644 index 0000000..2ac45ff --- /dev/null +++ b/themes/stack/assets/icons/copyright.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/themes/stack/assets/icons/date.svg b/themes/stack/assets/icons/date.svg new file mode 100644 index 0000000..ed92a90 --- /dev/null +++ b/themes/stack/assets/icons/date.svg @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/themes/stack/assets/icons/hash.svg b/themes/stack/assets/icons/hash.svg new file mode 100644 index 0000000..e00ab1d --- /dev/null +++ b/themes/stack/assets/icons/hash.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/themes/stack/assets/icons/home.svg b/themes/stack/assets/icons/home.svg new file mode 100644 index 0000000..ed5fd53 --- /dev/null +++ b/themes/stack/assets/icons/home.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/themes/stack/assets/icons/infinity.svg b/themes/stack/assets/icons/infinity.svg new file mode 100644 index 0000000..fb40b24 --- /dev/null +++ b/themes/stack/assets/icons/infinity.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/themes/stack/assets/icons/language.svg b/themes/stack/assets/icons/language.svg new file mode 100644 index 0000000..66ede1c --- /dev/null +++ b/themes/stack/assets/icons/language.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/themes/stack/assets/icons/link.svg b/themes/stack/assets/icons/link.svg new file mode 100644 index 0000000..3c87803 --- /dev/null +++ b/themes/stack/assets/icons/link.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/themes/stack/assets/icons/messages.svg b/themes/stack/assets/icons/messages.svg new file mode 100644 index 0000000..725d75c --- /dev/null +++ b/themes/stack/assets/icons/messages.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/themes/stack/assets/icons/rss.svg b/themes/stack/assets/icons/rss.svg new file mode 100644 index 0000000..b92ea8f --- /dev/null +++ b/themes/stack/assets/icons/rss.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/themes/stack/assets/icons/search.svg b/themes/stack/assets/icons/search.svg new file mode 100644 index 0000000..a0b0ddc --- /dev/null +++ b/themes/stack/assets/icons/search.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/themes/stack/assets/icons/tag.svg b/themes/stack/assets/icons/tag.svg new file mode 100644 index 0000000..ef7a63d --- /dev/null +++ b/themes/stack/assets/icons/tag.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/themes/stack/assets/icons/toggle-left.svg b/themes/stack/assets/icons/toggle-left.svg new file mode 100644 index 0000000..c3048d4 --- /dev/null +++ b/themes/stack/assets/icons/toggle-left.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/themes/stack/assets/icons/toggle-right.svg b/themes/stack/assets/icons/toggle-right.svg new file mode 100644 index 0000000..d3edd63 --- /dev/null +++ b/themes/stack/assets/icons/toggle-right.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/themes/stack/assets/icons/user.svg b/themes/stack/assets/icons/user.svg new file mode 100644 index 0000000..9c92c87 --- /dev/null +++ b/themes/stack/assets/icons/user.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/themes/stack/assets/img/avatar.png b/themes/stack/assets/img/avatar.png new file mode 100644 index 0000000..d90352c Binary files /dev/null and b/themes/stack/assets/img/avatar.png differ diff --git a/themes/stack/assets/jsconfig.json b/themes/stack/assets/jsconfig.json new file mode 100644 index 0000000..040177a --- /dev/null +++ b/themes/stack/assets/jsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "paths": { + "*": [ + "*" + ] + }, + "lib": ["es2020", "dom"], + "jsx": "preserve" + } +} \ No newline at end of file diff --git a/themes/stack/assets/scss/breakpoints.scss b/themes/stack/assets/scss/breakpoints.scss new file mode 100644 index 0000000..e9e9de7 --- /dev/null +++ b/themes/stack/assets/scss/breakpoints.scss @@ -0,0 +1,17 @@ +$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; + } + } +} diff --git a/themes/stack/assets/scss/custom.scss b/themes/stack/assets/scss/custom.scss new file mode 100644 index 0000000..61fa80f --- /dev/null +++ b/themes/stack/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/themes/stack/assets/scss/external/normalize.scss b/themes/stack/assets/scss/external/normalize.scss new file mode 100644 index 0000000..c45a85f --- /dev/null +++ b/themes/stack/assets/scss/external/normalize.scss @@ -0,0 +1,349 @@ +/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */ + +/* Document + ========================================================================== */ + +/** + * 1. Correct the line height in all browsers. + * 2. Prevent adjustments of font size after orientation changes in iOS. + */ + + html { + line-height: 1.15; /* 1 */ + -webkit-text-size-adjust: 100%; /* 2 */ + } + + /* Sections + ========================================================================== */ + + /** + * Remove the margin in all browsers. + */ + + body { + margin: 0; + } + + /** + * Render the `main` element consistently in IE. + */ + + main { + display: block; + } + + /** + * Correct the font size and margin on `h1` elements within `section` and + * `article` contexts in Chrome, Firefox, and Safari. + */ + + h1 { + font-size: 2em; + margin: 0.67em 0; + } + + /* Grouping content + ========================================================================== */ + + /** + * 1. Add the correct box sizing in Firefox. + * 2. Show the overflow in Edge and IE. + */ + + hr { + box-sizing: content-box; /* 1 */ + height: 0; /* 1 */ + overflow: visible; /* 2 */ + } + + /** + * 1. Correct the inheritance and scaling of font size in all browsers. + * 2. Correct the odd `em` font sizing in all browsers. + */ + + pre { + font-family: monospace, monospace; /* 1 */ + font-size: 1em; /* 2 */ + } + + /* Text-level semantics + ========================================================================== */ + + /** + * Remove the gray background on active links in IE 10. + */ + + a { + background-color: transparent; + } + + /** + * 1. Remove the bottom border in Chrome 57- + * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. + */ + + abbr[title] { + border-bottom: none; /* 1 */ + text-decoration: underline; /* 2 */ + text-decoration: underline dotted; /* 2 */ + } + + /** + * Add the correct font weight in Chrome, Edge, and Safari. + */ + + b, + strong { + font-weight: bolder; + } + + /** + * 1. Correct the inheritance and scaling of font size in all browsers. + * 2. Correct the odd `em` font sizing in all browsers. + */ + + code, + kbd, + samp { + font-family: monospace, monospace; /* 1 */ + font-size: 1em; /* 2 */ + } + + /** + * Add the correct font size in all browsers. + */ + + small { + font-size: 80%; + } + + /** + * Prevent `sub` and `sup` elements from affecting the line height in + * all browsers. + */ + + sub, + sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; + } + + sub { + bottom: -0.25em; + } + + sup { + top: -0.5em; + } + + /* Embedded content + ========================================================================== */ + + /** + * Remove the border on images inside links in IE 10. + */ + + img { + border-style: none; + } + + /* Forms + ========================================================================== */ + + /** + * 1. Change the font styles in all browsers. + * 2. Remove the margin in Firefox and Safari. + */ + + button, + input, + optgroup, + select, + textarea { + font-family: inherit; /* 1 */ + font-size: 100%; /* 1 */ + line-height: 1.15; /* 1 */ + margin: 0; /* 2 */ + } + + /** + * Show the overflow in IE. + * 1. Show the overflow in Edge. + */ + + button, + input { /* 1 */ + overflow: visible; + } + + /** + * Remove the inheritance of text transform in Edge, Firefox, and IE. + * 1. Remove the inheritance of text transform in Firefox. + */ + + button, + select { /* 1 */ + text-transform: none; + } + + /** + * Correct the inability to style clickable types in iOS and Safari. + */ + + button, + [type="button"], + [type="reset"], + [type="submit"] { + -webkit-appearance: button; + } + + /** + * Remove the inner border and padding in Firefox. + */ + + button::-moz-focus-inner, + [type="button"]::-moz-focus-inner, + [type="reset"]::-moz-focus-inner, + [type="submit"]::-moz-focus-inner { + border-style: none; + padding: 0; + } + + /** + * Restore the focus styles unset by the previous rule. + */ + + button:-moz-focusring, + [type="button"]:-moz-focusring, + [type="reset"]:-moz-focusring, + [type="submit"]:-moz-focusring { + outline: 1px dotted ButtonText; + } + + /** + * Correct the padding in Firefox. + */ + + fieldset { + padding: 0.35em 0.75em 0.625em; + } + + /** + * 1. Correct the text wrapping in Edge and IE. + * 2. Correct the color inheritance from `fieldset` elements in IE. + * 3. Remove the padding so developers are not caught out when they zero out + * `fieldset` elements in all browsers. + */ + + legend { + box-sizing: border-box; /* 1 */ + color: inherit; /* 2 */ + display: table; /* 1 */ + max-width: 100%; /* 1 */ + padding: 0; /* 3 */ + white-space: normal; /* 1 */ + } + + /** + * Add the correct vertical alignment in Chrome, Firefox, and Opera. + */ + + progress { + vertical-align: baseline; + } + + /** + * Remove the default vertical scrollbar in IE 10+. + */ + + textarea { + overflow: auto; + } + + /** + * 1. Add the correct box sizing in IE 10. + * 2. Remove the padding in IE 10. + */ + + [type="checkbox"], + [type="radio"] { + box-sizing: border-box; /* 1 */ + padding: 0; /* 2 */ + } + + /** + * Correct the cursor style of increment and decrement buttons in Chrome. + */ + + [type="number"]::-webkit-inner-spin-button, + [type="number"]::-webkit-outer-spin-button { + height: auto; + } + + /** + * 1. Correct the odd appearance in Chrome and Safari. + * 2. Correct the outline style in Safari. + */ + + [type="search"] { + -webkit-appearance: textfield; /* 1 */ + outline-offset: -2px; /* 2 */ + } + + /** + * Remove the inner padding in Chrome and Safari on macOS. + */ + + [type="search"]::-webkit-search-decoration { + -webkit-appearance: none; + } + + /** + * 1. Correct the inability to style clickable types in iOS and Safari. + * 2. Change font properties to `inherit` in Safari. + */ + + ::-webkit-file-upload-button { + -webkit-appearance: button; /* 1 */ + font: inherit; /* 2 */ + } + + /* Interactive + ========================================================================== */ + + /* + * Add the correct display in Edge, IE 10+, and Firefox. + */ + + details { + display: block; + } + + /* + * Add the correct display in all browsers. + */ + + summary { + display: list-item; + } + + /* Misc + ========================================================================== */ + + /** + * Add the correct display in IE 10+. + */ + + template { + display: none; + } + + /** + * Add the correct display in IE 10. + */ + + [hidden] { + display: none; + } \ No newline at end of file diff --git a/themes/stack/assets/scss/general.scss b/themes/stack/assets/scss/general.scss new file mode 100644 index 0000000..e12bb0e --- /dev/null +++ b/themes/stack/assets/scss/general.scss @@ -0,0 +1,31 @@ +a { + text-decoration: none; + color: var(--accent-color); + + &:hover { + color: var(--accent-color-darker); + } + + &.link { + box-shadow: 0px -2px 0px rgba(var(--link-background-color), var(--link-background-opacity)) inset; + transition: all 0.3s ease; + + &:hover { + box-shadow: 0px calc(-1rem * var(--article-line-height)) 0px rgba(var(--link-background-color), var(--link-background-opacity-hover)) inset; + } + } +} + +.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); + + a { + color: var(--body-text-color); + } +} diff --git a/themes/stack/assets/scss/grid.scss b/themes/stack/assets/scss/grid.scss new file mode 100644 index 0000000..9284da9 --- /dev/null +++ b/themes/stack/assets/scss/grid.scss @@ -0,0 +1,101 @@ +.container { + margin-left: auto; + margin-right: auto; + + .left-sidebar { + order: -3; + max-width: var(--left-sidebar-max-width); + } + + .right-sidebar { + order: -1; + max-width: var(--right-sidebar-max-width); + + /// Display right sidebar when min-width: lg + @include respond(lg) { + display: flex; + } + } + + &.extended { + @include respond(md) { + max-width: 1024px; + --left-sidebar-max-width: 25%; + --right-sidebar-max-width: 30%; + } + + @include respond(lg) { + max-width: 1280px; + --left-sidebar-max-width: 20%; + --right-sidebar-max-width: 30%; + } + + @include respond(xl) { + max-width: 1536px; + --left-sidebar-max-width: 15%; + --right-sidebar-max-width: 25%; + } + } + + &.compact { + @include respond(md) { + --left-sidebar-max-width: 25%; + max-width: 768px; + } + + @include respond(lg) { + max-width: 1024px; + --left-sidebar-max-width: 20%; + } + + @include respond(xl) { + max-width: 1280px; + } + } +} + +.flex { + display: flex; + flex-direction: row; + + &.column { + flex-direction: column; + } + + &.on-phone--column { + flex-direction: column; + @include respond(md) { + flex-direction: unset; + } + } + + .full-width { + width: 100%; + } +} + +main.main { + order: -2; + min-width: 0; + max-width: 100%; + flex-grow: 1; + display: flex; + flex-direction: column; + gap: var(--section-separation); + + @include respond(md) { + padding-top: var(--main-top-padding); + } +} + +.main-container { + min-height: 100vh; + align-items: flex-start; + padding: 0 15px; + gap: var(--section-separation); + padding-top: var(--main-top-padding); + + @include respond(md) { + padding: 0 20px; + } +} diff --git a/themes/stack/assets/scss/partials/article.scss b/themes/stack/assets/scss/partials/article.scss new file mode 100644 index 0000000..6c2feb1 --- /dev/null +++ b/themes/stack/assets/scss/partials/article.scss @@ -0,0 +1,278 @@ +/* Default article style */ +.article-list { + display: flex; + flex-direction: column; + gap: var(--section-separation); + + article { + display: flex; + flex-direction: column; + background-color: var(--card-background); + box-shadow: var(--shadow-l1); + border-radius: var(--card-border-radius); + overflow: hidden; + + transition: box-shadow 0.3s ease; + + &:hover { + box-shadow: var(--shadow-l2); + } + + .article-image { + img { + width: 100%; + height: 150px; + object-fit: cover; + + @include respond(md) { + height: 200px; + } + + @include respond(xl) { + height: 250px; + } + } + } + + @for $i from 1 through length($defaultTagBackgrounds) { + &:nth-child(#{length($defaultTagBackgrounds)}n + #{$i}) { + .article-category a { + background: nth($defaultTagBackgrounds, $i); + color: nth($defaultTagColors, $i); + } + } + } + } +} + +.article-details { + display: flex; + flex-direction: column; + justify-content: center; + padding: var(--card-padding); + gap: 15px; +} + +.article-title { + font-family: var(--article-font-family); + font-weight: 600; + margin: 0; + color: var(--card-text-color-main); + font-size: 2.2rem; + + @include respond(xl) { + font-size: 2.4rem; + } + + a { + color: var(--card-text-color-main); + + &:hover { + color: var(--card-text-color-main); + } + } +} + +.article-subtitle { + font-weight: normal; + color: var(--card-text-color-secondary); + line-height: 1.5; + margin: 0; + font-size: 1.75rem; + @include respond(xl) { + font-size: 2rem; + } +} + +.article-title-wrapper { + display: flex; + flex-direction: column; + gap: 8px; +} + +.article-time, +.article-translations { + display: flex; + color: var(--card-text-color-tertiary); + gap: 15px; + + svg { + vertical-align: middle; + width: 20px; + height: 20px; + stroke-width: 1.33; + flex-shrink: 0; + } + + time, + a { + font-size: 1.4rem; + color: var(--card-text-color-tertiary); + } + + & > div { + display: inline-flex; + align-items: center; + gap: 15px; + } +} + +.article-time { + flex-wrap: wrap; +} + +.article-translations { + & > div { + flex-wrap: wrap; + } +} + +.article-category, +.article-tags { + display: flex; + gap: 10px; + flex-wrap: wrap; + + a { + color: var(--accent-color-text); + background-color: var(--accent-color); + padding: 8px 16px; + border-radius: var(--tag-border-radius); + display: inline-block; + font-size: 1.4rem; + transition: background-color 0.5s ease; + + &:hover { + color: var(--accent-color-text); + background-color: var(--accent-color-darker); + } + } +} + +/* Compact style article list */ +.article-list--compact { + border-radius: var(--card-border-radius); + box-shadow: var(--shadow-l1); + background-color: var(--card-background); + --image-size: 50px; + + @include respond(md) { + --image-size: 60px; + } + + article { + & > a { + display: flex; + align-items: center; + padding: var(--small-card-padding); + gap: 15px; + } + + &:not(:last-of-type) { + border-bottom: 1.5px solid var(--card-separator-color); + } + + .article-details { + flex-grow: 1; + padding: 0; + min-height: var(--image-size); + gap: 10px; + } + + .article-title { + margin: 0; + font-size: 1.6rem; + + @include respond(md) { + font-size: 1.8rem; + } + } + + .article-image { + img { + width: var(--image-size); + height: var(--image-size); + object-fit: cover; + } + } + + .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; + } + } +} + +/* Tile style article list */ +.article-list--tile { + article { + border-radius: var(--card-border-radius); + overflow: hidden; + position: relative; + height: 350px; + width: 250px; + box-shadow: var(--shadow-l1); + transition: box-shadow 0.3s ease; + background-color: var(--card-background); + + &:hover { + box-shadow: var(--shadow-l2); + } + + &.has-image { + .article-details { + background-color: rgba(#000, 0.25); + } + + .article-title { + color: #fff; + } + } + + .article-image { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + + img { + width: 100%; + height: 100%; + object-fit: cover; + } + } + + .article-details { + border-radius: var(--card-border-radius); + position: relative; + height: 100%; + width: 100%; + display: flex; + flex-direction: column; + justify-content: flex-end; + z-index: 2; + padding: 15px; + + @include respond(sm) { + padding: 20px; + } + } + + .article-title { + font-size: 2rem; + font-weight: 500; + color: var(--card-text-color-main); + + @include respond(sm) { + font-size: 2.2rem; + } + } + } +} diff --git a/themes/stack/assets/scss/partials/base.scss b/themes/stack/assets/scss/partials/base.scss new file mode 100644 index 0000000..efb4b8f --- /dev/null +++ b/themes/stack/assets/scss/partials/base.scss @@ -0,0 +1,38 @@ +html { + font-size: 62.5%; + overflow-y: scroll; +} + +* { + box-sizing: border-box; +} + +body { + background: var(--body-background); + margin: 0; + font-family: var(--base-font-family); + font-size: 1.6rem; + -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; +} +/**/ \ No newline at end of file diff --git a/themes/stack/assets/scss/partials/comments/disqusjs.scss b/themes/stack/assets/scss/partials/comments/disqusjs.scss new file mode 100644 index 0000000..eb270e3 --- /dev/null +++ b/themes/stack/assets/scss/partials/comments/disqusjs.scss @@ -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) + } +} diff --git a/themes/stack/assets/scss/partials/footer.scss b/themes/stack/assets/scss/partials/footer.scss new file mode 100644 index 0000000..ccb7394 --- /dev/null +++ b/themes/stack/assets/scss/partials/footer.scss @@ -0,0 +1,30 @@ +footer.site-footer { + padding: 20px 0 var(--section-separation) 0; + font-size: 1.4rem; + line-height: 1.75; + + &:before { + content: ""; + display: block; + height: 3px; + width: 50px; + background: var(--body-text-color); + margin-bottom: 20px; + } + + .copyright { + color: var(--accent-color); + font-weight: bold; + margin-bottom: 5px; + } + + .powerby { + color: var(--body-text-color); + font-weight: normal; + font-size: 1.2rem; + + a { + color: var(--body-text-color); + } + } +} diff --git a/themes/stack/assets/scss/partials/highlight/common.scss b/themes/stack/assets/scss/partials/highlight/common.scss new file mode 100644 index 0000000..c680fa4 --- /dev/null +++ b/themes/stack/assets/scss/partials/highlight/common.scss @@ -0,0 +1,428 @@ +/* Background */ +.chroma { + color: $color; + background-color: $background-color; +} + +/* Other */ +.chroma .x { +} + +/* Error */ +.chroma .err { + color: $error-color; +} + +/* LineTableTD */ +.chroma .lntd { + vertical-align: top; + padding: 0; + margin: 0; + border: 0; +} + +/* LineTable */ +.chroma .lntable { + border-spacing: 0; + padding: 0; + margin: 0; + border: 0; + width: 100%; + display: block; + + > tbody { + display: block; + width: 100%; + > tr { + display: flex; + width: 100%; + > td:last-child { + overflow-x: auto; + } + } + } +} + +/* LineHighlight */ +.chroma .hl { + display: block; + width: 100%; + background-color: #ffffcc; +} + +/* LineNumbersTable */ +.chroma .lnt { + margin-right: 0.4em; + padding: 0 0.4em 0 0.4em; + color: #7f7f7f; + display: block; +} + +/* LineNumbers */ +.chroma .ln { + margin-right: 0.4em; + padding: 0 0.4em 0 0.4em; + color: #7f7f7f; +} + +/* Keyword */ +.chroma .k { + color: $keyword-color; +} + +/* KeywordConstant */ +.chroma .kc { + color: $keyword-color; +} + +/* KeywordDeclaration */ +.chroma .kd { + color: $keyword-color; +} + +/* KeywordNamespace */ +.chroma .kn { + color: #f92672; +} + +/* KeywordPseudo */ +.chroma .kp { + color: $keyword-color; +} + +/* KeywordReserved */ +.chroma .kr { + color: $keyword-color; +} + +/* KeywordType */ +.chroma .kt { + color: $keyword-color; +} + +/* Name */ +.chroma .n { + color: $text-color; +} + +/* NameAttribute */ +.chroma .na { + color: $name-color; +} + +/* NameBuiltin */ +.chroma .nb { + color: $text-color; +} + +/* NameBuiltinPseudo */ +.chroma .bp { + color: $text-color; +} + +/* NameClass */ +.chroma .nc { + color: $name-color; +} + +/* NameConstant */ +.chroma .no { + color: $keyword-color; +} + +/* NameDecorator */ +.chroma .nd { + color: $name-color; +} + +/* NameEntity */ +.chroma .ni { + color: $text-color; +} + +/* NameException */ +.chroma .ne { + color: $name-color; +} + +/* NameFunction */ +.chroma .nf { + color: $name-color; +} + +/* NameFunctionMagic */ +.chroma .fm { + color: $text-color; +} + +/* NameLabel */ +.chroma .nl { + color: $text-color; +} + +/* NameNamespace */ +.chroma .nn { + color: $text-color; +} + +/* NameOther */ +.chroma .nx { + color: $name-color; +} + +/* NameProperty */ +.chroma .py { + color: $text-color; +} + +/* NameTag */ +.chroma .nt { + color: #f92672; +} + +/* NameVariable */ +.chroma .nv { + color: $text-color; +} + +/* NameVariableClass */ +.chroma .vc { + color: $text-color; +} + +/* NameVariableGlobal */ +.chroma .vg { + color: $text-color; +} + +/* NameVariableInstance */ +.chroma .vi { + color: $text-color; +} + +/* NameVariableMagic */ +.chroma .vm { + color: $text-color; +} + +/* Literal */ +.chroma .l { + color: #ae81ff; +} + +/* LiteralDate */ +.chroma .ld { + color: $literal-color; +} + +/* LiteralString */ +.chroma .s { + color: $literal-color; +} + +/* LiteralStringAffix */ +.chroma .sa { + color: $literal-color; +} + +/* LiteralStringBacktick */ +.chroma .sb { + color: $literal-color; +} + +/* LiteralStringChar */ +.chroma .sc { + color: $literal-color; +} + +/* LiteralStringDelimiter */ +.chroma .dl { + color: $literal-color; +} + +/* LiteralStringDoc */ +.chroma .sd { + color: $literal-color; +} + +/* LiteralStringDouble */ +.chroma .s2 { + color: $literal-color; +} + +/* LiteralStringEscape */ +.chroma .se { + color: #ae81ff; +} + +/* LiteralStringHeredoc */ +.chroma .sh { + color: $literal-color; +} + +/* LiteralStringInterpol */ +.chroma .si { + color: $literal-color; +} + +/* LiteralStringOther */ +.chroma .sx { + color: $literal-color; +} + +/* LiteralStringRegex */ +.chroma .sr { + color: $literal-color; +} + +/* LiteralStringSingle */ +.chroma .s1 { + color: $literal-color; +} + +/* LiteralStringSymbol */ +.chroma .ss { + color: $literal-color; +} + +/* LiteralNumber */ +.chroma .m { + color: #ae81ff; +} + +/* LiteralNumberBin */ +.chroma .mb { + color: #ae81ff; +} + +/* LiteralNumberFloat */ +.chroma .mf { + color: #ae81ff; +} + +/* LiteralNumberHex */ +.chroma .mh { + color: #ae81ff; +} + +/* LiteralNumberInteger */ +.chroma .mi { + color: #ae81ff; +} + +/* LiteralNumberIntegerLong */ +.chroma .il { + color: #ae81ff; +} + +/* LiteralNumberOct */ +.chroma .mo { + color: #ae81ff; +} + +/* Operator */ +.chroma .o { + color: #f92672; +} + +/* OperatorWord */ +.chroma .ow { + color: #f92672; +} + +/* Punctuation */ +.chroma .p { + color: $text-color; +} + +/* Comment */ +.chroma .c { + color: #75715e; +} + +/* CommentHashbang */ +.chroma .ch { + color: #75715e; +} + +/* CommentMultiline */ +.chroma .cm { + color: #75715e; +} + +/* CommentSingle */ +.chroma .c1 { + color: #75715e; +} + +/* CommentSpecial */ +.chroma .cs { + color: #75715e; +} + +/* CommentPreproc */ +.chroma .cp { + color: #75715e; +} + +/* CommentPreprocFile */ +.chroma .cpf { + color: #75715e; +} + +/* Generic */ +.chroma .g { +} + +/* GenericDeleted */ +.chroma .gd { + color: #f92672; +} + +/* GenericEmph */ +.chroma .ge { + font-style: italic; +} + +/* GenericError */ +.chroma .gr { +} + +/* GenericHeading */ +.chroma .gh { +} + +/* GenericInserted */ +.chroma .gi { + color: $name-color; +} + +/* GenericOutput */ +.chroma .go { +} + +/* GenericPrompt */ +.chroma .gp { +} + +/* GenericStrong */ +.chroma .gs { + font-weight: bold; +} + +/* GenericSubheading */ +.chroma .gu { + color: #75715e; +} + +/* GenericTraceback */ +.chroma .gt { +} + +/* GenericUnderline */ +.chroma .gl { +} + +/* TextWhitespace */ +.chroma .w { +} diff --git a/themes/stack/assets/scss/partials/highlight/dark.scss b/themes/stack/assets/scss/partials/highlight/dark.scss new file mode 100644 index 0000000..0d3f330 --- /dev/null +++ b/themes/stack/assets/scss/partials/highlight/dark.scss @@ -0,0 +1,14 @@ +/* +* Style: monokai +* https://xyproto.github.io/splash/docs/monokai.html +*/ + +$color: #f8f8f2; +$background-color: #272822; +$error-color: #bb0064; +$keyword-color: #66d9ef; +$text-color: $color; +$name-color: #a6e22e; +$literal-color: #e6db74; + +@import "common.scss"; diff --git a/themes/stack/assets/scss/partials/highlight/light.scss b/themes/stack/assets/scss/partials/highlight/light.scss new file mode 100644 index 0000000..174b649 --- /dev/null +++ b/themes/stack/assets/scss/partials/highlight/light.scss @@ -0,0 +1,14 @@ +/* +* Style: monokailight +* https://xyproto.github.io/splash/docs/monokailight.html +*/ + +$color: #272822; +$background-color: #fafafa; +$error-color: #960050; +$keyword-color: #00a8c8; +$text-color: #111111; +$name-color: #75af00; +$literal-color: #d88200; + +@import "common.scss"; diff --git a/themes/stack/assets/scss/partials/layout/404.scss b/themes/stack/assets/scss/partials/layout/404.scss new file mode 100644 index 0000000..d9d8752 --- /dev/null +++ b/themes/stack/assets/scss/partials/layout/404.scss @@ -0,0 +1,6 @@ +.not-found-card { + background-color: var(--card-background); + box-shadow: var(--shadow-l1); + border-radius: var(--card-border-radius); + padding: var(--card-padding); +} diff --git a/themes/stack/assets/scss/partials/layout/article.scss b/themes/stack/assets/scss/partials/layout/article.scss new file mode 100644 index 0000000..10e2706 --- /dev/null +++ b/themes/stack/assets/scss/partials/layout/article.scss @@ -0,0 +1,461 @@ +.article-page { + &.hide-sidebar-sm .left-sidebar { + display: none; + + @include respond(md) { + display: inherit; + } + } + + .main-article { + background: var(--card-background); + border-radius: var(--card-border-radius); + box-shadow: var(--shadow-l1); + overflow: hidden; + + .article-header { + .article-image { + img { + height: auto; + width: 100%; + max-height: 50vh; + object-fit: cover; + } + } + + .article-details { + padding: var(--card-padding); + padding-bottom: 0; + } + } + + .article-content { + margin: var(--card-padding) 0; + color: var(--card-text-color-main); + + .footnotes { + font-family: var(--base-font-family); + } + + img { + max-width: 100%; + height: auto; + } + } + + .article-footer { + margin: var(--card-padding); + margin-top: 0; + + section:not(:first-child) { + margin-top: var(--card-padding); + } + + section { + color: var(--card-text-color-tertiary); + text-transform: uppercase; + display: flex; + align-items: center; + font-size: 1.4rem; + gap: 15px; + + svg { + width: 20px; + height: 20px; + stroke-width: 1.33; + } + } + + .article-tags { + text-transform: unset; + } + + .article-copyright, + .article-lastmod { + a { + color: var(--body-text-color); + } + + a.link { + box-shadow: unset; + } + } + } + } +} + +.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:first-of-type::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; + } + } + } +} + +.related-content { + overflow-x: auto; + padding-bottom: 15px; + + & > .flex { + float: left; + } + + article { + margin-right: 15px; + flex-shrink: 0; + overflow: hidden; + width: 250px; + height: 150px; + + .article-title { + font-size: 1.8rem; + margin: 0; + } + + &.has-image { + .article-details { + padding: 20px; + background: linear-gradient(0deg, rgba(0, 0, 0, 0.25) 0%, rgba(0, 0, 0, 0.75) 100%); + } + } + } +} + +.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; + } + + h1, + h2, + h3, + h4, + h5, + h6 { + margin-inline-start: calc((var(--card-padding)) * -1); + padding-inline-start: calc(var(--card-padding) - var(--heading-border-size)); + border-inline-start: var(--heading-border-size) solid var(--accent-color); + position: relative; + + a.header-anchor { + transition: opacity 0.3s ease; + opacity: 0; + position: absolute; + left: 0; + width: var(--card-padding); + text-align: center; + color: var(--accent-color); + + &:before { + content: "#"; + } + } + + &:hover, + &:focus { + a.header-anchor { + opacity: 1; + } + } + } + + figure { + text-align: center; + + figcaption { + font-size: 1.4rem; + color: var(--card-text-color-secondary); + } + } + + blockquote { + position: relative; + margin: 1.5em 0; + border-inline-start: 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); + + .cite { + display: block; + text-align: right; + font-size: 0.75em; + + a { + text-decoration: underline; + } + } + } + + hr { + width: 100px; + margin: 40px auto; + background: var(--card-text-color-tertiary); + height: 2px; + border: 0; + opacity: 0.55; + } + + code { + color: var(--code-text-color); + background-color: var(--code-background-color); + padding: 2px 4px; + border-radius: var(--tag-border-radius); + font-family: var(--code-font-family); + } + + a, + code { + word-break: break-word; + } + + .gallery { + position: relative; + display: flex; + flex-direction: row; + justify-content: center; + margin: 1.5em 0; + gap: 10px; + + figure { + margin: 0; + } + } + + pre { + overflow-x: auto; + display: block; + background-color: var(--pre-background-color); + color: var(--pre-text-color); + font-family: var(--code-font-family); + line-height: 1.428571429; + word-break: break-all; + padding: var(--card-padding); + // keep Codeblocks LTR + [dir="rtl"] & { + direction: ltr; + } + code { + color: unset; + border: none; + background: none; + padding: 0; + } + } + + .highlight { + background-color: var(--pre-background-color); + padding: var(--card-padding); + position: relative; + + &:hover { + .copyCodeButton { + opacity: 1; + } + } + // keep Codeblocks LTR + [dir="rtl"] & { + direction: ltr; + } + pre { + margin: initial; + padding: 0; + margin: 0; + width: auto; + } + } + + .copyCodeButton { + position: absolute; + top: calc(var(--card-padding)); + right: calc(var(--card-padding)); + background: var(--card-background); + border: none; + box-shadow: var(--shadow-l2); + border-radius: var(--tag-border-radius); + padding: 8px 16px; + color: var(--card-text-color-main); + cursor: pointer; + font-size: 14px; + opacity: 0; + transition: opacity 0.3s ease; + } + + .table-wrapper { + padding: 0 var(--card-padding); + overflow-x: auto; + display: block; + } + + table { + width: 100%; + border-collapse: collapse; + border-spacing: 0; + margin-bottom: 1.5em; + font-size: 0.96em; + } + + th, + td { + text-align: left; + padding: 4px 8px 4px 10px; + border: 1px solid var(--table-border-color); + } + + td { + vertical-align: top; + } + + tr:nth-child(even) { + background-color: var(--tr-even-background-color); + } + + .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; + } + } + + .gitlab-embed-snippets { + margin: 0 !important; + + .file-holder.snippet-file-content { + margin-block-end: 0 !important; + margin-block-start: 0 !important; + margin-left: calc((var(--card-padding)) * -1) !important; + margin-right: calc((var(--card-padding)) * -1) !important; + padding: 0 var(--card-padding) !important; + } + } + + /// 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); + } + + /// Make long KaTeX equations scrollable in the x-axis + .katex-display > .katex { + overflow-x: auto; + overflow-y: hidden; + } + + kbd { + border: 1px solid var(--kbd-border-color); + font-weight: bold; + font-size: 0.9em; + line-height: 1; + padding: 2px 4px; + border-radius: 4px; + display: inline-block; + } +} diff --git a/themes/stack/assets/scss/partials/layout/list.scss b/themes/stack/assets/scss/partials/layout/list.scss new file mode 100644 index 0000000..d7815ca --- /dev/null +++ b/themes/stack/assets/scss/partials/layout/list.scss @@ -0,0 +1,71 @@ +.section-card { + border-radius: var(--card-border-radius); + background-color: var(--card-background); + padding: var(--small-card-padding); + box-shadow: var(--shadow-l1); + display: flex; + align-items: center; + gap: 20px; + + --separation: 15px; + + .section-term { + font-size: 2.2rem; + margin: 0; + color: var(--card-text-color-main); + } + + .section-description { + font-weight: normal; + color: var(--card-text-color-secondary); + font-size: 1.6rem; + margin: 0; + } + + .section-details { + flex-grow: 1; + display: flex; + flex-direction: column; + gap: 8px; + } + + .section-image { + img { + width: 60px; + height: 60px; + } + } + + .section-count { + color: var(--card-text-color-tertiary); + font-size: 1.4rem; + margin: 0; + font-weight: bold; + text-transform: uppercase; + } +} + +.subsection-list { + overflow-x: auto; + + .article-list--tile { + display: flex; + padding-bottom: 15px; + + article { + width: 250px; + height: 150px; + margin-right: 20px; + flex-shrink: 0; + + .article-title { + margin: 0; + font-size: 1.8rem; + } + + .article-details { + padding: 20px; + } + } + } +} diff --git a/themes/stack/assets/scss/partials/layout/search.scss b/themes/stack/assets/scss/partials/layout/search.scss new file mode 100644 index 0000000..89cdcef --- /dev/null +++ b/themes/stack/assets/scss/partials/layout/search.scss @@ -0,0 +1,82 @@ +.search-form { + 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; + inset-inline-start: 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; + inset-inline-end: 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/themes/stack/assets/scss/partials/menu.scss b/themes/stack/assets/scss/partials/menu.scss new file mode 100644 index 0000000..5b6be17 --- /dev/null +++ b/themes/stack/assets/scss/partials/menu.scss @@ -0,0 +1,229 @@ +/*! + * Hamburgers + * @description Tasty CSS-animated hamburgers + * @author Jonathan Suh @jonsuh + * @site https://jonsuh.com/hamburgers + * @link https://github.com/jonsuh/hamburgers + */ + +.hamburger { + padding-top: 10px; + display: inline-block; + cursor: pointer; + transition-property: opacity, filter; + transition-duration: 0.15s; + transition-timing-function: linear; + font: inherit; + color: inherit; + text-transform: none; + background-color: transparent; + border: 0; + margin: 0; + overflow: visible; +} +.hamburger:hover { + opacity: 0.7; +} +.hamburger.is-active:hover { + opacity: 0.7; +} +.hamburger.is-active .hamburger-inner, +.hamburger.is-active .hamburger-inner::before, +.hamburger.is-active .hamburger-inner::after { + background-color: #000; +} + +.hamburger-box { + width: 30px; + height: 24px; + display: inline-block; + position: relative; +} + +.hamburger-inner { + display: block; + top: 50%; + margin-top: -2px; +} + +.hamburger-inner, +.hamburger-inner::before, +.hamburger-inner::after { + width: 30px; + height: 2px; + background-color: var(--card-text-color-main); + border-radius: 4px; + position: absolute; + transition-property: transform; + transition-duration: 0.15s; + transition-timing-function: ease; +} +.hamburger-inner::before, +.hamburger-inner::after { + content: ""; + display: block; +} +.hamburger-inner::before { + top: -10px; +} +.hamburger-inner::after { + bottom: -10px; +} + +.hamburger--spin .hamburger-inner { + transition-duration: 0.22s; + transition-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); +} +.hamburger--spin .hamburger-inner::before { + transition: top 0.1s 0.25s ease-in, opacity 0.1s ease-in; +} +.hamburger--spin .hamburger-inner::after { + transition: bottom 0.1s 0.25s ease-in, transform 0.22s cubic-bezier(0.55, 0.055, 0.675, 0.19); +} + +.hamburger--spin.is-active .hamburger-inner { + transform: rotate(225deg); + transition-delay: 0.12s; + transition-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); +} +.hamburger--spin.is-active .hamburger-inner::before { + top: 0; + opacity: 0; + transition: top 0.1s ease-out, opacity 0.1s 0.12s ease-out; +} +.hamburger--spin.is-active .hamburger-inner::after { + bottom: 0; + transform: rotate(-90deg); + transition: bottom 0.1s ease-out, transform 0.22s 0.12s cubic-bezier(0.215, 0.61, 0.355, 1); +} + +#toggle-menu { + background: none; + border: none; + position: absolute; + right: 0; + top: 0; + z-index: 2; + cursor: pointer; + + [dir="rtl"] & { + left: 0; + right: auto; + } + + @include respond(md) { + display: none; + } + + outline: none; + + &.is-active { + .hamburger-inner, + .hamburger-inner::before, + .hamburger-inner::after { + background-color: var(--accent-color); + } + } +} + +/* Menu style */ +#main-menu { + list-style: none; + overflow-y: auto; + flex-grow: 1; + font-size: 1.4rem; + background-color: var(--card-background); + + box-shadow: var(--shadow-l1); + display: none; + margin: 0 calc(var(--container-padding) * -1); + + padding: 30px 30px; + + @include respond(xl) { + padding: 15px 0; + } + + &, .menu-bottom-section ol { + flex-direction: column; + gap: 30px; + + @include respond(xl) { + gap: 25px; + } + } + + &.show { + display: flex; + } + + @include respond(md) { + align-items: flex-end; + display: flex; + background-color: transparent; + padding: 0; + box-shadow: none; + margin: 0; + } + + li { + position: relative; + vertical-align: middle; + padding: 0; + + @include respond(md) { + width: 100%; + } + + svg { + stroke: currentColor; + stroke-width: 1.33; + width: 20px; + height: 20px; + } + + a { + height: 100%; + display: inline-flex; + align-items: center; + color: var(--body-text-color); + gap: var(--menu-icon-separation); + } + + span { + flex: 1; + } + + &.current { + a { + color: var(--accent-color); + font-weight: bold; + } + } + + &.menu-bottom-section { + margin-top: auto; + + ol { + display: flex; + padding-left: 0; + } + } + } +} + +.menu-social { + list-style: none; + padding: 0; + margin: 0; + display: flex; + flex-direction: row; + gap: 10px; + + svg { + width: 24px; + height: 24px; + stroke: var(--body-text-color); + stroke-width: 1.33; + } +} diff --git a/themes/stack/assets/scss/partials/pagination.scss b/themes/stack/assets/scss/partials/pagination.scss new file mode 100644 index 0000000..ca46780 --- /dev/null +++ b/themes/stack/assets/scss/partials/pagination.scss @@ -0,0 +1,21 @@ +.pagination { + display: flex; + background-color: var(--card-background); + box-shadow: var(--shadow-l1); + border-radius: var(--card-border-radius); + overflow: hidden; + flex-wrap: wrap; + + .page-link { + padding: 16px 32px; + display: inline-flex; + + &.current { + font-weight: bold; + background-color: var(--card-background-selected); + color: var(--card-text-color-main); + } + + color: var(--card-text-color-secondary); + } +} diff --git a/themes/stack/assets/scss/partials/sidebar.scss b/themes/stack/assets/scss/partials/sidebar.scss new file mode 100644 index 0000000..a6a77c5 --- /dev/null +++ b/themes/stack/assets/scss/partials/sidebar.scss @@ -0,0 +1,199 @@ +.sidebar { + &.sticky { + @include respond(md) { + position: sticky; + } + } +} + +.left-sidebar { + display: flex; + flex-direction: column; + flex-shrink: 0; + align-self: stretch; + gap: var(--sidebar-element-separation); + max-width: none; + width: 100%; + position: relative; + + --sidebar-avatar-size: 100px; + --sidebar-element-separation: 20px; + --emoji-size: 40px; + --emoji-font-size: 20px; + + @include respond(md) { + width: auto; + padding-top: var(--main-top-padding); + padding-bottom: var(--main-top-padding); + max-height: 100vh; + } + + @include respond(2xl) { + --sidebar-avatar-size: 120px; + --sidebar-element-separation: 25px; + --emoji-size: 40px; + } + + &.sticky { + top: 0; + } + + &.compact { + --sidebar-avatar-size: 80px; + --emoji-size: 30px; + --emoji-font-size: 15px; + + header { + @include respond(lg) { + flex-direction: row; + } + + .site-meta { + gap: 5px; + } + + .site-name { + font-size: 1.4rem; + + @include respond(2xl) { + font-size: 1.75rem; + } + } + + .site-description { + font-size: 1.4rem; + } + } + } +} + +.right-sidebar { + width: 100%; + display: none; + flex-direction: column; + gap: var(--widget-separation); + + &.sticky { + top: 0; + } + + @include respond(lg) { + padding-top: var(--main-top-padding); + padding-bottom: var(--main-top-padding); + } +} + +.sidebar header { + z-index: 1; + transition: box-shadow 0.5s ease; + display: flex; + flex-direction: column; + gap: var(--sidebar-element-separation); + + @include respond(md) { + padding: 0; + } + + .site-avatar { + position: relative; + margin: 0; + width: var(--sidebar-avatar-size); + height: var(--sidebar-avatar-size); + flex-shrink: 0; + + .site-logo { + width: 100%; + height: 100%; + border-radius: 100%; + box-shadow: var(--shadow-l1); + } + + .emoji { + position: absolute; + width: var(--emoji-size); + height: var(--emoji-size); + line-height: var(--emoji-size); + border-radius: 100%; + bottom: 0; + right: 0; + text-align: center; + font-size: var(--emoji-font-size); + background-color: var(--card-background); + box-shadow: var(--shadow-l2); + } + } + + .site-meta { + display: flex; + flex-direction: column; + gap: 10px; + justify-content: center; + } + + .site-name { + color: var(--accent-color); + margin: 0; + font-size: 1.6rem; + + @include respond(2xl) { + font-size: 1.8rem; + } + } + + .site-description { + color: var(--body-text-color); + font-weight: normal; + margin: 0; + font-size: 1.4rem; + + @include respond(2xl) { + font-size: 1.6rem; + } + } +} + +[data-scheme="dark"] { + #dark-mode-toggle { + color: var(--accent-color); + font-weight: 700; + + .icon-tabler-toggle-left { + display: none; + } + + .icon-tabler-toggle-right { + display: unset; + } + } +} + +#dark-mode-toggle { + margin-top: auto; + color: var(--body-text-color); + display: flex; + align-items: center; + cursor: pointer; + gap: var(--menu-icon-separation); + + .icon-tabler-toggle-right { + display: none; + } +} + +#i18n-switch { + color: var(--body-text-color); + display: inline-flex; + align-content: center; + gap: var(--menu-icon-separation); + + select { + border: 0; + background-color: transparent; + color: var(--body-text-color); + + option { + color: var(--card-text-color-main); + background-color: var(--card-background); + } + } +} diff --git a/themes/stack/assets/scss/partials/widgets.scss b/themes/stack/assets/scss/partials/widgets.scss new file mode 100644 index 0000000..42cfcc2 --- /dev/null +++ b/themes/stack/assets/scss/partials/widgets.scss @@ -0,0 +1,67 @@ +.widget { + display: flex; + flex-direction: column; + + .widget-icon { + svg { + width: 32px; + height: 32px; + stroke-width: 1.6; + color: var(--body-text-color); + } + } +} + +/* Tag cloud widget */ +.tagCloud { + .tagCloud-tags { + display: flex; + flex-wrap: wrap; + gap: 10px; + + a { + background: var(--card-background); + box-shadow: var(--shadow-l1); + border-radius: var(--tag-border-radius); + padding: 8px 20px; + color: var(--card-text-color-main); + font-size: 1.4rem; + transition: box-shadow 0.3s ease; + + &:hover { + box-shadow: var(--shadow-l2); + } + } + } +} + +/* 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 { + &:not(:last-of-type) { + border-bottom: 1.5px solid var(--card-separator-color); + } + + a { + font-size: 1.4rem; + padding: 18px 25px; + display: flex; + + span.year { + flex: 1; + color: var(--card-text-color-main); + font-weight: bold; + } + + span.count { + color: var(--card-text-color-tertiary); + } + } + } +} diff --git a/themes/stack/assets/scss/style.scss b/themes/stack/assets/scss/style.scss new file mode 100644 index 0000000..e50d400 --- /dev/null +++ b/themes/stack/assets/scss/style.scss @@ -0,0 +1,28 @@ +/*! +* Hugo Theme Stack +* +* @author: Jimmy Cai +* @website: https://jimmycai.com +* @link: https://github.com/CaiJimmy/hugo-theme-stack +*/ + +@import "breakpoints.scss"; +@import "variables.scss"; +@import "grid.scss"; + +@import "external/normalize.scss"; + +@import "partials/menu.scss"; +@import "partials/article.scss"; +@import "partials/widgets.scss"; +@import "partials/footer.scss"; +@import "partials/pagination.scss"; +@import "partials/sidebar.scss"; +@import "partials/base.scss"; +@import "partials/layout/article.scss"; +@import "partials/layout/list.scss"; +@import "partials/layout/404.scss"; +@import "partials/layout/search.scss"; + +@import "general.scss"; +@import "custom.scss"; diff --git a/themes/stack/assets/scss/variables.scss b/themes/stack/assets/scss/variables.scss new file mode 100644 index 0000000..97810a1 --- /dev/null +++ b/themes/stack/assets/scss/variables.scss @@ -0,0 +1,167 @@ +$defaultTagBackgrounds: #8ea885, #df7988, #0177b8, #ffb900, #6b69d6; +$defaultTagColors: #fff, #fff, #fff, #fff, #fff; + +/* +* Global style +*/ +:root { + --main-top-padding: 35px; + + @include respond(xl) { + --main-top-padding: 50px; + } + + --body-background: #f5f5fa; + + --accent-color: #34495e; + --accent-color-darker: #2c3e50; + --accent-color-text: #fff; + --body-text-color: #707070; + + --tag-border-radius: 4px; + + --section-separation: 40px; + + --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); + } +} + +/** +* Global font family +*/ +:root { + --sys-font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Droid Sans", "Helvetica Neue"; + --zh-font-family: "PingFang SC", "Hiragino Sans GB", "Droid Sans Fallback", "Microsoft YaHei"; + + --base-font-family: "Lato", var(--sys-font-family), var(--zh-font-family), sans-serif; + --code-font-family: Menlo, Monaco, Consolas, "Courier New", var(--zh-font-family), monospace; +} + +/* +* Card style +*/ +:root { + --card-background: #fff; + --card-background-selected: #eaeaea; + + --card-text-color-main: #000; + --card-text-color-secondary: #747474; + --card-text-color-tertiary: #767676; + --card-separator-color: rgba(218, 218, 218, 0.5); + + --card-border-radius: 10px; + + --card-padding: 20px; + + @include respond(md) { + --card-padding: 25px; + } + + @include respond(xl) { + --card-padding: 30px; + } + + --small-card-padding: 25px 20px; + + @include respond(md) { + --small-card-padding: 25px; + } + + &[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); + --card-text-color-secondary: rgba(255, 255, 255, 0.7); + --card-text-color-tertiary: rgba(255, 255, 255, 0.5); + --card-separator-color: rgba(255, 255, 255, 0.12); + } +} + +/** +* Article content font settings +*/ +:root { + --article-font-family: var(--base-font-family); + --article-font-size: 1.6rem; + + @include respond(md) { + --article-font-size: 1.7rem; + } + + --article-line-height: 1.85; +} + +/* +* Article content style +*/ +:root { + --blockquote-border-size: 4px; + --blockquote-background-color: rgb(248 248 248); + + --heading-border-size: 4px; + + --link-background-color: 189, 195, 199; + --link-background-opacity: 0.5; + --link-background-opacity-hover: 0.7; + + --pre-background-color: #272822; + --pre-text-color: #f8f8f2; + + --code-background-color: rgba(0, 0, 0, 0.12); + --code-text-color: #808080; + + --table-border-color: #dadada; + --tr-even-background-color: #efefee; + + --kbd-border-color: #dadada; + + &[data-scheme="dark"] { + --code-background-color: #272822; + --code-text-color: rgba(255, 255, 255, 0.9); + + --table-border-color: #717171; + --tr-even-background-color: #545454; + + --blockquote-background-color: rgb(75 75 75); + } +} + +/* +* Shadow style +* Thanks to https://www.figma.com/community/plugin/744987207861965946/Shadow-picker +*/ +:root { + --shadow-l1: 0px 4px 8px rgba(0, 0, 0, 0.04), 0px 0px 2px rgba(0, 0, 0, 0.06), 0px 0px 1px rgba(0, 0, 0, 0.04); + --shadow-l2: 0px 10px 20px rgba(0, 0, 0, 0.04), 0px 2px 6px rgba(0, 0, 0, 0.04), 0px 0px 1px rgba(0, 0, 0, 0.04); + --shadow-l3: 0px 10px 20px rgba(0, 0, 0, 0.04), 0px 2px 6px rgba(0, 0, 0, 0.04), 0px 0px 1px rgba(0, 0, 0, 0.04); + --shadow-l4: 0px 24px 32px rgba(0, 0, 0, 0.04), 0px 16px 24px rgba(0, 0, 0, 0.04), 0px 4px 8px rgba(0, 0, 0, 0.04), + 0px 0px 1px rgba(0, 0, 0, 0.04); +} + +[data-scheme="light"] { + --pre-text-color: #272822; + --pre-background-color: #fafafa; + @import "partials/highlight/light.scss"; +} + +[data-scheme="dark"] { + --pre-text-color: #f8f8f2; + --pre-background-color: #272822; + @import "partials/highlight/dark.scss"; +} + +:root { + --menu-icon-separation: 40px; + --container-padding: 15px; + --widget-separation: var(--section-separation); +} diff --git a/themes/stack/assets/ts/color.ts b/themes/stack/assets/ts/color.ts new file mode 100644 index 0000000..50581d1 --- /dev/null +++ b/themes/stack/assets/ts/color.ts @@ -0,0 +1,63 @@ +interface colorScheme { + hash: string, /// Regenerate color scheme when the image hash is changed + DarkMuted: { + hex: string, + rgb: Number[], + bodyTextColor: string + }, + Vibrant: { + hex: string, + rgb: Number[], + bodyTextColor: string + } +} + +let colorsCache: { [key: string]: colorScheme } = {}; + +if (localStorage.hasOwnProperty('StackColorsCache')) { + try { + colorsCache = JSON.parse(localStorage.getItem('StackColorsCache')); + } + catch (e) { + colorsCache = {}; + } +} + +async function getColor(key: string, hash: string, imageURL: string) { + if (!key) { + /** + * If no key is provided, do not cache the result + */ + return await Vibrant.from(imageURL).getPalette(); + } + + if (!colorsCache.hasOwnProperty(key) || colorsCache[key].hash !== hash) { + /** + * If key is provided, but not found in cache, or the hash mismatches => Regenerate color scheme + */ + const palette = await Vibrant.from(imageURL).getPalette(); + + colorsCache[key] = { + hash: hash, + Vibrant: { + hex: palette.Vibrant.hex, + rgb: palette.Vibrant.rgb, + bodyTextColor: palette.Vibrant.bodyTextColor + }, + DarkMuted: { + hex: palette.DarkMuted.hex, + rgb: palette.DarkMuted.rgb, + bodyTextColor: palette.DarkMuted.bodyTextColor + } + } + + /* Save the result in localStorage */ + localStorage.setItem('StackColorsCache', JSON.stringify(colorsCache)); + } + + return colorsCache[key]; +} + +export { + getColor +} \ No newline at end of file diff --git a/themes/stack/assets/ts/colorScheme.ts b/themes/stack/assets/ts/colorScheme.ts new file mode 100644 index 0000000..978e98e --- /dev/null +++ b/themes/stack/assets/ts/colorScheme.ts @@ -0,0 +1,92 @@ +type colorScheme = 'light' | 'dark' | 'auto'; + +class StackColorScheme { + private localStorageKey = 'StackColorScheme'; + private currentScheme: colorScheme; + private systemPreferScheme: colorScheme; + + constructor(toggleEl: HTMLElement) { + this.bindMatchMedia(); + this.currentScheme = this.getSavedScheme(); + if (window.matchMedia('(prefers-color-scheme: dark)').matches === true) + this.systemPreferScheme = 'dark' + else + this.systemPreferScheme = 'light'; + + this.dispatchEvent(document.documentElement.dataset.scheme as colorScheme); + + if (toggleEl) + this.bindClick(toggleEl); + + if (document.body.style.transition == '') + document.body.style.setProperty('transition', 'background-color .3s ease'); + } + + private saveScheme() { + localStorage.setItem(this.localStorageKey, this.currentScheme); + } + + private bindClick(toggleEl: HTMLElement) { + toggleEl.addEventListener('click', (e) => { + if (this.isDark()) { + /// Disable dark mode + this.currentScheme = 'light'; + } + else { + this.currentScheme = 'dark'; + } + + this.setBodyClass(); + + if (this.currentScheme == this.systemPreferScheme) { + /// Set to auto + this.currentScheme = 'auto'; + } + + this.saveScheme(); + }) + } + + private isDark() { + return (this.currentScheme == 'dark' || this.currentScheme == 'auto' && this.systemPreferScheme == 'dark'); + } + + private dispatchEvent(colorScheme: colorScheme) { + const event = new CustomEvent('onColorSchemeChange', { + detail: colorScheme + }); + window.dispatchEvent(event); + } + + private setBodyClass() { + if (this.isDark()) { + document.documentElement.dataset.scheme = 'dark'; + } + else { + document.documentElement.dataset.scheme = 'light'; + } + + this.dispatchEvent(document.documentElement.dataset.scheme as colorScheme); + } + + private getSavedScheme(): colorScheme { + const savedScheme = localStorage.getItem(this.localStorageKey); + + if (savedScheme == 'light' || savedScheme == 'dark' || savedScheme == 'auto') return savedScheme; + else return 'auto'; + } + + private bindMatchMedia() { + window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => { + if (e.matches) { + this.systemPreferScheme = 'dark'; + } + else { + this.systemPreferScheme = 'light'; + } + this.setBodyClass(); + }); + } +} + +export default StackColorScheme; diff --git a/themes/stack/assets/ts/createElement.ts b/themes/stack/assets/ts/createElement.ts new file mode 100644 index 0000000..3a1e85e --- /dev/null +++ b/themes/stack/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/themes/stack/assets/ts/gallery.ts b/themes/stack/assets/ts/gallery.ts new file mode 100644 index 0000000..9840f1e --- /dev/null +++ b/themes/stack/assets/ts/gallery.ts @@ -0,0 +1,186 @@ +declare global { + interface Window { + PhotoSwipe: any; + PhotoSwipeUI_Default: any + } +} + +interface PhotoSwipeItem { + w: number; + h: number; + src: string; + msrc: string; + title?: string; + el: HTMLElement; +} + +class StackGallery { + private galleryUID: number; + private items: PhotoSwipeItem[] = []; + + constructor(container: HTMLElement, galleryUID = 1) { + if (window.PhotoSwipe == undefined || window.PhotoSwipeUI_Default == undefined) { + console.error("PhotoSwipe lib not loaded."); + return; + } + + this.galleryUID = galleryUID; + + StackGallery.createGallery(container); + this.loadItems(container); + this.bindClick(); + } + + private loadItems(container: HTMLElement) { + this.items = []; + + const figures = container.querySelectorAll('figure.gallery-image'); + + for (const el of figures) { + const figcaption = el.querySelector('figcaption'), + img = el.querySelector('img'); + + let aux: PhotoSwipeItem = { + w: parseInt(img.getAttribute('width')), + h: parseInt(img.getAttribute('height')), + src: img.src, + msrc: img.getAttribute('data-thumb') || img.src, + el: el + } + + if (figcaption) { + aux.title = figcaption.innerHTML; + } + + this.items.push(aux); + } + } + + public static createGallery(container: HTMLElement) { + /// The process of wrapping image with figure tag is done using JavaScript instead of only Hugo markdown render hook + /// because it can not detect whether image is being wrapped by a link or not + /// and it lead to a invalid HTML construction (
) + + const images = container.querySelectorAll('img.gallery-image'); + for (const img of Array.from(images)) { + /// Images are wrapped with figure tag if the paragraph has only images without texts + /// This is done to allow inline images within paragraphs + const paragraph = img.closest('p'); + + if (!paragraph || !container.contains(paragraph)) continue; + + if (paragraph.textContent.trim() == '') { + /// Once we insert figcaption, this check no longer works + /// So we add a class to paragraph to mark it + paragraph.classList.add('no-text'); + } + + let isNewLineImage = paragraph.classList.contains('no-text'); + if (!isNewLineImage) continue; + + const hasLink = img.parentElement.tagName == 'A'; + + let el: HTMLElement = img; + /// Wrap image with figure tag, with flex-grow and flex-basis values extracted from img's data attributes + const figure = document.createElement('figure'); + figure.style.setProperty('flex-grow', img.getAttribute('data-flex-grow') || '1'); + figure.style.setProperty('flex-basis', img.getAttribute('data-flex-basis') || '0'); + if (hasLink) { + /// Wrap 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 tag if image was not wrapped by 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 = []; + + for (const figure of figuresEl) { + if (!currentGallery.length) { + /// First iteration + currentGallery = [figure]; + } + else if (figure.previousElementSibling === currentGallery[currentGallery.length - 1]) { + /// Adjacent figures + currentGallery.push(figure); + } + else if (currentGallery.length) { + /// End gallery + StackGallery.wrap(currentGallery); + currentGallery = [figure]; + } + } + + if (currentGallery.length > 0) { + StackGallery.wrap(currentGallery); + } + } + + /** + * Wrap adjacent figure tags with div.gallery + * @param figures + */ + public static wrap(figures: HTMLElement[]) { + const galleryContainer = document.createElement('div'); + galleryContainer.className = 'gallery'; + + const parentNode = figures[0].parentNode, + first = figures[0]; + + parentNode.insertBefore(galleryContainer, first) + + for (const figure of figures) { + galleryContainer.appendChild(figure); + } + } + + public open(index: number) { + const pswp = document.querySelector('.pswp') as HTMLDivElement; + const ps = new window.PhotoSwipe(pswp, window.PhotoSwipeUI_Default, this.items, { + index: index, + galleryUID: this.galleryUID, + getThumbBoundsFn: (index) => { + const thumbnail = this.items[index].el.getElementsByTagName('img')[0], + pageYScroll = window.pageYOffset || document.documentElement.scrollTop, + rect = thumbnail.getBoundingClientRect(); + + return { x: rect.left, y: rect.top + pageYScroll, w: rect.width }; + } + }); + + ps.init(); + } + + private bindClick() { + for (const [index, item] of this.items.entries()) { + const a = item.el.querySelector('a'); + + a.addEventListener('click', (e) => { + e.preventDefault(); + this.open(index); + }) + } + } +} + +export default StackGallery; \ No newline at end of file diff --git a/themes/stack/assets/ts/main.ts b/themes/stack/assets/ts/main.ts new file mode 100644 index 0000000..f3160ae --- /dev/null +++ b/themes/stack/assets/ts/main.ts @@ -0,0 +1,112 @@ +/*! +* Hugo Theme Stack +* +* @author: Jimmy Cai +* @website: https://jimmycai.com +* @link: https://github.com/CaiJimmy/hugo-theme-stack +*/ +import StackGallery from "ts/gallery"; +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: () => { + /** + * Bind menu event + */ + menu(); + + const articleContent = document.querySelector('.article-content') as HTMLElement; + if (articleContent) { + new StackGallery(articleContent); + setupSmoothAnchors(); + setupScrollspy(); + } + + /** + * Add linear gradient background to tile style article + */ + const articleTile = document.querySelector('.article-list--tile'); + if (articleTile) { + let observer = new IntersectionObserver(async (entries, observer) => { + entries.forEach(entry => { + if (!entry.isIntersecting) return; + observer.unobserve(entry.target); + + const articles = entry.target.querySelectorAll('article.has-image'); + articles.forEach(async articles => { + const image = articles.querySelector('img'), + imageURL = image.src, + key = image.getAttribute('data-key'), + hash = image.getAttribute('data-hash'), + articleDetails: HTMLDivElement = articles.querySelector('.article-details'); + + const colors = await getColor(key, hash, imageURL); + + articleDetails.style.background = ` + linear-gradient(0deg, + rgba(${colors.DarkMuted.rgb[0]}, ${colors.DarkMuted.rgb[1]}, ${colors.DarkMuted.rgb[2]}, 0.5) 0%, + rgba(${colors.Vibrant.rgb[0]}, ${colors.Vibrant.rgb[1]}, ${colors.Vibrant.rgb[2]}, 0.75) 100%)`; + }) + }) + }); + + 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')); + } +} + +window.addEventListener('load', () => { + setTimeout(function () { + Stack.init(); + }, 0); +}) + +declare global { + interface Window { + createElement: any; + Stack: any + } +} + +window.Stack = Stack; +window.createElement = createElement; \ No newline at end of file diff --git a/themes/stack/assets/ts/menu.ts b/themes/stack/assets/ts/menu.ts new file mode 100644 index 0000000..34615ba --- /dev/null +++ b/themes/stack/assets/ts/menu.ts @@ -0,0 +1,83 @@ +/** + * Slide up/down + * Code from https://dev.to/bmsvieira/vanilla-js-slidedown-up-4dkn + * @param target + * @param duration + */ +let slideUp = (target: HTMLElement, duration = 500) => { + target.classList.add('transiting'); + target.style.transitionProperty = 'height, margin, padding'; + target.style.transitionDuration = duration + 'ms'; + ///target.style.boxSizing = 'border-box'; + target.style.height = target.offsetHeight + 'px'; + target.offsetHeight; + target.style.overflow = 'hidden'; + target.style.height = "0"; + target.style.paddingTop = "0"; + target.style.paddingBottom = "0"; + target.style.marginTop = "0"; + target.style.marginBottom = "0"; + window.setTimeout(() => { + target.classList.remove('show') + target.style.removeProperty('height'); + target.style.removeProperty('padding-top'); + target.style.removeProperty('padding-bottom'); + target.style.removeProperty('margin-top'); + target.style.removeProperty('margin-bottom'); + target.style.removeProperty('overflow'); + target.style.removeProperty('transition-duration'); + target.style.removeProperty('transition-property'); + target.classList.remove('transiting'); + }, duration); +} + +let slideDown = (target: HTMLElement, duration = 500) => { + target.classList.add('transiting'); + target.style.removeProperty('display'); + + target.classList.add('show'); + + let height = target.offsetHeight; + target.style.overflow = 'hidden'; + target.style.height = "0"; + target.style.paddingTop = "0"; + target.style.paddingBottom = "0"; + target.style.marginTop = "0"; + target.style.marginBottom = "0"; + target.offsetHeight; + ///target.style.boxSizing = 'border-box'; + target.style.transitionProperty = "height, margin, padding"; + target.style.transitionDuration = duration + 'ms'; + target.style.height = height + 'px'; + target.style.removeProperty('padding-top'); + target.style.removeProperty('padding-bottom'); + target.style.removeProperty('margin-top'); + target.style.removeProperty('margin-bottom'); + window.setTimeout(() => { + target.style.removeProperty('height'); + target.style.removeProperty('overflow'); + target.style.removeProperty('transition-duration'); + target.style.removeProperty('transition-property'); + target.classList.remove('transiting'); + }, duration); +} + +let slideToggle = (target, duration = 500) => { + if (window.getComputedStyle(target).display === 'none') { + return slideDown(target, duration); + } else { + return slideUp(target, duration); + } +} + +export default function () { + const toggleMenu = document.getElementById('toggle-menu'); + if (toggleMenu) { + toggleMenu.addEventListener('click', () => { + if (document.getElementById('main-menu').classList.contains('transiting')) return; + document.body.classList.toggle('show-menu'); + slideToggle(document.getElementById('main-menu'), 300); + toggleMenu.classList.toggle('is-active'); + }); + } +} \ No newline at end of file diff --git a/themes/stack/assets/ts/scrollspy.ts b/themes/stack/assets/ts/scrollspy.ts new file mode 100644 index 0000000..8a14085 --- /dev/null +++ b/themes/stack/assets/ts/scrollspy.ts @@ -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): 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) { + 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 }; \ No newline at end of file diff --git a/themes/stack/assets/ts/search.tsx b/themes/stack/assets/ts/search.tsx new file mode 100644 index 0000000..1c81dd1 --- /dev/null +++ b/themes/stack/assets/ts/search.tsx @@ -0,0 +1,333 @@ +interface pageData { + title: string, + date: string, + permalink: string, + content: string, + image?: string, + preview: string, + matchCount: number +} + +interface match { + start: number, + end: 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; + + /// Check if there's already value in the search input + if (this.input.value.trim() !== '') { + this.doSearch(this.input.value.split(' ')); + } + else { + this.handleQueryString(); + } + + this.bindQueryStringChange(); + this.bindSearchForm(); + } + + /** + * 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(`${replaceHTMLEnt(str.substring(item.start, end))}`); + 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 + } + + const contentMatchAll = item.content.matchAll(regex); + for (const match of Array.from(contentMatchAll)) { + contentMatches.push({ + start: match.index, + end: match.index + match[0].length + }); + } + + 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 + return results.sort((a, b) => { + return b.matchCount - a.matchCount; + }); + } + + 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()); + const parser = new DOMParser(); + + for (const item of this.data) { + item.content = parser.parseFromString(item.content, 'text/html').body.innerText; + } + } + + return this.data; + } + + private bindSearchForm() { + let lastSearch = ''; + + const eventHandler = (e) => { + e.preventDefault(); + const keywords = this.input.value.trim(); + + Search.updateQueryString(keywords, true); + + if (keywords === '') { + lastSearch = ''; + 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
+ +
+

+
+
+ {item.image && +
+ +
+ } +
+
; + } +} + +declare global { + interface Window { + searchResultTitleTemplate: string; + } +} + +window.addEventListener('load', () => { + setTimeout(function () { + const searchForm = document.querySelector('.search-form') as HTMLFormElement, + searchInput = searchForm.querySelector('input') as HTMLInputElement, + searchResultList = document.querySelector('.search-result--list') as HTMLDivElement, + searchResultTitle = document.querySelector('.search-result--title') as HTMLHeadingElement; + + new Search({ + form: searchForm, + input: searchInput, + list: searchResultList, + resultTitle: searchResultTitle, + resultTitleTemplate: window.searchResultTitleTemplate + }); + }, 0); +}) + +export default Search; \ No newline at end of file diff --git a/themes/stack/assets/ts/smoothAnchors.ts b/themes/stack/assets/ts/smoothAnchors.ts new file mode 100644 index 0000000..16ab6a3 --- /dev/null +++ b/themes/stack/assets/ts/smoothAnchors.ts @@ -0,0 +1,37 @@ +// 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(); + + const targetId = decodeURI(aElement.getAttribute("href").substring(1)), + target = document.getElementById(targetId) as HTMLElement, + offset = target.getBoundingClientRect().top - document.documentElement.getBoundingClientRect().top; + + window.history.pushState({}, "", aElement.getAttribute("href")); + scrollTo({ + top: offset, + behavior: "smooth" + }); + }); + }); +} + +export { setupSmoothAnchors }; \ No newline at end of file diff --git a/themes/stack/config.yaml b/themes/stack/config.yaml new file mode 100644 index 0000000..b1a0bff --- /dev/null +++ b/themes/stack/config.yaml @@ -0,0 +1,149 @@ +module: + hugoVersion: + extended: true + min: "0.87.0" + +params: + mainSections: + - post + featuredImageField: image + rssFullContent: true + favicon: + + footer: + since: + customText: + + dateFormat: + published: Jan 02, 2006 + lastUpdated: Jan 02, 2006 15:04 MST + + sidebar: + compact: false + emoji: + subtitle: + avatar: + enabled: true + local: true + src: img/avatar.png + + article: + headingAnchor: false + 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: + + beaudar: + repo: + issueTerm: pathname + label: + theme: + + remark42: + host: + site: + locale: + + vssue: + platform: + owner: + repo: + clientId: + clientSecret: + autoCreateIssue: false + + # Waline client configuration see: https://waline.js.org/en/reference/client/props.html + waline: + serverURL: + lang: + visitor: + avatar: + emoji: + - https://cdn.jsdelivr.net/gh/walinejs/emojis/weibo + requiredMeta: + - nick + - mail + locale: + admin: Admin + placeholder: + + twikoo: + envId: + region: + path: + lang: + + giscus: + repo: + repoID: + category: + categoryID: + mapping: + strict: + lightTheme: + darkTheme: + reactionsEnabled: 1 + emitMetadata: 0 + inputPosition: + lang: + + gitalk: + owner: + admin: + repo: + clientID: + clientSecret: + + cusdis: + host: + id: + + widgets: + homepage: [] + page: [] + + opengraph: + twitter: + # Your Twitter username + site: + + # Available values: summary, summary_large_image + card: summary_large_image + + defaultImage: + opengraph: + enabled: false + local: false + src: + + colorScheme: + # Display toggle + toggle: true + + # Available values: auto, light, dark + default: auto + + imageProcessing: + cover: + enabled: true + content: + enabled: true diff --git a/themes/stack/data/external.yaml b/themes/stack/data/external.yaml new file mode 100644 index 0000000..4fc0bca --- /dev/null +++ b/themes/stack/data/external.yaml @@ -0,0 +1,44 @@ +Vibrant: + - src: https://cdn.jsdelivr.net/npm/node-vibrant@3.1.6/dist/vibrant.min.js + integrity: sha256-awcR2jno4kI5X0zL8ex0vi2z+KMkF24hUW8WePSA9HM= + type: script + +PhotoSwipe: + - src: https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe.min.js + integrity: sha256-ePwmChbbvXbsO02lbM3HoHbSHTHFAeChekF1xKJdleo= + type: script + defer: true + + - src: https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe-ui-default.min.js + integrity: sha256-UKkzOn/w1mBxRmLLGrSeyB4e1xbrp4xylgAWb3M42pU= + type: script + defer: true + + - src: https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/default-skin/default-skin.min.css + type: style + + - src: https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe.min.css + type: style + +KaTeX: + - src: https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css + integrity: sha384-n8MVd4RsNIU0tAv4ct0nTaAbDJwPJzDEaqSD1odI+WdtXRGWt2kTvGFasHpSy3SV + type: style + + - src: https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.js + integrity: sha384-XjKyOOlGwcjNTAIQHIpgOno0Hl1YQqzUOEleOLALmuqehneUG+vnGctmUb0ZY0l8 + type: script + defer: true + + - src: https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/contrib/auto-render.min.js + integrity: sha384-+VBxd3r6XgURycqtZ117nYw44OOcIax56Z4dCRWbxyPt0Koah1uHoK0o4+/RRE05 + 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 diff --git a/themes/stack/go.mod b/themes/stack/go.mod new file mode 100644 index 0000000..63df635 --- /dev/null +++ b/themes/stack/go.mod @@ -0,0 +1,3 @@ +module github.com/CaiJimmy/hugo-theme-stack/v3 + +go 1.17 diff --git a/themes/stack/i18n/ar.yaml b/themes/stack/i18n/ar.yaml new file mode 100644 index 0000000..45fd749 --- /dev/null +++ b/themes/stack/i18n/ar.yaml @@ -0,0 +1,74 @@ +toggleMenu: + other: اخفي القائمة + +darkMode: + other: الوضع الداكن + +list: + page: + one: "{{ .Count }} صفحه" + other: "{{ .Count }} صفحات" + + section: + other: قسم + + subsection: + one: قسم فرعي + other: اقسام فرعية + +article: + back: + other: خلف + + tableOfContents: + other: جدول المحتويات + + relatedContent: + other: محتوى مشابهه + + lastUpdatedOn: + other: التعديل الاخير + + readingTime: + one: "تُقرأ خلال دقيقة" + other: "تُقرأ خلال {{ .Count }} دقائق" + +notFound: + title: + other: غير موجود + + subtitle: + other: تعذر العثور على الصفحة المطلوبة. + +widget: + archives: + title: + other: الارشيفات + + more: + other: اكثر + + tagCloud: + title: + other: وسوم + + categoriesCloud: + title: + other: التصنيفات + +search: + title: + other: بحث + + placeholder: + other: اكتب... + + resultTitle: + other: "#PAGES_COUNT نتيجة (#TIME_SECONDS ثواني)" + +footer: + builtWith: + other: "مبني باستخدام {{ .Generator }}" + + designedBy: + other: "قالب {{ .Theme }} مصمم من {{ .DesignedBy }}" diff --git a/themes/stack/i18n/be.yaml b/themes/stack/i18n/be.yaml new file mode 100644 index 0000000..605cd08 --- /dev/null +++ b/themes/stack/i18n/be.yaml @@ -0,0 +1,72 @@ +toggleMenu: + other: Паказаць/схаваць меню + +darkMode: + other: Цёмны рэжым + +list: + page: + one: "{{ .Count }} старонка" + few: "{{ .Count }} старонкі" + many: "{{ .Count }} старонак" + other: "{{ .Count }} старонкі" + + section: + other: Раздзел + + subsection: + one: Падраздзел + few: Падраздзелы + many: Падраздзелы + other: Падраздзелы + +article: + back: + other: Назад + + relatedContent: + other: Таксама рэкамендуем + + lastUpdatedOn: + other: Абноўлена + + tableOfContents: + other: Змест + + readingTime: + 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 }} diff --git a/themes/stack/i18n/bg.yaml b/themes/stack/i18n/bg.yaml new file mode 100644 index 0000000..cf64a56 --- /dev/null +++ b/themes/stack/i18n/bg.yaml @@ -0,0 +1,73 @@ +toggleMenu: + other: Покажи Меню + +darkMode: + other: Тъмен Режим + +list: + page: + one: "{{ .Count }} страница" + other: "{{ .Count }} страници" + + section: + other: Секция + + subsection: + one: Подсекция + other: Подсекции + +article: + back: + other: Назад + + tableOfContents: + other: Съдържание + + relatedContent: + other: Свързано Съдържание + + lastUpdatedOn: + other: Последна промяна на + + readingTime: + one: "{{ .Count }} minute read" + other: "{{ .Count }} minute read" + +notFound: + title: + other: Не е намерено + + subtitle: + other: Страницата която търсите не е открита + +widget: + archives: + title: + other: Архиви + + more: + other: Повече + + tagCloud: + title: + other: Тагове + categoriesCloud: + title: + other: Категории + +search: + title: + other: Търсене + + placeholder: + other: Напишете нещо... + + resultTitle: + other: "#PAGES_COUNT страници (#TIME_SECONDS секунди)" + +footer: + builtWith: + other: Създадено с {{ .Generator }} + + designedBy: + other: Тема {{ .Theme }} създадена от {{ .DesignedBy }} diff --git a/themes/stack/i18n/bn.yaml b/themes/stack/i18n/bn.yaml new file mode 100644 index 0000000..36f25a3 --- /dev/null +++ b/themes/stack/i18n/bn.yaml @@ -0,0 +1,73 @@ +toggleMenu: + other: টগল মেনু + +darkMode: + other: ডার্ক মোড + +list: + page: + one: "{{ .Count }} পাতা" + other: "{{ .Count }} পাতা" + + section: + other: অনুচ্ছেদ + + subsection: + one: উপ-অনুচ্ছেদ + other: উপ-অনুচ্ছেদ + +article: + back: + other: পেছনে + + tableOfContents: + other: সূচিপত্র + + relatedContent: + other: সম্পর্কিত বিষয়বস্তু + + lastUpdatedOn: + other: সর্বশেষ আপডেট করা হয়েছে + + readingTime: + one: "{{ .Count }} মিনিটে পড়া যাবে" + other: "{{ .Count }} মিনিটে পড়া যাবে" + +notFound: + title: + other: পাওয়া যায়নি + + subtitle: + other: এই পাতাটি বিদ্যমান নেই + +widget: + archives: + title: + other: আর্কাইভ + + more: + other: আরও + + tagCloud: + title: + other: ট্যাগ + categoriesCloud: + title: + other: বিভাগ + +search: + title: + other: অনুসন্ধান + + placeholder: + other: কিছু টাইপ করুন... + + resultTitle: + other: "#PAGES_COUNT পাতা (#TIME_SECONDS সেকেন্ড)" + +footer: + builtWith: + other: "{{ .Generator }} দিয়ে নির্মিত" + + designedBy: + other: "থিম {{ .Theme }} ডিজাইন করেছেন {{ .DesignedBy }}" diff --git a/themes/stack/i18n/ca.yaml b/themes/stack/i18n/ca.yaml new file mode 100644 index 0000000..af4167e --- /dev/null +++ b/themes/stack/i18n/ca.yaml @@ -0,0 +1,73 @@ +toggleMenu: + other: Toggle Menu + +darkMode: + other: Mode fosc + +list: + page: + one: "{{ .Count }} pàgina" + other: "{{ .Count }} pàgines" + + section: + other: Secció + + subsection: + one: Subsecció + other: Subseccions + +article: + back: + other: Tornar + + tableOfContents: + other: Taula de contingut + + relatedContent: + other: Continguts relacionats + + lastUpdatedOn: + other: Última vegada actualitzat + + readingTime: + one: "{{ .Count }} minut a llegir" + other: "{{ .Count }} minuts a llegir" + +notFound: + title: + other: No Trobat + + subtitle: + other: Aquesta pàgina no existeix + +widget: + archives: + title: + other: Arxiu + + more: + other: Més + + tagCloud: + title: + other: Etiquetes + categoriesCloud: + title: + other: Categories + +search: + title: + other: Cerca + + placeholder: + other: Tecleja alguna cosa... + + resultTitle: + other: "#PAGES_COUNT pàgines en (#TIME_SECONDS segons)" + +footer: + builtWith: + other: Creat amb {{ .Generator }} + + designedBy: + other: Tema {{ .Theme }} dissenyat per {{ .DesignedBy }} diff --git a/themes/stack/i18n/cs.yaml b/themes/stack/i18n/cs.yaml new file mode 100644 index 0000000..7b9b519 --- /dev/null +++ b/themes/stack/i18n/cs.yaml @@ -0,0 +1,73 @@ +toggleMenu: + other: Skrýt menu + +darkMode: + other: Tmavý režim + +list: + page: + one: "{{ .Count }} stránka" + other: "{{ .Count }} stránek" + + section: + other: Kategorie + + subsection: + one: Podkategorie + other: Podkategorie + +article: + back: + other: Zpět + + tableOfContents: + other: Obsah + + relatedContent: + other: Související + + lastUpdatedOn: + other: Naposledy aktualizováno + + readingTime: + one: "{{ .Count }} minuta" + other: "{{ .Count }} minut" + +notFound: + title: + other: Nenalezeno + + subtitle: + other: Tato stránka neexistuje + +widget: + archives: + title: + other: Archivy + + more: + other: Více + + tagCloud: + title: + other: Štítky + categoriesCloud: + title: + other: Kategorie + +search: + title: + other: Hledat + + placeholder: + other: Zadejte něco... + + resultTitle: + other: "#PAGES_COUNT stránek (#TIME_SECONDS sekund)" + +footer: + builtWith: + other: Vytvořeno pomocí {{ .Generator }} + + designedBy: + other: Šablona {{ .Theme }} od {{ .DesignedBy }} diff --git a/themes/stack/i18n/de.yaml b/themes/stack/i18n/de.yaml new file mode 100644 index 0000000..7ab5b56 --- /dev/null +++ b/themes/stack/i18n/de.yaml @@ -0,0 +1,74 @@ +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 + + relatedContent: + 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 + + categoriesCloud: + title: + other: Kategorien + +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 }} diff --git a/themes/stack/i18n/el.yaml b/themes/stack/i18n/el.yaml new file mode 100644 index 0000000..27a147d --- /dev/null +++ b/themes/stack/i18n/el.yaml @@ -0,0 +1,70 @@ +toggleMenu: + other: Εναλλαγή Μενού + +darkMode: + other: Σκοτεινό θέμα + +list: + page: + one: "{{ .Count }} σελιδα" + other: "{{ .Count }} σελιδες" + + section: + other: Ενότητα + + subsection: + one: Υποενότητα + other: Υποενότητες + +article: + back: + other: Πισω + + tableOfContents: + other: Πινακας περιεχομενων + + relatedContent: + 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 }} diff --git a/themes/stack/i18n/en.yaml b/themes/stack/i18n/en.yaml new file mode 100644 index 0000000..fb07328 --- /dev/null +++ b/themes/stack/i18n/en.yaml @@ -0,0 +1,73 @@ +toggleMenu: + other: Toggle Menu + +darkMode: + other: Dark Mode + +list: + page: + one: "{{ .Count }} page" + other: "{{ .Count }} pages" + + section: + other: Section + + subsection: + one: Subsection + other: Subsections + +article: + back: + other: Back + + tableOfContents: + other: Table of contents + + relatedContent: + other: Related content + + 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 + +widget: + archives: + title: + other: Archives + + more: + other: More + + tagCloud: + title: + other: Tags + categoriesCloud: + title: + other: Categories + +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/themes/stack/i18n/es.yaml b/themes/stack/i18n/es.yaml new file mode 100644 index 0000000..6b4b3af --- /dev/null +++ b/themes/stack/i18n/es.yaml @@ -0,0 +1,73 @@ +toggleMenu: + other: Ocultar menú + +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 + + relatedContent: + other: Contenidos relacionados + + lastUpdatedOn: + other: Última actualización + + readingTime: + one: "Tiempo de lectura {{ .Count }} minuto" + other: "Tiempo de lectura {{ .Count }} minutos" + +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 + categoriesCloud: + title: + other: Categorías + +search: + title: + other: Búsqueda + + placeholder: + other: Escribe 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 }} diff --git a/themes/stack/i18n/fa.yaml b/themes/stack/i18n/fa.yaml new file mode 100644 index 0000000..3655757 --- /dev/null +++ b/themes/stack/i18n/fa.yaml @@ -0,0 +1,73 @@ +toggleMenu: + other: منو + +darkMode: + other: حالت شب + +list: + page: + one: "{{ .Count }} صفحه" + other: "{{ .Count }} صفحه" + + section: + other: بخش + + subsection: + one: زیربخش + other: زیربخش + +article: + back: + other: قبلی + + tableOfContents: + other: فهرست + + relatedContent: + other: مطالب مرتبط + + lastUpdatedOn: + other: آخرین بروزرسانی در + + readingTime: + one: "مطالعه در {{ .Count }} دقیقه" + other: "مطالعه در {{ .Count }} دقیقه" + +notFound: + title: + other: یافت نشد + + subtitle: + other: این صحه وجود ندارد + +widget: + archives: + title: + other: آرشیو + + more: + other: بیشتر + + tagCloud: + title: + other: تگ ها + categoriesCloud: + title: + other: دسته بندی + +search: + title: + other: جستجو + + placeholder: + other: تایپ کنید ... + + resultTitle: + other: "#PAGES_COUNT صفحه (#TIME_SECONDS ثانیه)" + +footer: + builtWith: + other: قدرت گرفته از {{ .Generator }} + + designedBy: + other: قالب {{ .Theme }} ساخته شده توسط {{ .DesignedBy }} diff --git a/themes/stack/i18n/fr.yaml b/themes/stack/i18n/fr.yaml new file mode 100644 index 0000000..4e6e1f8 --- /dev/null +++ b/themes/stack/i18n/fr.yaml @@ -0,0 +1,72 @@ +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 + + relatedContent: + 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 + subtitle: + other: Cette page n'existe pas. + +widget: + archives: + title: + other: Archives + + more: + other: Autres + + tagCloud: + title: + other: Mots clés + categoriesCloud: + title: + other: Catégories + +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/themes/stack/i18n/hi.yaml b/themes/stack/i18n/hi.yaml new file mode 100644 index 0000000..27316fe --- /dev/null +++ b/themes/stack/i18n/hi.yaml @@ -0,0 +1,73 @@ +toggleMenu: + other: मेनू टॉगल करें + +darkMode: + other: डार्क मोड + +list: + page: + one: "{{ .Count }} पेज" + other: "{{ .Count }} पेज" + + section: + other: अनुभाग + + subsection: + one: उपधारा + other: उपखंड + +article: + back: + other: पीछे + + tableOfContents: + other: विषयसूची + + relatedContent: + other: संबंधित सामग्री + + lastUpdatedOn: + other: अंतिम बार अपडेट किया गया + + readingTime: + one: "{{ .Count }} मिनट पढ़ें" + other: "{{ .Count }} मिनट पढ़ें" + +notFound: + title: + other: 404 नहीं मिला। + + subtitle: + other: यह पृष्ठ मौजूद नहीं है। + +widget: + archives: + title: + other: अभिलेखागार + + more: + other: अधिक + + tagCloud: + title: + other: टैग + categoriesCloud: + title: + other: श्रेणियाँ + +search: + title: + other: खोज + + placeholder: + other: कुछ लिखें... + + resultTitle: + other: "#PAGES_COUNT पेज (#TIME_SECONDS सेकंड)" + +footer: + builtWith: + other: निर्मित {{ .Generator }} के साथ + + designedBy: + other: थीम {{ .Theme }} द्वारा डिज़ाइन किया गया {{ .DesignedBy }} diff --git a/themes/stack/i18n/hu.yaml b/themes/stack/i18n/hu.yaml new file mode 100644 index 0000000..5e5af92 --- /dev/null +++ b/themes/stack/i18n/hu.yaml @@ -0,0 +1,73 @@ +toggleMenu: + other: Menü Kapcsolása + +darkMode: + other: Sötét Mód + +list: + page: + one: "{{ .Count }} oldal" + other: "{{ .Count }} oldalak" + + section: + other: Szekció + + subsection: + one: Alszekció + other: Alszekciók + +article: + back: + other: Vissza + + tableOfContents: + other: Tartalomjegyzék + + relatedContent: + other: Kapcsolódó tartalom + + lastUpdatedOn: + other: Utolsó frissítés időpontja + + readingTime: + one: "{{ .Count }} percnyi olvasmány" + other: "{{ .Count }} percnyi olvasmány" + +notFound: + title: + other: Nem található + + subtitle: + other: Ez az oldal nem létezik + +widget: + archives: + title: + other: Archívum + + more: + other: Több + + tagCloud: + title: + other: Cimkék + categoriesCloud: + title: + other: Kategóriák + +search: + title: + other: Keresés + + placeholder: + other: Írj valamit... + + resultTitle: + other: "#PAGES_COUNT oldal (#TIME_SECONDS másodperc alatt)" + +footer: + builtWith: + other: "{{ .Generator }} használatával készült" + + designedBy: + other: A {{ .Theme }} dizájnt {{ .DesignedBy }} tervezte diff --git a/themes/stack/i18n/id.yaml b/themes/stack/i18n/id.yaml new file mode 100644 index 0000000..8104351 --- /dev/null +++ b/themes/stack/i18n/id.yaml @@ -0,0 +1,73 @@ +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 + + relatedContent: + other: Konten terkait + + lastUpdatedOn: + other: Terakhir diperbarui pada + + readingTime: + one: "Waktu Membaca: {{ .Count }} menit" + other: "Waktu Membaca: {{ .Count }} menit" + +notFound: + title: + other: Tidak ditemukan + subtitle: + other: Halaman yang Anda akses tidak ditemukan. + +widget: + archives: + title: + other: Arsip + + more: + other: Lebih + + tagCloud: + title: + other: Tag + + categoriesCloud: + title: + other: Kategori + +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/themes/stack/i18n/it.yaml b/themes/stack/i18n/it.yaml new file mode 100644 index 0000000..0cbeeab --- /dev/null +++ b/themes/stack/i18n/it.yaml @@ -0,0 +1,73 @@ +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 + + relatedContent: + 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 + + categoriesCloud: + title: + other: Categorie + +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 }} diff --git a/themes/stack/i18n/ja.yaml b/themes/stack/i18n/ja.yaml new file mode 100644 index 0000000..12352ee --- /dev/null +++ b/themes/stack/i18n/ja.yaml @@ -0,0 +1,70 @@ +toggleMenu: + other: メニューを開く・閉じる + +darkMode: + other: ダークモード + +list: + page: + other: "{{ .Count }} ページ目" + + section: + other: セクション + + subsection: + other: サブセクション + +article: + back: + other: 前のページ + + tableOfContents: + other: 目次 + + relatedContent: + other: 関連するコンテンツ + + lastUpdatedOn: + other: 最終更新 + + readingTime: + other: "読了時間: {{ .Count }}分" + +notFound: + title: + other: 404 Not Found + subtitle: + other: 指定されたページは存在しません。 + +widget: + archives: + title: + other: アーカイブ + + more: + other: さらに見る + + tagCloud: + title: + other: タグ + + categoriesCloud: + title: + other: カテゴリ + +search: + title: + other: 検索 + + placeholder: + other: 入力... + + resultTitle: + other: "#PAGES_COUNT 件 (#TIME_SECONDS 秒)" + +footer: + builtWith: + other: "{{ .Generator }} で構築されています。" + + designedBy: + other: テーマ {{ .Theme }} は {{ .DesignedBy }} によって設計されています。 diff --git a/themes/stack/i18n/ko.yaml b/themes/stack/i18n/ko.yaml new file mode 100644 index 0000000..11cdc4a --- /dev/null +++ b/themes/stack/i18n/ko.yaml @@ -0,0 +1,72 @@ +toggleMenu: + other: 메뉴 여닫기 + +darkMode: + other: 다크 모드 + +list: + page: + one: "{{ .Count }} 페이지" + other: "{{ .Count }} 페이지" + + section: + other: 섹션 + + subsection: + one: 서브섹션 + other: 서브섹션 + +article: + back: + other: 뒤로가기 + + tableOfContents: + other: 목차 + + relatedContent: + other: 관련 글 + + lastUpdatedOn: + other: "마지막 수정: " + + readingTime: + one: "{{ .Count }} 분 정도" + other: "{{ .Count }} 분 정도" + +notFound: + title: + other: 찾을 수 없음 + + subtitle: + other: 페이지를 찾을 수 없습니다. + +widget: + archives: + title: + other: 보관함 + more: + other: 더보기 + + categoriesCloud: + title: + 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/themes/stack/i18n/nl.yaml b/themes/stack/i18n/nl.yaml new file mode 100644 index 0000000..b5bf264 --- /dev/null +++ b/themes/stack/i18n/nl.yaml @@ -0,0 +1,59 @@ +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: + relatedContent: + other: Gerelateerde inhoud + lastUpdatedOn: + other: Laatst bijgewerkt op + readingTime: + other: "{{ .Count }} leestijd" + +notFound: + title: + other: Niet gevonden + subtitle: + other: Deze pagina bestaat niet. + +widget: + archives: + title: + other: Archief + more: + other: Meer + tagCloud: + title: + other: Tags + + categoriesCloud: + title: + other: Categorie + +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 }} diff --git a/themes/stack/i18n/oc.yaml b/themes/stack/i18n/oc.yaml new file mode 100644 index 0000000..1415350 --- /dev/null +++ b/themes/stack/i18n/oc.yaml @@ -0,0 +1,72 @@ +toggleMenu: + other: Alternar menú + +darkMode: + other: Mòde fosc + +list: + page: + one: "{{ .Count }} pagina" + other: "{{ .Count }} paginas" + + section: + other: Seccion + + subsection: + one: Josseccion + other: Josseccions + +article: + back: + other: Tornar + + tableOfContents: + other: Taula de contengut + + relatedContent: + other: Contenguts relacionats + + lastUpdatedOn: + other: Darrièra actualizacion + readingTime: + one: "{{ .Count }} minuta de lectura" + other: "{{ .Count }} minutas de lectura" + +notFound: + title: + other: Non trobat + + subtitle: + other: Aquesta pagina existís pas + +widget: + archives: + title: + other: Archiu + + more: + other: Mai + + tagCloud: + title: + other: Etiquetas + categoriesCloud: + title: + other: Categorias + +search: + title: + other: Cercar + + placeholder: + other: Picatz quicòm... + + resultTitle: + other: "#PAGES_COUNT paginas dins (#TIME_SECONDS segons)" + +footer: + builtWith: + other: Creat amb {{ .Generator }} + + designedBy: + other: Tàma {{ .Theme }} concebut per {{ .DesignedBy }} diff --git a/themes/stack/i18n/pl.yaml b/themes/stack/i18n/pl.yaml new file mode 100644 index 0000000..9f2982f --- /dev/null +++ b/themes/stack/i18n/pl.yaml @@ -0,0 +1,82 @@ +toggleMenu: + other: Przełącz Menu + +darkMode: + other: Tryb ciemny + +list: + page: + one: "{{ .Count }} strona" + few: "{{ .Count }} strony" + many: "{{ .Count }} stron" + other: "{{ .Count }} stron" + + section: + other: Sekcja + + subsection: + one: Podsekcja + few: Podsekcje + many: Podsekcji + other: Podsekcji + +article: + back: + other: Wróć + + tableOfContents: + other: Spis treści + + relatedContent: + other: Powiązane artykuły + + lastUpdatedOn: + other: Ostatnio zaktualizowany + + readingTime: + one: "Przeczytasz w {{ .Count }} minutę" + few: "Przeczytasz w {{ .Count }} minuty" + many: "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 + + categoriesCloud: + title: + other: Kategorie + +search: + title: + other: Szukaj + + placeholder: + other: Wpisz coś... + + resultTitle: + one: "#PAGES_COUNT strona (#TIME_SECONDS sekund)" + few: "#PAGES_COUNT strony (#TIME_SECONDS sekund)" + many: "#PAGES_COUNT stron (#TIME_SECONDS sekund)" + other: "#PAGES_COUNT stron (#TIME_SECONDS sekund)" + +footer: + builtWith: + other: Zbudowano z {{ .Generator }} + + designedBy: + other: Motyw {{ .Theme }} zaprojektowany przez {{ .DesignedBy }} diff --git a/themes/stack/i18n/pt-br.yaml b/themes/stack/i18n/pt-br.yaml new file mode 100644 index 0000000..2784f3d --- /dev/null +++ b/themes/stack/i18n/pt-br.yaml @@ -0,0 +1,67 @@ +toggleMenu: + other: Alternar Menu + +darkMode: + other: Modo Escuro + +list: + page: + one: "{{ .Count }} página" + other: "{{ .Count }} páginas" + + section: + other: Seção + + subsection: + one: Subseção + other: Subseções + +article: + back: + other: Voltar + + tableOfContents: + other: Índice + + relatedContent: + 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 + subtitle: + other: Esta página não existe. + +widget: + archives: + title: + other: Arquivos + more: + other: Mais + tagCloud: + title: + other: Tags + categoriesCloud: + title: + other: Categorias + +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/themes/stack/i18n/pt-pt.yaml b/themes/stack/i18n/pt-pt.yaml new file mode 100644 index 0000000..f524969 --- /dev/null +++ b/themes/stack/i18n/pt-pt.yaml @@ -0,0 +1,67 @@ +toggleMenu: + other: Alternar Menu + +darkMode: + other: Modo Escuro + +list: + page: + one: "{{ .Count }} página" + other: "{{ .Count }} páginas" + + section: + other: Secção + + subsection: + one: Subsecção + other: Subsecções + +article: + back: + other: Voltar + + tableOfContents: + other: Índice + + relatedContent: + other: Conteúdo relacionado + + lastUpdatedOn: + other: Última atualização a + + readingTime: + one: "{{ .Count }} minuto de leitura" + other: "{{ .Count }} minutos de leitura" + +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 + categoriesCloud: + title: + other: Categorias + +search: + title: + other: Pesquisa + placeholder: + other: Escreva 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/themes/stack/i18n/ru.yaml b/themes/stack/i18n/ru.yaml new file mode 100644 index 0000000..b1f1e51 --- /dev/null +++ b/themes/stack/i18n/ru.yaml @@ -0,0 +1,63 @@ +toggleMenu: + other: Показать/скрыть меню + +darkMode: + other: Тёмный режим + +list: + page: + one: "{{ .Count }} страница" + few: "{{ .Count }} страницы" + many: "{{ .Count }} страниц" + other: "{{ .Count }} страниц" + + section: + other: Раздел + + subsection: + one: Подраздел + few: Подразделы + many: Подразделы + other: Подразделы + +article: + back: + other: Назад + relatedContent: + other: Также рекомендуем + lastUpdatedOn: + other: Обновлено + tableOfContents: + other: Содержание + readingTime: + 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 }} diff --git a/themes/stack/i18n/sk.yaml b/themes/stack/i18n/sk.yaml new file mode 100644 index 0000000..59c680e --- /dev/null +++ b/themes/stack/i18n/sk.yaml @@ -0,0 +1,71 @@ +toggleMenu: + other: Skryť menu + +darkMode: + other: Tmavý režim + +list: + page: + one: "{{ .Count }} stránka" + other: "{{ .Count }} stránok" + + section: + other: Kategórie + + subsection: + one: Podkategória + other: Podkategórie + +article: + back: + other: Späť + + tableOfContents: + other: Obsah + + relatedContent: + other: Súvisiace + + lastUpdatedOn: + other: Naposledy aktualizované + + readingTime: + one: "{{ .Count }} minúta" + other: "{{ .Count }} minút" + +notFound: + title: + other: Nenájdené + + subtitle: + other: Tato stránka neexistuje + +widget: + archives: + title: + other: Archívy + more: + other: Viac + tagCloud: + title: + other: Štítky + categoriesCloud: + title: + other: Kategórie + +search: + title: + other: Hladať + + placeholder: + other: Zadajte niečo... + + resultTitle: + other: "#PAGES_COUNT stránok (#TIME_SECONDS sekúnd)" + +footer: + builtWith: + other: Vytvorené pomocou {{ .Generator }} + + designedBy: + other: Šablóna {{ .Theme }} od {{ .DesignedBy }} diff --git a/themes/stack/i18n/th.yaml b/themes/stack/i18n/th.yaml new file mode 100644 index 0000000..9982c06 --- /dev/null +++ b/themes/stack/i18n/th.yaml @@ -0,0 +1,70 @@ +toggleMenu: + other: สลับเมนู + +darkMode: + other: ธีมมืด + +list: + page: + one: "{{ .Count }} หน้า" + other: "{{ .Count }} หน้า" + + section: + other: หมวดหมู่ + + subsection: + one: หมวดหมู่ย่อย + other: หมวดหมู่ย่อยอื่นๆ + +article: + back: + other: กลับไป + + tableOfContents: + other: สารบัญ + + relatedContent: + 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 }} diff --git a/themes/stack/i18n/tr.yaml b/themes/stack/i18n/tr.yaml new file mode 100644 index 0000000..2dbb9d5 --- /dev/null +++ b/themes/stack/i18n/tr.yaml @@ -0,0 +1,53 @@ +toggleMenu: + other: Menüyü Gizle + +darkMode: + other: Koyu Mod + +list: + page: + one: "{{ .Count }} makale" + other: "{{ .Count }} makale" + + section: + other: Bölüm + + subsection: + one: Alt bölüm + other: Alt bölümler + +article: + relatedContent: + other: Alakalı içerikler + lastUpdatedOn: + other: Son güncelleme + +notFound: + title: + other: Bulunamadı + subtitle: + other: Aradığınız sayfa mevcut değil. + +widget: + archives: + title: + other: Arşiv + more: + other: Daha fazla + tagCloud: + title: + other: Etiketler + +search: + title: + other: Arama + placeholder: + other: Birşeyler yazın... + resultTitle: + other: "#PAGES_COUNT sayfa (#TIME_SECONDS saniye)" + +footer: + builtWith: + other: "{{ .Generator }} ile oluşturuldu." + designedBy: + other: "{{ .Theme }} teması {{ .DesignedBy }} tarafından tasarlandı" diff --git a/themes/stack/i18n/uk.yaml b/themes/stack/i18n/uk.yaml new file mode 100644 index 0000000..3bd4f71 --- /dev/null +++ b/themes/stack/i18n/uk.yaml @@ -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: Зміст + + relatedContent: + 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 }} diff --git a/themes/stack/i18n/vi.yaml b/themes/stack/i18n/vi.yaml new file mode 100644 index 0000000..13dc624 --- /dev/null +++ b/themes/stack/i18n/vi.yaml @@ -0,0 +1,74 @@ +toggleMenu: + other: Hiển thị Menu + +darkMode: + other: Chế độ nền tối + +list: + page: + one: "{{ .Count }} trang" + other: "{{ .Count }} trang" + + section: + other: Section + + subsection: + one: Subsection + other: Subsections + +article: + back: + other: Trở lại + + tableOfContents: + other: Mục lục + + relatedContent: + other: Bài viết liên quan + + lastUpdatedOn: + other: Cập nhật lần cuối + + readingTime: + one: "{{ .Count }} phút đọc" + other: "{{ .Count }} phút đọc" + +notFound: + title: + other: Không tìm thấy + + subtitle: + other: Trang này không tồn tại + +widget: + archives: + title: + other: Lưu trữ + + more: + other: Còn nữa + + tagCloud: + title: + other: Nhãn dán + + categoriesCloud: + title: + other: Chuỗi bài + +search: + title: + other: Tìm kiếm + + placeholder: + other: Nhập từ khoá ... + + resultTitle: + other: "#PAGES_COUNT trang (#TIME_SECONDS giây)" + +footer: + builtWith: + other: Built with {{ .Generator }} + + designedBy: + other: Theme {{ .Theme }} thiết kế bởi {{ .DesignedBy }} diff --git a/themes/stack/i18n/zh-cn.yaml b/themes/stack/i18n/zh-cn.yaml new file mode 100644 index 0000000..da39c8f --- /dev/null +++ b/themes/stack/i18n/zh-cn.yaml @@ -0,0 +1,67 @@ +toggleMenu: + other: 切换菜单 + +darkMode: + other: 暗色模式 + +list: + page: "{{ .Count }} 个页面" + + section: 章节 + + subsection: 子章节 + +article: + back: + other: 返回 + + tableOfContents: + other: 目录 + + relatedContent: + other: 相关文章 + + lastUpdatedOn: + other: 最后更新于 + + readingTime: + other: "阅读时长: {{ .Count }} 分钟" + +notFound: + title: + other: 404 错误 + subtitle: + other: 页面不存在 + +widget: + archives: + title: + other: 归档 + + more: + other: 更多 + + tagCloud: + title: + other: 标签云 + + categoriesCloud: + title: + other: 分类 + +search: + title: + other: 搜索 + + placeholder: + other: 输入关键词... + + resultTitle: + other: "#PAGES_COUNT 个结果 (用时 #TIME_SECONDS 秒)" + +footer: + builtWith: + other: 使用 {{ .Generator }} 构建 + + designedBy: + other: 主题 {{ .Theme }} 由 {{ .DesignedBy }} 设计 diff --git a/themes/stack/i18n/zh-hk.yaml b/themes/stack/i18n/zh-hk.yaml new file mode 100644 index 0000000..9e3c246 --- /dev/null +++ b/themes/stack/i18n/zh-hk.yaml @@ -0,0 +1,73 @@ +toggleMenu: + other: 切換選單 + +darkMode: + other: 深色模式 + +list: + page: + one: "第 {{ .Count }} 頁" + other: "第 {{ .Count }} 頁" + + section: + other: Section + + subsection: + one: Subsection + other: Subsections + +article: + back: + other: 返回 + + tableOfContents: + other: 目錄 + + relatedContent: + other: 相關內容 + + lastUpdatedOn: + other: 上次改過於 + + readingTime: + one: "需要 {{ .Count }} 分鐘閱讀" + other: "需要 {{ .Count }} 分鐘閱讀" + +notFound: + title: + other: Not Found + + subtitle: + other: 頁面不存在 + +widget: + archives: + title: + other: Archives + + more: + other: 更多 + + tagCloud: + title: + other: Tags + categoriesCloud: + title: + other: Categories + +search: + title: + other: 搜尋 + + placeholder: + other: Type 關鍵字... + + resultTitle: + other: "#PAGES_COUNT pages (#TIME_SECONDS seconds)" + +footer: + builtWith: + other: Built with {{ .Generator }} + + designedBy: + other: 主題 {{ .Theme }} 由 {{ .DesignedBy }} 設計 diff --git a/themes/stack/i18n/zh-tw.yaml b/themes/stack/i18n/zh-tw.yaml new file mode 100644 index 0000000..4b71572 --- /dev/null +++ b/themes/stack/i18n/zh-tw.yaml @@ -0,0 +1,73 @@ +toggleMenu: + other: 切換選單 + +darkMode: + other: 夜晚模式 + +list: + page: + one: "第 {{ .Count }} 頁" + other: "第 {{ .Count }} 頁" + + section: + other: 段落 + + subsection: + one: 小節 + other: 小節 + +article: + back: + other: 返回 + + tableOfContents: + other: 目錄 + + relatedContent: + other: 相關文章 + + lastUpdatedOn: + other: 最後更新 + + readingTime: + one: "閱讀時間: {{ .Count }} 分鐘" + other: "閱讀時間: {{ .Count }} 分鐘" + +notFound: + title: + other: 404 錯誤 + + subtitle: + other: 頁面不存在 + +widget: + archives: + title: + other: 紀錄 + + more: + other: 更多 + + tagCloud: + title: + other: 標籤雲 + categoriesCloud: + title: + other: 分類 + +search: + title: + other: 搜尋 + + placeholder: + other: 輸入關鍵字... + + resultTitle: + other: "#PAGES_COUNT 個結果 (用時 #TIME_SECONDS 秒)" + +footer: + builtWith: + other: 使用 {{ .Generator }} 建立 + + designedBy: + other: 主題 {{ .Theme }} 由 {{ .DesignedBy }} 設計 diff --git a/themes/stack/images/screenshot.png b/themes/stack/images/screenshot.png new file mode 100644 index 0000000..efcfd8a Binary files /dev/null and b/themes/stack/images/screenshot.png differ diff --git a/themes/stack/images/tn.png b/themes/stack/images/tn.png new file mode 100644 index 0000000..f85fd75 Binary files /dev/null and b/themes/stack/images/tn.png differ diff --git a/themes/stack/layouts/404.html b/themes/stack/layouts/404.html new file mode 100644 index 0000000..98f4a67 --- /dev/null +++ b/themes/stack/layouts/404.html @@ -0,0 +1,45 @@ +{{ define "main" }} +
+

{{ T "notFound.title" }}

+

{{ T "notFound.subtitle" }}

+
+ + {{- $query := first 1 (where .Site.Pages "Layout" "==" "search") -}} + {{- $searchPage := index $query 0 -}} + + {{- with $searchPage -}} +
+

+ + + + +

+
+ +
+

+
+
+ + + + {{- $opts := dict "minify" hugo.IsProduction "JSXFactory" "createElement" -}} + {{- $searchScript := resources.Get "ts/search.tsx" | js.Build $opts -}} + + + + {{- end -}} + {{ partialCached "footer/footer" . }} +{{ end }} \ No newline at end of file diff --git a/themes/stack/layouts/_default/_markup/render-heading.html b/themes/stack/layouts/_default/_markup/render-heading.html new file mode 100644 index 0000000..f79308a --- /dev/null +++ b/themes/stack/layouts/_default/_markup/render-heading.html @@ -0,0 +1,6 @@ + + {{- if site.Params.Article.HeadingAnchor -}} + + {{- end -}} + {{ .Text | safeHTML }} + \ No newline at end of file diff --git a/themes/stack/layouts/_default/_markup/render-image.html b/themes/stack/layouts/_default/_markup/render-image.html new file mode 100644 index 0000000..0ed5584 --- /dev/null +++ b/themes/stack/layouts/_default/_markup/render-image.html @@ -0,0 +1,41 @@ +{{- $image := .Page.Resources.GetMatch (printf "%s" (.Destination | safeURL)) -}} +{{- $Permalink := .Destination | relURL | safeURL -}} +{{- $alt := .PlainText | safeHTML -}} +{{- $Width := 0 -}} +{{- $Height := 0 -}} +{{- $Srcset := "" -}} + +{{/* 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 -}} + +{{ . }} \ No newline at end of file diff --git a/themes/stack/layouts/_default/_markup/render-link.html b/themes/stack/layouts/_default/_markup/render-link.html new file mode 100644 index 0000000..843854d --- /dev/null +++ b/themes/stack/layouts/_default/_markup/render-link.html @@ -0,0 +1,3 @@ +{{ .Text | safeHTML }} \ No newline at end of file diff --git a/themes/stack/layouts/_default/archives.html b/themes/stack/layouts/_default/archives.html new file mode 100644 index 0000000..5d5243c --- /dev/null +++ b/themes/stack/layouts/_default/archives.html @@ -0,0 +1,35 @@ +{{ define "body-class" }}template-archives{{ end }} +{{ define "main" }} +
+ {{- $taxonomy := $.Site.GetPage "taxonomyTerm" "categories" -}} + {{- $terms := $taxonomy.Pages -}} + {{ if $terms }} +

{{ $taxonomy.Title }}

+
+
+ {{ range $terms }} + {{ partial "article-list/tile" (dict "context" . "size" "250x150" "Type" "taxonomy") }} + {{ end }} +
+
+ {{ end }} +
+ + {{ $pages := where .Site.RegularPages "Type" "in" .Site.Params.mainSections }} + {{ $notHidden := where .Site.RegularPages "Params.hidden" "!=" true }} + {{ $filtered := ($pages | intersect $notHidden) }} + + {{ range $filtered.GroupByDate "2006" }} + {{ $id := lower (replace .Key " " "-") }} +
+

{{ .Key }}

+
+ {{ range .Pages }} + {{ partial "article-list/compact" . }} + {{ end }} +
+
+ {{ end }} + + {{ partialCached "footer/footer" . }} +{{ end }} diff --git a/themes/stack/layouts/_default/baseof.html b/themes/stack/layouts/_default/baseof.html new file mode 100644 index 0000000..83fdaa3 --- /dev/null +++ b/themes/stack/layouts/_default/baseof.html @@ -0,0 +1,28 @@ + + + + {{- partial "head/head.html" . -}} + {{- block "head" . -}}{{ end }} + + + {{- partial "head/colorScheme" . -}} + + {{/* The container is wider when there's any activated widget */}} + {{- $hasWidget := false -}} + {{- range .Site.Params.widgets -}} + {{- if gt (len .) 0 -}} + {{- $hasWidget = true -}} + {{- end -}} + {{- end -}} +
+ {{- block "left-sidebar" . -}} + {{ partial "sidebar/left.html" . }} + {{- end -}} + {{- block "right-sidebar" . -}}{{ end }} +
+ {{- block "main" . }}{{- end }} +
+
+ {{ partial "footer/include.html" . }} + + diff --git a/themes/stack/layouts/_default/list.html b/themes/stack/layouts/_default/list.html new file mode 100644 index 0000000..9bc618d --- /dev/null +++ b/themes/stack/layouts/_default/list.html @@ -0,0 +1,85 @@ +{{ define "main" }} +
+

+ {{ if eq .Parent (.GetPage "/") }} + {{ T "list.section" }} + {{ else }} + {{ .Parent.Title }} + {{ end }} +

+ +
+
+

{{ T "list.page" (len .Pages) }}

+

{{ .Title }}

+ {{ with .Params.description }} +

{{ . }}

+ {{ end }} +
+ + {{- $image := partialCached "helper/image" (dict "Context" . "Type" "section") .RelPermalink "section" -}} + {{ if $image.exists }} +
+ {{ if $image.resource }} + {{- $Permalink := $image.resource.RelPermalink -}} + {{- $Width := $image.resource.Width -}} + {{- $Height := $image.resource.Height -}} + + {{- if (default true .Page.Site.Params.imageProcessing.cover.enabled) -}} + {{- $thumbnail := $image.resource.Fill "120x120" -}} + {{- $Permalink = $thumbnail.RelPermalink -}} + {{- $Width = $thumbnail.Width -}} + {{- $Height = $thumbnail.Height -}} + {{- end -}} + + + {{ else }} + + {{ end }} +
+ {{ end }} +
+
+ + {{- $subsections := .Sections -}} + {{- $pages := .Pages | complement $subsections -}} + + {{- if eq (len $pages) 0 -}} + {{/* If there are no normal pages, display subsections in list style, with pagination */}} + {{/* This happens with taxonomies like categories or tags */}} + {{- $pages = $subsections -}} + {{- $subsections = slice -}} + {{- end -}} + + {{- with $subsections -}} + + {{- end -}} + + {{/* List only pages that are not a subsection */}} + {{ $paginator := .Paginate $pages }} +
+ {{ range $paginator.Pages }} + {{ partial "article-list/compact" . }} + {{ end }} +
+ + {{- partial "pagination.html" . -}} + + {{ partialCached "footer/footer" . }} +{{ end }} + +{{ define "right-sidebar" }} + {{ partial "sidebar/right.html" (dict "Context" . "Scope" "homepage") }} +{{ end }} \ No newline at end of file diff --git a/themes/stack/layouts/_default/rss.xml b/themes/stack/layouts/_default/rss.xml new file mode 100644 index 0000000..3d2e592 --- /dev/null +++ b/themes/stack/layouts/_default/rss.xml @@ -0,0 +1,48 @@ +{{- $pctx := . -}} +{{- if .IsHome -}}{{ $pctx = .Site }}{{- end -}} +{{- $pages := slice -}} +{{- if or $.IsHome $.IsSection -}} +{{- $pages = $pctx.RegularPages -}} +{{- else -}} +{{- $pages = $pctx.Pages -}} +{{- end -}} +{{- $pages := where $pages "Params.hidden" "!=" true -}} +{{- $limit := .Site.Config.Services.RSS.Limit -}} +{{- if ge $limit 1 -}} +{{- $pages = $pages | first $limit -}} +{{- end -}} +{{- printf "" | safeHTML }} + + + {{ if eq .Title .Site.Title }}{{ .Site.Title }}{{ else }}{{ with .Title }}{{.}} on {{ end }}{{ .Site.Title }}{{ end }} + {{ .Permalink }} + Recent content {{ if ne .Title .Site.Title }}{{ with .Title }}in {{.}} {{ end }}{{ end }}on {{ .Site.Title }} + Hugo -- gohugo.io{{ with .Site.LanguageCode }} + {{.}}{{end}}{{ with .Site.Params.Author.email }} + {{.}}{{ with $.Site.Params.Author.name }} ({{.}}){{end}}{{end}}{{ with .Site.Params.Author.email }} + {{.}}{{ with $.Site.Params.Author.name }} ({{.}}){{end}}{{end}}{{ with .Site.Copyright }} + {{.}}{{end}}{{ if not .Date.IsZero }} + {{ (index $pages.ByLastmod.Reverse 0).Lastmod.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}{{ end }} + {{- with .OutputFormats.Get "RSS" -}} + {{ printf "" .Permalink .MediaType | safeHTML }} + {{- end -}} + {{ range $pages }} + {{- $content := safeHTML (.Summary | html) -}} + {{- if .Site.Params.rssFullContent -}} + {{- $content = safeHTML (.Content | html) -}} + {{- end -}} + + {{ .Title }} + {{ .Permalink }} + {{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }} + {{ with .Site.Params.Author.email }}{{.}}{{ with $.Site.Params.Author.name }} ({{.}}){{end}}{{end}} + {{ .Permalink }} + + {{- $image := partial "helper/image" (dict "Context" . "Type" "rss") -}} + {{- if $image.exists -}} + {{ "<" | html }}img src="{{ $image.permalink | absURL }}" alt="Featured image of post {{ .Title }}" {{ "/>" | html}} + {{- end -}}{{ $content }} + + {{ end }} + + diff --git a/themes/stack/layouts/_default/single.html b/themes/stack/layouts/_default/single.html new file mode 100644 index 0000000..5f300bf --- /dev/null +++ b/themes/stack/layouts/_default/single.html @@ -0,0 +1,46 @@ +{{ define "body-class" }} + article-page + {{/* + Enable the right sidebar if + - Widget different from 'TOC' is enabled + - TOC is enabled and not empty + */}} + {{- $HasWidgetNotTOC := false -}} + {{- $TOCWidgetEnabled := false -}} + {{- range .Site.Params.widgets.page -}} + {{- if ne .type "toc" -}} + {{ $HasWidgetNotTOC = true -}} + {{- else -}} + {{ $TOCWidgetEnabled = true -}} + {{- end -}} + {{- end -}} + + {{- $TOCManuallyDisabled := eq .Params.toc false -}} + {{- $TOCEnabled := and (not $TOCManuallyDisabled) $TOCWidgetEnabled -}} + {{- $hasTOC := ge (len .TableOfContents) 100 -}} + {{- .Scratch.Set "TOCEnabled" (and $TOCEnabled $hasTOC) -}} + + {{- .Scratch.Set "hasWidget" (or $HasWidgetNotTOC (and $TOCEnabled $hasTOC)) -}} +{{ end }} + +{{ define "main" }} + {{ partial "article/article.html" . }} + + {{ if .Params.links }} + {{ partial "article/components/links" . }} + {{ end }} + + {{ partial "article/components/related-content" . }} + + {{ if not (eq .Params.comments false) }} + {{ partial "comments/include" . }} + {{ end }} + + {{ partialCached "footer/footer" . }} + + {{ partialCached "article/components/photoswipe" . }} +{{ end }} + +{{ define "right-sidebar" }} + {{ if .Scratch.Get "hasWidget" }}{{ partial "sidebar/right.html" (dict "Context" . "Scope" "page") }}{{ end}} +{{ end }} diff --git a/themes/stack/layouts/index.html b/themes/stack/layouts/index.html new file mode 100644 index 0000000..0cd0b88 --- /dev/null +++ b/themes/stack/layouts/index.html @@ -0,0 +1,19 @@ +{{ define "main" }} + {{ $pages := where .Site.RegularPages "Type" "in" .Site.Params.mainSections }} + {{ $notHidden := where .Site.RegularPages "Params.hidden" "!=" true }} + {{ $filtered := ($pages | intersect $notHidden) }} + {{ $pag := .Paginate ($filtered) }} + +
+ {{ range $index, $element := $pag.Pages }} + {{ partial "article-list/default" . }} + {{ end }} +
+ + {{- partial "pagination.html" . -}} + {{- partial "footer/footer" . -}} +{{ end }} + +{{ define "right-sidebar" }} + {{ partial "sidebar/right.html" (dict "Context" . "Scope" "homepage") }} +{{ end }} \ No newline at end of file diff --git a/themes/stack/layouts/page/search.html b/themes/stack/layouts/page/search.html new file mode 100644 index 0000000..fbfb74d --- /dev/null +++ b/themes/stack/layouts/page/search.html @@ -0,0 +1,33 @@ +{{ define "body-class" }}template-search{{ end }} +{{ define "head" }} + {{- with .OutputFormats.Get "json" -}} + + {{- end -}} +{{ end }} +{{ define "main" }} +
+

+ + +

+ + +
+ +
+

+
+
+ + + +{{- $opts := dict "minify" hugo.IsProduction "JSXFactory" "createElement" -}} +{{- $searchScript := resources.Get "ts/search.tsx" | js.Build $opts -}} + + +{{ partialCached "footer/footer" . }} +{{ end }} \ No newline at end of file diff --git a/themes/stack/layouts/page/search.json b/themes/stack/layouts/page/search.json new file mode 100644 index 0000000..5d4e627 --- /dev/null +++ b/themes/stack/layouts/page/search.json @@ -0,0 +1,26 @@ +{{- $pages := where .Site.RegularPages "Type" "in" .Site.Params.mainSections -}} +{{- $notHidden := where .Site.RegularPages "Params.hidden" "!=" true -}} +{{- $filtered := ($pages | intersect $notHidden) -}} + +{{- $result := slice -}} + +{{- range $filtered -}} + {{- $data := dict "title" .Title "date" .Date "permalink" .Permalink "content" (.Plain) -}} + + {{- $image := partialCached "helper/image" (dict "Context" . "Type" "articleList") .RelPermalink "articleList" -}} + {{- if $image.exists -}} + {{- $imagePermalink := "" -}} + {{- if and $image.resource (default true .Page.Site.Params.imageProcessing.cover.enabled) -}} + {{- $thumbnail := $image.resource.Fill "120x120" -}} + {{- $imagePermalink = (absURL $thumbnail.Permalink) -}} + {{- else -}} + {{- $imagePermalink = $image.permalink -}} + {{- end -}} + + {{- $data = merge $data (dict "image" (absURL $imagePermalink)) -}} + {{- end -}} + + {{- $result = $result | append $data -}} +{{- end -}} + +{{ jsonify $result }} \ No newline at end of file diff --git a/themes/stack/layouts/partials/article-list/compact.html b/themes/stack/layouts/partials/article-list/compact.html new file mode 100644 index 0000000..376512a --- /dev/null +++ b/themes/stack/layouts/partials/article-list/compact.html @@ -0,0 +1,40 @@ +
+ +
+

+ {{- .Title -}} +

+
+ +
+
+ + {{- $image := partialCached "helper/image" (dict "Context" . "Type" "articleList") .RelPermalink "articleList" -}} + {{ if $image.exists }} +
+ {{ if $image.resource }} + {{- $Permalink := $image.resource.RelPermalink -}} + {{- $Width := $image.resource.Width -}} + {{- $Height := $image.resource.Height -}} + + {{- if (default true .Page.Site.Params.imageProcessing.cover.enabled) -}} + {{- $thumbnail := $image.resource.Fill "120x120" -}} + {{- $Permalink = $thumbnail.RelPermalink -}} + {{- $Width = $thumbnail.Width -}} + {{- $Height = $thumbnail.Height -}} + {{- end -}} + + {{ .Title }} + {{ else }} + Featured image of post {{ .Title }} + {{ end }} +
+ {{ end }} +
+
\ No newline at end of file diff --git a/themes/stack/layouts/partials/article-list/default.html b/themes/stack/layouts/partials/article-list/default.html new file mode 100644 index 0000000..02e0b30 --- /dev/null +++ b/themes/stack/layouts/partials/article-list/default.html @@ -0,0 +1,4 @@ +{{ $image := partialCached "helper/image" (dict "Context" . "Type" "articleList") .RelPermalink "articleList" }} +
+ {{ partial "article/components/header" . }} +
\ No newline at end of file diff --git a/themes/stack/layouts/partials/article-list/tile.html b/themes/stack/layouts/partials/article-list/tile.html new file mode 100644 index 0000000..be5744b --- /dev/null +++ b/themes/stack/layouts/partials/article-list/tile.html @@ -0,0 +1,39 @@ +{{ $image := partialCached "helper/image" (dict "Context" .context "Type" .Type) .context.RelPermalink .Type }} +
+ + + {{ if $image.exists }} +
+ {{ if $image.resource }} + {{- $imageRaw := $image.resource | resources.Fingerprint "md5" -}} + {{- $Permalink := $imageRaw.RelPermalink -}} + {{- $Width := $imageRaw.Width -}} + {{- $Height := $imageRaw.Height -}} + + {{- if .context.Site.Params.imageProcessing.cover.enabled -}} + {{- $thumbnail := $imageRaw.Fill .size -}} + {{- $Permalink = $thumbnail.RelPermalink -}} + {{- $Width = $thumbnail.Width -}} + {{- $Height = $thumbnail.Height -}} + {{- end -}} + + Featured image of post {{ .context.Title }} + {{ else }} + + {{ end }} +
+ {{ end }} + +
+

+ {{- .context.Title -}} +

+
+
+
diff --git a/themes/stack/layouts/partials/article/article.html b/themes/stack/layouts/partials/article/article.html new file mode 100644 index 0000000..f3f7e90 --- /dev/null +++ b/themes/stack/layouts/partials/article/article.html @@ -0,0 +1,11 @@ +
+ {{ partial "article/components/header" . }} + + {{ partial "article/components/content" . }} + + {{ partial "article/components/footer" . }} + + {{ if or .Params.math .Site.Params.article.math }} + {{ partialCached "article/components/math.html" . }} + {{ end }} +
\ No newline at end of file diff --git a/themes/stack/layouts/partials/article/components/content.html b/themes/stack/layouts/partials/article/components/content.html new file mode 100644 index 0000000..61e536c --- /dev/null +++ b/themes/stack/layouts/partials/article/components/content.html @@ -0,0 +1,5 @@ +
+ + {{ $wrappedTable := printf "
${1}
" }} + {{ .Content | replaceRE "((?:.|\n)+?
)" $wrappedTable | safeHTML }} +
diff --git a/themes/stack/layouts/partials/article/components/details.html b/themes/stack/layouts/partials/article/components/details.html new file mode 100644 index 0000000..7c27302 --- /dev/null +++ b/themes/stack/layouts/partials/article/components/details.html @@ -0,0 +1,61 @@ +
+ {{ if .Params.categories }} + + {{ end }} + +
+

+ + {{- .Title -}} + +

+ + {{ with .Params.description }} +

+ {{ . }} +

+ {{ end }} +
+ + {{ $showReadingTime := .Params.readingTime | default (.Site.Params.article.readingTime) }} + {{ $showDate := not .Date.IsZero }} + {{ $showFooter := or $showDate $showReadingTime }} + {{ if $showFooter }} +
+ {{ if $showDate }} +
+ {{ partial "helper/icon" "date" }} + +
+ {{ end }} + + {{ if $showReadingTime }} +
+ {{ partial "helper/icon" "clock" }} + +
+ {{ end }} +
+ {{ end }} + + {{ if .IsTranslated }} + + {{ end }} +
diff --git a/themes/stack/layouts/partials/article/components/footer.html b/themes/stack/layouts/partials/article/components/footer.html new file mode 100644 index 0000000..9795880 --- /dev/null +++ b/themes/stack/layouts/partials/article/components/footer.html @@ -0,0 +1,19 @@ + diff --git a/themes/stack/layouts/partials/article/components/header.html b/themes/stack/layouts/partials/article/components/header.html new file mode 100644 index 0000000..7582ec2 --- /dev/null +++ b/themes/stack/layouts/partials/article/components/header.html @@ -0,0 +1,35 @@ +
+ {{- $image := partialCached "helper/image" (dict "Context" . "Type" "article") .RelPermalink "article" -}} + {{ if $image.exists }} +
+ + {{ if $image.resource }} + {{- $Permalink := $image.resource.RelPermalink -}} + {{- $Width := $image.resource.Width -}} + {{- $Height := $image.resource.Height -}} + {{- $Srcset := "" -}} + + {{- if (default true .Page.Site.Params.imageProcessing.cover.enabled) -}} + {{- $thumbnail := $image.resource.Resize "800x" -}} + {{- $thumbnailRetina := $image.resource.Resize "1600x" -}} + {{- $Srcset = printf "%s 800w, %s 1600w" $thumbnail.RelPermalink $thumbnailRetina.RelPermalink -}} + {{- $Permalink = $thumbnail.RelPermalink -}} + {{- $Width = $thumbnail.Width -}} + {{- $Height = $thumbnail.Height -}} + {{- end -}} + + Featured image of post {{ .Title }} + {{ else }} + Featured image of post {{ .Title }} + {{ end }} + +
+ {{ end }} + + {{ partialCached "article/components/details" . .RelPermalink }} +
\ No newline at end of file diff --git a/themes/stack/layouts/partials/article/components/links.html b/themes/stack/layouts/partials/article/components/links.html new file mode 100644 index 0000000..a2ac26c --- /dev/null +++ b/themes/stack/layouts/partials/article/components/links.html @@ -0,0 +1,30 @@ +
+ {{ range $i, $link := .Params.links }} + + {{ end }} +
\ No newline at end of file diff --git a/themes/stack/layouts/partials/article/components/math.html b/themes/stack/layouts/partials/article/components/math.html new file mode 100644 index 0000000..b41e4c9 --- /dev/null +++ b/themes/stack/layouts/partials/article/components/math.html @@ -0,0 +1,14 @@ +{{- partial "helper/external" (dict "Context" . "Namespace" "KaTeX") -}} + diff --git a/themes/stack/layouts/partials/article/components/photoswipe.html b/themes/stack/layouts/partials/article/components/photoswipe.html new file mode 100644 index 0000000..c33ff49 --- /dev/null +++ b/themes/stack/layouts/partials/article/components/photoswipe.html @@ -0,0 +1,68 @@ + + + +{{- partial "helper/external" (dict "Context" . "Namespace" "PhotoSwipe") -}} \ No newline at end of file diff --git a/themes/stack/layouts/partials/article/components/related-content.html b/themes/stack/layouts/partials/article/components/related-content.html new file mode 100644 index 0000000..aba88e3 --- /dev/null +++ b/themes/stack/layouts/partials/article/components/related-content.html @@ -0,0 +1,13 @@ +{{ $related := (where (.Site.RegularPages.Related .) "Params.hidden" "!=" true) | first 5 }} +{{ with $related }} + +{{ end }} \ No newline at end of file diff --git a/themes/stack/layouts/partials/article/components/tags.html b/themes/stack/layouts/partials/article/components/tags.html new file mode 100644 index 0000000..aae38b4 --- /dev/null +++ b/themes/stack/layouts/partials/article/components/tags.html @@ -0,0 +1,7 @@ +{{ if .Params.Tags }} +
+ {{ range (.GetTerms "tags") }} + {{ .LinkTitle }} + {{ end }} +
+{{ end }} \ No newline at end of file diff --git a/themes/stack/layouts/partials/comments/include.html b/themes/stack/layouts/partials/comments/include.html new file mode 100644 index 0000000..a3d0618 --- /dev/null +++ b/themes/stack/layouts/partials/comments/include.html @@ -0,0 +1,3 @@ +{{ if .Site.Params.comments.enabled }} + {{ partial (printf "comments/provider/%s" .Site.Params.comments.provider) . }} +{{ end }} \ No newline at end of file diff --git a/themes/stack/layouts/partials/comments/provider/beaudar.html b/themes/stack/layouts/partials/comments/provider/beaudar.html new file mode 100644 index 0000000..761801f --- /dev/null +++ b/themes/stack/layouts/partials/comments/provider/beaudar.html @@ -0,0 +1,46 @@ + + + + + diff --git a/themes/stack/layouts/partials/comments/provider/cactus.html b/themes/stack/layouts/partials/comments/provider/cactus.html new file mode 100644 index 0000000..ae172d3 --- /dev/null +++ b/themes/stack/layouts/partials/comments/provider/cactus.html @@ -0,0 +1,29 @@ +{{- with .Site.Params.comments.cactus -}} +{{- partial "helper/external" (dict "Context" $ "Namespace" "Cactus") -}} + + + +
+ + +{{- end -}} diff --git a/themes/stack/layouts/partials/comments/provider/cusdis.html b/themes/stack/layouts/partials/comments/provider/cusdis.html new file mode 100644 index 0000000..a89746c --- /dev/null +++ b/themes/stack/layouts/partials/comments/provider/cusdis.html @@ -0,0 +1,21 @@ +{{- $host := default "https://cusdis.com" .Site.Params.comments.cusdis.host -}} +
+ + + diff --git a/themes/stack/layouts/partials/comments/provider/disqus.html b/themes/stack/layouts/partials/comments/provider/disqus.html new file mode 100644 index 0000000..fb17b77 --- /dev/null +++ b/themes/stack/layouts/partials/comments/provider/disqus.html @@ -0,0 +1,22 @@ +
+ {{ template "_internal/disqus.html" . }} +
+ + + + \ No newline at end of file diff --git a/themes/stack/layouts/partials/comments/provider/disqusjs.html b/themes/stack/layouts/partials/comments/provider/disqusjs.html new file mode 100644 index 0000000..967c38c --- /dev/null +++ b/themes/stack/layouts/partials/comments/provider/disqusjs.html @@ -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" | toCSS | minify -}} + + +
+
+ + +
+{{- end -}} \ No newline at end of file diff --git a/themes/stack/layouts/partials/comments/provider/giscus.html b/themes/stack/layouts/partials/comments/provider/giscus.html new file mode 100644 index 0000000..fa355b7 --- /dev/null +++ b/themes/stack/layouts/partials/comments/provider/giscus.html @@ -0,0 +1,52 @@ +{{- with .Site.Params.comments.giscus -}} + + +{{- end -}} diff --git a/themes/stack/layouts/partials/comments/provider/gitalk.html b/themes/stack/layouts/partials/comments/provider/gitalk.html new file mode 100644 index 0000000..54bfcba --- /dev/null +++ b/themes/stack/layouts/partials/comments/provider/gitalk.html @@ -0,0 +1,31 @@ +{{- with .Site.Params.comments.gitalk -}} +
+ + + + +{{ end }} diff --git a/themes/stack/layouts/partials/comments/provider/remark42.html b/themes/stack/layouts/partials/comments/provider/remark42.html new file mode 100644 index 0000000..18acf1b --- /dev/null +++ b/themes/stack/layouts/partials/comments/provider/remark42.html @@ -0,0 +1,29 @@ +{{- with .Site.Params.comments.remark42 -}} +
+ +{{- end -}} diff --git a/themes/stack/layouts/partials/comments/provider/twikoo.html b/themes/stack/layouts/partials/comments/provider/twikoo.html new file mode 100644 index 0000000..8c22507 --- /dev/null +++ b/themes/stack/layouts/partials/comments/provider/twikoo.html @@ -0,0 +1,58 @@ + +
+ + +{{- with .Site.Params.comments.twikoo -}} + +{{- end -}} diff --git a/themes/stack/layouts/partials/comments/provider/utterances.html b/themes/stack/layouts/partials/comments/provider/utterances.html new file mode 100644 index 0000000..b1e370b --- /dev/null +++ b/themes/stack/layouts/partials/comments/provider/utterances.html @@ -0,0 +1,46 @@ + + + + + diff --git a/themes/stack/layouts/partials/comments/provider/vssue.html b/themes/stack/layouts/partials/comments/provider/vssue.html new file mode 100644 index 0000000..79ac381 --- /dev/null +++ b/themes/stack/layouts/partials/comments/provider/vssue.html @@ -0,0 +1,27 @@ +{{- with .Site.Params.comments.vssue -}} + + +
+ + + + + +{{- end -}} diff --git a/themes/stack/layouts/partials/comments/provider/waline.html b/themes/stack/layouts/partials/comments/provider/waline.html new file mode 100644 index 0000000..9d1a2a0 --- /dev/null +++ b/themes/stack/layouts/partials/comments/provider/waline.html @@ -0,0 +1,34 @@ + + +
+ + +{{- with .Site.Params.comments.waline -}} +{{- $config := dict "el" "#waline" "dark" `html[data-scheme="dark"]` -}} +{{- $replaceKeys := dict "serverurl" "serverURL" "requiredmeta" "requiredMeta" "wordlimit" "wordLimit" "pagesize" "pageSize" "imageuploader" "imageUploader" "texrenderer" "texRenderer" -}} + +{{- range $key, $val := . -}} + {{- if ne $val nil -}} + {{- $replaceKey := index $replaceKeys $key -}} + {{- $k := default $key $replaceKey -}} + + {{- $config = merge $config (dict $k $val) -}} + {{- end -}} +{{- end -}} + + +{{- end -}} diff --git a/themes/stack/layouts/partials/data/description.html b/themes/stack/layouts/partials/data/description.html new file mode 100644 index 0000000..7f6cc32 --- /dev/null +++ b/themes/stack/layouts/partials/data/description.html @@ -0,0 +1,17 @@ + +{{ $description := .Site.Params.sidebar.subtitle }} + + +{{ if .Site.Params.description }} + {{ $description = .Site.Params.description }} +{{ end }} + +{{ if .Description }} + + {{ $description = .Description }} +{{ else if .IsPage }} + + {{ $description = .Summary }} +{{ end }} + +{{ return (replaceRE "\n" " " $description | plainify) }} \ No newline at end of file diff --git a/themes/stack/layouts/partials/data/title.html b/themes/stack/layouts/partials/data/title.html new file mode 100644 index 0000000..85a7bc7 --- /dev/null +++ b/themes/stack/layouts/partials/data/title.html @@ -0,0 +1,38 @@ +{{- $title := .Title -}} +{{- $siteTitle := .Site.Title -}} + +{{- if .IsHome -}} + + + + {{ $pages := where .Site.RegularPages "Section" "in" .Site.Params.mainSections }} + {{ $notHidden := where .Site.RegularPages "Params.hidden" "!=" true }} + {{ $filtered := ($pages | intersect $notHidden) }} + {{ $pag := .Paginate ($filtered) }} + + {{ if .Paginator.HasPrev }} + + {{ $title = printf "%s - %s" .Paginator $siteTitle }} + {{ else }} + {{ $title = $siteTitle}} + {{ end }} +{{- else if eq .Kind "term" -}} + + + + {{ $notHidden := where .Pages "Params.hidden" "!=" true }} + {{ $pag := .Paginate ($notHidden) }} + + + {{ $title = slice (title .Data.Singular) ": " $title }} + + {{ if .Paginator.HasPrev }} + + {{ $title = $title | append " - " .Paginator }} + {{ end }} + + {{ $title = $title | append " - " $siteTitle }} + {{ $title = delimit $title "" }} +{{- end -}} + +{{ return $title }} \ No newline at end of file diff --git a/themes/stack/layouts/partials/footer/components/custom-font.html b/themes/stack/layouts/partials/footer/components/custom-font.html new file mode 100644 index 0000000..8e2ff37 --- /dev/null +++ b/themes/stack/layouts/partials/footer/components/custom-font.html @@ -0,0 +1,11 @@ + \ No newline at end of file diff --git a/themes/stack/layouts/partials/footer/components/script.html b/themes/stack/layouts/partials/footer/components/script.html new file mode 100644 index 0000000..3dc96cb --- /dev/null +++ b/themes/stack/layouts/partials/footer/components/script.html @@ -0,0 +1,12 @@ +{{- partial "helper/external" (dict "Context" . "Namespace" "Vibrant") -}} + +{{- $opts := dict "minify" hugo.IsProduction -}} +{{- $script := resources.Get "ts/main.ts" | js.Build $opts | fingerprint -}} + + + +{{- with resources.Get "ts/custom.ts" -}} + {{/* Place your custom script in HUGO_SITE_FOLDER/assets/ts/custom.ts */}} + {{- $customScript := . | js.Build $opts | fingerprint -}} + +{{- end -}} \ No newline at end of file diff --git a/themes/stack/layouts/partials/footer/custom.html b/themes/stack/layouts/partials/footer/custom.html new file mode 100644 index 0000000..e69de29 diff --git a/themes/stack/layouts/partials/footer/footer.html b/themes/stack/layouts/partials/footer/footer.html new file mode 100644 index 0000000..179357b --- /dev/null +++ b/themes/stack/layouts/partials/footer/footer.html @@ -0,0 +1,23 @@ +{{- $ThemeVersion := "3.30.0" -}} + diff --git a/themes/stack/layouts/partials/footer/include.html b/themes/stack/layouts/partials/footer/include.html new file mode 100644 index 0000000..4b50a88 --- /dev/null +++ b/themes/stack/layouts/partials/footer/include.html @@ -0,0 +1,3 @@ +{{ partialCached "footer/components/script.html" . }} +{{ partialCached "footer/components/custom-font.html" . }} +{{ partial "footer/custom.html" . }} \ No newline at end of file diff --git a/themes/stack/layouts/partials/head/colorScheme.html b/themes/stack/layouts/partials/head/colorScheme.html new file mode 100644 index 0000000..42f4dd8 --- /dev/null +++ b/themes/stack/layouts/partials/head/colorScheme.html @@ -0,0 +1,39 @@ +{{- $defaultColorScheme := default "auto" .Site.Params.colorScheme.default -}} +{{- if not (default false .Site.Params.colorScheme.toggle) -}} + {{/* If toggle is disabled, force default scheme */}} + +{{- else -}} + {{/* Otherwise set to default scheme only if no preference is set by user */}} + +{{- end -}} + + diff --git a/themes/stack/layouts/partials/head/custom.html b/themes/stack/layouts/partials/head/custom.html new file mode 100644 index 0000000..e69de29 diff --git a/themes/stack/layouts/partials/head/head.html b/themes/stack/layouts/partials/head/head.html new file mode 100644 index 0000000..a7991c1 --- /dev/null +++ b/themes/stack/layouts/partials/head/head.html @@ -0,0 +1,26 @@ + + + +{{- $description := partialCached "data/description" . .RelPermalink -}} + +{{ with .Params.Keywords }}{{ end }} + +{{- $title := partial "data/title" . -}} +{{ $title }} + + + +{{- partial "head/style.html" . -}} +{{- partial "head/script.html" . -}} +{{- partial "head/opengraph/include.html" . -}} + +{{- range .AlternativeOutputFormats -}} + +{{- end -}} + +{{ with .Site.Params.favicon }} + +{{ end }} + +{{- template "_internal/google_analytics.html" . -}} +{{- partial "head/custom.html" . -}} diff --git a/themes/stack/layouts/partials/head/opengraph/include.html b/themes/stack/layouts/partials/head/opengraph/include.html new file mode 100644 index 0000000..3a43f48 --- /dev/null +++ b/themes/stack/layouts/partials/head/opengraph/include.html @@ -0,0 +1,2 @@ +{{ partial "head/opengraph/provider/base" . }} +{{ partial "head/opengraph/provider/twitter" . }} \ No newline at end of file diff --git a/themes/stack/layouts/partials/head/opengraph/provider/base.html b/themes/stack/layouts/partials/head/opengraph/provider/base.html new file mode 100644 index 0000000..055745d --- /dev/null +++ b/themes/stack/layouts/partials/head/opengraph/provider/base.html @@ -0,0 +1,43 @@ +{{- $title := partialCached "data/title" . .RelPermalink -}} +{{- $description := partialCached "data/description" . .RelPermalink -}} + + + + + + + +{{- with .Params.locale -}} + +{{- end -}} + +{{- if .IsPage -}} + + {{- range .Params.tags -}} + + {{- end -}} +{{- end -}} + +{{- if .IsPage -}} + {{- if not .Date.IsZero -}} + + {{- end -}} + {{- if not .Lastmod.IsZero -}} + + {{- end -}} +{{- else -}} + {{- if not .Site.Lastmod.IsZero -}} + + {{- end -}} +{{- end -}} + +{{ $image := partialCached "helper/image" (dict "Context" . "Type" "opengraph") .RelPermalink "opengraph" }} +{{- if $image.exists -}} + +{{- end -}} \ No newline at end of file diff --git a/themes/stack/layouts/partials/head/opengraph/provider/twitter.html b/themes/stack/layouts/partials/head/opengraph/provider/twitter.html new file mode 100644 index 0000000..c39eba3 --- /dev/null +++ b/themes/stack/layouts/partials/head/opengraph/provider/twitter.html @@ -0,0 +1,16 @@ +{{- with .Site.Params.opengraph.twitter.site -}} + + +{{- end -}} + +{{- $title := partialCached "data/title" . .RelPermalink -}} +{{- $description := partialCached "data/description" . .RelPermalink -}} + + + + +{{- $image := partialCached "helper/image" (dict "Context" . "Type" "opengraph") .RelPermalink "opengraph" -}} +{{- if $image.exists -}} + + +{{- end -}} \ No newline at end of file diff --git a/themes/stack/layouts/partials/head/script.html b/themes/stack/layouts/partials/head/script.html new file mode 100644 index 0000000..e69de29 diff --git a/themes/stack/layouts/partials/head/style.html b/themes/stack/layouts/partials/head/style.html new file mode 100644 index 0000000..30ca26e --- /dev/null +++ b/themes/stack/layouts/partials/head/style.html @@ -0,0 +1,3 @@ +{{ $sass := resources.Get "scss/style.scss" }} +{{ $style := $sass | toCSS | minify | resources.Fingerprint "sha256" }} + \ No newline at end of file diff --git a/themes/stack/layouts/partials/helper/external.html b/themes/stack/layouts/partials/helper/external.html new file mode 100644 index 0000000..88d9525 --- /dev/null +++ b/themes/stack/layouts/partials/helper/external.html @@ -0,0 +1,29 @@ +{{- $List := index .Context.Site.Data.external .Namespace -}} +{{- with $List -}} + {{- range . -}} + {{- if eq .type "script" -}} + + {{- else if eq .type "style" -}} + + {{- else -}} + {{- errorf "Error: unknown external resource type: %s" .type -}} + {{- end -}} + {{- end -}} +{{- else -}} + {{- errorf "Error: external resource '%s' is not found" .Namespace -}} +{{- end -}} \ No newline at end of file diff --git a/themes/stack/layouts/partials/helper/icon.html b/themes/stack/layouts/partials/helper/icon.html new file mode 100644 index 0000000..72162e8 --- /dev/null +++ b/themes/stack/layouts/partials/helper/icon.html @@ -0,0 +1,6 @@ +{{- $iconFile := resources.GetMatch (printf "icons/%s.svg" .) -}} +{{- if $iconFile -}} + {{- $iconFile.Content | safeHTML -}} +{{- else -}} + {{- errorf "Error: icon '%s.svg' is not found under 'assets/icons' folder" . -}} +{{- end -}} \ No newline at end of file diff --git a/themes/stack/layouts/partials/helper/image.html b/themes/stack/layouts/partials/helper/image.html new file mode 100644 index 0000000..11fc3b6 --- /dev/null +++ b/themes/stack/layouts/partials/helper/image.html @@ -0,0 +1,61 @@ +{{ $result := dict "exists" false "permalink" nil "resource" nil "isDefault" false }} +{{ $imageField := default "image" .Context.Site.Params.featuredImageField }} +{{ $imageValue := index .Context.Params $imageField }} + +{{ if $imageValue }} + + {{ $result = merge $result (dict "exists" true) }} + {{ $url := urls.Parse $imageValue }} + + {{ if or (eq $url.Scheme "http") (eq $url.Scheme "https") }} + + {{ $result = merge $result (dict "permalink" $imageValue) }} + {{ else }} + {{ $pageResourceImage := .Context.Resources.GetMatch (printf "%s" ($imageValue | safeURL)) }} + + {{ if $pageResourceImage }} + + {{ $result = merge $result (dict "permalink" $pageResourceImage.RelPermalink) }} + + + {{ if ne (path.Ext $imageValue) ".svg" }} + {{ $result = merge $result (dict "resource" $pageResourceImage) }} + {{ end }} + {{ else }} + + {{ $result = merge $result (dict "permalink" (relURL $imageValue)) }} + {{ end }} + + {{ end }} + +{{ else if and (ne .Type nil) (index .Context.Site.Params.defaultImage .Type) }} + + {{ $defaultImageSetting := index .Context.Site.Params.defaultImage .Type }} + + {{ if $defaultImageSetting.enabled }} + {{ $result = merge $result (dict "isDefault" true) }} + {{ $result = merge $result (dict "exists" true) }} + + {{ if $defaultImageSetting.local }} + {{ $siteResourceImage := resources.GetMatch (printf "%s" ($defaultImageSetting.src | safeURL)) }} + + {{ if $siteResourceImage }} + + {{ $result = merge $result (dict "permalink" $siteResourceImage.RelPermalink) }} + {{ $result = merge $result (dict "resource" $siteResourceImage) }} + {{ else }} + + {{ errorf "Failed loading image: %q" $defaultImageSetting.src }} + {{ $result = merge $result (dict "exists" false) }} + {{ end }} + + {{ else }} + + {{ $result = merge $result (dict "permalink" (relURL $defaultImageSetting.src)) }} + {{ end }} + + {{ end }} + +{{ end }} + +{{ return $result }} diff --git a/themes/stack/layouts/partials/pagination.html b/themes/stack/layouts/partials/pagination.html new file mode 100644 index 0000000..7756050 --- /dev/null +++ b/themes/stack/layouts/partials/pagination.html @@ -0,0 +1,26 @@ +{{ if gt .Paginator.TotalPages 1 }} + +{{ end }} diff --git a/themes/stack/layouts/partials/sidebar/left.html b/themes/stack/layouts/partials/sidebar/left.html new file mode 100644 index 0000000..21e7d3e --- /dev/null +++ b/themes/stack/layouts/partials/sidebar/left.html @@ -0,0 +1,103 @@ + diff --git a/themes/stack/layouts/partials/sidebar/right.html b/themes/stack/layouts/partials/sidebar/right.html new file mode 100644 index 0000000..6333f10 --- /dev/null +++ b/themes/stack/layouts/partials/sidebar/right.html @@ -0,0 +1,16 @@ +{{- $scope := default "homepage" .Scope -}} +{{- $context := .Context -}} +{{- with (index .Context.Site.Params.widgets $scope) -}} + +{{ end }} \ No newline at end of file diff --git a/themes/stack/layouts/partials/widget/archives.html b/themes/stack/layouts/partials/widget/archives.html new file mode 100644 index 0000000..1f2abc5 --- /dev/null +++ b/themes/stack/layouts/partials/widget/archives.html @@ -0,0 +1,35 @@ +{{- $query := first 1 (where .Context.Site.Pages "Layout" "==" "archives") -}} +{{- $context := .Context -}} +{{- $limit := default 5 .Params.limit -}} +{{- if $query -}} + {{- $archivesPage := index $query 0 -}} +
+
+ {{ partial "helper/icon" "infinity" }} +
+

{{ T "widget.archives.title" }}

+ + {{ $pages := where $context.Site.RegularPages "Type" "in" $context.Site.Params.mainSections }} + {{ $notHidden := where $context.Site.RegularPages "Params.hidden" "!=" true }} + {{ $filtered := ($pages | intersect $notHidden) }} + {{ $archives := $filtered.GroupByDate "2006" }} + +
+ {{ range $index, $item := first (add $limit 1) ($archives) }} + {{- $id := lower (replace $item.Key " " "-") -}} + + {{ end }} +
+
+{{- else -}} + {{- warnf "Archives page not found. Create a page with layout: archives." -}} +{{- end -}} \ No newline at end of file diff --git a/themes/stack/layouts/partials/widget/categories.html b/themes/stack/layouts/partials/widget/categories.html new file mode 100644 index 0000000..10c8a35 --- /dev/null +++ b/themes/stack/layouts/partials/widget/categories.html @@ -0,0 +1,16 @@ +{{- $context := .Context -}} +{{- $limit := default 10 .Params.limit -}} +
+
+ {{ partial "helper/icon" "categories" }} +
+

{{ T "widget.categoriesCloud.title" }}

+ +
+ {{ range first $limit $context.Site.Taxonomies.categories.ByCount }} + + {{ .Page.Title }} + + {{ end }} +
+
diff --git a/themes/stack/layouts/partials/widget/search.html b/themes/stack/layouts/partials/widget/search.html new file mode 100644 index 0000000..7b0fc73 --- /dev/null +++ b/themes/stack/layouts/partials/widget/search.html @@ -0,0 +1,16 @@ +{{- $query := first 1 (where .Context.Site.Pages "Layout" "==" "search") -}} +{{- if $query -}} + {{- $searchPage := index $query 0 -}} +
+

+ + + + +

+
+{{- else -}} + {{- warnf "Search page not found. Create a page with layout: search." -}} +{{- end -}} \ No newline at end of file diff --git a/themes/stack/layouts/partials/widget/tag-cloud.html b/themes/stack/layouts/partials/widget/tag-cloud.html new file mode 100644 index 0000000..e64e5e2 --- /dev/null +++ b/themes/stack/layouts/partials/widget/tag-cloud.html @@ -0,0 +1,16 @@ +{{- $context := .Context -}} +{{- $limit := default 10 .Params.limit -}} +
+
+ {{ partial "helper/icon" "tag" }} +
+

{{ T "widget.tagCloud.title" }}

+ +
+ {{ range first $limit $context.Site.Taxonomies.tags.ByCount }} + + {{ .Page.Title }} + + {{ end }} +
+
\ No newline at end of file diff --git a/themes/stack/layouts/partials/widget/toc.html b/themes/stack/layouts/partials/widget/toc.html new file mode 100644 index 0000000..e311de3 --- /dev/null +++ b/themes/stack/layouts/partials/widget/toc.html @@ -0,0 +1,12 @@ +{{ if (.Context.Scratch.Get "TOCEnabled") }} +
+
+ {{ partial "helper/icon" "hash" }} +
+

{{ T "article.tableOfContents" }}

+ +
+ {{ .Context.TableOfContents }} +
+
+{{ end }} \ No newline at end of file diff --git a/themes/stack/layouts/shortcodes/bilibili.html b/themes/stack/layouts/shortcodes/bilibili.html new file mode 100644 index 0000000..cb72e43 --- /dev/null +++ b/themes/stack/layouts/shortcodes/bilibili.html @@ -0,0 +1,23 @@ +{{ $vid := (.Get 0) }} +{{ $videopage := default 1 (.Get 1) }} +{{ $basicQuery := querify "page" $videopage "high_quality" 1 "as_wide" 1 }} +{{ $videoQuery := "" }} + +{{ if strings.HasPrefix (lower $vid) "av" }} + {{ $videoQuery = querify "aid" (strings.TrimPrefix "av" (lower $vid)) }} +{{ else if strings.HasPrefix (lower $vid) "bv" }} + {{ $videoQuery = querify "bvid" $vid }} +{{ else }} +

Bilibili 视频av号或BV号错误!请检查视频av号或BV号是否正确

+

当前视频av或BV号:{{ $vid }},视频分P:{{ $videopage }}

+{{ end }} + +
+ +
diff --git a/themes/stack/layouts/shortcodes/gitlab.html b/themes/stack/layouts/shortcodes/gitlab.html new file mode 100644 index 0000000..b5a16cf --- /dev/null +++ b/themes/stack/layouts/shortcodes/gitlab.html @@ -0,0 +1,4 @@ + \ No newline at end of file diff --git a/themes/stack/layouts/shortcodes/quote.html b/themes/stack/layouts/shortcodes/quote.html new file mode 100644 index 0000000..09bb07c --- /dev/null +++ b/themes/stack/layouts/shortcodes/quote.html @@ -0,0 +1,15 @@ +
+

{{ .Inner | markdownify }}

+ {{- if or (.Get "author") (.Get "source") -}} + + {{- if .Get "author" -}} + + {{- .Get "author" -}}{{- if .Get "source" -}}, {{ end -}} + + {{- end -}} + {{- with .Get "url" -}}{{- end -}} + {{ .Get "source" }} + {{- if .Get "url" -}}{{- end -}} + + {{- end -}} +
\ No newline at end of file diff --git a/themes/stack/layouts/shortcodes/tencent.html b/themes/stack/layouts/shortcodes/tencent.html new file mode 100644 index 0000000..463dec5 --- /dev/null +++ b/themes/stack/layouts/shortcodes/tencent.html @@ -0,0 +1,10 @@ +{{ $vid := .Get 0 }} +
+ +
\ No newline at end of file diff --git a/themes/stack/layouts/shortcodes/video.html b/themes/stack/layouts/shortcodes/video.html new file mode 100644 index 0000000..5324754 --- /dev/null +++ b/themes/stack/layouts/shortcodes/video.html @@ -0,0 +1,14 @@ +{{- $src := .Get "src" | default (.Get 0) -}} +
+ +
diff --git a/themes/stack/layouts/shortcodes/youtube.html b/themes/stack/layouts/shortcodes/youtube.html new file mode 100644 index 0000000..2f85931 --- /dev/null +++ b/themes/stack/layouts/shortcodes/youtube.html @@ -0,0 +1,13 @@ +{{- $pc := .Page.Site.Config.Privacy.YouTube -}} +{{- if not $pc.Disable -}} +{{- $ytHost := cond $pc.PrivacyEnhanced "www.youtube-nocookie.com" "www.youtube.com" -}} +{{- $id := .Get "id" | default (.Get 0) -}} +
+ +
+{{ end -}} \ No newline at end of file diff --git a/themes/stack/netlify.toml b/themes/stack/netlify.toml new file mode 100644 index 0000000..b7cd29f --- /dev/null +++ b/themes/stack/netlify.toml @@ -0,0 +1,26 @@ +[build] + publish = "exampleSite/public" + +[build.environment] + HUGO_VERSION = "0.124.0" + HUGO_THEME = "repo" + +[context.production] + command = "cd exampleSite && hugo --gc --themesDir ../.. -b ${URL}" + [context.production.environment] + HUGO_ENV = "production" + +[context.branch-deploy] + command = "cd exampleSite && hugo --gc --themesDir ../.. -b ${DEPLOY_PRIME_URL}" + +[context.deploy-preview] + command = "cd exampleSite && hugo --gc --themesDir ../.. -b ${DEPLOY_PRIME_URL}" + +[[plugins]] + 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" diff --git a/themes/stack/theme.toml b/themes/stack/theme.toml new file mode 100644 index 0000000..6c5ea62 --- /dev/null +++ b/themes/stack/theme.toml @@ -0,0 +1,27 @@ +# theme.toml template for a Hugo theme +# See https://github.com/gohugoio/hugoThemes#themetoml for an example + +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://github.com/CaiJimmy/hugo-theme-stack" +demosite = "https://demo.stack.jimmycai.com" + +tags = ["blog", "responsive", "clean", "light", "dark", "personal"] + +features = [ + "disqus", + "photoswipe", + "opengraph", + "widgets", + "darkmode", + "table of contents", + "search", +] + +min_version = "0.123.0" + +[author] +name = "Jimmy Cai" +homepage = "https://jimmycai.com"