From 871470c4cd13a02e947680864574a84eae246cf0 Mon Sep 17 00:00:00 2001 From: zilch40 Date: Mon, 16 Nov 2020 20:31:39 +0800 Subject: add PWA support && tweak some pages --- README.md | 4 + archetypes/archives.md | 2 +- assets/css/common.scss | 10 +- assets/css/hulga-dark.scss | 11 +- assets/js/pwa.js | 39 ++++ assets/sw.js | 388 ++++++++++++++++++++++++++++++++++++++ i18n/en.toml | 5 +- i18n/zh-cn.toml | 5 +- layouts/_default/baseof.html | 4 +- layouts/_default/taxonomy.html | 4 +- layouts/archives/single.html | 10 +- layouts/index.html | 5 +- layouts/partials/footer.html | 3 + layouts/partials/head.html | 117 +++++++----- layouts/partials/nav.html | 4 +- layouts/partials/paginator.html | 4 +- layouts/partials/post-header.html | 37 ++++ layouts/partials/post.html | 43 +---- layouts/partials/posts.html | 4 +- layouts/partials/pwa.html | 3 + static/manifest.json | 54 ++++++ 21 files changed, 642 insertions(+), 114 deletions(-) create mode 100644 assets/js/pwa.js create mode 100644 assets/sw.js create mode 100644 layouts/partials/post-header.html create mode 100644 layouts/partials/pwa.html create mode 100644 static/manifest.json diff --git a/README.md b/README.md index 45450b1..71b6466 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ - archives pages - TOC sidebar - `prefers-color-scheme` media support +- PWA support ## Install @@ -76,6 +77,9 @@ theme = "hulga" # enable hero section's is-bold effect heroBold = true + # enable PWA, prepare your icons and DON'T forget to modify manifest.json + pwa = true + # to enable different hightlight themes in light/dark mode [markup] [markup.highlight] diff --git a/archetypes/archives.md b/archetypes/archives.md index bbfa7b0..3a0f057 100644 --- a/archetypes/archives.md +++ b/archetypes/archives.md @@ -1,5 +1,5 @@ --- -title: "Archives" +title: {{ i18n "archives" }} description: "Archives" type: archives --- diff --git a/assets/css/common.scss b/assets/css/common.scss index 05e63b9..82ba77b 100644 --- a/assets/css/common.scss +++ b/assets/css/common.scss @@ -32,7 +32,9 @@ border-color: $primary; color: $white; } - +a { + color: $primary; +} /* fixed footer */ body { display: flex; @@ -49,9 +51,6 @@ body { position: relative; z-index: 1; } -a { - color: $primary; -} /* link */ // u @@ -101,6 +100,9 @@ a { .post-meta { opacity: 0.9; } +.counter-tag { + border-left: 1px solid $white; +} /* posts */ .post-box { diff --git a/assets/css/hulga-dark.scss b/assets/css/hulga-dark.scss index 2594cfb..9263319 100644 --- a/assets/css/hulga-dark.scss +++ b/assets/css/hulga-dark.scss @@ -8,7 +8,7 @@ // $archblue: #1793d0; {{ if .Site.Params.primaryColor }} -$primary: {{ .Site.Params.primaryColor }}; +$primary: lighten({{ .Site.Params.primaryColor }}, 5); {{end}} // Import only what you need from Bulma @@ -32,9 +32,12 @@ $pagination-border-color: $border; @import "../sass/dark/overrides"; // dark override -.sidebar { - height: 100%; -} .footer { background: #282f2f; } +.counter-tag { + border-left: 1px solid #282f2f; +} +.tag:not(body) { + background-color: rgba($black-bis, 0.2); +} \ No newline at end of file diff --git a/assets/js/pwa.js b/assets/js/pwa.js new file mode 100644 index 0000000..74b3c68 --- /dev/null +++ b/assets/js/pwa.js @@ -0,0 +1,39 @@ +{{ $swfile := resources.Get "/sw.js" }} +{{ $sw := $swfile | resources.Minify }} +if('serviceWorker' in navigator) { + const PREFETCH = true; + const PREFETCH_LINK_RELS = ['index','next', 'prev', 'prefetch']; + function prefetchCache() { + if(navigator.serviceWorker.controller) { + let links = document.querySelectorAll( + PREFETCH_LINK_RELS.map((rel) => { + return 'link[rel='+rel+']'; + }).join(',') + ); + if(links.length > 0) { + Array.from(links) + .map((link) => { + let href = link.getAttribute('href'); + navigator.serviceWorker.controller.postMessage({ + action : 'cache', + url : href, + }); + }); + } + } + } + + navigator.serviceWorker + .register('{{ $sw.Permalink | relURL }}', { scope: '/' }) + .then(() => { + console.log('Service Worker Registered'); + }); + + navigator.serviceWorker + .ready + .then(() => { + if(PREFETCH) { + prefetchCache(); + } + }); +} \ No newline at end of file diff --git a/assets/sw.js b/assets/sw.js new file mode 100644 index 0000000..042741d --- /dev/null +++ b/assets/sw.js @@ -0,0 +1,388 @@ +const CACHE_VERSION = 1; + +const BASE_CACHE_FILES = [ + '/css/hulga.min.css', + '/css/toc.min.css', + '/css/hulga-dark.min.css', + '/css/katex.min.css', + '/css/monokai.css', + '/css/monokailight.css', + '/js/tocbot.min.js', + '/js/anchor.min.js', + '/js/auto-render.min.js', + '/js/katex.min.js', + '/js/vanilla-back-to-top.min.js', + '/manifest.json', + '/favicon.ico', +]; + +const OFFLINE_CACHE_FILES = [ + '/index.html' +]; + +const NOT_FOUND_CACHE_FILES = [ + '/404.html', +]; + +const OFFLINE_PAGE = '/index.html'; +const NOT_FOUND_PAGE = '/404.html'; + +const CACHE_VERSIONS = { + assets: 'assets-v' + CACHE_VERSION, + content: 'content-v' + CACHE_VERSION, + offline: 'offline-v' + CACHE_VERSION, + notFound: '404-v' + CACHE_VERSION, +}; + +// Define MAX_TTL's in SECONDS for specific file extensions +const MAX_TTL = { + '/': 3600, + html: 3600, + json: 86400, + js: 86400, + css: 86400, +}; + +const CACHE_BLACKLIST = [ + (str) => { + return !str.startsWith('https://blog.zilch40.wang'); + }, +]; + +const SUPPORTED_METHODS = [ + 'GET', +]; + +/** + * isBlackListed + * @param {string} url + * @returns {boolean} + */ +function isBlacklisted(url) { + return (CACHE_BLACKLIST.length > 0) ? !CACHE_BLACKLIST.filter((rule) => { + if (typeof rule === 'function') { + return !rule(url); + } else { + return false; + } + }).length : false +} + +/** + * getFileExtension + * @param {string} url + * @returns {string} + */ +function getFileExtension(url) { + let extension = url.split('.').reverse()[0].split('?')[0]; + return (extension.endsWith('/')) ? '/' : extension; +} + +/** + * getTTL + * @param {string} url + */ +function getTTL(url) { + if (typeof url === 'string') { + let extension = getFileExtension(url); + if (typeof MAX_TTL[extension] === 'number') { + return MAX_TTL[extension]; + } else { + return null; + } + } else { + return null; + } +} + +/** + * installServiceWorker + * @returns {Promise} + */ +function installServiceWorker() { + return Promise.all( + [ + caches.open(CACHE_VERSIONS.assets) + .then( + (cache) => { + return cache.addAll(BASE_CACHE_FILES); + } + ), + caches.open(CACHE_VERSIONS.offline) + .then( + (cache) => { + return cache.addAll(OFFLINE_CACHE_FILES); + } + ), + caches.open(CACHE_VERSIONS.notFound) + .then( + (cache) => { + return cache.addAll(NOT_FOUND_CACHE_FILES); + } + ) + ] + ) + .then(() => { + return self.skipWaiting(); + }); +} + +/** + * cleanupLegacyCache + * @returns {Promise} + */ +function cleanupLegacyCache() { + + let currentCaches = Object.keys(CACHE_VERSIONS) + .map( + (key) => { + return CACHE_VERSIONS[key]; + } + ); + + return new Promise( + (resolve, reject) => { + + caches.keys() + .then( + (keys) => { + return legacyKeys = keys.filter( + (key) => { + return !~currentCaches.indexOf(key); + } + ); + } + ) + .then( + (legacy) => { + if (legacy.length) { + Promise.all( + legacy.map( + (legacyKey) => { + return caches.delete(legacyKey) + } + ) + ) + .then( + () => { + resolve() + } + ) + .catch( + (err) => { + reject(err); + } + ); + } else { + resolve(); + } + } + ) + .catch( + () => { + reject(); + } + ); + + } + ); +} + +function precacheUrl(url) { + if (!isBlacklisted(url)) { + caches.open(CACHE_VERSIONS.content) + .then((cache) => { + cache.match(url) + .then((response) => { + if (!response) { + return fetch(url) + } else { + // already in cache, nothing to do. + return null + } + }) + .then((response) => { + if (response) { + return cache.put(url, response.clone()); + } else { + return null; + } + }); + }) + } +} + + + +self.addEventListener( + 'install', event => { + event.waitUntil( + Promise.all([ + installServiceWorker(), + self.skipWaiting(), + ]) + ); + } +); + +// The activate handler takes care of cleaning up old caches. +self.addEventListener( + 'activate', event => { + event.waitUntil( + Promise.all( + [ + cleanupLegacyCache(), + self.clients.claim(), + self.skipWaiting(), + ] + ) + .catch( + (err) => { + event.skipWaiting(); + } + ) + ); + } +); + +self.addEventListener( + 'fetch', event => { + + event.respondWith( + caches.open(CACHE_VERSIONS.content) + .then( + (cache) => { + + return cache.match(event.request) + .then( + (response) => { + + if (response) { + + let headers = response.headers.entries(); + let date = null; + + for (let pair of headers) { + if (pair[0] === 'date') { + date = new Date(pair[1]); + } + } + + if (date) { + let age = parseInt((new Date().getTime() - date.getTime()) / 1000); + let ttl = getTTL(event.request.url); + + if (ttl && age > ttl) { + + return new Promise( + (resolve) => { + + return fetch(event.request.clone()) + .then( + (updatedResponse) => { + if (updatedResponse) { + cache.put(event.request, updatedResponse.clone()); + resolve(updatedResponse); + } else { + resolve(response) + } + } + ) + .catch( + () => { + resolve(response); + } + ); + + } + ) + .catch( + (err) => { + return response; + } + ); + } else { + return response; + } + + } else { + return response; + } + + } else { + return null; + } + } + ) + .then( + (response) => { + if (response) { + return response; + } else { + return fetch(event.request.clone()) + .then( + (response) => { + + if (response.status < 400) { + if (~SUPPORTED_METHODS.indexOf(event.request.method) && !isBlacklisted(event.request.url)) { + cache.put(event.request, response.clone()); + } + return response; + } else { + return caches.open(CACHE_VERSIONS.notFound).then((cache) => { + return cache.match(NOT_FOUND_PAGE); + }) + } + } + ) + .then((response) => { + if (response) { + return response; + } + }) + .catch( + () => { + + return caches.open(CACHE_VERSIONS.offline) + .then( + (offlineCache) => { + return offlineCache.match(OFFLINE_PAGE) + } + ) + + } + ); + } + } + ) + .catch( + (error) => { + console.error(' Error in fetch handler:', error); + throw error; + } + ); + } + ) + ); + + } +); + + +self.addEventListener('message', (event) => { + + if ( + typeof event.data === 'object' && + typeof event.data.action === 'string' + ) { + switch (event.data.action) { + case 'cache': + precacheUrl(event.data.url); + break; + default: + console.log('Unknown action: ' + event.data.action); + break; + } + } + +}); diff --git a/i18n/en.toml b/i18n/en.toml index c6d1075..788bc1b 100644 --- a/i18n/en.toml +++ b/i18n/en.toml @@ -10,5 +10,8 @@ other = "Previous" [next_page] other = "Next" +[archives_count] +other = "There are {{ .Count }} articles in this blog" + [archives] -other = "There are {{ .Count }} articles in this blog" \ No newline at end of file +other = "Archives" diff --git a/i18n/zh-cn.toml b/i18n/zh-cn.toml index 551b33b..9afedbe 100644 --- a/i18n/zh-cn.toml +++ b/i18n/zh-cn.toml @@ -10,5 +10,8 @@ other = "上一页" [next_page] other = "下一页" +[archives_count] +other = "本博客共 {{ .Count }} 篇博文" + [archives] -other = "本博客共 {{ .Count }} 篇博文" \ No newline at end of file +other = "归档" \ No newline at end of file diff --git a/layouts/_default/baseof.html b/layouts/_default/baseof.html index ea5525d..4a3a564 100644 --- a/layouts/_default/baseof.html +++ b/layouts/_default/baseof.html @@ -1,5 +1,5 @@ - - + + {{- partial "head.html" . -}} diff --git a/layouts/_default/taxonomy.html b/layouts/_default/taxonomy.html index 5344be9..23de21f 100644 --- a/layouts/_default/taxonomy.html +++ b/layouts/_default/taxonomy.html @@ -8,7 +8,7 @@ {{ if eq .Data.Singular "category" }}
-

