├── assets
├── css
│ ├── custom.css
│ ├── notice.css
│ └── syntax.css
├── scss
│ ├── custom.scss
│ ├── water
│ │ ├── builds
│ │ │ ├── dark.css
│ │ │ ├── light.css
│ │ │ └── water.css
│ │ ├── parts
│ │ │ ├── _links.css
│ │ │ ├── _core.css
│ │ │ ├── _code.css
│ │ │ ├── _print.css
│ │ │ ├── _base.css
│ │ │ ├── _typography.css
│ │ │ ├── _range.css
│ │ │ ├── _misc.css
│ │ │ └── _forms.css
│ │ ├── variables-light.css
│ │ └── variables-dark.css
│ ├── _share_buttons.scss
│ └── main.scss
├── img
│ └── default-404.jpeg
└── js
│ ├── main.js
│ └── disqusloader.js
├── .gitignore
├── layouts
├── partials
│ ├── custom_head.html
│ ├── rfc3339.html
│ ├── current_output_format.html
│ ├── list_page_title.html
│ ├── head
│ │ ├── keywords.html
│ │ ├── description.html
│ │ ├── author.html
│ │ ├── cover.html
│ │ └── icon.html
│ ├── footer.html
│ ├── paginator.html
│ ├── disqus.html
│ ├── list_page_content.html
│ ├── related.html
│ ├── script.html
│ ├── meta.html
│ ├── head.html
│ ├── schema_jsonld.html
│ ├── header.html
│ └── share_buttons.html
├── _default
│ ├── single.md
│ ├── _markup
│ │ ├── render-heading.html
│ │ ├── render-link.html
│ │ ├── render-image.amp.html
│ │ └── render-image.html
│ ├── archives.html
│ ├── list.html
│ ├── index.md
│ ├── baseof.html
│ ├── single.html
│ ├── terms.html
│ ├── index.html
│ └── baseof.amp.html
├── robots.txt
├── shortcodes
│ ├── details.html
│ ├── audio.html
│ ├── video.html
│ ├── admonition.html
│ └── notice.html
├── 404.html
└── sitemap.xml
├── .stylelintrc.json
├── static
├── favicon.ico
├── zoom.js
│ ├── dist
│ │ ├── index.js
│ │ ├── index.d.ts
│ │ ├── zoom.d.ts
│ │ ├── common.d.ts
│ │ ├── zoom.css
│ │ ├── zoom-image.d.ts
│ │ ├── common.js
│ │ ├── zoom.js
│ │ └── zoom-image.js
│ ├── package.json
│ ├── LICENSE
│ ├── LICENSE.original
│ └── README.md
├── favicon-16x16.png
├── favicon-32x32.png
├── apple-touch-icon.png
├── android-chrome-192x192.png
├── android-chrome-512x512.png
├── site.webmanifest
└── sitemap.xsl
├── archetypes
└── default.md
├── .prettierrc.json
├── package.json
├── theme.toml
├── icons
├── note.svg
├── info.svg
├── tip.svg
└── warning.svg
├── README.md
├── LICENSE
├── i18n
├── zh-Hans.yaml
└── en.yaml
└── config.toml.example
/assets/css/custom.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/scss/custom.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/layouts/partials/custom_head.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/layouts/_default/single.md:
--------------------------------------------------------------------------------
1 | {{ .RawContent }}
--------------------------------------------------------------------------------
/layouts/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Sitemap: {{ "sitemap.xml" | absURL }}
3 |
--------------------------------------------------------------------------------
/.stylelintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "stylelint-config-standard-scss"
3 | }
4 |
--------------------------------------------------------------------------------
/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuanji-dev/futu/HEAD/static/favicon.ico
--------------------------------------------------------------------------------
/static/zoom.js/dist/index.js:
--------------------------------------------------------------------------------
1 | export { defaultConfig, dismissZoom, zoom } from "./zoom.js";
2 |
--------------------------------------------------------------------------------
/static/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuanji-dev/futu/HEAD/static/favicon-16x16.png
--------------------------------------------------------------------------------
/static/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuanji-dev/futu/HEAD/static/favicon-32x32.png
--------------------------------------------------------------------------------
/assets/img/default-404.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuanji-dev/futu/HEAD/assets/img/default-404.jpeg
--------------------------------------------------------------------------------
/static/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuanji-dev/futu/HEAD/static/apple-touch-icon.png
--------------------------------------------------------------------------------
/static/zoom.js/dist/index.d.ts:
--------------------------------------------------------------------------------
1 | export { Config, defaultConfig, dismissZoom, zoom } from "./zoom.js";
2 |
--------------------------------------------------------------------------------
/static/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuanji-dev/futu/HEAD/static/android-chrome-192x192.png
--------------------------------------------------------------------------------
/static/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yuanji-dev/futu/HEAD/static/android-chrome-512x512.png
--------------------------------------------------------------------------------
/assets/scss/water/builds/dark.css:
--------------------------------------------------------------------------------
1 | /**
2 | * Forced dark theme version
3 | */
4 |
5 | @import "../variables-dark";
6 | @import "../parts/core";
7 |
--------------------------------------------------------------------------------
/assets/scss/water/builds/light.css:
--------------------------------------------------------------------------------
1 | /**
2 | * Forced light theme version
3 | */
4 |
5 | @import "../variables-light";
6 | @import "../parts/core";
7 |
--------------------------------------------------------------------------------
/layouts/partials/rfc3339.html:
--------------------------------------------------------------------------------
1 | {{/* https://golang.org/src/time/format.go?s=15423:15465#L78 */}}
2 | {{ return (.Format "2006-01-02T15:04:05Z07:00") }}
3 |
--------------------------------------------------------------------------------
/assets/scss/water/parts/_links.css:
--------------------------------------------------------------------------------
1 | a {
2 | text-decoration: none;
3 | color: var(--links);
4 | }
5 |
6 | a:hover {
7 | text-decoration: underline;
8 | }
9 |
--------------------------------------------------------------------------------
/layouts/partials/current_output_format.html:
--------------------------------------------------------------------------------
1 | {{/* https://github.com/gohugoio/hugo/issues/9368 */}}
2 | {{ return (index (complement .AlternativeOutputFormats .OutputFormats) 0) }}
3 |
--------------------------------------------------------------------------------
/layouts/partials/list_page_title.html:
--------------------------------------------------------------------------------
1 | {{ $title := .Title }}
2 | {{ if .Data.Plural }}
3 | {{ $title = printf "%s: %s" (i18n .Data.Plural) .Title }}
4 | {{ end }}
5 | {{ return $title }}
6 |
--------------------------------------------------------------------------------
/assets/scss/water/parts/_core.css:
--------------------------------------------------------------------------------
1 | @import "base";
2 | @import "typography";
3 | @import "forms";
4 | @import "range";
5 | @import "links";
6 | @import "code";
7 | @import "misc";
8 | @import "print";
9 |
--------------------------------------------------------------------------------
/layouts/_default/_markup/render-heading.html:
--------------------------------------------------------------------------------
1 | {{ .Text | safeHTML }}
2 |
--------------------------------------------------------------------------------
/archetypes/default.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "{{ replace .Name "-" " " | title }}"
3 | date: {{ .Date }}
4 | draft: true
5 | tags: []
6 | keywords: []
7 | slug: ""
8 | ---
9 |
10 | **Insert Lead paragraph here.**
11 |
12 | ## New Cool Posts
13 |
--------------------------------------------------------------------------------
/layouts/partials/head/keywords.html:
--------------------------------------------------------------------------------
1 | {{ $keywords := slice }}
2 | {{ if .Keywords }}
3 | {{ $keywords = .Keywords }}
4 | {{- else if .Site.Params.keywords -}}
5 | {{ $keywords = .Site.Params.keywords }}
6 | {{- end -}}
7 | {{ return $keywords }}
8 |
--------------------------------------------------------------------------------
/layouts/shortcodes/details.html:
--------------------------------------------------------------------------------
1 | {{ .Scratch.Set "lastp" (sub (.Params | len) 1) }}
2 |
3 |
4 | {{ .Get (.Scratch.Get "lastp") }}
5 |
6 | {{ .Inner }}
7 |
8 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 120,
3 | "plugins": ["prettier-plugin-go-template"],
4 | "overrides": [
5 | {
6 | "files": ["*.html"],
7 | "options": {
8 | "parser": "go-template"
9 | }
10 | }
11 | ]
12 | }
13 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "devDependencies": {
3 | "prettier": "^3.1.0",
4 | "prettier-plugin-go-template": "^0.0.15",
5 | "stylelint": "^15.11.0",
6 | "stylelint-config-standard": "^34.0.0",
7 | "stylelint-config-standard-scss": "^11.1.0"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/layouts/_default/_markup/render-link.html:
--------------------------------------------------------------------------------
1 | {{ .Text | safeHTML }}
2 | {{- /* Strip trailing newline. */ -}}
3 |
--------------------------------------------------------------------------------
/layouts/_default/archives.html:
--------------------------------------------------------------------------------
1 | {{- define "main" }}
2 | {{ $pages := where site.RegularPages "Type" "in" site.Params.mainSections }}
3 | {{ $pages = where $pages "Draft" "eq" false }}
4 | {{ partial "list_page_content.html" (dict "title" .Title "pages" $pages "total" (len $pages)) }}
5 | {{- end }}
6 |
--------------------------------------------------------------------------------
/assets/scss/water/builds/water.css:
--------------------------------------------------------------------------------
1 | /**
2 | * Automatic version:
3 | * Uses light theme by default but switches to dark theme
4 | * if a system-wide theme preference is set on the user's device.
5 | */
6 |
7 | @import "../variables-light";
8 | @import "../variables-dark";
9 | @import "../parts/core";
10 |
--------------------------------------------------------------------------------
/layouts/shortcodes/audio.html:
--------------------------------------------------------------------------------
1 |
2 |
5 | {{ with .Get "caption" }}{{ . }}{{ end }}
6 |
7 |
--------------------------------------------------------------------------------
/layouts/shortcodes/video.html:
--------------------------------------------------------------------------------
1 |
2 |
5 | {{ with .Get "caption" }}{{ . }}{{ end }}
6 |
7 |
--------------------------------------------------------------------------------
/static/zoom.js/dist/zoom.d.ts:
--------------------------------------------------------------------------------
1 | export type Config = {
2 | padding: number;
3 | paddingNarrow: number;
4 | dismissScrollDelta: number;
5 | dismissTouchDelta: number;
6 | };
7 | export declare const defaultConfig: Config;
8 | export declare function zoom(img: HTMLImageElement, cfg?: Config): void;
9 | export declare function dismissZoom(): void;
10 |
--------------------------------------------------------------------------------
/layouts/partials/head/description.html:
--------------------------------------------------------------------------------
1 | {{ $description := "" }}
2 | {{ if .Description }}
3 | {{ $description = .Description | safeHTML }}
4 | {{- else if .IsPage -}}
5 | {{ $description = .Summary | plainify }}
6 | {{- else if .Site.Params.description -}}
7 | {{ $description = .Site.Params.description | safeHTML }}
8 | {{- end -}}
9 | {{ return $description }}
10 |
--------------------------------------------------------------------------------
/static/site.webmanifest:
--------------------------------------------------------------------------------
1 | {
2 | "name": "",
3 | "short_name": "",
4 | "icons": [
5 | { "src": "/android-chrome-192x192.png", "sizes": "192x192", "type": "image/png" },
6 | { "src": "/android-chrome-512x512.png", "sizes": "512x512", "type": "image/png" }
7 | ],
8 | "theme_color": "#ffffff",
9 | "background_color": "#ffffff",
10 | "display": "standalone"
11 | }
12 |
--------------------------------------------------------------------------------
/layouts/partials/footer.html:
--------------------------------------------------------------------------------
1 |
2 | {{ (printf (i18n "powered") (printf `Hugo %s` .Site.Hugo.Version)) | safeHTML }}
3 | |
4 | {{ i18n "theme" }}
5 | futu@v2
6 |
7 | Copyright ©
8 | {{ .Site.Params.Since }}-{{ now.Format "2006" }}
9 | {{ .Site.Author.name }} All Rights Reserved.
10 |
11 |
--------------------------------------------------------------------------------
/layouts/404.html:
--------------------------------------------------------------------------------
1 | {{ define "main" }}
2 | {{ $image := resources.GetMatch "img/404.*" }}
3 | {{ if not $image }}
4 | {{ $image = resources.GetMatch "img/default-404.jpeg" }}
5 | {{ end }}
6 | {{ $image := $image.Resize (print $image.Width "x" $image.Height " webp") }}
7 |
14 | {{ end }}
15 |
--------------------------------------------------------------------------------
/layouts/_default/list.html:
--------------------------------------------------------------------------------
1 | {{- define "title" }}{{ partial "list_page_title.html" . }} | {{ .Site.Title }}{{ end -}}
2 | {{- define "main" }}
3 | {{ $title := partial "list_page_title.html" . }}
4 | {{ $pages := where .Pages "Draft" "eq" false }}
5 | {{ $paginator := .Paginate $pages (.Site.Params.paginate | default 10) }}
6 | {{ partial "list_page_content.html" (dict "title" $title "pages" $paginator.Pages "total" (len $pages) ) }}
7 | {{ partial "paginator.html" $paginator }}
8 | {{- end }}
9 |
--------------------------------------------------------------------------------
/static/zoom.js/dist/common.d.ts:
--------------------------------------------------------------------------------
1 | export declare function viewportWidth(docElem: HTMLElement): number;
2 | export declare function viewportHeight(docElem: HTMLElement): number;
3 | export declare function usableWidth(docElem: HTMLElement, offset: number): number;
4 | export declare function usableHeight(docElem: HTMLElement, offset: number): number;
5 | export declare function toOffset(padding: number): number;
6 | export declare function wrap(elem: Node, wrapper: Node): void;
7 | export declare function unwrap(elem: Node, wrapper: Node): void;
8 |
--------------------------------------------------------------------------------
/layouts/partials/head/author.html:
--------------------------------------------------------------------------------
1 | {{ $author := newScratch }}
2 | {{ if .Params.author }}
3 | {{ $author.Set "name" (.Params.author | safeHTML) }}
4 | {{ with .Params.author_url }}
5 | {{ $author.Set "url" (. | safeHTML) }}
6 | {{ end }}
7 | {{ else }}
8 | {{ $author.Set "name" (.Site.Params.author.name | safeHTML) }}
9 | {{ with $.Site.Params.author.url }}
10 | {{ $author.Set "url" (. | safeHTML) }}
11 | {{ else }}
12 | {{ $author.Set "url" $.Site.BaseURL }}
13 | {{ end }}
14 | {{ end }}
15 | {{ return $author }}
16 |
--------------------------------------------------------------------------------
/layouts/partials/paginator.html:
--------------------------------------------------------------------------------
1 | {{ if or .Prev .Next }}
2 |
3 |
17 | {{ end }}
18 |
--------------------------------------------------------------------------------
/layouts/_default/index.md:
--------------------------------------------------------------------------------
1 | # {{ .Site.Title }}
2 | {{ $pages := where site.RegularPages "Type" "in" site.Params.mainSections }}
3 | {{- $pages := where $pages "Draft" "eq" false }}
4 | {{ i18n "archiveCounter" (len $pages) }}
5 |
6 | {{ range $pages.GroupByPublishDate "2006" }}
7 | {{- if ne .Key "0001" -}}
8 | ## {{ .Key }}
9 | {{- range .Pages }}
10 | {{- $postPath := replace .File.Path " " "%20" }}
11 | - {{ .PublishDate.Format "01-02" }} [{{ .Title }}](content/{{ $postPath }})
12 | {{- end }}
13 | {{- end }}
14 | {{ end }}
15 |
--------------------------------------------------------------------------------
/theme.toml:
--------------------------------------------------------------------------------
1 | # theme.toml template for a Hugo theme
2 | # See https://github.com/gohugoio/hugoThemesSiteBuilder#theme-configuration for an example
3 |
4 | name = "Futu"
5 | license = "MIT"
6 | licenselink = "https://github.com/yuanji-dev/futu/blob/master/LICENSE"
7 | description = "Yet another minimal hugo theme, built-in styles are modified upon water.css."
8 | homepage = "https://github.com/yuanji-dev/futu"
9 | tags = ["blog", "minimal"]
10 | features = []
11 | min_version = "0.108.0"
12 |
13 | [author]
14 | name = "Yuanji"
15 | homepage = "https://blog.yuanji.dev"
16 |
--------------------------------------------------------------------------------
/layouts/partials/disqus.html:
--------------------------------------------------------------------------------
1 | {{ $currentOutputFormat := (partial "current_output_format.html" .) }}
2 | {{ if and hugo.IsProduction (ne $currentOutputFormat.Name "amp") }}
3 | {{ with .Site.Config.Services.Disqus.Shortname }}
4 | {{ $disqusLoader := resources.Get "js/disqusloader.js" | minify }}
5 |
6 |
7 |
12 | {{ end }}
13 | {{ end }}
14 |
--------------------------------------------------------------------------------
/layouts/partials/head/cover.html:
--------------------------------------------------------------------------------
1 | {{ $image := "" }}
2 | {{- with $.Params.images -}}
3 | {{ $image = index . 0 | absURL }}
4 | {{ else -}}
5 | {{- $images := $.Resources.ByType "image" -}}
6 | {{- $featured := $images.GetMatch "*feature*" -}}
7 | {{- if not $featured }}{{ $featured = $images.GetMatch "{*cover*,*thumbnail*}" }}{{ end -}}
8 | {{- with $featured -}}
9 | {{ $image = $featured.Permalink }}
10 | {{- else -}}
11 | {{- with $.Site.Params.images -}}
12 | {{ $image = index . 0 | absURL }}
13 | {{- end -}}
14 | {{- end -}}
15 | {{- end }}
16 | {{ return $image }}
17 |
--------------------------------------------------------------------------------
/assets/scss/water/parts/_code.css:
--------------------------------------------------------------------------------
1 | code,
2 | samp,
3 | time {
4 | background: var(--background);
5 | color: var(--code);
6 | padding: 2.5px 5px;
7 | border-radius: 6px;
8 | font-size: 1em;
9 | }
10 |
11 | pre > code {
12 | padding: 10px;
13 | display: block;
14 | overflow-x: auto;
15 | }
16 |
17 | var {
18 | color: var(--variable);
19 | font-style: normal;
20 | font-family: monospace;
21 | }
22 |
23 | kbd {
24 | background: var(--background);
25 | border: 1px solid var(--border);
26 | border-radius: 2px;
27 | color: var(--text-main);
28 | padding: 2px 4px 2px 4px;
29 | }
30 |
--------------------------------------------------------------------------------
/icons/note.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/icons/info.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/assets/scss/water/variables-light.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --background-body: #fff;
3 | --background: #efefef;
4 | --background-alt: #f7f7f7;
5 | --selection: #9e9e9e;
6 | --text-main: #363636;
7 | --text-bright: #000;
8 | --text-muted: #70777f;
9 | --links: #0076d1;
10 | --focus: #0096bfab;
11 | --border: #dbdbdb;
12 | --code: #000;
13 | --animation-duration: 0.1s;
14 | --button-base: #d0cfcf;
15 | --button-hover: #9b9b9b;
16 | --scrollbar-thumb: #aaa;
17 | --scrollbar-thumb-hover: var(--button-hover);
18 | --form-placeholder: #949494;
19 | --form-text: #1d1d1d;
20 | --variable: #39a33c;
21 | --highlight: #ff0;
22 | }
23 |
--------------------------------------------------------------------------------
/icons/tip.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/layouts/_default/baseof.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ block "title" . }}
6 | {{ if .IsHome }}{{ else }}{{ if .Title }}{{ .Title }} | {{ end }}{{ end }}{{ site.Title }}
7 | {{ end }}
8 |
9 | {{- partial "head.html" . -}}
10 | {{- partial "custom_head.html" . -}}
11 |
12 |
13 | {{- partial "header.html" . -}}
14 |
15 | {{- block "main" . }}
16 | {{- end }}
17 |
18 |
19 | {{- partial "script.html" . -}}
20 |
21 |
22 |
--------------------------------------------------------------------------------
/layouts/partials/list_page_content.html:
--------------------------------------------------------------------------------
1 |
2 | {{ .title }}
3 | {{ i18n "archiveCounter" .total }}
4 |
5 | {{ range .pages.GroupByPublishDate "2006" }}
6 | {{- if ne .Key "0001" }}
7 | {{ .Key }}
8 | {{ i18n "archiveCounter" (len .Pages) }}
9 |
10 | {{ range .Pages }}
11 | -
12 | {{ .Title }}
13 |
14 |
15 | {{ end }}
16 |
17 | {{ end }}
18 | {{ end }}
19 |
--------------------------------------------------------------------------------
/static/zoom.js/dist/zoom.css:
--------------------------------------------------------------------------------
1 | :not(.zoom-overlay-open) img.zoom-cursor {
2 | cursor: zoom-in;
3 | }
4 | img.zoom-img,
5 | .zoom-img-wrapper {
6 | position: relative;
7 | z-index: 666;
8 | transition: all 300ms;
9 | }
10 | .zoom-overlay-open img.zoom-img {
11 | cursor: zoom-out;
12 | }
13 | .zoom-overlay {
14 | z-index: 420;
15 | background: #fff;
16 | position: fixed;
17 | top: 0;
18 | left: 0;
19 | right: 0;
20 | bottom: 0;
21 | pointer-events: none;
22 | filter: opacity(0);
23 | transition: filter 300ms ease-in-out;
24 | }
25 | .zoom-overlay-open .zoom-overlay {
26 | filter: opacity(1);
27 | }
28 | .zoom-overlay-open,
29 | .zoom-overlay-transitioning {
30 | cursor: default;
31 | }
32 |
--------------------------------------------------------------------------------
/layouts/_default/single.html:
--------------------------------------------------------------------------------
1 | {{ define "main" -}}
2 |
3 |
4 | {{ .Title }}
5 | {{- partial "meta.html" . -}}
6 |
7 | {{ if (.Params.toc | default (.Site.Params.toc | default false )) }}
8 | {{ with .TableOfContents }}
9 | {{ if ne . "" }}
10 |
11 | {{ i18n "TOC" }}
12 | {{ . }}
13 |
14 | {{ end }}
15 | {{ end }}
16 | {{ end }}
17 | {{ .Content }}
18 |
19 | {{- partial "share_buttons.html" . -}}
20 | {{- partial "related.html" . -}}
21 | {{- partial "disqus.html" . -}}
22 |
23 | {{- end }}
24 |
--------------------------------------------------------------------------------
/icons/warning.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/static/zoom.js/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@nishanths/zoom.js",
3 | "version": "4.4.0",
4 | "description": "Medium.com-like image zoom plugin with no dependecies (fork of fat/zoom.js)",
5 | "repository": {
6 | "type": "git",
7 | "url": "https://github.com/nishanths/zoom.js"
8 | },
9 | "bugs": {
10 | "url": "https://github.com/nishanths/zoom.js/issues"
11 | },
12 | "author": "Nishanth Shanmugham",
13 | "homepage": "https://github.com/nishanths/zoom.js",
14 | "keywords": [
15 | "zoom",
16 | "image"
17 | ],
18 | "license": "MIT",
19 | "private": false,
20 | "main": "",
21 | "module": "dist/index.js",
22 | "types": "dist/index.d.ts",
23 | "files": [
24 | "dist",
25 | "LICENSE.original"
26 | ],
27 | "devDependencies": {
28 | "typescript": "^5.0.4"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/layouts/partials/related.html:
--------------------------------------------------------------------------------
1 | {{ $related := .Site.RegularPages.Related . }}
2 | {{ $related = where $related "Draft" "eq" false | first 5 }}
3 | {{ if (.Params.enableRelated | default (.Site.Params.enableRelated | default false)) }}
4 | {{ with $related }}
5 |
20 | {{ end }}
21 | {{ end }}
22 |
--------------------------------------------------------------------------------
/assets/scss/water/parts/_print.css:
--------------------------------------------------------------------------------
1 | @media print {
2 | body,
3 | pre,
4 | code,
5 | summary,
6 | details,
7 | button,
8 | input,
9 | textarea {
10 | background-color: #fff;
11 | }
12 |
13 | button,
14 | input,
15 | textarea {
16 | border: 1px solid #000;
17 | }
18 |
19 | body,
20 | h1,
21 | h2,
22 | h3,
23 | h4,
24 | h5,
25 | h6,
26 | pre,
27 | code,
28 | button,
29 | input,
30 | textarea,
31 | footer,
32 | summary,
33 | strong {
34 | color: #000;
35 | }
36 |
37 | summary::marker {
38 | color: #000;
39 | }
40 |
41 | summary::-webkit-details-marker {
42 | color: #000;
43 | }
44 |
45 | tbody tr:nth-child(even) {
46 | background-color: #f2f2f2;
47 | }
48 |
49 | a {
50 | color: #00f;
51 | text-decoration: underline;
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/layouts/partials/head/icon.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ with .Params.icon }}
5 | {{ $href := print "data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20viewBox=%220%200%20100%20100%22%3E%3Ctext%20x=%2250%25%22%20y=%2250%25%22%20style=%22dominant-baseline:central;text-anchor:middle;font-size:90px;%22%3E" . "%3C/text%3E%3C/svg%3E" }}
6 |
7 | {{ end }}
8 |
9 |
10 |
--------------------------------------------------------------------------------
/static/zoom.js/dist/zoom-image.d.ts:
--------------------------------------------------------------------------------
1 | export declare class ZoomImage {
2 | readonly img: HTMLImageElement;
3 | private oldTransform;
4 | private wrapper;
5 | private overlay;
6 | private offset;
7 | private dismissCompleteNotified;
8 | private dismissCompleteCallbacks;
9 | private dismissModifiedDOM;
10 | constructor(img: HTMLImageElement, offset: number);
11 | private static makeOverlay;
12 | private static makeWrapper;
13 | private static elemOffset;
14 | private hackForceRepaint;
15 | private zoomModifyDOM;
16 | private dismissModifyDOM;
17 | private zoomAnimate;
18 | private dismissAnimate;
19 | zoom(): void;
20 | onDismissComplete(f: () => void): void;
21 | private notifyDismissComplete;
22 | dismiss(): void;
23 | dismissImmediate(): void;
24 | }
25 |
--------------------------------------------------------------------------------
/layouts/shortcodes/admonition.html:
--------------------------------------------------------------------------------
1 | {{ if .IsNamedParams -}}
2 | {{ if eq (.Get "details") "true" -}}
3 |
4 |
5 | {{ .Get 1 }}
6 |
7 |
8 | {{ $.Page.RenderString .Inner }}
9 |
10 |
11 | {{- else -}}
12 |
13 |
{{ .Get 1 }}
14 |
15 | {{ $.Page.RenderString .Inner }}
16 |
17 |
18 | {{- end }}
19 | {{- else -}}
20 | {{ if eq (.Get 2) "true" -}}
21 |
22 |
23 | {{ .Get 1 }}
24 |
25 |
26 | {{ $.Page.RenderString .Inner }}
27 |
28 |
29 | {{- else -}}
30 |
31 |
{{ .Get 1 }}
32 |
33 | {{ $.Page.RenderString .Inner }}
34 |
35 |
36 | {{- end }}
37 | {{- end }}
38 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Futu
2 |
3 | Yet another minimal hugo theme, built-in styles are modified upon [water.css](https://github.com/kognise/water.css).
4 |
5 | ## Features
6 |
7 | - Minimal
8 | - Multilingual
9 | - [AMP](https://amp.dev/)
10 | - …
11 |
12 | ## Installation
13 |
14 | **Note: minimum supported Hugo version is [v0.108.0](https://github.com/gohugoio/hugo/releases/tag/v0.108.0)**
15 |
16 | You can copy [config.toml.example](config.toml.example) to your Hugo project, rename it to `config.toml` and change configurations as you want.
17 |
18 | To overwrite default favicons, you can generate your owns on [favicon.io](https://favicon.io/), put all extracted files directly under `static` directory of your own project.
19 |
20 | ## Used by
21 |
22 | This theme is used by:
23 |
24 | - [Yuanji's Blog](https://blog.yuanji.dev/)
25 |
26 | ## License
27 |
28 | [MIT](https://choosealicense.com/licenses/mit/)
29 |
--------------------------------------------------------------------------------
/assets/scss/water/parts/_base.css:
--------------------------------------------------------------------------------
1 | html {
2 | scrollbar-color: var(--scrollbar-thumb) var(--background-body);
3 | scrollbar-width: thin;
4 | }
5 |
6 | body {
7 | font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 'Segoe UI Emoji', 'Apple Color Emoji', 'Noto Color Emoji', sans-serif;
8 | line-height: 1.4;
9 | max-width: 800px;
10 | margin: 20px auto;
11 | padding: 0 10px;
12 | word-wrap: break-word;
13 | color: var(--text-main);
14 | background: var(--background-body);
15 | text-rendering: optimizeLegibility;
16 | }
17 |
18 | button,
19 | input,
20 | textarea {
21 | transition:
22 | background-color var(--animation-duration) linear,
23 | border-color var(--animation-duration) linear,
24 | color var(--animation-duration) linear,
25 | box-shadow var(--animation-duration) linear,
26 | transform var(--animation-duration) ease;
27 | }
28 |
--------------------------------------------------------------------------------
/layouts/shortcodes/notice.html:
--------------------------------------------------------------------------------
1 | {{/* https://github.com/martignoni/hugo-notice */}}
2 | {{/* Available notice types: warning, info, note, tip */}}
3 | {{- $noticeType := .Get 0 | default "note" -}}
4 |
5 | {{/* Workaround markdownify inconsistency for single/multiple paragraphs */}}
6 | {{- $raw := (markdownify .Inner | chomp) -}}
7 | {{- $block := findRE "(?is)^<(?:address|article|aside|blockquote|canvas|dd|div|dl|dt|fieldset|figcaption|figure|footer|form|h(?:1|2|3|4|5|6)|header|hgroup|hr|li|main|nav|noscript|ol|output|p|pre|section|table|tfoot|ul|video)\\b" $raw 1 -}}
8 |
9 |
10 |
11 | {{ printf "icons/%s.svg" $noticeType | readFile | safeHTML }}
12 |
13 | {{- i18n $noticeType -}}
14 |
15 | {{- if or $block (not $raw) }}
16 | {{ $raw }}
17 | {{ else }}
18 |
{{ $raw }}
19 | {{ end -}}
20 |
21 |
--------------------------------------------------------------------------------
/layouts/partials/script.html:
--------------------------------------------------------------------------------
1 | {{ $mainJS := resources.Get "js/main.js" | minify | fingerprint }}
2 |
3 | {{ template "_internal/google_analytics.html" . }}
4 | {{ if hugo.IsProduction }}
5 | {{ with $.Site.Params.cfBeaconToken -}}
6 |
7 |
12 |
13 | {{ end }}
14 | {{ end }}
15 | {{ if .Site.Params.enableZoomJS }}
16 |
31 | {{ end }}
32 |
--------------------------------------------------------------------------------
/assets/js/main.js:
--------------------------------------------------------------------------------
1 | const osDark = window.matchMedia("(prefers-color-scheme: dark)");
2 | const modeSwitch = document.getElementById("mode-switch");
3 |
4 | function darkModeOn() {
5 | document.documentElement.classList.add("dark-mode");
6 | }
7 | function darkModeOff() {
8 | document.documentElement.classList.remove("dark-mode");
9 | }
10 |
11 | const osDarkListener = (event) => {
12 | if (event.matches) {
13 | darkModeOn();
14 | } else {
15 | darkModeOff();
16 | }
17 | };
18 | osDark.addEventListener("change", osDarkListener);
19 | osDarkListener(osDark);
20 |
21 | modeSwitch.addEventListener("click", () => {
22 | if (document.documentElement.classList.contains("dark-mode")) {
23 | darkModeOff();
24 | localStorage.setItem("darkMode", "off");
25 | } else {
26 | darkModeOn();
27 | localStorage.setItem("darkMode", "on");
28 | }
29 | });
30 |
31 | if (localStorage.getItem("darkMode") === "on") {
32 | darkModeOn();
33 | } else if (localStorage.getItem("darkMode") === "off") {
34 | darkModeOff();
35 | }
36 |
--------------------------------------------------------------------------------
/layouts/_default/terms.html:
--------------------------------------------------------------------------------
1 | {{- define "title" }}{{ i18n .Data.Plural }} | {{ .Site.Title }}{{ end -}}
2 | {{- define "main" -}}
3 | {{ $name := .Data.Plural -}}
4 | {{ $terms := .Data.Terms.ByCount -}}
5 | {{ $length := len $terms -}}
6 | {{ $taxonomy := i18n $name (dict "Count" $length) -}}
7 |
8 | {{ i18n .Data.Plural }}
9 |
10 | {{ if eq $length 0 }}
11 | {{ i18n "zeroTaxonomyCounter" (dict "Taxonomy" $taxonomy) }}
12 | {{ else }}
13 | {{ i18n "taxonomyCounter" (dict "Count" $length "Taxonomy" $taxonomy) }}
14 | {{ end }}
15 |
16 |
17 | {{ range $key, $value := $terms }}
18 | {{ $termURL := print ($name | relLangURL) "/" ($value.Term | urlize) }}
19 | {{ if $.Site.Params.uglyURLs }}
20 | {{ $termURL = print $termURL ".html" }}
21 | {{ else }}
22 | {{ $termURL = print $termURL "/" }}
23 | {{ end }}
24 | #{{ $value.Term }}
25 | {{ $value.Count }}
26 | {{ end -}}
27 | {{- end }}
28 |
--------------------------------------------------------------------------------
/layouts/partials/meta.html:
--------------------------------------------------------------------------------
1 |
2 | {{ $createDate := .Date.Format (.Site.Params.dateFormatToUse | default "2006-01-02") }}
3 | {{ $updateDate := .Lastmod.Format (.Site.Params.dateFormatToUse | default "2006-01-02") }}
4 | 📅
5 |
6 | {{ if ne $createDate $updateDate }}
7 | ({{ with .GitInfo }}
8 | {{ $gitBranch := $.Site.Params.git.branch | default "main" }}
9 | {{ (i18n "updated") }}
10 | {{ else }}
11 | {{ (i18n "updated") }}
12 | {{ end }})
13 | {{ end }}
14 | {{ with .Params.tags -}}
15 | | 🏷️
16 | {{ range $i, $t := . -}}
17 | {{ if $i }},{{ end }}
18 | #{{ $t }}
19 | {{- end -}}
20 | {{- end }}
21 |
22 |
--------------------------------------------------------------------------------
/assets/scss/_share_buttons.scss:
--------------------------------------------------------------------------------
1 | .resp-sharing-button {
2 | border-radius: 5px;
3 | transition: 25ms ease-out;
4 | padding: 0.5em 0.75em;
5 |
6 | &:hover {
7 | opacity: 0.8;
8 | }
9 |
10 | &__link {
11 | display: inline-block;
12 | text-decoration: none;
13 | color: #fff;
14 | margin-right: 0.5em;
15 | }
16 |
17 | &__icon {
18 | display: inline-block;
19 | fill: #fff;
20 | stroke: none;
21 |
22 | svg {
23 | width: 1em;
24 | height: 1em;
25 | margin: 0;
26 | vertical-align: middle;
27 | }
28 | }
29 |
30 | &--twitter {
31 | background-color: #000;
32 | }
33 |
34 | &--bluesky {
35 | background-color: #0285ff;
36 | }
37 |
38 | &--telegram {
39 | background-color: #26a5e4;
40 | }
41 |
42 | &--douban {
43 | background-color: #2d963d;
44 | }
45 |
46 | &--reddit {
47 | background-color: #ff4500;
48 | }
49 |
50 | &--facebook {
51 | background-color: #0866ff;
52 | }
53 |
54 | &--pocket {
55 | background-color: #ef3f56;
56 | }
57 |
58 | &--email {
59 | background-color: #777;
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2021 Yuanji
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/assets/scss/water/variables-dark.css:
--------------------------------------------------------------------------------
1 | :root.dark-mode {
2 | --background-body: #202b38;
3 | --background: #161f27;
4 | --background-alt: #1a242f;
5 | --selection: #1c76c5;
6 | --text-main: #dbdbdb;
7 | --text-bright: #fff;
8 | --text-muted: #a9b1ba;
9 | --links: #41adff;
10 | --focus: #0096bfab;
11 | --border: #526980;
12 | --code: #ffbe85;
13 | --animation-duration: 0.1s;
14 | --button-base: #0c151c;
15 | --button-hover: #040a0f;
16 | --scrollbar-thumb: var(--button-hover);
17 | --scrollbar-thumb-hover: #000;
18 | --form-placeholder: #a9a9a9;
19 | --form-text: #fff;
20 | --variable: #d941e2;
21 | --highlight: #efdb43;
22 | --select-arrow: url("data:image/svg+xml;charset=utf-8,%3C?xml version='1.0' encoding='utf-8'?%3E %3Csvg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' height='62.5' width='116.9' fill='%23efefef'%3E %3Cpath d='M115.3,1.6 C113.7,0 111.1,0 109.5,1.6 L58.5,52.7 L7.4,1.6 C5.8,0 3.2,0 1.6,1.6 C0,3.2 0,5.8 1.6,7.4 L55.5,61.3 C56.3,62.1 57.3,62.5 58.4,62.5 C59.4,62.5 60.5,62.1 61.3,61.3 L115.2,7.4 C116.9,5.8 116.9,3.2 115.3,1.6Z'/%3E %3C/svg%3E");
23 | }
24 |
--------------------------------------------------------------------------------
/static/zoom.js/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2016 Nishanth Shanmugham
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | SOFTWARE.
20 |
--------------------------------------------------------------------------------
/static/zoom.js/LICENSE.original:
--------------------------------------------------------------------------------
1 | Copyright (c) 2013 @fat
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/layouts/_default/_markup/render-image.amp.html:
--------------------------------------------------------------------------------
1 | {{ $image := .Page.Resources.GetMatch .Destination }}
2 | {{/* https://github.com/gohugoio/hugo/issues/5030 */}}
3 | {{ if ne $image.MediaType.SubType "gif" }}
4 | {{/* https://discourse.gohugo.io/t/image-conversion-without-resizing/32429 */}}
5 | {{ $image = $image.Resize (print $image.Width "x" $image.Height " webp") }}
6 | {{ end }}
7 |
8 | {{ if .IsBlock }}
9 |
10 |
18 | {{ with .Title }}{{ . | safeHTML }}{{ else }}{{ .Text | safeHTML }}{{ end }}
19 |
20 | {{ else }}
21 |
29 | {{ with .Title }}{{ . }}{{ else }}{{ .Text }}{{ end }}
30 | {{ end }}
31 |
--------------------------------------------------------------------------------
/i18n/zh-Hans.yaml:
--------------------------------------------------------------------------------
1 | prevPage:
2 | other: 上一页
3 | nextPage:
4 | other: 下一页
5 | wordCount:
6 | one: "约 {{ .Count }} 字"
7 | other: "约 {{ .Count }} 字"
8 | readingTime:
9 | one: "阅读 {{ .Count }} 分钟"
10 | other: "阅读 {{ .Count }} 分钟"
11 | readMore:
12 | other: "阅读更多"
13 | archive:
14 | other: "归档"
15 | tags:
16 | one: "标签"
17 | other: "标签"
18 | categories:
19 | one: "分类"
20 | other: "分类"
21 | archiveCounter:
22 | one: "共计 {{ .Count }} 篇"
23 | other: "共计 {{ .Count }} 篇"
24 | zeroTaxonomyCounter:
25 | other: "暂无{{ .Taxonomy }}"
26 | taxonomyCounter:
27 | one: "共计 {{ .Count }} 个{{ .Taxonomy }}"
28 | other: "共计 {{ .Count }} 个{{ .Taxonomy }}"
29 | powered:
30 | other: "由 %s 强力驱动"
31 | theme:
32 | other: "主题"
33 | caution:
34 | other: "请注意!"
35 | cautionMessage:
36 | other: "文中内容可能已过时,请谨慎使用。"
37 | TOC:
38 | other: "文章目录"
39 | search:
40 | other: "搜索"
41 | updated:
42 | other: "更新"
43 | viewAll:
44 | other: "查看全部"
45 | latest:
46 | other: "最新%s"
47 | seeAlso:
48 | other: "猜你喜欢"
49 | verified:
50 | other: "这个博客已通过认证"
51 | shareThisPost:
52 | other: "分享本文"
53 | warning:
54 | other: "警告"
55 | note:
56 | other: "注释"
57 | info:
58 | other: "信息"
59 | tip:
60 | other: "提示"
61 |
--------------------------------------------------------------------------------
/layouts/_default/_markup/render-image.html:
--------------------------------------------------------------------------------
1 | {{ $image := .Page.Resources.GetMatch .Destination }}
2 | {{/* https://github.com/gohugoio/hugo/issues/5030 */}}
3 | {{ if ne $image.MediaType.SubType "gif" }}
4 | {{/* https://discourse.gohugo.io/t/image-conversion-without-resizing/32429 */}}
5 | {{ $image = $image.Resize (print $image.Width "x" $image.Height " webp") }}
6 | {{ end }}
7 |
8 | {{ if .IsBlock }}
9 |
10 |
19 | {{ with .Title }}{{ . | safeHTML }}{{ else }}{{ .Text | safeHTML }}{{ end }}
20 |
21 | {{ else }}
22 |
31 | {{ with .Title }}{{ . | safeHTML }}{{ else }}{{ .Text | safeHTML }}{{ end }}
32 | {{ end }}
33 |
--------------------------------------------------------------------------------
/layouts/_default/index.html:
--------------------------------------------------------------------------------
1 | {{ define "main" }}
2 | {{ $sectionSlice := slice }}
3 | {{ range site.Params.mainSections }}
4 | {{ $section := site.GetPage "section" . }}
5 | {{ $sectionSlice = $sectionSlice | append (dict "section" $section "date" (index $section.RegularPages.ByPublishDate.Reverse 0).PublishDate) }}
6 | {{ end }}
7 | {{ range sort $sectionSlice "date" "desc" }}
8 |
9 |
10 | {{ printf (i18n "latest") .section.Title }}
11 |
12 |
13 | {{- $pages := where .section.RegularPages "Draft" "eq" false }}
14 | {{ range first (site.Params.homePaginate | default 5) $pages.ByPublishDate.Reverse }}
15 | -
16 | {{ .Title }}
17 |
18 |
19 | {{ end }}
20 | -
21 | {{ i18n "viewAll" }} →
22 |
23 |
24 |
25 | {{ end }}
26 | {{ end }}
27 |
--------------------------------------------------------------------------------
/layouts/sitemap.xml:
--------------------------------------------------------------------------------
1 | {{ printf "" | safeHTML }}
2 | {{ printf "" ( "sitemap.xsl" | relURL ) | safeHTML }}
3 |
5 | {{ range .Data.Pages }}
6 | {{- if and (eq .Draft false) .Permalink -}}
7 |
8 | {{ .Permalink }}{{ if not .Lastmod.IsZero }}
9 | {{ safeHTML ( .Lastmod.Format "2006-01-02T15:04:05-07:00" ) }}{{ end }}{{ with .Sitemap.ChangeFreq }}
10 | {{ . }}{{ end }}{{ if ge .Sitemap.Priority 0.0 }}
11 | {{ .Sitemap.Priority }}{{ end }}{{ if .IsTranslated }}{{ range .Translations }}
12 | {{ end }}
17 | {{ end }}
22 |
23 | {{- end -}}
24 | {{ end }}
25 |
26 |
--------------------------------------------------------------------------------
/assets/scss/water/parts/_typography.css:
--------------------------------------------------------------------------------
1 | h1 {
2 | font-size: 2.2em;
3 | margin-top: 0;
4 | }
5 |
6 | h1,
7 | h2,
8 | h3,
9 | h4,
10 | h5,
11 | h6 {
12 | margin-bottom: 12px;
13 | margin-top: 24px;
14 | }
15 |
16 | h1,
17 | h2,
18 | h3,
19 | h4,
20 | h5,
21 | h6,
22 | strong {
23 | color: var(--text-bright);
24 | }
25 |
26 | h1,
27 | h2,
28 | h3,
29 | h4,
30 | h5,
31 | h6,
32 | b,
33 | strong,
34 | th {
35 | font-weight: 600;
36 | }
37 |
38 | q::before {
39 | content: none;
40 | }
41 |
42 | q::after {
43 | content: none;
44 | }
45 |
46 | blockquote,
47 | q {
48 | border-left: 4px solid var(--focus);
49 | margin: 1.5em 0;
50 | padding: 0.5em 1em;
51 | font-style: italic;
52 | }
53 |
54 | blockquote > footer {
55 | font-style: normal;
56 | border: 0;
57 | }
58 |
59 | blockquote cite {
60 | font-style: normal;
61 | }
62 |
63 | address {
64 | font-style: normal;
65 | }
66 |
67 | a[href^='mailto\:']::before {
68 | content: '📧 ';
69 | }
70 |
71 | a[href^='tel\:']::before {
72 | content: '📞 ';
73 | }
74 |
75 | a[href^='sms\:']::before {
76 | content: '💬 ';
77 | }
78 |
79 | mark {
80 | background-color: var(--highlight);
81 | border-radius: 2px;
82 | padding: 0 2px 0 2px;
83 | color: #000;
84 | }
85 |
86 | a > code,
87 | a > strong {
88 | color: inherit;
89 | }
90 |
--------------------------------------------------------------------------------
/i18n/en.yaml:
--------------------------------------------------------------------------------
1 | prevPage:
2 | other: Prev
3 | nextPage:
4 | other: Next
5 | wordCount:
6 | one: "{{ .Count }} word"
7 | other: "{{ .Count }} words"
8 | readingTime:
9 | one: "{{ .Count }} min read"
10 | other: "{{ .Count }} mins read"
11 | readMore:
12 | other: "Read more..."
13 | archive:
14 | other: "Archive"
15 | tags:
16 | one: "Tag"
17 | other: "Tags"
18 | categories:
19 | one: "Category"
20 | other: "Categories"
21 | archiveCounter:
22 | one: "{{ .Count }} Item In Total"
23 | other: "{{ .Count }} Items In Total"
24 | zeroTaxonomyCounter:
25 | other: "No {{ .Taxonomy }}"
26 | taxonomyCounter:
27 | one: "{{ .Count }} {{ .Taxonomy }} In Total"
28 | other: "{{ .Count }} {{ .Taxonomy }} In Total"
29 | powered:
30 | other: "Powered by %s"
31 | theme:
32 | other: "Theme"
33 | caution:
34 | other: "Caution!"
35 | cautionMessage:
36 | other: "This article may contain outdated content."
37 | TOC:
38 | other: "What's on this Page"
39 | search:
40 | other: "Search"
41 | updated:
42 | other: "Updated"
43 | viewAll:
44 | other: "View all"
45 | latest:
46 | other: "Latest %s"
47 | seeAlso:
48 | other: "See also"
49 | verified:
50 | other: "This blog is verified"
51 | shareThisPost:
52 | other: "Share this post"
53 | warning:
54 | other: "Warning"
55 | note:
56 | other: "Note"
57 | info:
58 | other: "Info"
59 | tip:
60 | other: "Tip"
61 |
--------------------------------------------------------------------------------
/static/zoom.js/dist/common.js:
--------------------------------------------------------------------------------
1 | // viewportWidth and viewportHeight return the width and height of the viewport.
2 | // The value does not include space occupied by any scrollbars. docElem must be
3 | // a document.documentElement value.
4 | export function viewportWidth(docElem) { return docElem.clientWidth; }
5 | export function viewportHeight(docElem) { return docElem.clientHeight; }
6 | // usableWidth and usableHeight return the maximum width and height of the
7 | // viewport that can be used to show the zoomed image. The value is defined
8 | // to be the viewport width (or height) minus the specified offset.
9 | // docElem must be a document.documentElement value.
10 | export function usableWidth(docElem, offset) { return viewportWidth(docElem) - offset; }
11 | export function usableHeight(docElem, offset) { return viewportHeight(docElem) - offset; }
12 | // toOffset converts a padding value to an offset value.
13 | //
14 | // Note that the internal API of this program is based largely on offset values,
15 | // but the public API is based on padding values, hence the necessity
16 | // for the conversion.
17 | export function toOffset(padding) { return padding * 2; }
18 | // wrap wraps elem inside wrapper.
19 | export function wrap(elem, wrapper) {
20 | var _a;
21 | (_a = elem.parentNode) === null || _a === void 0 ? void 0 : _a.insertBefore(wrapper, elem);
22 | wrapper.appendChild(elem);
23 | }
24 | // unwrap undoes the operation done by the wrap function.
25 | export function unwrap(elem, wrapper) {
26 | var _a, _b;
27 | (_a = wrapper.parentNode) === null || _a === void 0 ? void 0 : _a.insertBefore(elem, wrapper);
28 | (_b = wrapper.parentNode) === null || _b === void 0 ? void 0 : _b.removeChild(wrapper);
29 | }
30 |
--------------------------------------------------------------------------------
/layouts/partials/head.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | {{ partial "head/icon.html" . }}
10 | {{ template "_internal/opengraph.html" . }}
11 | {{ template "_internal/twitter_cards.html" . }}
12 | {{- with .Site.Params.telegramChannel -}}
13 |
14 | {{ end -}}
15 | {{ partial "schema_jsonld.html" . }}
16 | {{ range .AlternativeOutputFormats -}}
17 | {{ printf `` .Rel .MediaType.Type .Permalink $.Title | safeHTML }}
18 | {{ end -}}
19 | {{ if .IsTranslated }}
20 | {{- range .AllTranslations -}}
21 |
22 | {{ end -}}
23 | {{ end }}
24 | {{ with .Site.Params.googleVerification }}
25 |
26 | {{ end }}
27 | {{ if .Site.Params.enableZoomJS }}
28 |
29 | {{ end }}
30 | {{ $styles := slice }}
31 | {{ $styles = $styles | append (resources.Get "scss/main.scss" | toCSS) (resources.Get "scss/custom.scss" | toCSS) }}
32 | {{ $styles = $styles | append (resources.Get "css/syntax.css") (resources.Get "css/notice.css") (resources.Get "css/custom.css") }}
33 | {{ $styles = $styles | resources.Concat "styles.css" | minify | fingerprint }}
34 |
35 |
40 |
--------------------------------------------------------------------------------
/assets/scss/water/parts/_range.css:
--------------------------------------------------------------------------------
1 | input[type='range'] {
2 | margin: 10px 0;
3 | padding: 10px 0;
4 | background: transparent;
5 | }
6 |
7 | input[type='range']:focus {
8 | outline: none;
9 | }
10 |
11 | input[type='range']::-webkit-slider-runnable-track {
12 | width: 100%;
13 | height: 9.5px;
14 | transition: 0.2s;
15 | background: var(--background);
16 | border-radius: 3px;
17 | }
18 |
19 | input[type='range']::-webkit-slider-thumb {
20 | box-shadow: 0 1px 1px #000, 0 0 1px #0d0d0d;
21 | height: 20px;
22 | width: 20px;
23 | border-radius: 50%;
24 | background: var(--border);
25 | -webkit-appearance: none;
26 | margin-top: -7px;
27 | }
28 |
29 | input[type='range']:focus::-webkit-slider-runnable-track {
30 | background: var(--background);
31 | }
32 |
33 | input[type='range']::-moz-range-track {
34 | width: 100%;
35 | height: 9.5px;
36 | transition: 0.2s;
37 | background: var(--background);
38 | border-radius: 3px;
39 | }
40 |
41 | input[type='range']::-moz-range-thumb {
42 | box-shadow: 1px 1px 1px #000, 0 0 1px #0d0d0d;
43 | height: 20px;
44 | width: 20px;
45 | border-radius: 50%;
46 | background: var(--border);
47 | }
48 |
49 | input[type='range']::-ms-track {
50 | width: 100%;
51 | height: 9.5px;
52 | background: transparent;
53 | border-color: transparent;
54 | border-width: 16px 0;
55 | color: transparent;
56 | }
57 |
58 | input[type='range']::-ms-fill-lower {
59 | background: var(--background);
60 | border: 0.2px solid #010101;
61 | border-radius: 3px;
62 | box-shadow: 1px 1px 1px #000, 0 0 1px #0d0d0d;
63 | }
64 |
65 | input[type='range']::-ms-fill-upper {
66 | background: var(--background);
67 | border: 0.2px solid #010101;
68 | border-radius: 3px;
69 | box-shadow: 1px 1px 1px #000, 0 0 1px #0d0d0d;
70 | }
71 |
72 | input[type='range']::-ms-thumb {
73 | box-shadow: 1px 1px 1px #000, 0 0 1px #0d0d0d;
74 | border: 1px solid #000;
75 | height: 20px;
76 | width: 20px;
77 | border-radius: 50%;
78 | background: var(--border);
79 | }
80 |
81 | input[type='range']:focus::-ms-fill-lower {
82 | background: var(--background);
83 | }
84 |
85 | input[type='range']:focus::-ms-fill-upper {
86 | background: var(--background);
87 | }
88 |
--------------------------------------------------------------------------------
/layouts/partials/schema_jsonld.html:
--------------------------------------------------------------------------------
1 | {{ if .IsHome }}
2 |
31 | {{ else if and .IsPage (ne .Params.layout "archives") }}
32 |
68 | {{ end }}
69 |
--------------------------------------------------------------------------------
/layouts/_default/baseof.amp.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{ if .Params.hasIframe -}}
7 |
8 | {{ end -}}
9 |
10 |
11 | {{- block "title" . }}
12 | {{ if .IsHome }}{{ else }}{{ if .Title }}{{ .Title }} | {{ end }}{{ end }}{{ site.Title }}
13 | {{- end }}
14 |
15 |
16 |
17 |
18 |
19 | {{ template "_internal/opengraph.html" . }}
20 | {{ template "_internal/twitter_cards.html" . }}
21 | {{ partial "schema_jsonld.html" . }}
22 |
23 |
30 |
31 |
32 | {{- partial "header.html" . -}}
33 |
34 | {{- block "main" . }}
35 | {{- end }}
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/assets/css/notice.css:
--------------------------------------------------------------------------------
1 | /* https://github.com/martignoni/hugo-notice */
2 | .notice {
3 | --title-color: #fff;
4 | --title-background-color: #6be;
5 | --content-color: #444;
6 | --content-background-color: #e7f2fa;
7 | }
8 |
9 | .notice.info {
10 | --title-background-color: #fb7;
11 | --content-background-color: #fec;
12 | }
13 |
14 | .notice.tip {
15 | --title-background-color: #5a5;
16 | --content-background-color: #efe;
17 | }
18 |
19 | .notice.warning {
20 | --title-background-color: #c33;
21 | --content-background-color: #fee;
22 | }
23 |
24 | /* Dark theme */
25 | @media (prefers-color-scheme: dark) {
26 | .notice {
27 | --title-color: #fff;
28 | --title-background-color: #069;
29 | --content-color: #ddd;
30 | --content-background-color: #023;
31 | }
32 |
33 | .notice.info {
34 | --title-background-color: #a50;
35 | --content-background-color: #420;
36 | }
37 |
38 | .notice.tip {
39 | --title-background-color: #363;
40 | --content-background-color: #121;
41 | }
42 |
43 | .notice.warning {
44 | --title-background-color: #800;
45 | --content-background-color: #400;
46 | }
47 | }
48 |
49 | html.dark-mode .notice {
50 | --title-color: #fff;
51 | --title-background-color: #069;
52 | --content-color: #ddd;
53 | --content-background-color: #023;
54 | }
55 |
56 | html.dark-mode .notice.info {
57 | --title-background-color: #a50;
58 | --content-background-color: #420;
59 | }
60 |
61 | html.dark-mode .notice.tip {
62 | --title-background-color: #363;
63 | --content-background-color: #121;
64 | }
65 |
66 | html.dark-mode .notice.warning {
67 | --title-background-color: #800;
68 | --content-background-color: #400;
69 | }
70 |
71 | /* Content */
72 | .notice {
73 | padding: 18px;
74 | line-height: 24px;
75 | margin-bottom: 24px;
76 | border-radius: 4px;
77 | color: var(--content-color);
78 | background: var(--content-background-color);
79 | }
80 |
81 | .notice p:last-child {
82 | margin-bottom: 0;
83 | }
84 |
85 | /* Title */
86 | .notice-title {
87 | margin: -18px -18px 12px;
88 | padding: 4px 18px;
89 | border-radius: 4px 4px 0 0;
90 | font-weight: 700;
91 | color: var(--title-color);
92 | background: var(--title-background-color);
93 | }
94 |
95 | /* Icon */
96 | .icon-notice {
97 | display: inline-flex;
98 | align-self: center;
99 | margin-right: 8px;
100 | }
101 |
102 | .icon-notice img,
103 | .icon-notice svg {
104 | height: 1em;
105 | width: 1em;
106 | fill: currentColor;
107 | }
108 |
109 | .icon-notice img,
110 | .icon-notice.baseline svg {
111 | top: 0.125em;
112 | position: relative;
113 | }
114 |
--------------------------------------------------------------------------------
/assets/scss/water/parts/_misc.css:
--------------------------------------------------------------------------------
1 | img,
2 | video {
3 | max-width: 100%;
4 | height: auto;
5 | }
6 |
7 | hr {
8 | border: none;
9 | border-top: 1px solid var(--border);
10 | }
11 |
12 | table {
13 | border-collapse: collapse;
14 | margin-bottom: 10px;
15 | width: 100%;
16 | table-layout: fixed;
17 | }
18 |
19 | table caption {
20 | text-align: left;
21 | }
22 |
23 | td,
24 | th {
25 | padding: 6px;
26 | text-align: left;
27 | vertical-align: top;
28 | word-wrap: break-word;
29 | }
30 |
31 | thead {
32 | border-bottom: 1px solid var(--border);
33 | }
34 |
35 | tfoot {
36 | border-top: 1px solid var(--border);
37 | }
38 |
39 | tbody tr:nth-child(even) {
40 | background-color: var(--background);
41 | }
42 |
43 | tbody tr:nth-child(even) button {
44 | background-color: var(--background-alt);
45 | }
46 |
47 | tbody tr:nth-child(even) button:hover {
48 | background-color: var(--background-body);
49 | }
50 |
51 | ::-webkit-scrollbar {
52 | height: 10px;
53 | width: 10px;
54 | }
55 |
56 | ::-webkit-scrollbar-track {
57 | background: var(--background);
58 | border-radius: 6px;
59 | }
60 |
61 | ::-webkit-scrollbar-thumb {
62 | background: var(--scrollbar-thumb);
63 | border-radius: 6px;
64 | }
65 |
66 | ::-webkit-scrollbar-thumb:hover {
67 | background: var(--scrollbar-thumb-hover);
68 | }
69 |
70 | ::selection {
71 | background-color: var(--selection);
72 | color: var(--text-bright);
73 | }
74 |
75 | details {
76 | display: flex;
77 | flex-direction: column;
78 | align-items: flex-start;
79 | background-color: var(--background-alt);
80 | padding: 10px 10px 0;
81 | margin: 1em 0;
82 | border-radius: 6px;
83 | overflow: hidden;
84 | }
85 |
86 | details[open] {
87 | padding: 10px;
88 | }
89 |
90 | details > :last-child {
91 | margin-bottom: 0;
92 | }
93 |
94 | details[open] summary {
95 | margin-bottom: 10px;
96 | }
97 |
98 | summary {
99 | display: list-item;
100 | background-color: var(--background);
101 | padding: 10px;
102 | margin: -10px -10px 0;
103 | cursor: pointer;
104 | outline: none;
105 | }
106 |
107 | summary:hover,
108 | summary:focus {
109 | text-decoration: underline;
110 | }
111 |
112 | details > :not(summary) {
113 | margin-top: 0;
114 | }
115 |
116 | summary::-webkit-details-marker {
117 | color: var(--text-main);
118 | }
119 |
120 | dialog {
121 | background-color: var(--background-alt);
122 | color: var(--text-main);
123 | border: none;
124 | border-radius: 6px;
125 | border-color: var(--border);
126 | padding: 10px 30px;
127 | }
128 |
129 | dialog > header:first-child {
130 | background-color: var(--background);
131 | border-radius: 6px 6px 0 0;
132 | margin: -10px -30px 10px;
133 | padding: 10px;
134 | text-align: center;
135 | }
136 |
137 | dialog::backdrop {
138 | background: #0000009c;
139 | backdrop-filter: blur(4px);
140 | }
141 |
142 | footer {
143 | border-top: 1px solid var(--border);
144 | padding-top: 10px;
145 | color: var(--text-muted);
146 | }
147 |
148 | body > footer {
149 | margin-top: 40px;
150 | }
151 |
--------------------------------------------------------------------------------
/assets/scss/water/parts/_forms.css:
--------------------------------------------------------------------------------
1 | button,
2 | select,
3 | input[type='submit'],
4 | input[type='reset'],
5 | input[type='button'],
6 | input[type='checkbox'],
7 | input[type='range'],
8 | input[type='radio'] {
9 | cursor: pointer;
10 | }
11 |
12 | input,
13 | select {
14 | display: block;
15 | }
16 |
17 | [type='checkbox'],
18 | [type='radio'] {
19 | display: initial;
20 | }
21 |
22 | input,
23 | button,
24 | textarea,
25 | select {
26 | color: var(--form-text);
27 | background-color: var(--background);
28 | font-family: inherit;
29 | font-size: inherit;
30 | margin-right: 6px;
31 | margin-bottom: 6px;
32 | padding: 10px;
33 | border: none;
34 | border-radius: 6px;
35 | outline: none;
36 | }
37 |
38 | button,
39 | input[type='submit'],
40 | input[type='reset'],
41 | input[type='button'] {
42 | background-color: var(--button-base);
43 | padding-right: 30px;
44 | padding-left: 30px;
45 | }
46 |
47 | button:hover,
48 | input[type='submit']:hover,
49 | input[type='reset']:hover,
50 | input[type='button']:hover {
51 | background: var(--button-hover);
52 | }
53 |
54 | input[type='color'] {
55 | min-height: 2rem;
56 | padding: 8px;
57 | cursor: pointer;
58 | }
59 |
60 | input[type='checkbox'],
61 | input[type='radio'] {
62 | height: 1em;
63 | width: 1em;
64 | }
65 |
66 | input[type='radio'] {
67 | border-radius: 100%;
68 | }
69 |
70 | input {
71 | vertical-align: top;
72 | }
73 |
74 | label {
75 | vertical-align: middle;
76 | margin-bottom: 4px;
77 | display: inline-block;
78 | }
79 |
80 | input:not([type='checkbox']):not([type='radio']),
81 | input[type='range'],
82 | select,
83 | button,
84 | textarea {
85 | -webkit-appearance: none;
86 | }
87 |
88 | textarea {
89 | display: block;
90 | margin-right: 0;
91 | box-sizing: border-box;
92 | resize: vertical;
93 | }
94 |
95 | textarea:not([cols]) {
96 | width: 100%;
97 | }
98 |
99 | textarea:not([rows]) {
100 | min-height: 40px;
101 | height: 140px;
102 | }
103 |
104 | select {
105 | background: var(--background) var(--select-arrow) calc(100% - 12px) 50% / 12px no-repeat;
106 | padding-right: 35px;
107 | }
108 |
109 | select::-ms-expand {
110 | display: none;
111 | }
112 |
113 | select[multiple] {
114 | padding-right: 10px;
115 | background-image: none;
116 | overflow-y: auto;
117 | }
118 |
119 | input:focus,
120 | select:focus,
121 | button:focus,
122 | textarea:focus {
123 | box-shadow: 0 0 0 2px var(--focus);
124 | }
125 |
126 | input[type='checkbox']:active,
127 | input[type='radio']:active,
128 | input[type='submit']:active,
129 | input[type='reset']:active,
130 | input[type='button']:active,
131 | input[type='range']:active,
132 | button:active {
133 | transform: translateY(2px);
134 | }
135 |
136 | input:disabled,
137 | select:disabled,
138 | button:disabled,
139 | textarea:disabled {
140 | cursor: not-allowed;
141 | opacity: 0.5;
142 | }
143 |
144 | ::placeholder {
145 | color: var(--form-placeholder);
146 | }
147 |
148 | fieldset {
149 | border: 1px var(--focus) solid;
150 | border-radius: 6px;
151 | margin: 0;
152 | margin-bottom: 12px;
153 | padding: 10px;
154 | }
155 |
156 | legend {
157 | font-size: 0.9em;
158 | font-weight: 600;
159 | }
160 |
--------------------------------------------------------------------------------
/config.toml.example:
--------------------------------------------------------------------------------
1 | baseURL = "example.com"
2 | defaultContentLanguage = "zh-Hans"
3 | defaultContentLanguageInSubdir = false
4 | title = "Someone's Blog"
5 | theme = "futu"
6 | enableRobotsTXT = true
7 | enableEmoji = true
8 | enableGitInfo = true
9 | hasCJKLanguage = true
10 | #disqusShortname = ""
11 | #googleAnalytics = ""
12 |
13 | [imaging]
14 | quality = 75
15 |
16 | [sitemap]
17 | changefreq = "weekly"
18 | priority = 0.5
19 | filename = "sitemap.xml"
20 |
21 | [params]
22 | keywords = []
23 | description = ""
24 | ampLogo = ""
25 | # Site creation time
26 | since = "2016"
27 | paginate = 10
28 | # The date format to use; for a list of valid formats, see https://gohugo.io/functions/format/
29 | dateFormatToUse = "2006/01/02"
30 | # Whether display TOC or not.
31 | toc = true
32 | enableRelated = true
33 | googleVerification = ""
34 | enableEmojiZoomJS = true
35 |
36 | [params.git]
37 | repo = "https://github.com/yuanji-dev/futu"
38 | branch = "main"
39 |
40 | [params.author]
41 | name = "Someone"
42 |
43 | [mediaTypes]
44 | [mediaTypes."text/plain"]
45 | suffixes = ["md"]
46 |
47 | [outputFormats.Markdown]
48 | mediaType = "text/markdown"
49 | isPlainText = true
50 | isHTML = false
51 |
52 | [outputs]
53 | home = ["HTML", "RSS"]
54 | page = ["HTML", "AMP", "Markdown"]
55 | section = ["HTML", "RSS", "Markdown"]
56 | taxonomy = ["HTML", "RSS"]
57 | taxonomyTerm = ["HTML"]
58 |
59 | [markup]
60 | [markup.tableOfContents]
61 | startLevel = 2
62 | [markup.goldmark.renderer]
63 | unsafe = true
64 | [markup.goldmark.parser]
65 | wrapStandAloneImageWithinParagraph = false
66 | [markup.highlight]
67 | anchorLineNos = true
68 | codeFences = true
69 | guessSyntax = true
70 | hl_Lines = ''
71 | lineAnchors = ''
72 | lineNoStart = 1
73 | lineNos = true
74 | lineNumbersInTable = false
75 | noClasses = false
76 | noHl = false
77 | style = 'monokai'
78 | tabWidth = 4
79 |
80 | [languages]
81 | [languages.zh-Hans]
82 | title = "一个人的博客"
83 | languageCode = 'zh-Hans'
84 | languageName = '简体中文'
85 | # https://gohugo.io/content-management/menus/
86 | [languages.zh-Hans.menu]
87 | [[languages.zh-Hans.menu.main]]
88 | name = "首页"
89 | weight = 10
90 | identifier = "home"
91 | url = "/"
92 | [[languages.zh-Hans.menu.main]]
93 | name = "归档"
94 | weight = 20
95 | identifier = "archives"
96 | url = "/archives/"
97 | [[languages.zh-Hans.menu.main]]
98 | name = "标签"
99 | weight = 30
100 | identifier = "tags"
101 | url = "/tags/"
102 | [[languages.zh-Hans.menu.main]]
103 | name = "关于"
104 | weight = 40
105 | identifier = "about"
106 | url = "/about/"
107 |
108 | [languages.en]
109 | title = "Someone's Blog"
110 | languageCode = 'en'
111 | languageName = 'English'
112 | [[languages.en.menu.main]]
113 | name = "Home"
114 | weight = 10
115 | identifier = "home"
116 | url = "/"
117 | [[languages.en.menu.main]]
118 | name = "Archives"
119 | weight = 20
120 | identifier = "archives"
121 | url = "/archives/"
122 | [[languages.en.menu.main]]
123 | name = "Tags"
124 | weight = 30
125 | identifier = "tags"
126 | url = "/tags/"
127 | [[languages.en.menu.main]]
128 | name = "About"
129 | weight = 40
130 | identifier = "about"
131 | url = "/about/"
132 |
--------------------------------------------------------------------------------
/layouts/partials/header.html:
--------------------------------------------------------------------------------
1 | {{ if .IsHome }}
2 |
3 | {{ else }}
4 |
5 | {{ end }}
6 |
24 |
68 |
--------------------------------------------------------------------------------
/assets/js/disqusloader.js:
--------------------------------------------------------------------------------
1 | /*
2 | disqusLoader.js v1.0
3 | A JavaScript plugin for lazy-loading Disqus comments widget.
4 | -
5 | By Osvaldas Valutis, www.osvaldas.info
6 | Available for use under the MIT License
7 | */
8 |
9 | ;( function( window, document, index )
10 | {
11 | 'use strict';
12 |
13 | var extendObj = function( defaults, options )
14 | {
15 | var prop, extended = {};
16 | for( prop in defaults )
17 | if( Object.prototype.hasOwnProperty.call( defaults, prop ))
18 | extended[ prop ] = defaults[ prop ];
19 |
20 | for( prop in options )
21 | if( Object.prototype.hasOwnProperty.call( options, prop ))
22 | extended[ prop ] = options[ prop ];
23 |
24 | return extended;
25 | },
26 | getOffset = function( el )
27 | {
28 | var rect = el.getBoundingClientRect();
29 | return { top: rect.top + document.body.scrollTop, left: rect.left + document.body.scrollLeft };
30 | },
31 | loadScript = function( url, callback )
32 | {
33 | var script = document.createElement( 'script' );
34 | script.src = url;
35 | script.async = true;
36 | script.setAttribute( 'data-timestamp', +new Date());
37 | script.addEventListener( 'load', function()
38 | {
39 | if( typeof callback === 'function' )
40 | callback();
41 | });
42 | ( document.head || document.body ).appendChild( script );
43 | },
44 | throttle = function(a,b){var c,d;return function(){var e=this,f=arguments,g=+new Date;c&&g window.innerHeight * laziness || winST - offset - instance.offsetHeight - ( window.innerHeight * laziness ) > 0 )
64 | return true;
65 |
66 | var tmp = document.getElementById( 'disqus_thread' );
67 | if( tmp ) tmp.removeAttribute( 'id' );
68 | instance.setAttribute( 'id', 'disqus_thread' );
69 | instance.disqusLoaderStatus = 'loaded';
70 |
71 | if( scriptStatus == 'loaded' )
72 | {
73 | DISQUS.reset({ reload: true, config: disqusConfig });
74 | }
75 | else // unloaded | loading
76 | {
77 | window.disqus_config = disqusConfig;
78 | if( scriptStatus == 'unloaded' )
79 | {
80 | scriptStatus = 'loading';
81 | loadScript( scriptUrl, function()
82 | {
83 | scriptStatus = 'loaded';
84 | });
85 | }
86 | }
87 | };
88 |
89 | window.addEventListener( 'scroll', throttle( throttleTO, init ));
90 | window.addEventListener( 'resize', throttle( throttleTO, init ));
91 |
92 | window.disqusLoader = function( element, options )
93 | {
94 | options = extendObj(
95 | {
96 | laziness: 1,
97 | throttle: 250,
98 | scriptUrl: false,
99 | disqusConfig: false,
100 |
101 | }, options );
102 |
103 | laziness = options.laziness + 1;
104 | throttleTO = options.throttle;
105 | disqusConfig = options.disqusConfig;
106 | scriptUrl = scriptUrl === false ? options.scriptUrl : scriptUrl; // set it only once
107 |
108 | if( typeof element === 'string' ) instance = document.querySelector( element );
109 | else if( typeof element.length === 'number' ) instance = element[ 0 ];
110 | else instance = element;
111 |
112 | if (instance) instance.disqusLoaderStatus = 'unloaded';
113 |
114 | init();
115 | };
116 |
117 | }( window, document, 0 ));
118 |
--------------------------------------------------------------------------------
/static/sitemap.xsl:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
11 | XML Sitemap
12 |
13 |
65 |
66 |
67 |
68 |
XML Sitemap
69 |
70 | This is a sitemap generated by Hugo to allow search engines to discover this blog's content.
71 |
72 |
73 | The xsl style copy from Ghost.
74 |
75 |
76 |
77 |
78 | | URL ( total) |
79 | Prio |
80 | Ch. Freq. |
81 | Last Modified |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 | |
90 |
91 |
92 |
93 |
94 |
95 |
96 | |
97 |
98 |
99 | |
100 |
101 |
102 | |
103 |
104 |
105 | |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
--------------------------------------------------------------------------------
/assets/scss/main.scss:
--------------------------------------------------------------------------------
1 | @import "water/builds/water";
2 | @import "share_buttons";
3 |
4 | body {
5 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, "PingFang SC",
6 | "Hiragino Sans GB", STHeiti, "Microsoft YaHei", sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
7 |
8 | img {
9 | display: block;
10 | margin: 0 auto;
11 | }
12 |
13 | table {
14 | margin-bottom: 0;
15 |
16 | &:not([class]) {
17 | display: block;
18 | white-space: nowrap;
19 | border-spacing: 0;
20 | border-collapse: collapse;
21 | overflow-x: auto;
22 | max-width: 100%;
23 | text-align: left;
24 | vertical-align: top;
25 | }
26 |
27 | caption {
28 | text-align: center;
29 | font-weight: 600;
30 | }
31 | }
32 |
33 | code,
34 | kbd,
35 | var,
36 | samp {
37 | font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
38 | font-style: normal;
39 | }
40 |
41 | > header > nav ul {
42 | padding: 0;
43 |
44 | li {
45 | display: inline-block;
46 | margin-right: 1em;
47 | margin-bottom: 0.25em;
48 | }
49 | }
50 |
51 | footer {
52 | border-top: medium none currentcolor;
53 | text-align: center;
54 | font-size: 0.8em;
55 | }
56 |
57 | audio,
58 | video {
59 | width: 100%;
60 | max-width: 100%;
61 | }
62 |
63 | figure {
64 | margin: 1em 0 0.5em;
65 | padding: 0;
66 |
67 | + p {
68 | margin-top: 0.5em;
69 | }
70 |
71 | figcaption {
72 | opacity: 0.65;
73 | font-size: 0.85em;
74 | text-align: center;
75 | }
76 | }
77 |
78 | hr {
79 | height: 1px;
80 | margin: 2em 0;
81 | border: 0;
82 | background: var(--border);
83 | }
84 |
85 | details {
86 | display: block;
87 | overflow: visible;
88 | background-color: var(--background);
89 | padding: 0 1em;
90 |
91 | nav {
92 | margin: 0.5em 0;
93 |
94 | ul {
95 | margin: 0;
96 | padding-left: 1em;
97 |
98 | li {
99 | display: list-item;
100 | margin-right: 1em;
101 | margin-bottom: 0.25em;
102 | }
103 | }
104 | }
105 |
106 | summary {
107 | margin: 0;
108 | }
109 |
110 | &[open] {
111 | padding: 0 1em 1em;
112 | }
113 | }
114 | }
115 |
116 | // article list styles
117 | article.home-section {
118 | + article.home-section {
119 | margin-top: 2em;
120 | }
121 | }
122 |
123 | ul.pages-list {
124 | list-style: none;
125 | margin: 1em 0;
126 | padding: 0;
127 |
128 | li {
129 | margin: 0.5em 0;
130 | display: flex;
131 | flex-direction: column-reverse;
132 |
133 | @media (min-width: "800px") {
134 | flex-direction: row;
135 | justify-content: space-between;
136 | align-items: center;
137 | }
138 |
139 | small {
140 | font-size: 0.75em;
141 | }
142 | }
143 | }
144 |
145 | // header styles
146 | .header-verification {
147 | font-size: 1.5em;
148 | color: var(--text-bright);
149 | font-weight: 600;
150 | margin-bottom: 0;
151 | margin-top: 24px;
152 | display: block;
153 |
154 | &::after {
155 | content: "";
156 | display: inline-block;
157 | background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%231d9bf0' viewBox='0 0 16 16'%3E%3Cpath d='M10.067.87a2.89 2.89 0 0 0-4.134 0l-.622.638-.89-.011a2.89 2.89 0 0 0-2.924 2.924l.01.89-.636.622a2.89 2.89 0 0 0 0 4.134l.637.622-.011.89a2.89 2.89 0 0 0 2.924 2.924l.89-.01.622.636a2.89 2.89 0 0 0 4.134 0l.622-.637.89.011a2.89 2.89 0 0 0 2.924-2.924l-.01-.89.636-.622a2.89 2.89 0 0 0 0-4.134l-.637-.622.011-.89a2.89 2.89 0 0 0-2.924-2.924l-.89.01-.622-.636zm.287 5.984-3 3a.5.5 0 0 1-.708 0l-1.5-1.5a.5.5 0 1 1 .708-.708L7 8.793l2.646-2.647a.5.5 0 0 1 .708.708z'/%3E%3C/svg%3E")
158 | no-repeat;
159 | width: 1em;
160 | height: 1em;
161 | margin-left: 5px;
162 | vertical-align: -15%;
163 | }
164 | }
165 |
166 | .header-official {
167 | margin-top: 0;
168 | color: var(--text-muted);
169 |
170 | svg {
171 | vertical-align: -10%;
172 | margin-right: 0.5em;
173 | }
174 | }
175 |
176 | // dark mode styles
177 | #mode-switch {
178 | vertical-align: -10%;
179 | padding: 0;
180 | font: inherit;
181 | background: 0 0;
182 | border: 0;
183 | }
184 |
185 | html.dark-mode #moon {
186 | display: none;
187 | }
188 |
189 | html:not(.dark-mode) #sun {
190 | display: none;
191 | }
192 |
193 | // other cleanup for water.css
194 | a[href^="mailto\:"]::before {
195 | content: "";
196 | }
197 |
198 | a[href^="tel\:"]::before {
199 | content: "";
200 | }
201 |
202 | a[href^="sms\:"]::before {
203 | content: "";
204 | }
205 |
206 | // fix zoom.js overlay in dark mode
207 | .zoom-overlay {
208 | background: var(--background-body);
209 | }
210 |
--------------------------------------------------------------------------------
/assets/css/syntax.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --chroma-hl-background: #e1dbd5;
3 | }
4 | :root.dark-mode {
5 | --chroma-hl-background: #39374a;
6 | }
7 | /* Background */ .bg { color: #575279; background-color: #faf4ed; }
8 | /* PreWrapper */ .chroma { color: #575279; }
9 | /* Other */ .chroma .x { }
10 | /* Error */ .chroma .err { color: #b4637a }
11 | /* CodeLine */ .chroma .cl { }
12 | /* LineLink */ .chroma .lnlinks { outline: none; text-decoration: none; color: inherit }
13 | /* LineTableTD */ .chroma .lntd { vertical-align: top; padding: 0; margin: 0; border: 0; }
14 | /* LineTable */ .chroma .lntable { border-spacing: 0; padding: 0; margin: 0; border: 0; }
15 | /* LineHighlight */ .chroma .hl { background-color: var(--chroma-hl-background); }
16 | /* LineNumbersTable */ .chroma .lnt { white-space: pre; -webkit-user-select: none; user-select: none; margin-right: 0.4em; padding: 0 0.4em 0 0.4em;color: #7f7f7f }
17 | /* LineNumbers */ .chroma .ln { white-space: pre; -webkit-user-select: none; user-select: none; margin-right: 0.4em; padding: 0 0.4em 0 0.4em;color: #7f7f7f }
18 | /* Line */ .chroma .line { display: flex; }
19 | /* Keyword */ .chroma .k { color: #286983 }
20 | /* KeywordConstant */ .chroma .kc { color: #286983 }
21 | /* KeywordDeclaration */ .chroma .kd { color: #286983 }
22 | /* KeywordNamespace */ .chroma .kn { color: #907aa9 }
23 | /* KeywordPseudo */ .chroma .kp { color: #286983 }
24 | /* KeywordReserved */ .chroma .kr { color: #286983 }
25 | /* KeywordType */ .chroma .kt { color: #286983 }
26 | /* Name */ .chroma .n { color: #d7827e }
27 | /* NameAttribute */ .chroma .na { color: #d7827e }
28 | /* NameBuiltin */ .chroma .nb { color: #d7827e }
29 | /* NameBuiltinPseudo */ .chroma .bp { color: #d7827e }
30 | /* NameClass */ .chroma .nc { color: #56949f }
31 | /* NameConstant */ .chroma .no { color: #ea9d34 }
32 | /* NameDecorator */ .chroma .nd { color: #797593 }
33 | /* NameEntity */ .chroma .ni { color: #d7827e }
34 | /* NameException */ .chroma .ne { color: #286983 }
35 | /* NameFunction */ .chroma .nf { color: #d7827e }
36 | /* NameFunctionMagic */ .chroma .fm { color: #d7827e }
37 | /* NameLabel */ .chroma .nl { color: #d7827e }
38 | /* NameNamespace */ .chroma .nn { color: #d7827e }
39 | /* NameOther */ .chroma .nx { }
40 | /* NameProperty */ .chroma .py { color: #d7827e }
41 | /* NameTag */ .chroma .nt { color: #d7827e }
42 | /* NameVariable */ .chroma .nv { color: #d7827e }
43 | /* NameVariableClass */ .chroma .vc { color: #d7827e }
44 | /* NameVariableGlobal */ .chroma .vg { color: #d7827e }
45 | /* NameVariableInstance */ .chroma .vi { color: #d7827e }
46 | /* NameVariableMagic */ .chroma .vm { color: #d7827e }
47 | /* Literal */ .chroma .l { color: #ea9d34 }
48 | /* LiteralDate */ .chroma .ld { color: #ea9d34 }
49 | /* LiteralString */ .chroma .s { color: #ea9d34 }
50 | /* LiteralStringAffix */ .chroma .sa { color: #ea9d34 }
51 | /* LiteralStringBacktick */ .chroma .sb { color: #ea9d34 }
52 | /* LiteralStringChar */ .chroma .sc { color: #ea9d34 }
53 | /* LiteralStringDelimiter */ .chroma .dl { color: #ea9d34 }
54 | /* LiteralStringDoc */ .chroma .sd { color: #ea9d34 }
55 | /* LiteralStringDouble */ .chroma .s2 { color: #ea9d34 }
56 | /* LiteralStringEscape */ .chroma .se { color: #286983 }
57 | /* LiteralStringHeredoc */ .chroma .sh { color: #ea9d34 }
58 | /* LiteralStringInterpol */ .chroma .si { color: #ea9d34 }
59 | /* LiteralStringOther */ .chroma .sx { color: #ea9d34 }
60 | /* LiteralStringRegex */ .chroma .sr { color: #ea9d34 }
61 | /* LiteralStringSingle */ .chroma .s1 { color: #ea9d34 }
62 | /* LiteralStringSymbol */ .chroma .ss { color: #ea9d34 }
63 | /* LiteralNumber */ .chroma .m { color: #ea9d34 }
64 | /* LiteralNumberBin */ .chroma .mb { color: #ea9d34 }
65 | /* LiteralNumberFloat */ .chroma .mf { color: #ea9d34 }
66 | /* LiteralNumberHex */ .chroma .mh { color: #ea9d34 }
67 | /* LiteralNumberInteger */ .chroma .mi { color: #ea9d34 }
68 | /* LiteralNumberIntegerLong */ .chroma .il { color: #ea9d34 }
69 | /* LiteralNumberOct */ .chroma .mo { color: #ea9d34 }
70 | /* Operator */ .chroma .o { color: #797593 }
71 | /* OperatorWord */ .chroma .ow { color: #797593 }
72 | /* Punctuation */ .chroma .p { color: #797593 }
73 | /* Comment */ .chroma .c { color: #9893a5 }
74 | /* CommentHashbang */ .chroma .ch { color: #9893a5 }
75 | /* CommentMultiline */ .chroma .cm { color: #9893a5 }
76 | /* CommentSingle */ .chroma .c1 { color: #9893a5 }
77 | /* CommentSpecial */ .chroma .cs { color: #9893a5 }
78 | /* CommentPreproc */ .chroma .cp { color: #9893a5 }
79 | /* CommentPreprocFile */ .chroma .cpf { color: #9893a5 }
80 | /* Generic */ .chroma .g { }
81 | /* GenericDeleted */ .chroma .gd { color: #b4637a }
82 | /* GenericEmph */ .chroma .ge { font-style: italic }
83 | /* GenericError */ .chroma .gr { }
84 | /* GenericHeading */ .chroma .gh { }
85 | /* GenericInserted */ .chroma .gi { color: #56949f }
86 | /* GenericOutput */ .chroma .go { }
87 | /* GenericPrompt */ .chroma .gp { }
88 | /* GenericStrong */ .chroma .gs { font-weight: bold }
89 | /* GenericSubheading */ .chroma .gu { color: #907aa9 }
90 | /* GenericTraceback */ .chroma .gt { }
91 | /* GenericUnderline */ .chroma .gl { }
92 | /* TextWhitespace */ .chroma .w { }
93 |
--------------------------------------------------------------------------------
/static/zoom.js/dist/zoom.js:
--------------------------------------------------------------------------------
1 | import { toOffset, usableWidth } from "./common.js";
2 | import { ZoomImage } from "./zoom-image.js";
3 | // Values for the currently active zoomed instance.
4 | // There can be at most one.
5 | let activeZoomImage = null;
6 | let initialScrollPos = null;
7 | let initialTouchPos = null;
8 | let closeScrollDelta = null;
9 | let closeTouchDelta = null;
10 | function openActiveZoom(img, c) {
11 | if (activeZoomImage !== null) {
12 | activeZoomImage.dismissImmediate();
13 | }
14 | if (img.naturalWidth === 0 || img.naturalHeight === 0) {
15 | // Spec: "These attributes return the intrinsic dimensions of the
16 | // image, or zero if the dimensions are not known."
17 | // https://html.spec.whatwg.org/multipage/embedded-content.html#dom-image-dev
18 | return;
19 | }
20 | const tooNarrow = img.width >= usableWidth(document.documentElement, toOffset(c.padding));
21 | activeZoomImage = new ZoomImage(img, toOffset(tooNarrow ? c.paddingNarrow : c.padding));
22 | initialScrollPos = null;
23 | initialTouchPos = null;
24 | closeScrollDelta = c.dismissScrollDelta;
25 | closeTouchDelta = c.dismissTouchDelta;
26 | activeZoomImage.zoom();
27 | addCloseListeners();
28 | }
29 | function closeActiveZoom() {
30 | if (activeZoomImage === null) {
31 | return;
32 | }
33 | removeCloseListeners();
34 | activeZoomImage.onDismissComplete(() => { activeZoomImage = null; });
35 | activeZoomImage.dismiss();
36 | closeScrollDelta = null;
37 | closeTouchDelta = null;
38 | initialScrollPos = null;
39 | initialTouchPos = null;
40 | }
41 | function addCloseListeners() {
42 | document.addEventListener("scroll", handleDocumentScroll, { passive: true });
43 | document.addEventListener("keyup", handleDocumentKeyup, { passive: true });
44 | document.addEventListener("touchstart", handleDocumentTouchStart, { passive: true });
45 | document.addEventListener("click", handleDocumentClick, { passive: true, capture: true });
46 | }
47 | function removeCloseListeners() {
48 | // NOTE: in removeEventListener calls, for the options parameter it is
49 | // necessary only for the "capture" property to match. The other
50 | // properties, such as "passive", don't matter. Accordingly even the
51 | // type defintions don't allow for other properties to be specified.
52 | document.removeEventListener("scroll", handleDocumentScroll);
53 | document.removeEventListener("keyup", handleDocumentKeyup);
54 | document.removeEventListener("touchstart", handleDocumentTouchStart);
55 | document.removeEventListener("click", handleDocumentClick, { capture: true });
56 | }
57 | function handleDocumentScroll() {
58 | if (closeScrollDelta === null) {
59 | // Should not happen since it is assigned in openActiveZoom.
60 | throw "null closeScrollDelta";
61 | }
62 | if (initialScrollPos === null) {
63 | initialScrollPos = window.scrollY;
64 | return;
65 | }
66 | const deltaY = Math.abs(initialScrollPos - window.scrollY);
67 | if (deltaY < closeScrollDelta) {
68 | return;
69 | }
70 | closeActiveZoom();
71 | }
72 | function handleDocumentKeyup(e) {
73 | if (e.code !== "Escape") {
74 | return;
75 | }
76 | closeActiveZoom();
77 | }
78 | function handleDocumentTouchStart(e) {
79 | if (e.touches.length === 0) {
80 | return;
81 | }
82 | initialTouchPos = e.touches[0].pageY;
83 | document.addEventListener("touchmove", handleDocumentTouchMove);
84 | }
85 | function handleDocumentTouchMove(e) {
86 | if (closeTouchDelta === null) {
87 | // Should not happen since it is assigned in openActiveZoom.
88 | throw "null closeTouchDelta";
89 | }
90 | if (initialTouchPos === null) {
91 | // Should not happen (see touchstart handler), but guard anyway.
92 | // This guard is also required to show TypeScript that the
93 | // variable is non-null below.
94 | return;
95 | }
96 | if (e.touches.length === 0) {
97 | return;
98 | }
99 | if (Math.abs(e.touches[0].pageY - initialTouchPos) < closeTouchDelta) {
100 | return;
101 | }
102 | closeActiveZoom();
103 | document.removeEventListener("touchmove", handleDocumentTouchMove);
104 | }
105 | function handleDocumentClick(e) {
106 | e.stopPropagation();
107 | closeActiveZoom();
108 | }
109 | export const defaultConfig = {
110 | padding: 40,
111 | paddingNarrow: 20,
112 | dismissScrollDelta: 15,
113 | dismissTouchDelta: 10,
114 | };
115 | // zoom zooms the specified image.
116 | //
117 | // The image will not be zoomed if its naturalWidth and naturalHeight properties
118 | // are 0 (usually because the values are unavailable).
119 | export function zoom(img, cfg = defaultConfig) {
120 | openActiveZoom(img, cfg !== undefined ? cfg : defaultConfig);
121 | }
122 | // dismissZoom programmatically dismisses the presently active zoom. It is a
123 | // no-op if there is no zoom active at the time of the call.
124 | export function dismissZoom() {
125 | closeActiveZoom();
126 | }
127 |
--------------------------------------------------------------------------------
/static/zoom.js/dist/zoom-image.js:
--------------------------------------------------------------------------------
1 | import { unwrap, usableHeight, usableWidth, viewportHeight, viewportWidth, wrap } from "./common.js";
2 | // scaleFactor returns the scale factor with which the image should be
3 | // transformed. The tw and th parameters specify the maximum usable width and
4 | // height in the viewport.
5 | function scaleFactor(img, tw, th) {
6 | const maxScaleFactor = img.naturalWidth / img.width;
7 | const ir = img.naturalWidth / img.naturalHeight;
8 | const tr = tw / th;
9 | if (img.naturalWidth < tw && img.naturalHeight < th) {
10 | return maxScaleFactor;
11 | }
12 | if (ir < tr) {
13 | return (th / img.naturalHeight) * maxScaleFactor;
14 | }
15 | return (tw / img.naturalWidth) * maxScaleFactor;
16 | }
17 | // ZoomImage manages a single zoom and dismiss lifecycle
18 | // on a
element.
19 | export class ZoomImage {
20 | constructor(img, offset) {
21 | this.dismissCompleteNotified = false;
22 | this.dismissCompleteCallbacks = [];
23 | // necessary because dismissModifyDOM() cannot be safely called multiple
24 | // times.
25 | this.dismissModifiedDOM = false;
26 | this.img = img;
27 | this.oldTransform = img.style.transform;
28 | this.wrapper = ZoomImage.makeWrapper();
29 | this.overlay = ZoomImage.makeOverlay();
30 | this.offset = offset;
31 | }
32 | static makeOverlay() {
33 | const ret = document.createElement("div");
34 | ret.classList.add("zoom-overlay");
35 | return ret;
36 | }
37 | static makeWrapper() {
38 | const ret = document.createElement("div");
39 | ret.classList.add("zoom-img-wrapper");
40 | return ret;
41 | }
42 | static elemOffset(elem, wnd, docElem) {
43 | const rect = elem.getBoundingClientRect();
44 | return {
45 | top: rect.top + wnd.scrollY - docElem.clientTop,
46 | left: rect.left + wnd.scrollX - docElem.clientLeft
47 | };
48 | }
49 | hackForceRepaint() {
50 | const x = this.img.naturalWidth;
51 | return x;
52 | }
53 | zoomModifyDOM() {
54 | this.img.classList.add("zoom-img");
55 | wrap(this.img, this.wrapper);
56 | document.body.appendChild(this.overlay);
57 | }
58 | dismissModifyDOM() {
59 | document.body.removeChild(this.overlay);
60 | unwrap(this.img, this.wrapper);
61 | this.img.classList.remove("zoom-img");
62 | }
63 | zoomAnimate(scale) {
64 | const imageOffset = ZoomImage.elemOffset(this.img, window, document.documentElement);
65 | const wx = window.scrollX + (viewportWidth(document.documentElement) / 2);
66 | const wy = window.scrollY + (viewportHeight(document.documentElement) / 2);
67 | const ix = imageOffset.left + (this.img.width / 2);
68 | const iy = imageOffset.top + (this.img.height / 2);
69 | // In img.style.transform, use "scale3d()", not "scale()". There
70 | // is an issue in macOS Safari version 16.3 (18614.4.6.1.6) and
71 | // possibly other versions, if the latter is used. The element
72 | // becomes invisible when zoomed in.
73 | //
74 | // In practice, the issue occurs more often (only?) with
75 | // elements near the top of the page being zoomed.
76 | //
77 | // As a side note, during debugging, the incorrectly invisible
78 | // element reappeared if the "scale()" value is <= 1. The issue
79 | // occurs only for "scale()" value > 1.
80 | //
81 | // In any case, "scale3d()" fixes the issue.
82 | this.img.style.transform = `scale3d(${scale},${scale},${scale})`;
83 | this.wrapper.style.transform = `translate3d(${wx - ix}px, ${wy - iy}px, 0)`;
84 | document.body.classList.add("zoom-overlay-open");
85 | }
86 | // NOTE: This method is idempotent, and can be called multiple times
87 | // safely.
88 | dismissAnimate() {
89 | document.body.classList.remove("zoom-overlay-open");
90 | this.img.style.transform = this.oldTransform;
91 | this.wrapper.style.transform = "none";
92 | }
93 | zoom() {
94 | this.zoomModifyDOM();
95 | // repaint before animating.
96 | // TODO: is this necessary?
97 | this.hackForceRepaint();
98 | this.zoomAnimate(scaleFactor(this.img, usableWidth(document.documentElement, this.offset), usableHeight(document.documentElement, this.offset)));
99 | }
100 | onDismissComplete(f) {
101 | this.dismissCompleteCallbacks.push(f);
102 | }
103 | notifyDismissComplete() {
104 | if (this.dismissCompleteNotified) {
105 | return;
106 | }
107 | this.dismissCompleteCallbacks.forEach(f => f());
108 | this.dismissCompleteNotified = true;
109 | }
110 | dismiss() {
111 | this.img.addEventListener("transitionend", () => {
112 | document.body.classList.remove("zoom-overlay-transitioning");
113 | if (!this.dismissModifiedDOM) {
114 | this.dismissModifyDOM();
115 | this.dismissModifiedDOM = true;
116 | }
117 | this.notifyDismissComplete();
118 | }, { once: true });
119 | document.body.classList.add("zoom-overlay-transitioning");
120 | this.dismissAnimate();
121 | }
122 | dismissImmediate() {
123 | this.dismissAnimate();
124 | document.body.classList.remove("zoom-overlay-transitioning");
125 | if (!this.dismissModifiedDOM) {
126 | this.dismissModifyDOM();
127 | this.dismissModifiedDOM = true;
128 | }
129 | this.notifyDismissComplete();
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/static/zoom.js/README.md:
--------------------------------------------------------------------------------
1 | # zoom.js
2 |
3 | An image zooming plugin, as seen on older versions of [medium.com][medium]. This
4 | project is a port of [`fat/zoom.js`][fat] but has no jQuery or Bootstrap
5 | dependencies.
6 |
7 | Version 4 is written in TypeScript, has a new API, includes typings, and has no
8 | dependencies.
9 |
10 | npm package: [https://www.npmjs.com/package/@nishanths/zoom.js][npm]
11 |
12 | ## Branches and versions
13 |
14 | No API backwards compatibility guarantees even within the same major version.
15 |
16 | * **v4**: The default branch. It contains code for version 4, which is the
17 | current major version.
18 | * **master**: Frozen and no longer maintained. The final version on this branch
19 | is 3.1.0.
20 |
21 | ## Demo
22 |
23 | [https://nishanths.github.io/zoom.js][demo]
24 |
25 | Zoom on an image by clicking on it.
26 |
27 | Dismiss the zoom by either clicking again on the image, clicking the overlay
28 | around the image, scrolling away, or hitting the `esc` key.
29 |
30 | ## Usage
31 |
32 | Install the package:
33 |
34 | ```
35 | npm i @nishanths/zoom.js
36 | ```
37 |
38 | Link the `src/zoom.css` file in your application:
39 |
40 | ```html
41 |
42 | ```
43 |
44 | Import and use symbols from the package:
45 |
46 | ```ts
47 | import { zoom } from "@nishanths/zoom.js"
48 | ```
49 |
50 | Note that the `package.json` for the package specifies the `module` property but
51 | not the `main` property. You may need a module-aware tool to correctly include
52 | the package in your bundle. For further reading, see this Stack Overflow
53 | [answer](https://stackoverflow.com/a/47537198/3309046) as a starting point.
54 |
55 | ## Building locally
56 |
57 | To build the package locally, run the following from the root directory
58 | of the repo:
59 |
60 | ```
61 | % npm install
62 | % make build
63 | ```
64 |
65 | This should produce a `dist` directory. The js files in the `dist`
66 | directory are ES modules.
67 |
68 | ## Documentation
69 |
70 | ### API
71 |
72 | ```ts
73 | // Config is the configuration provided to the zoom function.
74 | export type Config = {
75 | // padding defines the horizontal space and the vertical space around
76 | // the zoomed image.
77 | padding: number
78 |
79 | // paddingNarrow is similar to the padding property, except that it is
80 | // used if the viewport width is too narrow, such that the use of the
81 | // larger padding property may produce poor results.
82 | //
83 | // paddingNarrow should be <= padding, however this is not validated.
84 | paddingNarrow: number
85 |
86 | // dismissScrollDelta defines the vertical scrolling threshold at which
87 | // the zoomed image is dismissed by user interaction. The value is the pixel
88 | // difference between the original vertical scroll position and the
89 | // subsequent vertical scroll positions.
90 | dismissScrollDelta: number
91 |
92 | // dismissTouchDelta defines the vertical touch movement threshold at
93 | // which the zoomed image is dismissed by user interactoin. The value is the
94 | // pixel difference between the initial vertical touch position and
95 | // subsequent vertical touch movements.
96 | dismissTouchDelta: number
97 | }
98 |
99 | export const defaultConfig: Config = {
100 | padding: 40,
101 | paddingNarrow: 20,
102 | dismissScrollDelta: 15,
103 | dismissTouchDelta: 10,
104 | }
105 |
106 | // zoom zooms the specified image.
107 | //
108 | // The image will not be zoomed if its naturalWidth and naturalHeight properties
109 | // are 0 (usually because the values are unavailable).
110 | export function zoom(img: HTMLImageElement, cfg: Config = defaultConfig): void
111 |
112 | // dismissZoom programmatically dismisses the presently active zoom. It is a
113 | // no-op if there is no zoom active at the time of the call.
114 | export function dismissZoom(): void
115 | ```
116 |
117 | ### Examples
118 |
119 | The following TypeScript program makes all existing `
` elements on the page
120 | zoomable. Images are zoomed when they are clicked.
121 |
122 | ```ts
123 | import { zoom } from "@nishanths/zoom.js"
124 |
125 | function setupZoom(img: HTMLImageElement) {
126 | img.classList.add("zoom-cursor")
127 | img.addEventListener("click", () => { zoom(img) })
128 | }
129 |
130 | const imgs = [...document.querySelectorAll("img")]
131 | imgs.forEach(img => { setupZoom(img) })
132 | ```
133 |
134 | The following TypeScript program customizes only certain properties of a
135 | `Config`, keeping the defaults for the other properties.
136 |
137 | ```ts
138 | import { Config, defaultConfig } from "@nishanths/zoom.js"
139 |
140 | const customConfig: Config = {
141 | ...defaultConfig,
142 | padding: 30,
143 | }
144 | ```
145 |
146 | ### Notes
147 |
148 | All CSS class names used by the package are prefixed with `zoom-`.
149 |
150 | Add the class name `zoom-cursor` to a zoomable `
` element to use an
151 | [`zoom-in` cursor][zoom-in-cursor] instead of the default cursor for the
152 | image.
153 |
154 | The program appends the DOM node for the overlay element, which appears when an
155 | image is zoomed, to the end of `document.body`.
156 |
157 | While an image is zoomed, the program listens for `click` events on
158 | `document.body` with `useCapture` set to `true`, and the handler function calls
159 | `e.stopPropagation()`. This may interfere with other `click` event handlers on
160 | the page. The event listener is removed when the zoom is dismissed.
161 |
162 | When an image is zoomed, its `transform` style is replaced with a new value that
163 | is necessary for zooming. The old `transform` is restored when the zoom is
164 | dismissed.
165 |
166 | ### Browser compatibility
167 |
168 | I think any popular web browser versions released after 2016 should be supported
169 | by this package. Please read the source code for exact details.
170 |
171 | ## License
172 |
173 | The software in this repository is based on the original [`fat/zoom.js`][fat]
174 | project. The copyright notices and license notices from the original project are
175 | present in the `LICENSE.original` file at the root of this repository.
176 |
177 | New source code and modifications in this repository are licensed under an MIT
178 | license. See the `LICENSE` file at the root of the repository.
179 |
180 | [fat]: https://github.com/fat/zoom.js
181 | [medium]: https://medium.com
182 | [demo]: https://nishanths.github.io/zoom.js
183 | [zoom-in-cursor]: https://developer.mozilla.org/en-US/docs/Web/CSS/cursor
184 | [npm]: https://www.npmjs.com/package/@nishanths/zoom.js
185 |
--------------------------------------------------------------------------------
/layouts/partials/share_buttons.html:
--------------------------------------------------------------------------------
1 | {{ if (.Params.enableShare | default (.Site.Params.enableShare | default false )) }}
2 |
149 | {{ end }}
150 |
--------------------------------------------------------------------------------