+

{{ i18n "categories" }}

@@ -31,7 +31,7 @@ {{ if eq .Data.Singular "tag" }}
-

+

{{ i18n "tags" }}

diff --git a/layouts/archives/single.html b/layouts/archives/single.html index f19ccac..728e628 100644 --- a/layouts/archives/single.html +++ b/layouts/archives/single.html @@ -1,15 +1,17 @@ {{ define "main" }} -{{ partial "header.html" . }} +{{ partial "post-header.html" . }}
- {{ $count := len (where (where .Site.Pages "Type" "post") "Kind" "page") }} -

{{ i18n "archives" (dict "Count" $count) }}

{{ range (where (where .Site.Pages "Type" "post") "Kind" "page").GroupByDate "2006" }}
-

{{ .Key }}

+

{{ .Key }} + + {{ len .Pages }} + +

    {{ range .Pages }}
  • diff --git a/layouts/index.html b/layouts/index.html index ca7abab..62f5e5d 100644 --- a/layouts/index.html +++ b/layouts/index.html @@ -1 +1,4 @@ -{{ with .GetPage "/post" }}{{.Render}}{{end}} +{{ define "main" }} +{{- partial "header.html" . -}} +{{- partial "posts.html" . -}} +{{ end }} diff --git a/layouts/partials/footer.html b/layouts/partials/footer.html index b698406..7df832c 100644 --- a/layouts/partials/footer.html +++ b/layouts/partials/footer.html @@ -10,4 +10,7 @@

+{{ if .Site.Params.pwa }} +{{ partial "pwa.html" . }} +{{ end}} {{ template "_internal/google_analytics_async.html" . }} diff --git a/layouts/partials/head.html b/layouts/partials/head.html index de32ad1..4e9aac9 100644 --- a/layouts/partials/head.html +++ b/layouts/partials/head.html @@ -1,51 +1,72 @@ - - - - {{ if .IsHome -}} - {{ .Site.Title }} | {{ .Site.Params.subtitle}} - - - {{ if .Site.Params.keywords }} - - {{ end }} - {{ if .Site.Params.keywords }} - - {{ end }} - {{- else -}} - {{ .Title }} | {{ .Site.Title }} - - - {{ with .Params.date }} - - {{ end }} - {{ with .Params.lastmod }} - - {{ end }} - - - {{ if .Params.author -}} - - {{- end }} - {{- end }} + + + + + + + + + + + + + + + + + + {{- if .Site.Params.primaryColor -}} + + + {{- end -}} + {{ if .IsHome -}} + {{ .Site.Title }} | {{ .Site.Params.subtitle}} + + + {{ if .Site.Params.keywords }} + + {{ end }} + {{ if .Site.Params.keywords }} + + {{ end }} + {{- else -}} + {{ .Title }} | {{ .Site.Title }} + + + {{ with .Params.date }} + + {{ end }} + {{ with .Params.lastmod }} + + {{ end }} + + + {{ if .Params.author -}} + + {{- end }} + {{- end }} - {{ $options := (dict "outputStyle" "compressed" "includePaths" (slice "sass")) }} - {{ $lightScss := resources.Get "css/hulga.scss" }} - {{ $darkScss := resources.Get "css/hulga-dark.scss" }} - {{ if .Site.Params.postcss }} - {{ $light := $lightScss | resources.ExecuteAsTemplate "css/hulga.scss" . | resources.ToCSS $options | postCSS | resources.Minify }} - - {{ if .Site.Params.darkMedia }} - {{ $dark := $darkScss | resources.ExecuteAsTemplate "css/hulga-dark.scss" . | resources.ToCSS $options | postCSS | resources.Minify }} - - {{ end }} - {{ else }} - {{ $light := $lightScss | resources.ExecuteAsTemplate "css/hulga.scss" . | resources.ToCSS $options }} - - {{ if .Site.Params.darkMedia }} - {{ $dark := $darkScss | resources.ExecuteAsTemplate "css/hulga-dark.scss" . | resources.ToCSS $options }} - - {{ end }} - {{ end }} + {{ $options := (dict "outputStyle" "compressed" "includePaths" (slice "sass")) }} + {{ $lightScss := resources.Get "css/hulga.scss" }} + {{ $darkScss := resources.Get "css/hulga-dark.scss" }} + {{ if .Site.Params.postcss }} + {{ $light := $lightScss | resources.ExecuteAsTemplate "css/hulga.scss" . | resources.ToCSS $options | postCSS | resources.Minify }} + + {{ if .Site.Params.darkMedia }} + {{ $dark := $darkScss | resources.ExecuteAsTemplate "css/hulga-dark.scss" . | resources.ToCSS $options | postCSS | resources.Minify }} + + {{ end }} + {{ else }} + {{ $light := $lightScss | resources.ExecuteAsTemplate "css/hulga.scss" . | resources.ToCSS $options | resources.Minify }} + + {{ if .Site.Params.darkMedia }} + {{ $dark := $darkScss | resources.ExecuteAsTemplate "css/hulga-dark.scss" . | resources.ToCSS $options | resources.Minify }} + + {{ end }} + {{ end }} + {{ if .Site.Params.pwa }} + + {{ end }} - + \ No newline at end of file diff --git a/layouts/partials/nav.html b/layouts/partials/nav.html index cde1b5e..40337d1 100644 --- a/layouts/partials/nav.html +++ b/layouts/partials/nav.html @@ -1,8 +1,8 @@