From 92a047af0238317972a5a4da1e8997a43463f5f6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 1 May 2022 13:52:50 +0000 Subject: [PATCH] Edit "Docker Cheatsheet" (#174) --- 404.html | 2 +- assets/data/blog/cheatsheet/index.json | 2 +- assets/data/blog/moments/index.json | 2 +- assets/data/blog/programming/index.json | 2 +- assets/data/index.json | 2 +- assets/data/labels/index.json | 2 +- assets/data/post/about/index.json | 2 +- assets/data/post/ahk-numpad/index.json | 2 +- assets/data/post/bash-cht/index.json | 2 +- assets/data/post/c-review/index.json | 2 +- assets/data/post/compile-fenix/index.json | 2 +- .../data/post/compile-vim-python3/index.json | 2 +- assets/data/post/damn-gpg/index.json | 2 +- assets/data/post/docker-cht/index.json | 2 +- assets/data/post/docker-tag/index.json | 2 +- assets/data/post/dsa-init/index.json | 2 +- assets/data/post/fabricmc/index.json | 2 +- assets/data/post/ffmpeg-cht/index.json | 2 +- assets/data/post/from-python-to-js/index.json | 2 +- assets/data/post/get-wechat-emoji/index.json | 2 +- assets/data/post/gh-action-cache/index.json | 2 +- assets/data/post/git-cht/index.json | 2 +- assets/data/post/gridsome-pwa/index.json | 2 +- assets/data/post/hugo-circle-ci/index.json | 2 +- assets/data/post/hugo-toc/index.json | 2 +- assets/data/post/jupyter-cht/index.json | 2 +- assets/data/post/jupyter-raspi/index.json | 2 +- assets/data/post/jupyter-wolfram/index.json | 2 +- assets/data/post/linux-cht/index.json | 2 +- .../data/post/manjaro-first-glance/index.json | 2 +- assets/data/post/markdown-cht/index.json | 2 +- assets/data/post/md-cn-bold/index.json | 2 +- .../data/post/moving-blog-to-astro/index.json | 1 + assets/data/post/mysql-emoji/index.json | 2 +- assets/data/post/mysql-in-wsl/index.json | 2 +- assets/data/post/netease-music-bp/index.json | 1 + assets/data/post/npm-reg/index.json | 2 +- assets/data/post/pipenv-and-poetry/index.json | 2 +- assets/data/post/pkg-source/index.json | 2 +- assets/data/post/pwa-skipwaiting/index.json | 2 +- .../post/python-async-subprocess/index.json | 2 +- assets/data/post/raspi-tricks/index.json | 2 +- .../data/post/react-virtual-scroll/index.json | 2 +- assets/data/post/showcase/index.json | 2 +- assets/data/post/sql-query-perf/index.json | 2 +- assets/data/post/tmux-setup/index.json | 2 +- assets/data/post/tmux-ssh/index.json | 2 +- assets/data/post/upgrade-ubuntu/index.json | 2 +- assets/data/post/vim-ariline/index.json | 2 +- assets/data/post/vim-cht/index.json | 2 +- assets/data/post/vscode-setup/index.json | 2 +- assets/data/post/vue-hammer/index.json | 2 +- assets/data/post/vue-iblog/index.json | 2 +- assets/data/post/vue-pwa-1/index.json | 2 +- assets/data/post/vue-pwa-2/index.json | 2 +- assets/data/post/vue-pwa-3/index.json | 2 +- assets/data/post/why-flask/index.json | 2 +- assets/data/post/win-cmd-cht/index.json | 2 +- assets/data/post/win-cmd-font/index.json | 2 +- .../post/win11-explorer-patcher/index.json | 2 +- assets/data/post/wsl-cht/index.json | 2 +- assets/data/post/wtf-c/index.json | 2 +- assets/data/post/wtf-hugo/index.json | 2 +- assets/data/tag/docker/index.json | 2 +- assets/data/tag/hugo/index.json | 2 +- assets/data/tag/mysql/index.json | 2 +- assets/data/tag/pwa/index.json | 2 +- assets/data/tag/vue/index.json | 2 +- blog/cheatsheet/index.html | 9 +-- blog/moments/index.html | 6 +- blog/programming/index.html | 12 ++-- img/6c88ced2.jpg | Bin 0 -> 100032 bytes img/f498d489.png | Bin 24928 -> 0 bytes index.html | 17 ++--- labels/index.html | 4 +- offline/index.html | 2 +- post/about/index.html | 4 +- post/ahk-numpad/index.html | 10 +-- post/bash-cht/index.html | 28 ++++---- post/c-review/index.html | 14 ++-- post/compile-fenix/index.html | 10 +-- post/compile-vim-python3/index.html | 16 ++--- post/damn-gpg/index.html | 10 +-- post/docker-cht/index.html | 11 ++-- post/docker-tag/index.html | 6 +- post/dsa-init/index.html | 30 ++++----- post/fabricmc/index.html | 18 +++--- post/ffmpeg-cht/index.html | 10 +-- post/from-python-to-js/index.html | 42 ++++++------ post/get-wechat-emoji/index.html | 6 +- post/gh-action-cache/index.html | 10 +-- post/git-cht/index.html | 20 +++--- post/gridsome-pwa/index.html | 20 +++--- post/hugo-circle-ci/index.html | 16 ++--- post/hugo-toc/index.html | 26 ++++---- post/jupyter-cht/index.html | 8 +-- post/jupyter-raspi/index.html | 14 ++-- post/jupyter-wolfram/index.html | 18 +++--- post/linux-cht/index.html | 30 ++++----- post/manjaro-first-glance/index.html | 6 +- post/markdown-cht/index.html | 14 ++-- post/md-cn-bold/index.html | 8 +-- post/moving-blog-to-astro/index.html | 61 ++++++++++++++++++ post/mysql-emoji/index.html | 30 ++++----- post/mysql-in-wsl/index.html | 12 ++-- post/netease-music-bp/index.html | 45 +++++++++++++ post/npm-reg/index.html | 8 +-- post/pipenv-and-poetry/index.html | 22 +++---- post/pkg-source/index.html | 8 +-- post/pwa-skipwaiting/index.html | 38 +++++------ post/python-async-subprocess/index.html | 34 +++++----- post/raspi-tricks/index.html | 18 +++--- post/react-virtual-scroll/index.html | 6 +- post/showcase/index.html | 10 +-- post/sql-query-perf/index.html | 26 ++++---- post/tmux-setup/index.html | 8 +-- post/tmux-ssh/index.html | 14 ++-- post/upgrade-ubuntu/index.html | 6 +- post/vim-ariline/index.html | 10 +-- post/vim-cht/index.html | 22 +++---- post/vscode-setup/index.html | 16 ++--- post/vue-hammer/index.html | 22 +++---- post/vue-iblog/index.html | 14 ++-- post/vue-pwa-1/index.html | 34 +++++----- post/vue-pwa-2/index.html | 14 ++-- post/vue-pwa-3/index.html | 18 +++--- post/why-flask/index.html | 14 ++-- post/win-cmd-cht/index.html | 10 +-- post/win-cmd-font/index.html | 10 +-- post/win11-explorer-patcher/index.html | 18 +++--- post/wsl-cht/index.html | 20 +++--- post/wtf-c/index.html | 40 ++++++------ post/wtf-hugo/index.html | 26 ++++---- series/vue-pwa/index.html | 2 +- service-worker.js | 2 +- tag/android/index.html | 2 +- tag/c/index.html | 2 +- tag/docker/index.html | 7 +- tag/flask/index.html | 2 +- tag/gh-action/index.html | 2 +- tag/gridsome/index.html | 2 +- tag/hugo/index.html | 6 +- tag/javascript/index.html | 2 +- tag/jupyter/index.html | 2 +- tag/linux/index.html | 2 +- tag/minecraft/index.html | 2 +- tag/mysql/index.html | 6 +- tag/network/index.html | 2 +- tag/pwa/index.html | 6 +- tag/python/index.html | 2 +- tag/raspi/index.html | 2 +- tag/tmux/index.html | 2 +- tag/vim/index.html | 2 +- tag/vscode/index.html | 2 +- tag/vue/index.html | 6 +- tag/wolfram/index.html | 2 +- 156 files changed, 703 insertions(+), 587 deletions(-) create mode 100644 assets/data/post/moving-blog-to-astro/index.json create mode 100644 assets/data/post/netease-music-bp/index.json create mode 100644 img/6c88ced2.jpg delete mode 100644 img/f498d489.png create mode 100644 post/moving-blog-to-astro/index.html create mode 100644 post/netease-music-bp/index.html diff --git a/404.html b/404.html index b711af4fc..8c22915b2 100644 --- a/404.html +++ b/404.html @@ -12,7 +12,7 @@ } -
AC Dustbin

404 - not found

+
AC Dustbin

404 - not found

diff --git a/assets/data/blog/cheatsheet/index.json b/assets/data/blog/cheatsheet/index.json index bba8db417..d534ea11a 100644 --- a/assets/data/blog/cheatsheet/index.json +++ b/assets/data/blog/cheatsheet/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"label":{"id":"blog: cheatsheet","name":"cheatsheet","type":"blog","belongsTo":{"edges":[{"node":{"id":"77","title":"换源集合","path":"/post/pkg-source/","summary":"\n

各种换源,我要的都在这里了。针对天朝就不用国际语言了 😄

\n","createdAt":"2020-02-28T14:29:13.000Z","lastEditedAt":"2021-12-09T02:49:50.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: cheatsheet","type":"blog","color":"6655d6","name":"cheatsheet","path":"/blog/cheatsheet/"}]}},{"node":{"id":"81","title":"Linux (Ubuntu) Cheatsheet","path":"/post/linux-cht/","summary":"\n

Useful commands and tips about system operation. Things more about scripting is in Bash Cheatsheet

\n","createdAt":"2020-04-18T04:48:27.000Z","lastEditedAt":"2020-06-30T06:59:29.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: cheatsheet","type":"blog","color":"6655d6","name":"cheatsheet","path":"/blog/cheatsheet/"}]}},{"node":{"id":"82","title":"Bash Cheatsheet","path":"/post/bash-cht/","summary":"","createdAt":"2020-04-18T04:48:39.000Z","lastEditedAt":"2020-06-30T06:59:32.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: cheatsheet","type":"blog","color":"6655d6","name":"cheatsheet","path":"/blog/cheatsheet/"}]}},{"node":{"id":"83","title":"FFmpeg Cheatsheet","path":"/post/ffmpeg-cht/","summary":"","createdAt":"2020-03-21T23:56:57.000Z","lastEditedAt":"2020-06-30T09:14:31.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: cheatsheet","type":"blog","color":"6655d6","name":"cheatsheet","path":"/blog/cheatsheet/"}]}},{"node":{"id":"84","title":"Markdown Cheatsheet","path":"/post/markdown-cht/","summary":"\n

Tricks which are less known to markdown starters

\n","createdAt":"2020-04-18T04:48:51.000Z","lastEditedAt":"2020-07-17T15:18:28.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: cheatsheet","type":"blog","color":"6655d6","name":"cheatsheet","path":"/blog/cheatsheet/"}]}},{"node":{"id":"85","title":"Vim Cheatsheet","path":"/post/vim-cht/","summary":"","createdAt":"2020-04-18T04:48:56.000Z","lastEditedAt":"2020-07-01T13:01:11.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: cheatsheet","type":"blog","color":"6655d6","name":"cheatsheet","path":"/blog/cheatsheet/"},{"id":"tag: vim","type":"tag","color":"007f00","name":"vim","path":"/tag/vim/"}]}},{"node":{"id":"86","title":"Windows CMD Cheatsheet","path":"/post/win-cmd-cht/","summary":"","createdAt":"2020-04-18T04:49:03.000Z","lastEditedAt":"2020-08-06T13:16:42.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: cheatsheet","type":"blog","color":"6655d6","name":"cheatsheet","path":"/blog/cheatsheet/"}]}},{"node":{"id":"153","title":"Jupyter etc. Cheatsheet","path":"/post/jupyter-cht/","summary":"\n

主要是一些数据与绘图处理相关

\n","createdAt":"2020-12-05T01:25:56.000Z","lastEditedAt":"2021-09-20T13:32:35.000Z","image":"/blog/img/8c93085b.png","imageLazy":"AEAAkDASIAAhEBAxEB/8QAFgABAQE$AEE/8QAFRABAQ$AEH/xAAUAQE$AAA/8QAFBEB$AAAP/aAAwDAQACEQMRAD8AxxAB/9k=","logo":null,"labels":[{"id":"blog: cheatsheet","type":"blog","color":"6655d6","name":"cheatsheet","path":"/blog/cheatsheet/"},{"id":"tag: jupyter","type":"tag","color":"f37626","name":"jupyter","path":"/tag/jupyter/"}]}},{"node":{"id":"154","title":"Git CheatSheet","path":"/post/git-cht/","summary":"","createdAt":"2020-12-09T07:32:18.000Z","lastEditedAt":"2021-03-20T05:04:22.000Z","image":"/blog/img/4f1e424e.png","imageLazy":"AGAAkDASIAAhEBAxEB/8QAFgABAQE$AME/8QAGhABAAMAAwAAAAAAAAAAAAAAAQACESEx8P/EABQBAQ$AAD/xAAUEQE$AAA/9oADAMBAAIRAxEAPwDO2rfNEV5fdycRA//Z","logo":null,"labels":[{"id":"blog: cheatsheet","type":"blog","color":"6655d6","name":"cheatsheet","path":"/blog/cheatsheet/"}]}},{"node":{"id":"169","title":"高频公式表","path":"/post/freq-formula/","summary":"\n

名书镇贴

\n

东西慢慢加进来好了,顺便看看公式渲染的情况。

\n","createdAt":"2021-04-05T00:51:35.000Z","lastEditedAt":"2021-04-05T01:01:29.000Z","image":"/blog/img/9553808b.jpg","imageLazy":"AEAAkDASIAAhEBAxEB/8QAFgABAQE$AED/8QAFxABAQEB$EhEv/EABUBAQE$AME/8QAFBEB$AAAP/aAAwDAQACEQMRAD8AzmzU5gKBP//Z","logo":null,"labels":[{"id":"blog: cheatsheet","type":"blog","color":"6655d6","name":"cheatsheet","path":"/blog/cheatsheet/"}]}},{"node":{"id":"174","title":"Docker Cheatsheet","path":"/post/docker-cht/","summary":"\n

Docker commands are easily fogotten

\n","createdAt":"2021-07-31T09:58:17.000Z","lastEditedAt":"2021-09-11T13:38:15.000Z","image":"/blog/img/f498d489.png","imageLazy":"ACAAkDASIAAhEBAxEB/8QAFgABAQE$AID/8QAGBABAAMB$ECESL/xAAUAQE$AAC/8QAFREBAQ$AAH/2gAMAwEAAhEDEQA/AMLx2nAKjH//2Q==","logo":{"src":"/blog/img/a41c7f22.png","lazySrc":"AJAAkDASIAAhEBAxEB/8QAFgABAQE$gME/8QAHhAAAgAGAwAAAAAAAAAAAAAAAAECAxESITJhgZH/xAAUAQE$AAB/8QAFREBAQ$ACH/2gAMAwEAAhEDEQA/AM6oliLoVsvj0mtQCK//2Q=="},"labels":[{"id":"blog: cheatsheet","type":"blog","color":"6655d6","name":"cheatsheet","path":"/blog/cheatsheet/"},{"id":"tag: docker","type":"tag","color":"71cfff","name":"docker","path":"/tag/docker/"}]}},{"node":{"id":"175","title":"WSL Cheatsheet","path":"/post/wsl-cht/","summary":"\n

WSL issues or tricks I had to search for again and again

\n","createdAt":"2021-10-05T10:49:23.000Z","lastEditedAt":"2021-10-14T02:17:43.000Z","image":"/blog/img/f6b72683.png","imageLazy":"ADAAkDASIAAhEBAxEB/8QAFQABAQ$AAT/xAAZEAADAAM$RECFGH/xAAVAQEB$AAAf/EABURAQE$AAB/9oADAMBAAIRAxEAPwBXsYvhRWAFr//Z","logo":null,"labels":[{"id":"blog: cheatsheet","type":"blog","color":"6655d6","name":"cheatsheet","path":"/blog/cheatsheet/"}]}}]}}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"label":{"id":"blog: cheatsheet","name":"cheatsheet","type":"blog","belongsTo":{"edges":[{"node":{"id":"77","title":"换源集合","path":"/post/pkg-source/","summary":"\n

各种换源,我要的都在这里了。针对天朝就不用国际语言了 😄

\n","createdAt":"2020-02-28T14:29:13.000Z","lastEditedAt":"2021-12-09T02:49:50.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: cheatsheet","type":"blog","color":"6655d6","name":"cheatsheet","path":"/blog/cheatsheet/"}]}},{"node":{"id":"81","title":"Linux (Ubuntu) Cheatsheet","path":"/post/linux-cht/","summary":"\n

Useful commands and tips about system operation. Things more about scripting is in Bash Cheatsheet

\n","createdAt":"2020-04-18T04:48:27.000Z","lastEditedAt":"2020-06-30T06:59:29.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: cheatsheet","type":"blog","color":"6655d6","name":"cheatsheet","path":"/blog/cheatsheet/"}]}},{"node":{"id":"82","title":"Bash Cheatsheet","path":"/post/bash-cht/","summary":"","createdAt":"2020-04-18T04:48:39.000Z","lastEditedAt":"2020-06-30T06:59:32.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: cheatsheet","type":"blog","color":"6655d6","name":"cheatsheet","path":"/blog/cheatsheet/"}]}},{"node":{"id":"83","title":"FFmpeg Cheatsheet","path":"/post/ffmpeg-cht/","summary":"","createdAt":"2020-03-21T23:56:57.000Z","lastEditedAt":"2020-06-30T09:14:31.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: cheatsheet","type":"blog","color":"6655d6","name":"cheatsheet","path":"/blog/cheatsheet/"}]}},{"node":{"id":"84","title":"Markdown Cheatsheet","path":"/post/markdown-cht/","summary":"\n

Tricks which are less known to markdown starters

\n","createdAt":"2020-04-18T04:48:51.000Z","lastEditedAt":"2020-07-17T15:18:28.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: cheatsheet","type":"blog","color":"6655d6","name":"cheatsheet","path":"/blog/cheatsheet/"}]}},{"node":{"id":"85","title":"Vim Cheatsheet","path":"/post/vim-cht/","summary":"","createdAt":"2020-04-18T04:48:56.000Z","lastEditedAt":"2020-07-01T13:01:11.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: cheatsheet","type":"blog","color":"6655d6","name":"cheatsheet","path":"/blog/cheatsheet/"},{"id":"tag: vim","type":"tag","color":"007f00","name":"vim","path":"/tag/vim/"}]}},{"node":{"id":"86","title":"Windows CMD Cheatsheet","path":"/post/win-cmd-cht/","summary":"","createdAt":"2020-04-18T04:49:03.000Z","lastEditedAt":"2020-08-06T13:16:42.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: cheatsheet","type":"blog","color":"6655d6","name":"cheatsheet","path":"/blog/cheatsheet/"}]}},{"node":{"id":"153","title":"Jupyter etc. Cheatsheet","path":"/post/jupyter-cht/","summary":"\n

主要是一些数据与绘图处理相关

\n","createdAt":"2020-12-05T01:25:56.000Z","lastEditedAt":"2021-09-20T13:32:35.000Z","image":"/blog/img/8c93085b.png","imageLazy":"AEAAkDASIAAhEBAxEB/8QAFgABAQE$AEE/8QAFRABAQ$AEH/xAAUAQE$AAA/8QAFBEB$AAAP/aAAwDAQACEQMRAD8AxxAB/9k=","logo":null,"labels":[{"id":"blog: cheatsheet","type":"blog","color":"6655d6","name":"cheatsheet","path":"/blog/cheatsheet/"},{"id":"tag: jupyter","type":"tag","color":"f37626","name":"jupyter","path":"/tag/jupyter/"}]}},{"node":{"id":"154","title":"Git CheatSheet","path":"/post/git-cht/","summary":"","createdAt":"2020-12-09T07:32:18.000Z","lastEditedAt":"2021-03-20T05:04:22.000Z","image":"/blog/img/4f1e424e.png","imageLazy":"AGAAkDASIAAhEBAxEB/8QAFgABAQE$AME/8QAGhABAAMAAwAAAAAAAAAAAAAAAQACESEx8P/EABQBAQ$AAD/xAAUEQE$AAA/9oADAMBAAIRAxEAPwDO2rfNEV5fdycRA//Z","logo":null,"labels":[{"id":"blog: cheatsheet","type":"blog","color":"6655d6","name":"cheatsheet","path":"/blog/cheatsheet/"}]}},{"node":{"id":"169","title":"高频公式表","path":"/post/freq-formula/","summary":"\n

名书镇贴

\n

东西慢慢加进来好了,顺便看看公式渲染的情况。

\n","createdAt":"2021-04-05T00:51:35.000Z","lastEditedAt":"2021-04-05T01:01:29.000Z","image":"/blog/img/9553808b.jpg","imageLazy":"AEAAkDASIAAhEBAxEB/8QAFgABAQE$AED/8QAFxABAQEB$EhEv/EABUBAQE$AME/8QAFBEB$AAAP/aAAwDAQACEQMRAD8AzmzU5gKBP//Z","logo":null,"labels":[{"id":"blog: cheatsheet","type":"blog","color":"6655d6","name":"cheatsheet","path":"/blog/cheatsheet/"}]}},{"node":{"id":"174","title":"Docker Cheatsheet","path":"/post/docker-cht/","summary":"\n

Docker commands are easily forgotten

\n

Above image from scmagazine.com

\n","createdAt":"2021-07-31T09:58:17.000Z","lastEditedAt":"2022-05-01T13:49:08.000Z","image":"/blog/img/6c88ced2.jpg","imageLazy":"AFAAkDASIAAhEBAxEB/8QAFQABAQ$AAL/xAAWEAEBAQ$ASL/xAAVAQEB$ACA//EABQRAQ$AAD/2gAMAwEAAhEDEQA/ALt2oDSf/9k=","logo":{"src":"/blog/img/a41c7f22.png","lazySrc":"AJAAkDASIAAhEBAxEB/8QAFgABAQE$gME/8QAHhAAAgAGAwAAAAAAAAAAAAAAAAECAxESITJhgZH/xAAUAQE$AAB/8QAFREBAQ$ACH/2gAMAwEAAhEDEQA/AM6oliLoVsvj0mtQCK//2Q=="},"labels":[{"id":"blog: cheatsheet","type":"blog","color":"6655d6","name":"cheatsheet","path":"/blog/cheatsheet/"},{"id":"tag: docker","type":"tag","color":"71cfff","name":"docker","path":"/tag/docker/"}]}},{"node":{"id":"175","title":"WSL Cheatsheet","path":"/post/wsl-cht/","summary":"\n

WSL issues or tricks I had to search for again and again

\n","createdAt":"2021-10-05T10:49:23.000Z","lastEditedAt":"2021-10-14T02:17:43.000Z","image":"/blog/img/f6b72683.png","imageLazy":"ADAAkDASIAAhEBAxEB/8QAFQABAQ$AAT/xAAZEAADAAM$RECFGH/xAAVAQEB$AAAf/EABURAQE$AAB/9oADAMBAAIRAxEAPwBXsYvhRWAFr//Z","logo":null,"labels":[{"id":"blog: cheatsheet","type":"blog","color":"6655d6","name":"cheatsheet","path":"/blog/cheatsheet/"}]}}]}}},"context":{}} \ No newline at end of file diff --git a/assets/data/blog/moments/index.json b/assets/data/blog/moments/index.json index 5dd987bba..6ee07f260 100644 --- a/assets/data/blog/moments/index.json +++ b/assets/data/blog/moments/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"label":{"id":"blog: moments","name":"moments","type":"blog","belongsTo":{"edges":[{"node":{"id":"52","title":"Why is EEPROM called \"ROM\"?","path":"/post/eeprom/","summary":"","createdAt":"2020-01-07T12:16:01.000Z","lastEditedAt":"2020-07-10T12:43:29.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: moments","type":"blog","color":"3dae38","name":"moments","path":"/blog/moments/"}]}},{"node":{"id":"63","title":"Lucky GitHub Number","path":"/post/lucky-gh-1234/","summary":"\n

Record the lucky GitHub contribution number.

\n","createdAt":"2020-01-13T08:07:25.000Z","lastEditedAt":"2020-06-30T07:08:43.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: moments","type":"blog","color":"3dae38","name":"moments","path":"/blog/moments/"}]}},{"node":{"id":"111","title":"VM Ware 已经不在了,vmdk 怎么打开?","path":"/post/vmdk/","summary":"\n

Reference: https://www.nakivo.com/blog/extract-content-vmdk-files-step-step-guide/

\n","createdAt":"2020-07-11T01:39:00.000Z","lastEditedAt":"2020-07-11T01:39:00.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: moments","type":"blog","color":"3dae38","name":"moments","path":"/blog/moments/"}]}},{"node":{"id":"118","title":"谜之推荐?","path":"/post/explore-myself/","summary":"\n

GitHub 的推荐算法肯定有问题

\n","createdAt":"2020-07-12T03:31:10.000Z","lastEditedAt":"2020-07-14T10:50:59.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: moments","type":"blog","color":"3dae38","name":"moments","path":"/blog/moments/"}]}},{"node":{"id":"152","title":"THU Hole Joke","path":"/post/thuhole-joke/","summary":"\n

活干完了,推个 tag 吧!

\n","createdAt":"2020-11-23T11:25:16.000Z","lastEditedAt":"2020-11-23T11:25:45.000Z","image":"/blog/img/8564076e.png","imageLazy":"AOAAkDASIAAhEBAxEB/8QAFgABAQE$QIE/8QAFhABAQE$AEx/8QAFAEB$AAAP/EABQRAQ$AAD/2gAMAwEAAhEDEQA/ANk0pmkH/9k=","logo":null,"labels":[{"id":"blog: moments","type":"blog","color":"3dae38","name":"moments","path":"/blog/moments/"}]}},{"node":{"id":"158","title":"Gallery","path":"/post/gallery/","summary":"\n

All images to share.

\n","createdAt":"2021-01-16T14:46:40.000Z","lastEditedAt":"2021-06-30T08:45:32.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: moments","type":"blog","color":"3dae38","name":"moments","path":"/blog/moments/"}]}},{"node":{"id":"161","title":"燕园十二月令风物图","path":"/post/pku-all-year-round/","summary":"\n

大学国文习作一篇,仿《豳风·七月》所作。贴于博客,权当献丑。

\n

头图来自北京大学官微,谭诗颖摄。

\n","createdAt":"2020-12-30T00:00:00.000Z","lastEditedAt":"2021-02-04T08:34:48.000Z","image":"/blog/img/02fbfdc8.jpg","imageLazy":"AFAAkDASIAAhEBAxEB/8QAFQABAQ$AAL/xAAYEAEAAwE$QIRIf/EABQBAQ$AAH/xAAUEQE$AAA/9oADAMBAAIRAxEAPwCb5FuQkCH/2Q==","logo":null,"labels":[{"id":"blog: moments","type":"blog","color":"3dae38","name":"moments","path":"/blog/moments/"}]}},{"node":{"id":"165","title":"LCPU Logo Joke","path":"/post/lcpu-logo-joke/","summary":"\n

一定是魔法

\n","createdAt":"2021-03-31T02:48:17.000Z","lastEditedAt":"2021-03-31T02:48:17.000Z","image":"/blog/img/1a746189.png","imageLazy":"AFAAkDASIAAhEBAxEB/8QAFgABAQE$AEE/8QAFBAB$AAAP/EABQBAQ$AAD/xAAUEQE$AAA/9oADAMBAAIRAxEAPwDOgA//2Q==","logo":null,"labels":[{"id":"blog: moments","type":"blog","color":"3dae38","name":"moments","path":"/blog/moments/"}]}},{"node":{"id":"170","title":"Manjaro Linux 初体验","path":"/post/manjaro-first-glance/","summary":"\n

偏随笔性质的文章,也没什么新东西,就随便谈谈,直接用母语好了

\n","createdAt":"2021-04-19T13:50:07.000Z","lastEditedAt":"2021-04-19T14:31:41.000Z","image":"/blog/img/d75c565e.jpg","imageLazy":"AGAAkDASIAAhEBAxEB/8QAFgABAQE$AID/8QAGhAAAgID$ECEgMRUf/EABQBAQ$AAH/xAAUEQE$AAA/9oADAMBAAIRAxEAPwDHI1FKNVoi0ugAX//Z","logo":{"src":"/blog/img/f0f4d288.png","lazySrc":"AJAAkDASIAAhEBAxEB/8QAFQABAQ$AAP/xAAeEAACAQMFAAAAAAAAAAAAAAAAAQIDERITITNRof/EABQBAQ$AAL/xAAVEQEB$ABAP/aAAwDAQACEQMRAD8ARjDT2SasQxpd+iPAyIQmt//Z"},"labels":[{"id":"blog: moments","type":"blog","color":"3dae38","name":"moments","path":"/blog/moments/"},{"id":"tag: linux","type":"tag","color":"de4815","name":"linux","path":"/tag/linux/"}]}},{"node":{"id":"171","title":"Enabling Numpad on a Windows Laptop without Numpad","path":"/post/ahk-numpad/","summary":"\n

Sometimes you really need a numpad. For example when inputing a large quantity of numbers after an insane physics experiment.

\n","createdAt":"2021-05-08T08:43:49.000Z","lastEditedAt":"2021-05-08T08:43:49.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: moments","type":"blog","color":"3dae38","name":"moments","path":"/blog/moments/"}]}},{"node":{"id":"172","title":"Value of Open Source Software: A Personal View","path":"/post/value-of-oss/","summary":"\n

This article was submitted as an English class essay. Originally, I wanted to talk about open source landscape in China, but I found that just explaining what open source is takes hundreds of words. (这就是你把本来想写的部分鸽掉的理由?

\n","createdAt":"2021-06-25T10:35:34.000Z","lastEditedAt":"2021-06-25T10:35:34.000Z","image":null,"imageLazy":"","logo":{"src":"/blog/img/fa50d2ff.png","lazySrc":"AJAAkDASIAAhEBAxEB/8QAFwAAAwE$AEDBP/EAB0QAAICAQUAAAAAAAAAAAAAAAACARIDERMhM1H/xAAUAQE$AAA/8QAFREBAQ$AAH/2gAMAwEAAhEDEQA/ACsVWFVduvMmfTF7Il6WJiD/2Q=="},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"blog: moments","type":"blog","color":"3dae38","name":"moments","path":"/blog/moments/"}]}},{"node":{"id":"176","title":"Keep Old-fashioned Taskbar on the Left While Using Windows 11","path":"/post/win11-explorer-patcher/","summary":"\n

Windows 11 comes with awesome new features for developers as well as insane taskbar settings. Don't let the awful taskbar design stop you from upgrading!

\n","createdAt":"2021-10-11T07:21:25.000Z","lastEditedAt":"2021-10-11T10:41:16.000Z","image":"/blog/img/4748dfe8.png","imageLazy":"AHAAkDASIAAhEBAxEB/8QAFgABAQE$AME/8QAGBABAAMB$ECEVH/xAAVAQEB$ABAv/EABURAQE$AAB/9oADAMBAAIRAxEAPwDPCu24AXH/2Q==","logo":null,"labels":[{"id":"blog: moments","type":"blog","color":"3dae38","name":"moments","path":"/blog/moments/"}]}},{"node":{"id":"177","title":"听 PingCAP 黄东旭谈开源社区与文化","path":"/post/pingcap-lecture/","summary":"\n

旁听《开源软件与技术》课程特邀讲座,甚至白拿了一个 TiDB 的周边小礼品(会的 USB 线)

\n","createdAt":"2021-11-20T11:15:41.000Z","lastEditedAt":"2021-11-20T11:23:57.000Z","image":"/blog/img/f60d3cd0.png","imageLazy":"ADAAkDASIAAhEBAxEB/8QAFQABAQ$AAL/xAAYEAEAAwE$REhQf/EABUBAQE$AID/8QAFBEB$AAAP/aAAwDAQACEQMRAD8AXsRykgoL/9k=","logo":null,"labels":[{"id":"blog: moments","type":"blog","color":"3dae38","name":"moments","path":"/blog/moments/"}]}},{"node":{"id":"178","title":"腾讯会议 Linux 客户端无法正常结束共享的 Workaround","path":"/post/wemeet-screen-share/","summary":"\n

就改下窗口状态的事儿

\n","createdAt":"2022-02-27T09:02:27.000Z","lastEditedAt":"2022-02-27T09:02:27.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: moments","type":"blog","color":"3dae38","name":"moments","path":"/blog/moments/"}]}}]}}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"label":{"id":"blog: moments","name":"moments","type":"blog","belongsTo":{"edges":[{"node":{"id":"52","title":"Why is EEPROM called \"ROM\"?","path":"/post/eeprom/","summary":"","createdAt":"2020-01-07T12:16:01.000Z","lastEditedAt":"2020-07-10T12:43:29.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: moments","type":"blog","color":"3dae38","name":"moments","path":"/blog/moments/"}]}},{"node":{"id":"63","title":"Lucky GitHub Number","path":"/post/lucky-gh-1234/","summary":"\n

Record the lucky GitHub contribution number.

\n","createdAt":"2020-01-13T08:07:25.000Z","lastEditedAt":"2020-06-30T07:08:43.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: moments","type":"blog","color":"3dae38","name":"moments","path":"/blog/moments/"}]}},{"node":{"id":"111","title":"VM Ware 已经不在了,vmdk 怎么打开?","path":"/post/vmdk/","summary":"\n

Reference: https://www.nakivo.com/blog/extract-content-vmdk-files-step-step-guide/

\n","createdAt":"2020-07-11T01:39:00.000Z","lastEditedAt":"2020-07-11T01:39:00.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: moments","type":"blog","color":"3dae38","name":"moments","path":"/blog/moments/"}]}},{"node":{"id":"118","title":"谜之推荐?","path":"/post/explore-myself/","summary":"\n

GitHub 的推荐算法肯定有问题

\n","createdAt":"2020-07-12T03:31:10.000Z","lastEditedAt":"2020-07-14T10:50:59.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: moments","type":"blog","color":"3dae38","name":"moments","path":"/blog/moments/"}]}},{"node":{"id":"152","title":"THU Hole Joke","path":"/post/thuhole-joke/","summary":"\n

活干完了,推个 tag 吧!

\n","createdAt":"2020-11-23T11:25:16.000Z","lastEditedAt":"2020-11-23T11:25:45.000Z","image":"/blog/img/8564076e.png","imageLazy":"AOAAkDASIAAhEBAxEB/8QAFgABAQE$QIE/8QAFhABAQE$AEx/8QAFAEB$AAAP/EABQRAQ$AAD/2gAMAwEAAhEDEQA/ANk0pmkH/9k=","logo":null,"labels":[{"id":"blog: moments","type":"blog","color":"3dae38","name":"moments","path":"/blog/moments/"}]}},{"node":{"id":"158","title":"Gallery","path":"/post/gallery/","summary":"\n

All images to share.

\n","createdAt":"2021-01-16T14:46:40.000Z","lastEditedAt":"2021-06-30T08:45:32.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: moments","type":"blog","color":"3dae38","name":"moments","path":"/blog/moments/"}]}},{"node":{"id":"161","title":"燕园十二月令风物图","path":"/post/pku-all-year-round/","summary":"\n

大学国文习作一篇,仿《豳风·七月》所作。贴于博客,权当献丑。

\n

头图来自北京大学官微,谭诗颖摄。

\n","createdAt":"2020-12-30T00:00:00.000Z","lastEditedAt":"2021-02-04T08:34:48.000Z","image":"/blog/img/02fbfdc8.jpg","imageLazy":"AFAAkDASIAAhEBAxEB/8QAFQABAQ$AAL/xAAYEAEAAwE$QIRIf/EABQBAQ$AAH/xAAUEQE$AAA/9oADAMBAAIRAxEAPwCb5FuQkCH/2Q==","logo":null,"labels":[{"id":"blog: moments","type":"blog","color":"3dae38","name":"moments","path":"/blog/moments/"}]}},{"node":{"id":"165","title":"LCPU Logo Joke","path":"/post/lcpu-logo-joke/","summary":"\n

一定是魔法

\n","createdAt":"2021-03-31T02:48:17.000Z","lastEditedAt":"2021-03-31T02:48:17.000Z","image":"/blog/img/1a746189.png","imageLazy":"AFAAkDASIAAhEBAxEB/8QAFgABAQE$AEE/8QAFBAB$AAAP/EABQBAQ$AAD/xAAUEQE$AAA/9oADAMBAAIRAxEAPwDOgA//2Q==","logo":null,"labels":[{"id":"blog: moments","type":"blog","color":"3dae38","name":"moments","path":"/blog/moments/"}]}},{"node":{"id":"170","title":"Manjaro Linux 初体验","path":"/post/manjaro-first-glance/","summary":"\n

偏随笔性质的文章,也没什么新东西,就随便谈谈,直接用母语好了

\n","createdAt":"2021-04-19T13:50:07.000Z","lastEditedAt":"2021-04-19T14:31:41.000Z","image":"/blog/img/d75c565e.jpg","imageLazy":"AGAAkDASIAAhEBAxEB/8QAFgABAQE$AID/8QAGhAAAgID$ECEgMRUf/EABQBAQ$AAH/xAAUEQE$AAA/9oADAMBAAIRAxEAPwDHI1FKNVoi0ugAX//Z","logo":{"src":"/blog/img/f0f4d288.png","lazySrc":"AJAAkDASIAAhEBAxEB/8QAFQABAQ$AAP/xAAeEAACAQMFAAAAAAAAAAAAAAAAAQIDERITITNRof/EABQBAQ$AAL/xAAVEQEB$ABAP/aAAwDAQACEQMRAD8ARjDT2SasQxpd+iPAyIQmt//Z"},"labels":[{"id":"blog: moments","type":"blog","color":"3dae38","name":"moments","path":"/blog/moments/"},{"id":"tag: linux","type":"tag","color":"de4815","name":"linux","path":"/tag/linux/"}]}},{"node":{"id":"171","title":"Enabling Numpad on a Windows Laptop without Numpad","path":"/post/ahk-numpad/","summary":"\n

Sometimes you really need a numpad. For example when inputing a large quantity of numbers after an insane physics experiment.

\n","createdAt":"2021-05-08T08:43:49.000Z","lastEditedAt":"2021-05-08T08:43:49.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: moments","type":"blog","color":"3dae38","name":"moments","path":"/blog/moments/"}]}},{"node":{"id":"172","title":"Value of Open Source Software: A Personal View","path":"/post/value-of-oss/","summary":"\n

This article was submitted as an English class essay. Originally, I wanted to talk about open source landscape in China, but I found that just explaining what open source is takes hundreds of words. (这就是你把本来想写的部分鸽掉的理由?

\n","createdAt":"2021-06-25T10:35:34.000Z","lastEditedAt":"2021-06-25T10:35:34.000Z","image":null,"imageLazy":"","logo":{"src":"/blog/img/fa50d2ff.png","lazySrc":"AJAAkDASIAAhEBAxEB/8QAFwAAAwE$AEDBP/EAB0QAAICAQUAAAAAAAAAAAAAAAACARIDERMhM1H/xAAUAQE$AAA/8QAFREBAQ$AAH/2gAMAwEAAhEDEQA/ACsVWFVduvMmfTF7Il6WJiD/2Q=="},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"blog: moments","type":"blog","color":"3dae38","name":"moments","path":"/blog/moments/"}]}},{"node":{"id":"176","title":"Keep Old-fashioned Taskbar on the Left While Using Windows 11","path":"/post/win11-explorer-patcher/","summary":"\n

Windows 11 comes with awesome new features for developers as well as insane taskbar settings. Don't let the awful taskbar design stop you from upgrading!

\n","createdAt":"2021-10-11T07:21:25.000Z","lastEditedAt":"2021-10-11T10:41:16.000Z","image":"/blog/img/4748dfe8.png","imageLazy":"AHAAkDASIAAhEBAxEB/8QAFgABAQE$AME/8QAGBABAAMB$ECEVH/xAAVAQEB$ABAv/EABURAQE$AAB/9oADAMBAAIRAxEAPwDPCu24AXH/2Q==","logo":null,"labels":[{"id":"blog: moments","type":"blog","color":"3dae38","name":"moments","path":"/blog/moments/"}]}},{"node":{"id":"177","title":"听 PingCAP 黄东旭谈开源社区与文化","path":"/post/pingcap-lecture/","summary":"\n

旁听《开源软件与技术》课程特邀讲座,甚至白拿了一个 TiDB 的周边小礼品(会的 USB 线)

\n","createdAt":"2021-11-20T11:15:41.000Z","lastEditedAt":"2021-11-20T11:23:57.000Z","image":"/blog/img/f60d3cd0.png","imageLazy":"ADAAkDASIAAhEBAxEB/8QAFQABAQ$AAL/xAAYEAEAAwE$REhQf/EABUBAQE$AID/8QAFBEB$AAAP/aAAwDAQACEQMRAD8AXsRykgoL/9k=","logo":null,"labels":[{"id":"blog: moments","type":"blog","color":"3dae38","name":"moments","path":"/blog/moments/"}]}},{"node":{"id":"178","title":"腾讯会议 Linux 客户端无法正常结束共享的 Workaround","path":"/post/wemeet-screen-share/","summary":"\n

就改下窗口状态的事儿

\n","createdAt":"2022-02-27T09:02:27.000Z","lastEditedAt":"2022-02-27T09:02:27.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: moments","type":"blog","color":"3dae38","name":"moments","path":"/blog/moments/"}]}},{"node":{"id":"180","title":"免费下网易云会员歌曲 MP3 竟如此简单?","path":"/post/netease-music-bp/","summary":"\n

我坦白,我是标题党

\n","createdAt":"2022-05-01T08:50:29.000Z","lastEditedAt":"2022-05-01T08:50:29.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: moments","type":"blog","color":"3dae38","name":"moments","path":"/blog/moments/"}]}}]}}},"context":{}} \ No newline at end of file diff --git a/assets/data/blog/programming/index.json b/assets/data/blog/programming/index.json index bb7d31f7b..feb35dbb3 100644 --- a/assets/data/blog/programming/index.json +++ b/assets/data/blog/programming/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"label":{"id":"blog: programming","name":"programming","type":"blog","belongsTo":{"edges":[{"node":{"id":"18","title":"Showcase","path":"/post/showcase/","summary":"\n

测试一下 Markdown 渲染的效果 网页的 CSS

\n","createdAt":"2019-12-28T09:22:09.000Z","lastEditedAt":"2020-07-22T07:54:13.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"}]}},{"node":{"id":"22","title":"MySQL 存储 Emoji","path":"/post/mysql-emoji/","summary":"\n

最近需要使用 SQLAlchemy 存弹幕的内容,但是遇到了存 emoji 的问题。

\n","createdAt":"2019-12-23T14:21:24.000Z","lastEditedAt":"2020-09-26T07:41:43.000Z","image":null,"imageLazy":"","logo":{"src":"/blog/img/a1ba5d33.jpg","lazySrc":"AGAAkDASIAAhEBAxEB/8QAFQABAQ$AAT/xAAXEAEBAQEAAAAAAAAAAAAAAAAAIREx/8QAFAEB$AAAP/EABQRAQ$AAD/2gAMAwEAAhEDEQA/AK7vYAD/2Q=="},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: mysql","type":"tag","color":"f09011","name":"mysql","path":"/tag/mysql/"}]}},{"node":{"id":"23","title":"C Review","path":"/post/c-review/","summary":"\n

计概基础知识复习手记

\n","createdAt":"2019-12-29T10:22:51.000Z","lastEditedAt":"2020-06-30T07:52:36.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: c","type":"tag","color":"aaaaaa","name":"c","path":"/tag/c/"}]}},{"node":{"id":"25","title":"TOC in Hugo","path":"/post/hugo-toc/","summary":"\n

What a challenge to build toc in hugo!

\n","createdAt":"2019-12-31T15:17:22.000Z","lastEditedAt":"2020-06-30T07:33:17.000Z","image":null,"imageLazy":"","logo":{"src":"/blog/img/706fec00.png","lazySrc":"AJAAkDASIAAhEBAxEB/8QAFgABAQE$gEE/8QAGhAAAgMBAQAAAAAAAAAAAAAAABEBAgMzcf/EABUBAQE$AEC/8QAFREBAQ$AAH/2gAMAwEAAhEDEQA/ADSmU4uU0Z1Ukc59CK7X/9k="},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: hugo","type":"tag","color":"ff3db4","name":"hugo","path":"/tag/hugo/"}]}},{"node":{"id":"26","title":"WTFs in C","path":"/post/wtf-c/","summary":"\n

为了备战变态的计算概论考试, 不得已要研究一下 C 的奇奇怪怪的东西

\n","createdAt":"2020-01-01T08:59:08.000Z","lastEditedAt":"2020-06-30T07:43:45.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: c","type":"tag","color":"aaaaaa","name":"c","path":"/tag/c/"}]}},{"node":{"id":"28","title":"谜之 Hugo | AC's Blog","path":"/post/wtf-hugo/","summary":"","createdAt":"2020-01-03T13:19:16.000Z","lastEditedAt":"2020-11-02T04:31:46.000Z","image":null,"imageLazy":"","logo":{"src":"/blog/img/706fec00.png","lazySrc":"AJAAkDASIAAhEBAxEB/8QAFgABAQE$gEE/8QAGhAAAgMBAQAAAAAAAAAAAAAAABEBAgMzcf/EABUBAQE$AEC/8QAFREBAQ$AAH/2gAMAwEAAhEDEQA/ADSmU4uU0Z1Ukc59CK7X/9k="},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: hugo","type":"tag","color":"ff3db4","name":"hugo","path":"/tag/hugo/"}]}},{"node":{"id":"29","title":"我为什么要选 Flask?","path":"/post/why-flask/","summary":"\n

注意,这里不是说 Flask 有多好,而是。。用 Flask 用到怀疑人生!

\n","createdAt":"2020-01-03T13:00:00.000Z","lastEditedAt":"2021-02-22T08:02:38.000Z","image":null,"imageLazy":"","logo":{"src":"/blog/img/eb62ca24.png","lazySrc":"AIAAkDASIAAhEBAxEB/8QAFQABAQ$AAT/xAAUEAE$AAA/8QAFAEB$AAAP/EABQRAQ$AAD/2gAMAwEAAhEDEQA/AIgAf//Z"},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: python","type":"tag","color":"3c78a8","name":"python","path":"/tag/python/"},{"id":"tag: flask","type":"tag","color":"000000","name":"flask","path":"/tag/flask/"}]}},{"node":{"id":"30","title":"Change Win10 CMD Fonts","path":"/post/win-cmd-font/","summary":"\n

It took me quite a lot time to try to solve this...

\n

Note: this answer only works in non-English regions

\n","createdAt":"2020-01-03T07:39:10.000Z","lastEditedAt":"2020-07-03T01:31:22.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"}]}},{"node":{"id":"37","title":"Setup Vim Airline (in Termux)","path":"/post/vim-ariline/","summary":"","createdAt":"2019-02-09T00:00:00.000Z","lastEditedAt":"2021-02-22T01:59:03.000Z","image":"/blog/img/5a6f75bf.png","imageLazy":"ACAAkDASIAAhEBAxEB/8QAFQABAQ$AAP/xAAZEAEAAgM$RFDUYH/xAAUAQE$AAB/8QAFREBAQ$AAH/2gAMAwEAAhEDEQA/AJZur3OwFL//2Q==","logo":null,"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: vim","type":"tag","color":"007f00","name":"vim","path":"/tag/vim/"}]}},{"node":{"id":"51","title":"Deploying Hugo with CircleCI","path":"/post/hugo-circle-ci/","summary":"\n

Building blog sites manually is tedious and often leads to mistakes, especially when hosting on master/docs

\n","createdAt":"2019-10-27T12:12:30.000Z","lastEditedAt":"2020-06-30T07:28:06.000Z","image":null,"imageLazy":"","logo":{"src":"/blog/img/706fec00.png","lazySrc":"AJAAkDASIAAhEBAxEB/8QAFgABAQE$gEE/8QAGhAAAgMBAQAAAAAAAAAAAAAAABEBAgMzcf/EABUBAQE$AEC/8QAFREBAQ$AAH/2gAMAwEAAhEDEQA/ADSmU4uU0Z1Ukc59CK7X/9k="},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: hugo","type":"tag","color":"ff3db4","name":"hugo","path":"/tag/hugo/"}]}},{"node":{"id":"55","title":"Damning GPG Key","path":"/post/damn-gpg/","summary":"\n

Why GPG key keeps annoying me! 整天 Fail 有意思吗?

\n","createdAt":"2020-01-07T12:16:11.000Z","lastEditedAt":"2020-07-17T14:07:41.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"}]}},{"node":{"id":"64","title":"Jupyter on Raspberry Pi","path":"/post/jupyter-raspi/","summary":"","createdAt":"2020-02-26T12:20:29.000Z","lastEditedAt":"2020-07-01T12:33:55.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: raspi","type":"tag","color":"c31c4a","name":"raspi","path":"/tag/raspi/"},{"id":"tag: jupyter","type":"tag","color":"f37626","name":"jupyter","path":"/tag/jupyter/"}]}},{"node":{"id":"65","title":"Tricks about Raspberry Pi","path":"/post/raspi-tricks/","summary":"\n

Handy tricks when playing with raspberry pi. You should know them.

\n","createdAt":"2020-01-21T08:29:47.000Z","lastEditedAt":"2020-06-24T06:04:38.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: raspi","type":"tag","color":"c31c4a","name":"raspi","path":"/tag/raspi/"}]}},{"node":{"id":"66","title":"Working with tmux and SSH","path":"/post/tmux-ssh/","summary":"\n

Useful tips when SSH in remote device which uses tmux

\n","createdAt":"2020-01-21T13:55:36.000Z","lastEditedAt":"2020-07-03T01:54:18.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: tmux","type":"tag","color":"1bb91f","name":"tmux","path":"/tag/tmux/"}]}},{"node":{"id":"67","title":"Introduce and Setup Tmux","path":"/post/tmux-setup/","summary":"\n

Setup and introduce Tmux so that I can use it

\n","createdAt":"2020-01-21T13:55:42.000Z","lastEditedAt":"2020-07-10T12:46:29.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: tmux","type":"tag","color":"1bb91f","name":"tmux","path":"/tag/tmux/"}]}},{"node":{"id":"68","title":"用 Vue 做 PWA(一):开始","path":"/post/vue-pwa-1/","summary":"\n

Vue 党快速上手 PWA

\n","createdAt":"2020-01-30T07:59:20.000Z","lastEditedAt":"2020-09-04T23:59:48.000Z","image":"/blog/img/bda492f1.png","imageLazy":"ADAAkDASIAAhEBAxEB/8QAFgABAQE$AEE/8QAGRAAAgMB$ECEVGR/8QAFAEB$AAAv/EABYRAQEB$ABAv/aAAwDAQACEQMRAD8A0SVQdaToAaWX/9k=","logo":{"src":"/blog/img/32740c5b.png","lazySrc":"ADAAkDASIAAhEBAxEB/8QAFgABAQE$AEE/8QAFxABAQEBAAAAAAAAAAAAAAAAAFEBEf/EABUBAQE$AAB/8QAFREBAQ$AAH/2gAMAwEAAhEDEQA/AM1XmQFo/9k="},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: vue","type":"tag","color":"41b883","name":"vue","path":"/tag/vue/"},{"id":"tag: pwa","type":"tag","color":"5a0fc8","name":"pwa","path":"/tag/pwa/"},{"id":"series: vue-pwa","type":"series","color":"41b883","name":"vue-pwa","path":"/series/vue-pwa/"}]}},{"node":{"id":"69","title":"异步 & 异步获取命令输出","path":"/post/python-async-subprocess/","summary":"\n

与 JavaScript 的异步对比,初步了解 Python 的异步,并通过异步获取命令输出“实战”

\n","createdAt":"2020-02-05T10:53:16.000Z","lastEditedAt":"2020-06-30T08:58:31.000Z","image":null,"imageLazy":"","logo":{"src":"/blog/img/7338eb28.jpg","lazySrc":"AJAAkDASIAAhEBAxEB/8QAFwAAAwE$QIDBP/EAB8QAAIBAgcAAAAAAAAAAAAAAAABAgMSEzNBUWGBkf/EABQBAQ$AAH/xAAUEQE$AAA/9oADAMBAAIRAxEAPwCjcXFuTd2r2FxKvPgauf2jUIf/2Q=="},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: python","type":"tag","color":"3c78a8","name":"python","path":"/tag/python/"}]}},{"node":{"id":"70","title":"From Python to JavaScript","path":"/post/from-python-to-js/","summary":"\n

Although the title Top Ten Mistakes Python Programmers Make When Learning JavaScript may sound better, I have only noticed a few mistakes now. And I will update this post when I make more mistakes 😄

\n","createdAt":"2020-02-07T04:23:51.000Z","lastEditedAt":"2021-02-22T08:52:30.000Z","image":null,"imageLazy":"","logo":{"src":"/blog/img/db760f5a.png","lazySrc":"AJAAkDASIAAhEBAxEB/8QAFgABAQE$gAE/8QAHBAAAgICAwAAAAAAAAAAAAAAAAIBERMxM0Fx/8QAFAEB$AAAf/EABQRAQ$AAD/2gAMAwEAAhEDEQA/ACsJi1FdmelKOJvQAH//2Q=="},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: javascript","type":"tag","color":"f1da4e","name":"javascript","path":"/tag/javascript/"}]}},{"node":{"id":"71","title":"Mysql in WSL","path":"/post/mysql-in-wsl/","summary":"\n

😜 TL;DR

\n

查看日志/var/log/mysql/error.log,上互联网搜

\n","createdAt":"2020-02-07T08:59:02.000Z","lastEditedAt":"2020-06-24T05:55:14.000Z","image":null,"imageLazy":"","logo":{"src":"/blog/img/a1ba5d33.jpg","lazySrc":"AGAAkDASIAAhEBAxEB/8QAFQABAQ$AAT/xAAXEAEBAQEAAAAAAAAAAAAAAAAAIREx/8QAFAEB$AAAP/EABQRAQ$AAD/2gAMAwEAAhEDEQA/AK7vYAD/2Q=="},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: mysql","type":"tag","color":"f09011","name":"mysql","path":"/tag/mysql/"}]}},{"node":{"id":"73","title":"数算环境准备","path":"/post/dsa-init/","summary":"\n

g++ & VS Code 党对《数据结构与算法》的环境准备

\n

有必要解释一下名称的由来:Data Structure and Algorithm

\n","createdAt":"2020-02-19T08:50:19.000Z","lastEditedAt":"2020-07-01T13:00:05.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: c","type":"tag","color":"aaaaaa","name":"c","path":"/tag/c/"}]}},{"node":{"id":"75","title":"列出 USTC Docker 镜像的标签","path":"/post/docker-tag/","summary":"\n

不再为网络条件发愁!

\n","createdAt":"2020-02-28T13:38:39.000Z","lastEditedAt":"2020-06-21T12:56:24.000Z","image":null,"imageLazy":"","logo":{"src":"/blog/img/a41c7f22.png","lazySrc":"AJAAkDASIAAhEBAxEB/8QAFgABAQE$gME/8QAHhAAAgAGAwAAAAAAAAAAAAAAAAECAxESITJhgZH/xAAUAQE$AAB/8QAFREBAQ$ACH/2gAMAwEAAhEDEQA/AM6oliLoVsvj0mtQCK//2Q=="},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: docker","type":"tag","color":"71cfff","name":"docker","path":"/tag/docker/"}]}},{"node":{"id":"76","title":"Install Wolfram With Jupyter","path":"/post/jupyter-wolfram/","summary":"","createdAt":"2020-02-26T09:50:09.000Z","lastEditedAt":"2020-07-01T12:28:36.000Z","image":null,"imageLazy":"","logo":{"src":"/blog/img/fceb1de9.png","lazySrc":"AJAAkDASIAAhEBAxEB/8QAFgABAQE$QIE/8QAHhAAAgEDBQAAAAAAAAAAAAAAAAERAhITITJhYpH/xAAUAQE$AAB/8QAFBEB$AAAP/aAAwDAQACEQMRAD8AzKLZS0fAY+9PoPYiQL//2Q=="},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: raspi","type":"tag","color":"c31c4a","name":"raspi","path":"/tag/raspi/"},{"id":"tag: jupyter","type":"tag","color":"f37626","name":"jupyter","path":"/tag/jupyter/"},{"id":"tag: wolfram","type":"tag","color":"d70026","name":"wolfram","path":"/tag/wolfram/"}]}},{"node":{"id":"80","title":"Vue 加 Hammer 处理手势","path":"/post/vue-hammer/","summary":"\n

接近原生 App 体验,手势是少不了的。但是无论是 Vue 还是 Vuetify 对手势的支持并不好,于是需要 Hammer.js 支持。

\n","createdAt":"2020-01-27T08:33:10.000Z","lastEditedAt":"2020-07-03T02:20:23.000Z","image":null,"imageLazy":"","logo":{"src":"/blog/img/f846bbd9.png","lazySrc":"AJAAkDASIAAhEBAxEB/8QAFgABAQE$wAC/8QAGxABAAICAwAAAAAAAAAAAAAAAQACERIhUfD/xAAVAQEB$AAAf/EABURAQE$AAR/9oADAMBAAIRAxEAPwABq03QycY7h728TMoSP//Z"},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: vue","type":"tag","color":"41b883","name":"vue","path":"/tag/vue/"}]}},{"node":{"id":"87","title":"用 Vue 做 PWA(二):Runtime Caching","path":"/post/vue-pwa-2/","summary":"\n

本系列已经很久没更新了,在此讨论实践过程中遇到的 Runtime Caching 的一些问题。主要注意点是在处理跨域请求上。

\n","createdAt":"2020-04-18T04:49:20.000Z","lastEditedAt":"2021-03-13T00:27:46.000Z","image":"/blog/img/bda492f1.png","imageLazy":"ADAAkDASIAAhEBAxEB/8QAFgABAQE$AEE/8QAGRAAAgMB$ECEVGR/8QAFAEB$AAAv/EABYRAQEB$ABAv/aAAwDAQACEQMRAD8A0SVQdaToAaWX/9k=","logo":{"src":"/blog/img/32740c5b.png","lazySrc":"ADAAkDASIAAhEBAxEB/8QAFgABAQE$AEE/8QAFxABAQEBAAAAAAAAAAAAAAAAAFEBEf/EABUBAQE$AAB/8QAFREBAQ$AAH/2gAMAwEAAhEDEQA/AM1XmQFo/9k="},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: vue","type":"tag","color":"41b883","name":"vue","path":"/tag/vue/"},{"id":"tag: pwa","type":"tag","color":"5a0fc8","name":"pwa","path":"/tag/pwa/"},{"id":"series: vue-pwa","type":"series","color":"41b883","name":"vue-pwa","path":"/series/vue-pwa/"}]}},{"node":{"id":"88","title":"JS 无依赖获取阴历","path":"/post/js-yinli/","summary":"\n

Intl 是个好东西,但是还不是很强大,浏览器支持上也有 Node 和 Android 的小缺憾。

\n","createdAt":"2020-02-08T05:46:36.000Z","lastEditedAt":"2020-07-03T02:55:14.000Z","image":null,"imageLazy":"","logo":{"src":"/blog/img/db760f5a.png","lazySrc":"AJAAkDASIAAhEBAxEB/8QAFgABAQE$gAE/8QAHBAAAgICAwAAAAAAAAAAAAAAAAIBERMxM0Fx/8QAFAEB$AAAf/EABQRAQ$AAD/2gAMAwEAAhEDEQA/ACsJi1FdmelKOJvQAH//2Q=="},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: javascript","type":"tag","color":"f1da4e","name":"javascript","path":"/tag/javascript/"}]}},{"node":{"id":"96","title":"Writing Blog with GitHub Issue and Vue Apollo","path":"/post/vue-iblog/","summary":"\n

Some tricks when using vue-apollo to communicate with GitHub API v4

\n","createdAt":"2020-04-24T00:00:00.000Z","lastEditedAt":"2020-09-04T07:47:20.000Z","image":"/blog/img/e4670a56.png","imageLazy":"ADAAkDASIAAhEBAxEB/8QAFgABAQE$AEE/8QAFRABAQ$ABH/xAAUAQE$AAA/8QAFBEB$AAAP/aAAwDAQACEQMRAD8A2RQB/9k=","logo":{"src":"/blog/img/f846bbd9.png","lazySrc":"AJAAkDASIAAhEBAxEB/8QAFgABAQE$wAC/8QAGxABAAICAwAAAAAAAAAAAAAAAQACERIhUfD/xAAVAQEB$AAAf/EABURAQE$AAR/9oADAMBAAIRAxEAPwABq03QycY7h728TMoSP//Z"},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: vue","type":"tag","color":"41b883","name":"vue","path":"/tag/vue/"}]}},{"node":{"id":"97","title":"Two Points to Notice when Upgrading Ubuntu","path":"/post/upgrade-ubuntu/","summary":"\n

Ever feel frustated when a new release is available but it keeps saying no release found?

\n","createdAt":"2020-04-26T00:00:00.000Z","lastEditedAt":"2020-06-21T12:55:54.000Z","image":null,"imageLazy":"","logo":{"src":"/blog/img/f0f4d288.png","lazySrc":"AJAAkDASIAAhEBAxEB/8QAFQABAQ$AAP/xAAeEAACAQMFAAAAAAAAAAAAAAAAAQIDERITITNRof/EABQBAQ$AAL/xAAVEQEB$ABAP/aAAwDAQACEQMRAD8ARjDT2SasQxpd+iPAyIQmt//Z"},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: linux","type":"tag","color":"de4815","name":"linux","path":"/tag/linux/"}]}},{"node":{"id":"98","title":"Setup GitHub Action Cache the Right Way","path":"/post/gh-action-cache/","summary":"\n

What on earth is happening? Why my cache never hits?

\n","createdAt":"2020-06-21T09:16:37.000Z","lastEditedAt":"2020-07-06T10:52:21.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: gh-action","type":"tag","color":"006b75","name":"gh-action","path":"/tag/gh-action/"}]}},{"node":{"id":"109","title":"聊一聊 React 的 virtual scroll","path":"/post/react-virtual-scroll/","summary":"\n

也没啥高见,重复一下网上现有的资料而已

\n","createdAt":"2020-07-10T09:58:04.000Z","lastEditedAt":"2020-07-11T01:08:48.000Z","image":null,"imageLazy":"","logo":{"src":"/blog/img/db760f5a.png","lazySrc":"AJAAkDASIAAhEBAxEB/8QAFgABAQE$gAE/8QAHBAAAgICAwAAAAAAAAAAAAAAAAIBERMxM0Fx/8QAFAEB$AAAf/EABQRAQ$AAD/2gAMAwEAAhEDEQA/ACsJi1FdmelKOJvQAH//2Q=="},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: javascript","type":"tag","color":"f1da4e","name":"javascript","path":"/tag/javascript/"}]}},{"node":{"id":"140","title":"Getting Started with Minecraft Fabric Without Any Java Knowledge","path":"/post/fabricmc/","summary":"\n

Thats challenging but possible

\n","createdAt":"2020-08-08T03:38:04.000Z","lastEditedAt":"2021-02-22T08:40:43.000Z","image":null,"imageLazy":"","logo":{"src":"/blog/img/5ac73a6e.png","lazySrc":"AJAAkDASIAAhEBAxEB/8QAFQABAQ$AQT/xAAdEAACAQQDAAAAAAAAAAAAAAAAAgEREjFRE0Gh/8QAFQEBAQ$AAH/xAAUEQE$AAA/9oADAMBAAIRAxEAPwCVaJF2W6jQcjb8BsyAR//Z"},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: minecraft","type":"tag","color":"674a34","name":"minecraft","path":"/tag/minecraft/"}]}},{"node":{"id":"143","title":"手机端 GitHub Markdown 中文粗体显示的问题","path":"/post/md-cn-bold/","summary":"\n

这鬼问题应该只发生在安卓机上

\n","createdAt":"2020-08-22T00:53:07.000Z","lastEditedAt":"2020-08-22T01:07:36.000Z","image":null,"imageLazy":"","logo":{"src":"/blog/img/b0991834.png","lazySrc":"AJAAkDASIAAhEBAxEB/8QAFwAAAwE$AIDBP/EABwQAAICAgMAAAAAAAAAAAAAAAABAgMREiIjMf/EABQBAQ$AAD/xAAUEQE$AAA/9oADAMBAAIRAxEAPwCtdcXCHXFrVZ4mDCBeCgf/2Q=="},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: android","type":"tag","color":"3ddc84","name":"android","path":"/tag/android/"}]}},{"node":{"id":"145","title":"Use pipenv and poetry in a way that works","path":"/post/pipenv-and-poetry/","summary":"\n

Strange as the title is, they just don't work conveniently \"out of box\"

\n","createdAt":"2020-08-23T07:29:31.000Z","lastEditedAt":"2020-09-02T01:59:37.000Z","image":null,"imageLazy":"","logo":{"src":"/blog/img/7338eb28.jpg","lazySrc":"AJAAkDASIAAhEBAxEB/8QAFwAAAwE$QIDBP/EAB8QAAIBAgcAAAAAAAAAAAAAAAABAgMSEzNBUWGBkf/EABQBAQ$AAH/xAAUEQE$AAA/9oADAMBAAIRAxEAPwCjcXFuTd2r2FxKvPgauf2jUIf/2Q=="},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: python","type":"tag","color":"3c78a8","name":"python","path":"/tag/python/"}]}},{"node":{"id":"147","title":"用 Vue 做 PWA (三):理解生命周期","path":"/post/vue-pwa-3/","summary":"\n

虽然前两节的内容足以写出能用的 service worker,但是如果深入细节,仍然会有一些“匪夷所思”的现象发生。

\n","createdAt":"2020-09-03T02:40:57.000Z","lastEditedAt":"2020-09-08T03:09:07.000Z","image":"/blog/img/bda492f1.png","imageLazy":"ADAAkDASIAAhEBAxEB/8QAFgABAQE$AEE/8QAGRAAAgMB$ECEVGR/8QAFAEB$AAAv/EABYRAQEB$ABAv/aAAwDAQACEQMRAD8A0SVQdaToAaWX/9k=","logo":{"src":"/blog/img/32740c5b.png","lazySrc":"ADAAkDASIAAhEBAxEB/8QAFgABAQE$AEE/8QAFxABAQEBAAAAAAAAAAAAAAAAAFEBEf/EABUBAQE$AAB/8QAFREBAQ$AAH/2gAMAwEAAhEDEQA/AM1XmQFo/9k="},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: vue","type":"tag","color":"41b883","name":"vue","path":"/tag/vue/"},{"id":"tag: pwa","type":"tag","color":"5a0fc8","name":"pwa","path":"/tag/pwa/"},{"id":"series: vue-pwa","type":"series","color":"41b883","name":"vue-pwa","path":"/series/vue-pwa/"}]}},{"node":{"id":"149","title":"Gridsome is not that Ready for PWA","path":"/post/gridsome-pwa/","summary":"\n

How I am tring to build a nice gridsome-generated site with PWA support

\n

虽然可以勉强并入“用 Vue 做 PWA”系列,但想着能让更多人看到,就还是用国际语言吧

\n","createdAt":"2020-09-16T03:22:59.000Z","lastEditedAt":"2021-07-03T13:06:53.000Z","image":null,"imageLazy":"","logo":{"src":"/blog/img/aadfb904.png","lazySrc":"AJAAkDASIAAhEBAxEB/8QAFgABAQE$AME/8QAHBAAAgICAwAAAAAAAAAAAAAAAAECAxESITNR/8QAFQEBAQ$AAL/xAAVEQEB$AAAf/aAAwDAQACEQMRAD8ARjB1pLGuOWZ9a/RHpZIIkf/Z"},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: vue","type":"tag","color":"41b883","name":"vue","path":"/tag/vue/"},{"id":"tag: pwa","type":"tag","color":"5a0fc8","name":"pwa","path":"/tag/pwa/"},{"id":"tag: gridsome","type":"tag","color":"00a672","name":"gridsome","path":"/tag/gridsome/"}]}},{"node":{"id":"151","title":"How I compile and install Vim with +python3","path":"/post/compile-vim-python3/","summary":"\n

It's known to all that compiling is hard. Just record it.

\n","createdAt":"2020-11-15T06:36:25.000Z","lastEditedAt":"2020-11-15T06:53:18.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: vim","type":"tag","color":"007f00","name":"vim","path":"/tag/vim/"}]}},{"node":{"id":"156","title":"我是如何获得微信内置表情的","path":"/post/get-wechat-emoji/","summary":"\n

授人以鱼,不如授人以渔。虽然百度出来有很多下载资源,但并没有讲怎么获得的(毕竟天朝特色)

\n","createdAt":"2020-12-28T09:46:53.000Z","lastEditedAt":"2020-12-28T09:58:39.000Z","image":null,"imageLazy":"","logo":{"src":"/blog/img/b0991834.png","lazySrc":"AJAAkDASIAAhEBAxEB/8QAFwAAAwE$AIDBP/EABwQAAICAgMAAAAAAAAAAAAAAAABAgMREiIjMf/EABQBAQ$AAD/xAAUEQE$AAA/9oADAMBAAIRAxEAPwCtdcXCHXFrVZ4mDCBeCgf/2Q=="},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: android","type":"tag","color":"3ddc84","name":"android","path":"/tag/android/"}]}},{"node":{"id":"160","title":"NPM Registry: What If I Cannot Reach It?","path":"/post/npm-reg/","summary":"\n

The story of mirrors, ipv6, and yarn 2

\n","createdAt":"2021-02-01T08:18:23.000Z","lastEditedAt":"2021-02-01T08:18:23.000Z","image":"/blog/img/b056d3fb.jpg","imageLazy":"AEAAkDASIAAhEBAxEB/8QAFQABAQ$AAL/xAAXEAADAQ$AREC/8QAFQEBAQ$AAH/xAAVEQEB$AAEf/aAAwDAQACEQMRAD8AvLkiRdAIV//Z","logo":null,"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: network","type":"tag","color":"A5F306","name":"network","path":"/tag/network/"}]}},{"node":{"id":"162","title":"VSCode 与如何配置 VSCode","path":"/post/vscode-setup/","summary":"\n

这是一篇(尽量)新手向的文章,以 VSCode, Python, JavaScript (Vue) 为例,介绍编辑器的功能和配置。

\n

当然,没说必须选择 VSCode,大可选择 JetBrains 那一套。但是由于笔者平日折腾跨越的语言、领域较多(且前端居多),VSCode 是更好的选择,故只对 VSCode 有少量研究。

\n","createdAt":"2021-02-14T02:30:06.000Z","lastEditedAt":"2021-02-14T02:45:24.000Z","image":"/blog/img/be39e231.png","imageLazy":"ADAAkDASIAAhEBAxEB/8QAFQABAQ$AAH/xAAUEAE$AAA/8QAFQEBAQ$AAH/xAAUEQE$AAA/9oADAMBAAIRAxEAPwCAKP/Z","logo":{"src":"/blog/img/be39e231.png","lazySrc":"ADAAkDASIAAhEBAxEB/8QAFQABAQ$AAH/xAAUEAE$AAA/8QAFQEBAQ$AAH/xAAUEQE$AAA/9oADAMBAAIRAxEAPwCAKP/Z"},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: vscode","type":"tag","color":"0083d0","name":"vscode","path":"/tag/vscode/"}]}},{"node":{"id":"163","title":"A Simple Way to Use More Addons (e.g. Tampermonkey) on Newest Fenix (FireFox Android)","path":"/post/compile-fenix/","summary":"\n

Yes, very simple and... violent. It's about compiling your own copy of Fenix.

\n","createdAt":"2021-03-07T09:34:15.000Z","lastEditedAt":"2022-01-27T12:29:12.000Z","image":null,"imageLazy":"","logo":{"src":"/blog/img/b0991834.png","lazySrc":"AJAAkDASIAAhEBAxEB/8QAFwAAAwE$AIDBP/EABwQAAICAgMAAAAAAAAAAAAAAAABAgMREiIjMf/EABQBAQ$AAD/xAAUEQE$AAA/9oADAMBAAIRAxEAPwCtdcXCHXFrVZ4mDCBeCgf/2Q=="},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: android","type":"tag","color":"3ddc84","name":"android","path":"/tag/android/"}]}},{"node":{"id":"164","title":"数据库查询性能优化手记","path":"/post/sql-query-perf/","summary":"\n

简单的手记,没有什么系统性。相关的变量名是经过简单替换的,以免关注点偏离(bushi

\n","createdAt":"2021-03-09T10:08:35.000Z","lastEditedAt":"2021-08-12T11:59:56.000Z","image":null,"imageLazy":"","logo":{"src":"/blog/img/a1ba5d33.jpg","lazySrc":"AGAAkDASIAAhEBAxEB/8QAFQABAQ$AAT/xAAXEAEBAQEAAAAAAAAAAAAAAAAAIREx/8QAFAEB$AAAP/EABQRAQ$AAD/2gAMAwEAAhEDEQA/AK7vYAD/2Q=="},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: mysql","type":"tag","color":"f09011","name":"mysql","path":"/tag/mysql/"}]}},{"node":{"id":"172","title":"Value of Open Source Software: A Personal View","path":"/post/value-of-oss/","summary":"\n

This article was submitted as an English class essay. Originally, I wanted to talk about open source landscape in China, but I found that just explaining what open source is takes hundreds of words. (这就是你把本来想写的部分鸽掉的理由?

\n","createdAt":"2021-06-25T10:35:34.000Z","lastEditedAt":"2021-06-25T10:35:34.000Z","image":null,"imageLazy":"","logo":{"src":"/blog/img/fa50d2ff.png","lazySrc":"AJAAkDASIAAhEBAxEB/8QAFwAAAwE$AEDBP/EAB0QAAICAQUAAAAAAAAAAAAAAAACARIDERMhM1H/xAAUAQE$AAA/8QAFREBAQ$AAH/2gAMAwEAAhEDEQA/ACsVWFVduvMmfTF7Il6WJiD/2Q=="},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"blog: moments","type":"blog","color":"3dae38","name":"moments","path":"/blog/moments/"}]}},{"node":{"id":"173","title":"`skipWaiting()` with `StaleWhileRevalidate` the right way","path":"/post/pwa-skipwaiting/","summary":"\n

It is common to use workbox StaleWhileRevalidate strategy to cache resources which may take some time to fetch. Usually the resource needs to be updated but not immediately. However if the resource request takes too much time to complete, service worker's life cycle and some functionality may be impacted, especially self.skipWaiting().

\n","createdAt":"2021-06-28T04:31:10.000Z","lastEditedAt":"2021-06-28T10:11:41.000Z","image":null,"imageLazy":"","logo":{"src":"/blog/img/32740c5b.png","lazySrc":"ADAAkDASIAAhEBAxEB/8QAFgABAQE$AEE/8QAFxABAQEBAAAAAAAAAAAAAAAAAFEBEf/EABUBAQE$AAB/8QAFREBAQ$AAH/2gAMAwEAAhEDEQA/AM1XmQFo/9k="},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: pwa","type":"tag","color":"5a0fc8","name":"pwa","path":"/tag/pwa/"}]}}]}}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"label":{"id":"blog: programming","name":"programming","type":"blog","belongsTo":{"edges":[{"node":{"id":"18","title":"Showcase","path":"/post/showcase/","summary":"\n

测试一下 Markdown 渲染的效果 网页的 CSS

\n","createdAt":"2019-12-28T09:22:09.000Z","lastEditedAt":"2020-07-22T07:54:13.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"}]}},{"node":{"id":"22","title":"MySQL 存储 Emoji","path":"/post/mysql-emoji/","summary":"\n

最近需要使用 SQLAlchemy 存弹幕的内容,但是遇到了存 emoji 的问题。

\n","createdAt":"2019-12-23T14:21:24.000Z","lastEditedAt":"2020-09-26T07:41:43.000Z","image":null,"imageLazy":"","logo":{"src":"/blog/img/a1ba5d33.jpg","lazySrc":"AGAAkDASIAAhEBAxEB/8QAFQABAQ$AAT/xAAXEAEBAQEAAAAAAAAAAAAAAAAAIREx/8QAFAEB$AAAP/EABQRAQ$AAD/2gAMAwEAAhEDEQA/AK7vYAD/2Q=="},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: mysql","type":"tag","color":"f09011","name":"mysql","path":"/tag/mysql/"}]}},{"node":{"id":"23","title":"C Review","path":"/post/c-review/","summary":"\n

计概基础知识复习手记

\n","createdAt":"2019-12-29T10:22:51.000Z","lastEditedAt":"2020-06-30T07:52:36.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: c","type":"tag","color":"aaaaaa","name":"c","path":"/tag/c/"}]}},{"node":{"id":"25","title":"TOC in Hugo","path":"/post/hugo-toc/","summary":"\n

What a challenge to build toc in hugo!

\n","createdAt":"2019-12-31T15:17:22.000Z","lastEditedAt":"2020-06-30T07:33:17.000Z","image":null,"imageLazy":"","logo":{"src":"/blog/img/706fec00.png","lazySrc":"AJAAkDASIAAhEBAxEB/8QAFgABAQE$gEE/8QAGhAAAgMBAQAAAAAAAAAAAAAAABEBAgMzcf/EABUBAQE$AEC/8QAFREBAQ$AAH/2gAMAwEAAhEDEQA/ADSmU4uU0Z1Ukc59CK7X/9k="},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: hugo","type":"tag","color":"ff3db4","name":"hugo","path":"/tag/hugo/"}]}},{"node":{"id":"26","title":"WTFs in C","path":"/post/wtf-c/","summary":"\n

为了备战变态的计算概论考试, 不得已要研究一下 C 的奇奇怪怪的东西

\n","createdAt":"2020-01-01T08:59:08.000Z","lastEditedAt":"2020-06-30T07:43:45.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: c","type":"tag","color":"aaaaaa","name":"c","path":"/tag/c/"}]}},{"node":{"id":"28","title":"谜之 Hugo | AC's Blog","path":"/post/wtf-hugo/","summary":"","createdAt":"2020-01-03T13:19:16.000Z","lastEditedAt":"2020-11-02T04:31:46.000Z","image":null,"imageLazy":"","logo":{"src":"/blog/img/706fec00.png","lazySrc":"AJAAkDASIAAhEBAxEB/8QAFgABAQE$gEE/8QAGhAAAgMBAQAAAAAAAAAAAAAAABEBAgMzcf/EABUBAQE$AEC/8QAFREBAQ$AAH/2gAMAwEAAhEDEQA/ADSmU4uU0Z1Ukc59CK7X/9k="},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: hugo","type":"tag","color":"ff3db4","name":"hugo","path":"/tag/hugo/"}]}},{"node":{"id":"29","title":"我为什么要选 Flask?","path":"/post/why-flask/","summary":"\n

注意,这里不是说 Flask 有多好,而是。。用 Flask 用到怀疑人生!

\n","createdAt":"2020-01-03T13:00:00.000Z","lastEditedAt":"2021-02-22T08:02:38.000Z","image":null,"imageLazy":"","logo":{"src":"/blog/img/eb62ca24.png","lazySrc":"AIAAkDASIAAhEBAxEB/8QAFQABAQ$AAT/xAAUEAE$AAA/8QAFAEB$AAAP/EABQRAQ$AAD/2gAMAwEAAhEDEQA/AIgAf//Z"},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: python","type":"tag","color":"3c78a8","name":"python","path":"/tag/python/"},{"id":"tag: flask","type":"tag","color":"000000","name":"flask","path":"/tag/flask/"}]}},{"node":{"id":"30","title":"Change Win10 CMD Fonts","path":"/post/win-cmd-font/","summary":"\n

It took me quite a lot time to try to solve this...

\n

Note: this answer only works in non-English regions

\n","createdAt":"2020-01-03T07:39:10.000Z","lastEditedAt":"2020-07-03T01:31:22.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"}]}},{"node":{"id":"37","title":"Setup Vim Airline (in Termux)","path":"/post/vim-ariline/","summary":"","createdAt":"2019-02-09T00:00:00.000Z","lastEditedAt":"2021-02-22T01:59:03.000Z","image":"/blog/img/5a6f75bf.png","imageLazy":"ACAAkDASIAAhEBAxEB/8QAFQABAQ$AAP/xAAZEAEAAgM$RFDUYH/xAAUAQE$AAB/8QAFREBAQ$AAH/2gAMAwEAAhEDEQA/AJZur3OwFL//2Q==","logo":null,"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: vim","type":"tag","color":"007f00","name":"vim","path":"/tag/vim/"}]}},{"node":{"id":"51","title":"Deploying Hugo with CircleCI","path":"/post/hugo-circle-ci/","summary":"\n

Building blog sites manually is tedious and often leads to mistakes, especially when hosting on master/docs

\n","createdAt":"2019-10-27T12:12:30.000Z","lastEditedAt":"2020-06-30T07:28:06.000Z","image":null,"imageLazy":"","logo":{"src":"/blog/img/706fec00.png","lazySrc":"AJAAkDASIAAhEBAxEB/8QAFgABAQE$gEE/8QAGhAAAgMBAQAAAAAAAAAAAAAAABEBAgMzcf/EABUBAQE$AEC/8QAFREBAQ$AAH/2gAMAwEAAhEDEQA/ADSmU4uU0Z1Ukc59CK7X/9k="},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: hugo","type":"tag","color":"ff3db4","name":"hugo","path":"/tag/hugo/"}]}},{"node":{"id":"55","title":"Damning GPG Key","path":"/post/damn-gpg/","summary":"\n

Why GPG key keeps annoying me! 整天 Fail 有意思吗?

\n","createdAt":"2020-01-07T12:16:11.000Z","lastEditedAt":"2020-07-17T14:07:41.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"}]}},{"node":{"id":"64","title":"Jupyter on Raspberry Pi","path":"/post/jupyter-raspi/","summary":"","createdAt":"2020-02-26T12:20:29.000Z","lastEditedAt":"2020-07-01T12:33:55.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: raspi","type":"tag","color":"c31c4a","name":"raspi","path":"/tag/raspi/"},{"id":"tag: jupyter","type":"tag","color":"f37626","name":"jupyter","path":"/tag/jupyter/"}]}},{"node":{"id":"65","title":"Tricks about Raspberry Pi","path":"/post/raspi-tricks/","summary":"\n

Handy tricks when playing with raspberry pi. You should know them.

\n","createdAt":"2020-01-21T08:29:47.000Z","lastEditedAt":"2020-06-24T06:04:38.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: raspi","type":"tag","color":"c31c4a","name":"raspi","path":"/tag/raspi/"}]}},{"node":{"id":"66","title":"Working with tmux and SSH","path":"/post/tmux-ssh/","summary":"\n

Useful tips when SSH in remote device which uses tmux

\n","createdAt":"2020-01-21T13:55:36.000Z","lastEditedAt":"2020-07-03T01:54:18.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: tmux","type":"tag","color":"1bb91f","name":"tmux","path":"/tag/tmux/"}]}},{"node":{"id":"67","title":"Introduce and Setup Tmux","path":"/post/tmux-setup/","summary":"\n

Setup and introduce Tmux so that I can use it

\n","createdAt":"2020-01-21T13:55:42.000Z","lastEditedAt":"2020-07-10T12:46:29.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: tmux","type":"tag","color":"1bb91f","name":"tmux","path":"/tag/tmux/"}]}},{"node":{"id":"68","title":"用 Vue 做 PWA(一):开始","path":"/post/vue-pwa-1/","summary":"\n

Vue 党快速上手 PWA

\n","createdAt":"2020-01-30T07:59:20.000Z","lastEditedAt":"2020-09-04T23:59:48.000Z","image":"/blog/img/bda492f1.png","imageLazy":"ADAAkDASIAAhEBAxEB/8QAFgABAQE$AEE/8QAGRAAAgMB$ECEVGR/8QAFAEB$AAAv/EABYRAQEB$ABAv/aAAwDAQACEQMRAD8A0SVQdaToAaWX/9k=","logo":{"src":"/blog/img/32740c5b.png","lazySrc":"ADAAkDASIAAhEBAxEB/8QAFgABAQE$AEE/8QAFxABAQEBAAAAAAAAAAAAAAAAAFEBEf/EABUBAQE$AAB/8QAFREBAQ$AAH/2gAMAwEAAhEDEQA/AM1XmQFo/9k="},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: vue","type":"tag","color":"41b883","name":"vue","path":"/tag/vue/"},{"id":"tag: pwa","type":"tag","color":"5a0fc8","name":"pwa","path":"/tag/pwa/"},{"id":"series: vue-pwa","type":"series","color":"41b883","name":"vue-pwa","path":"/series/vue-pwa/"}]}},{"node":{"id":"69","title":"异步 & 异步获取命令输出","path":"/post/python-async-subprocess/","summary":"\n

与 JavaScript 的异步对比,初步了解 Python 的异步,并通过异步获取命令输出“实战”

\n","createdAt":"2020-02-05T10:53:16.000Z","lastEditedAt":"2020-06-30T08:58:31.000Z","image":null,"imageLazy":"","logo":{"src":"/blog/img/7338eb28.jpg","lazySrc":"AJAAkDASIAAhEBAxEB/8QAFwAAAwE$QIDBP/EAB8QAAIBAgcAAAAAAAAAAAAAAAABAgMSEzNBUWGBkf/EABQBAQ$AAH/xAAUEQE$AAA/9oADAMBAAIRAxEAPwCjcXFuTd2r2FxKvPgauf2jUIf/2Q=="},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: python","type":"tag","color":"3c78a8","name":"python","path":"/tag/python/"}]}},{"node":{"id":"70","title":"From Python to JavaScript","path":"/post/from-python-to-js/","summary":"\n

Although the title Top Ten Mistakes Python Programmers Make When Learning JavaScript may sound better, I have only noticed a few mistakes now. And I will update this post when I make more mistakes 😄

\n","createdAt":"2020-02-07T04:23:51.000Z","lastEditedAt":"2021-02-22T08:52:30.000Z","image":null,"imageLazy":"","logo":{"src":"/blog/img/db760f5a.png","lazySrc":"AJAAkDASIAAhEBAxEB/8QAFgABAQE$gAE/8QAHBAAAgICAwAAAAAAAAAAAAAAAAIBERMxM0Fx/8QAFAEB$AAAf/EABQRAQ$AAD/2gAMAwEAAhEDEQA/ACsJi1FdmelKOJvQAH//2Q=="},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: javascript","type":"tag","color":"f1da4e","name":"javascript","path":"/tag/javascript/"}]}},{"node":{"id":"71","title":"Mysql in WSL","path":"/post/mysql-in-wsl/","summary":"\n

😜 TL;DR

\n

查看日志/var/log/mysql/error.log,上互联网搜

\n","createdAt":"2020-02-07T08:59:02.000Z","lastEditedAt":"2020-06-24T05:55:14.000Z","image":null,"imageLazy":"","logo":{"src":"/blog/img/a1ba5d33.jpg","lazySrc":"AGAAkDASIAAhEBAxEB/8QAFQABAQ$AAT/xAAXEAEBAQEAAAAAAAAAAAAAAAAAIREx/8QAFAEB$AAAP/EABQRAQ$AAD/2gAMAwEAAhEDEQA/AK7vYAD/2Q=="},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: mysql","type":"tag","color":"f09011","name":"mysql","path":"/tag/mysql/"}]}},{"node":{"id":"73","title":"数算环境准备","path":"/post/dsa-init/","summary":"\n

g++ & VS Code 党对《数据结构与算法》的环境准备

\n

有必要解释一下名称的由来:Data Structure and Algorithm

\n","createdAt":"2020-02-19T08:50:19.000Z","lastEditedAt":"2020-07-01T13:00:05.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: c","type":"tag","color":"aaaaaa","name":"c","path":"/tag/c/"}]}},{"node":{"id":"75","title":"列出 USTC Docker 镜像的标签","path":"/post/docker-tag/","summary":"\n

不再为网络条件发愁!

\n","createdAt":"2020-02-28T13:38:39.000Z","lastEditedAt":"2020-06-21T12:56:24.000Z","image":null,"imageLazy":"","logo":{"src":"/blog/img/a41c7f22.png","lazySrc":"AJAAkDASIAAhEBAxEB/8QAFgABAQE$gME/8QAHhAAAgAGAwAAAAAAAAAAAAAAAAECAxESITJhgZH/xAAUAQE$AAB/8QAFREBAQ$ACH/2gAMAwEAAhEDEQA/AM6oliLoVsvj0mtQCK//2Q=="},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: docker","type":"tag","color":"71cfff","name":"docker","path":"/tag/docker/"}]}},{"node":{"id":"76","title":"Install Wolfram With Jupyter","path":"/post/jupyter-wolfram/","summary":"","createdAt":"2020-02-26T09:50:09.000Z","lastEditedAt":"2020-07-01T12:28:36.000Z","image":null,"imageLazy":"","logo":{"src":"/blog/img/fceb1de9.png","lazySrc":"AJAAkDASIAAhEBAxEB/8QAFgABAQE$QIE/8QAHhAAAgEDBQAAAAAAAAAAAAAAAAERAhITITJhYpH/xAAUAQE$AAB/8QAFBEB$AAAP/aAAwDAQACEQMRAD8AzKLZS0fAY+9PoPYiQL//2Q=="},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: raspi","type":"tag","color":"c31c4a","name":"raspi","path":"/tag/raspi/"},{"id":"tag: jupyter","type":"tag","color":"f37626","name":"jupyter","path":"/tag/jupyter/"},{"id":"tag: wolfram","type":"tag","color":"d70026","name":"wolfram","path":"/tag/wolfram/"}]}},{"node":{"id":"80","title":"Vue 加 Hammer 处理手势","path":"/post/vue-hammer/","summary":"\n

接近原生 App 体验,手势是少不了的。但是无论是 Vue 还是 Vuetify 对手势的支持并不好,于是需要 Hammer.js 支持。

\n","createdAt":"2020-01-27T08:33:10.000Z","lastEditedAt":"2020-07-03T02:20:23.000Z","image":null,"imageLazy":"","logo":{"src":"/blog/img/f846bbd9.png","lazySrc":"AJAAkDASIAAhEBAxEB/8QAFgABAQE$wAC/8QAGxABAAICAwAAAAAAAAAAAAAAAQACERIhUfD/xAAVAQEB$AAAf/EABURAQE$AAR/9oADAMBAAIRAxEAPwABq03QycY7h728TMoSP//Z"},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: vue","type":"tag","color":"41b883","name":"vue","path":"/tag/vue/"}]}},{"node":{"id":"87","title":"用 Vue 做 PWA(二):Runtime Caching","path":"/post/vue-pwa-2/","summary":"\n

本系列已经很久没更新了,在此讨论实践过程中遇到的 Runtime Caching 的一些问题。主要注意点是在处理跨域请求上。

\n","createdAt":"2020-04-18T04:49:20.000Z","lastEditedAt":"2021-03-13T00:27:46.000Z","image":"/blog/img/bda492f1.png","imageLazy":"ADAAkDASIAAhEBAxEB/8QAFgABAQE$AEE/8QAGRAAAgMB$ECEVGR/8QAFAEB$AAAv/EABYRAQEB$ABAv/aAAwDAQACEQMRAD8A0SVQdaToAaWX/9k=","logo":{"src":"/blog/img/32740c5b.png","lazySrc":"ADAAkDASIAAhEBAxEB/8QAFgABAQE$AEE/8QAFxABAQEBAAAAAAAAAAAAAAAAAFEBEf/EABUBAQE$AAB/8QAFREBAQ$AAH/2gAMAwEAAhEDEQA/AM1XmQFo/9k="},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: vue","type":"tag","color":"41b883","name":"vue","path":"/tag/vue/"},{"id":"tag: pwa","type":"tag","color":"5a0fc8","name":"pwa","path":"/tag/pwa/"},{"id":"series: vue-pwa","type":"series","color":"41b883","name":"vue-pwa","path":"/series/vue-pwa/"}]}},{"node":{"id":"88","title":"JS 无依赖获取阴历","path":"/post/js-yinli/","summary":"\n

Intl 是个好东西,但是还不是很强大,浏览器支持上也有 Node 和 Android 的小缺憾。

\n","createdAt":"2020-02-08T05:46:36.000Z","lastEditedAt":"2020-07-03T02:55:14.000Z","image":null,"imageLazy":"","logo":{"src":"/blog/img/db760f5a.png","lazySrc":"AJAAkDASIAAhEBAxEB/8QAFgABAQE$gAE/8QAHBAAAgICAwAAAAAAAAAAAAAAAAIBERMxM0Fx/8QAFAEB$AAAf/EABQRAQ$AAD/2gAMAwEAAhEDEQA/ACsJi1FdmelKOJvQAH//2Q=="},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: javascript","type":"tag","color":"f1da4e","name":"javascript","path":"/tag/javascript/"}]}},{"node":{"id":"96","title":"Writing Blog with GitHub Issue and Vue Apollo","path":"/post/vue-iblog/","summary":"\n

Some tricks when using vue-apollo to communicate with GitHub API v4

\n","createdAt":"2020-04-24T00:00:00.000Z","lastEditedAt":"2020-09-04T07:47:20.000Z","image":"/blog/img/e4670a56.png","imageLazy":"ADAAkDASIAAhEBAxEB/8QAFgABAQE$AEE/8QAFRABAQ$ABH/xAAUAQE$AAA/8QAFBEB$AAAP/aAAwDAQACEQMRAD8A2RQB/9k=","logo":{"src":"/blog/img/f846bbd9.png","lazySrc":"AJAAkDASIAAhEBAxEB/8QAFgABAQE$wAC/8QAGxABAAICAwAAAAAAAAAAAAAAAQACERIhUfD/xAAVAQEB$AAAf/EABURAQE$AAR/9oADAMBAAIRAxEAPwABq03QycY7h728TMoSP//Z"},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: vue","type":"tag","color":"41b883","name":"vue","path":"/tag/vue/"}]}},{"node":{"id":"97","title":"Two Points to Notice when Upgrading Ubuntu","path":"/post/upgrade-ubuntu/","summary":"\n

Ever feel frustated when a new release is available but it keeps saying no release found?

\n","createdAt":"2020-04-26T00:00:00.000Z","lastEditedAt":"2020-06-21T12:55:54.000Z","image":null,"imageLazy":"","logo":{"src":"/blog/img/f0f4d288.png","lazySrc":"AJAAkDASIAAhEBAxEB/8QAFQABAQ$AAP/xAAeEAACAQMFAAAAAAAAAAAAAAAAAQIDERITITNRof/EABQBAQ$AAL/xAAVEQEB$ABAP/aAAwDAQACEQMRAD8ARjDT2SasQxpd+iPAyIQmt//Z"},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: linux","type":"tag","color":"de4815","name":"linux","path":"/tag/linux/"}]}},{"node":{"id":"98","title":"Setup GitHub Action Cache the Right Way","path":"/post/gh-action-cache/","summary":"\n

What on earth is happening? Why my cache never hits?

\n","createdAt":"2020-06-21T09:16:37.000Z","lastEditedAt":"2020-07-06T10:52:21.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: gh-action","type":"tag","color":"006b75","name":"gh-action","path":"/tag/gh-action/"}]}},{"node":{"id":"109","title":"聊一聊 React 的 virtual scroll","path":"/post/react-virtual-scroll/","summary":"\n

也没啥高见,重复一下网上现有的资料而已

\n","createdAt":"2020-07-10T09:58:04.000Z","lastEditedAt":"2020-07-11T01:08:48.000Z","image":null,"imageLazy":"","logo":{"src":"/blog/img/db760f5a.png","lazySrc":"AJAAkDASIAAhEBAxEB/8QAFgABAQE$gAE/8QAHBAAAgICAwAAAAAAAAAAAAAAAAIBERMxM0Fx/8QAFAEB$AAAf/EABQRAQ$AAD/2gAMAwEAAhEDEQA/ACsJi1FdmelKOJvQAH//2Q=="},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: javascript","type":"tag","color":"f1da4e","name":"javascript","path":"/tag/javascript/"}]}},{"node":{"id":"140","title":"Getting Started with Minecraft Fabric Without Any Java Knowledge","path":"/post/fabricmc/","summary":"\n

Thats challenging but possible

\n","createdAt":"2020-08-08T03:38:04.000Z","lastEditedAt":"2021-02-22T08:40:43.000Z","image":null,"imageLazy":"","logo":{"src":"/blog/img/5ac73a6e.png","lazySrc":"AJAAkDASIAAhEBAxEB/8QAFQABAQ$AQT/xAAdEAACAQQDAAAAAAAAAAAAAAAAAgEREjFRE0Gh/8QAFQEBAQ$AAH/xAAUEQE$AAA/9oADAMBAAIRAxEAPwCVaJF2W6jQcjb8BsyAR//Z"},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: minecraft","type":"tag","color":"674a34","name":"minecraft","path":"/tag/minecraft/"}]}},{"node":{"id":"143","title":"手机端 GitHub Markdown 中文粗体显示的问题","path":"/post/md-cn-bold/","summary":"\n

这鬼问题应该只发生在安卓机上

\n","createdAt":"2020-08-22T00:53:07.000Z","lastEditedAt":"2020-08-22T01:07:36.000Z","image":null,"imageLazy":"","logo":{"src":"/blog/img/b0991834.png","lazySrc":"AJAAkDASIAAhEBAxEB/8QAFwAAAwE$AIDBP/EABwQAAICAgMAAAAAAAAAAAAAAAABAgMREiIjMf/EABQBAQ$AAD/xAAUEQE$AAA/9oADAMBAAIRAxEAPwCtdcXCHXFrVZ4mDCBeCgf/2Q=="},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: android","type":"tag","color":"3ddc84","name":"android","path":"/tag/android/"}]}},{"node":{"id":"145","title":"Use pipenv and poetry in a way that works","path":"/post/pipenv-and-poetry/","summary":"\n

Strange as the title is, they just don't work conveniently \"out of box\"

\n","createdAt":"2020-08-23T07:29:31.000Z","lastEditedAt":"2020-09-02T01:59:37.000Z","image":null,"imageLazy":"","logo":{"src":"/blog/img/7338eb28.jpg","lazySrc":"AJAAkDASIAAhEBAxEB/8QAFwAAAwE$QIDBP/EAB8QAAIBAgcAAAAAAAAAAAAAAAABAgMSEzNBUWGBkf/EABQBAQ$AAH/xAAUEQE$AAA/9oADAMBAAIRAxEAPwCjcXFuTd2r2FxKvPgauf2jUIf/2Q=="},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: python","type":"tag","color":"3c78a8","name":"python","path":"/tag/python/"}]}},{"node":{"id":"147","title":"用 Vue 做 PWA (三):理解生命周期","path":"/post/vue-pwa-3/","summary":"\n

虽然前两节的内容足以写出能用的 service worker,但是如果深入细节,仍然会有一些“匪夷所思”的现象发生。

\n","createdAt":"2020-09-03T02:40:57.000Z","lastEditedAt":"2020-09-08T03:09:07.000Z","image":"/blog/img/bda492f1.png","imageLazy":"ADAAkDASIAAhEBAxEB/8QAFgABAQE$AEE/8QAGRAAAgMB$ECEVGR/8QAFAEB$AAAv/EABYRAQEB$ABAv/aAAwDAQACEQMRAD8A0SVQdaToAaWX/9k=","logo":{"src":"/blog/img/32740c5b.png","lazySrc":"ADAAkDASIAAhEBAxEB/8QAFgABAQE$AEE/8QAFxABAQEBAAAAAAAAAAAAAAAAAFEBEf/EABUBAQE$AAB/8QAFREBAQ$AAH/2gAMAwEAAhEDEQA/AM1XmQFo/9k="},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: vue","type":"tag","color":"41b883","name":"vue","path":"/tag/vue/"},{"id":"tag: pwa","type":"tag","color":"5a0fc8","name":"pwa","path":"/tag/pwa/"},{"id":"series: vue-pwa","type":"series","color":"41b883","name":"vue-pwa","path":"/series/vue-pwa/"}]}},{"node":{"id":"149","title":"Gridsome is not that Ready for PWA","path":"/post/gridsome-pwa/","summary":"\n

How I am tring to build a nice gridsome-generated site with PWA support

\n

虽然可以勉强并入“用 Vue 做 PWA”系列,但想着能让更多人看到,就还是用国际语言吧

\n","createdAt":"2020-09-16T03:22:59.000Z","lastEditedAt":"2021-07-03T13:06:53.000Z","image":null,"imageLazy":"","logo":{"src":"/blog/img/aadfb904.png","lazySrc":"AJAAkDASIAAhEBAxEB/8QAFgABAQE$AME/8QAHBAAAgICAwAAAAAAAAAAAAAAAAECAxESITNR/8QAFQEBAQ$AAL/xAAVEQEB$AAAf/aAAwDAQACEQMRAD8ARjB1pLGuOWZ9a/RHpZIIkf/Z"},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: vue","type":"tag","color":"41b883","name":"vue","path":"/tag/vue/"},{"id":"tag: pwa","type":"tag","color":"5a0fc8","name":"pwa","path":"/tag/pwa/"},{"id":"tag: gridsome","type":"tag","color":"00a672","name":"gridsome","path":"/tag/gridsome/"}]}},{"node":{"id":"151","title":"How I compile and install Vim with +python3","path":"/post/compile-vim-python3/","summary":"\n

It's known to all that compiling is hard. Just record it.

\n","createdAt":"2020-11-15T06:36:25.000Z","lastEditedAt":"2020-11-15T06:53:18.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: vim","type":"tag","color":"007f00","name":"vim","path":"/tag/vim/"}]}},{"node":{"id":"156","title":"我是如何获得微信内置表情的","path":"/post/get-wechat-emoji/","summary":"\n

授人以鱼,不如授人以渔。虽然百度出来有很多下载资源,但并没有讲怎么获得的(毕竟天朝特色)

\n","createdAt":"2020-12-28T09:46:53.000Z","lastEditedAt":"2020-12-28T09:58:39.000Z","image":null,"imageLazy":"","logo":{"src":"/blog/img/b0991834.png","lazySrc":"AJAAkDASIAAhEBAxEB/8QAFwAAAwE$AIDBP/EABwQAAICAgMAAAAAAAAAAAAAAAABAgMREiIjMf/EABQBAQ$AAD/xAAUEQE$AAA/9oADAMBAAIRAxEAPwCtdcXCHXFrVZ4mDCBeCgf/2Q=="},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: android","type":"tag","color":"3ddc84","name":"android","path":"/tag/android/"}]}},{"node":{"id":"160","title":"NPM Registry: What If I Cannot Reach It?","path":"/post/npm-reg/","summary":"\n

The story of mirrors, ipv6, and yarn 2

\n","createdAt":"2021-02-01T08:18:23.000Z","lastEditedAt":"2021-02-01T08:18:23.000Z","image":"/blog/img/b056d3fb.jpg","imageLazy":"AEAAkDASIAAhEBAxEB/8QAFQABAQ$AAL/xAAXEAADAQ$AREC/8QAFQEBAQ$AAH/xAAVEQEB$AAEf/aAAwDAQACEQMRAD8AvLkiRdAIV//Z","logo":null,"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: network","type":"tag","color":"A5F306","name":"network","path":"/tag/network/"}]}},{"node":{"id":"162","title":"VSCode 与如何配置 VSCode","path":"/post/vscode-setup/","summary":"\n

这是一篇(尽量)新手向的文章,以 VSCode, Python, JavaScript (Vue) 为例,介绍编辑器的功能和配置。

\n

当然,没说必须选择 VSCode,大可选择 JetBrains 那一套。但是由于笔者平日折腾跨越的语言、领域较多(且前端居多),VSCode 是更好的选择,故只对 VSCode 有少量研究。

\n","createdAt":"2021-02-14T02:30:06.000Z","lastEditedAt":"2021-02-14T02:45:24.000Z","image":"/blog/img/be39e231.png","imageLazy":"ADAAkDASIAAhEBAxEB/8QAFQABAQ$AAH/xAAUEAE$AAA/8QAFQEBAQ$AAH/xAAUEQE$AAA/9oADAMBAAIRAxEAPwCAKP/Z","logo":{"src":"/blog/img/be39e231.png","lazySrc":"ADAAkDASIAAhEBAxEB/8QAFQABAQ$AAH/xAAUEAE$AAA/8QAFQEBAQ$AAH/xAAUEQE$AAA/9oADAMBAAIRAxEAPwCAKP/Z"},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: vscode","type":"tag","color":"0083d0","name":"vscode","path":"/tag/vscode/"}]}},{"node":{"id":"163","title":"A Simple Way to Use More Addons (e.g. Tampermonkey) on Newest Fenix (FireFox Android)","path":"/post/compile-fenix/","summary":"\n

Yes, very simple and... violent. It's about compiling your own copy of Fenix.

\n","createdAt":"2021-03-07T09:34:15.000Z","lastEditedAt":"2022-01-27T12:29:12.000Z","image":null,"imageLazy":"","logo":{"src":"/blog/img/b0991834.png","lazySrc":"AJAAkDASIAAhEBAxEB/8QAFwAAAwE$AIDBP/EABwQAAICAgMAAAAAAAAAAAAAAAABAgMREiIjMf/EABQBAQ$AAD/xAAUEQE$AAA/9oADAMBAAIRAxEAPwCtdcXCHXFrVZ4mDCBeCgf/2Q=="},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: android","type":"tag","color":"3ddc84","name":"android","path":"/tag/android/"}]}},{"node":{"id":"164","title":"数据库查询性能优化手记","path":"/post/sql-query-perf/","summary":"\n

简单的手记,没有什么系统性。相关的变量名是经过简单替换的,以免关注点偏离(bushi

\n","createdAt":"2021-03-09T10:08:35.000Z","lastEditedAt":"2021-08-12T11:59:56.000Z","image":null,"imageLazy":"","logo":{"src":"/blog/img/a1ba5d33.jpg","lazySrc":"AGAAkDASIAAhEBAxEB/8QAFQABAQ$AAT/xAAXEAEBAQEAAAAAAAAAAAAAAAAAIREx/8QAFAEB$AAAP/EABQRAQ$AAD/2gAMAwEAAhEDEQA/AK7vYAD/2Q=="},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: mysql","type":"tag","color":"f09011","name":"mysql","path":"/tag/mysql/"}]}},{"node":{"id":"172","title":"Value of Open Source Software: A Personal View","path":"/post/value-of-oss/","summary":"\n

This article was submitted as an English class essay. Originally, I wanted to talk about open source landscape in China, but I found that just explaining what open source is takes hundreds of words. (这就是你把本来想写的部分鸽掉的理由?

\n","createdAt":"2021-06-25T10:35:34.000Z","lastEditedAt":"2021-06-25T10:35:34.000Z","image":null,"imageLazy":"","logo":{"src":"/blog/img/fa50d2ff.png","lazySrc":"AJAAkDASIAAhEBAxEB/8QAFwAAAwE$AEDBP/EAB0QAAICAQUAAAAAAAAAAAAAAAACARIDERMhM1H/xAAUAQE$AAA/8QAFREBAQ$AAH/2gAMAwEAAhEDEQA/ACsVWFVduvMmfTF7Il6WJiD/2Q=="},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"blog: moments","type":"blog","color":"3dae38","name":"moments","path":"/blog/moments/"}]}},{"node":{"id":"173","title":"`skipWaiting()` with `StaleWhileRevalidate` the right way","path":"/post/pwa-skipwaiting/","summary":"\n

It is common to use workbox StaleWhileRevalidate strategy to cache resources which may take some time to fetch. Usually the resource needs to be updated but not immediately. However if the resource request takes too much time to complete, service worker's life cycle and some functionality may be impacted, especially self.skipWaiting().

\n","createdAt":"2021-06-28T04:31:10.000Z","lastEditedAt":"2021-06-28T10:11:41.000Z","image":null,"imageLazy":"","logo":{"src":"/blog/img/32740c5b.png","lazySrc":"ADAAkDASIAAhEBAxEB/8QAFgABAQE$AEE/8QAFxABAQEBAAAAAAAAAAAAAAAAAFEBEf/EABUBAQE$AAB/8QAFREBAQ$AAH/2gAMAwEAAhEDEQA/AM1XmQFo/9k="},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: pwa","type":"tag","color":"5a0fc8","name":"pwa","path":"/tag/pwa/"}]}},{"node":{"id":"179","title":"Another Move of My Blog?","path":"/post/moving-blog-to-astro/","summary":"\n

It really needs a serious discussion.

\n","createdAt":"2022-04-06T04:00:10.000Z","lastEditedAt":"2022-05-01T08:55:31.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"}]}}]}}},"context":{}} \ No newline at end of file diff --git a/assets/data/index.json b/assets/data/index.json index eb8ed9fc6..469933764 100644 --- a/assets/data/index.json +++ b/assets/data/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"allLabel":{"edges":[{"node":{"name":"moments","description":"听说我不发朋友圈?","logo":"/blog/img/fa50d2ff.png","logoLazy":"AJAAkDASIAAhEBAxEB/8QAFwAAAwE$AEDBP/EAB0QAAICAQUAAAAAAAAAAAAAAAACARIDERMhM1H/xAAUAQE$AAA/8QAFREBAQ$AAH/2gAMAwEAAhEDEQA/ACsVWFVduvMmfTF7Il6WJiD/2Q==","path":"/blog/moments/"}},{"node":{"name":"cheatsheet","description":"One sheet to get them all!","logo":"/blog/img/2795820f.jpg","logoLazy":"AJAAkDASIAAhEBAxEB/8QAFgABAQE$AEE/8QAFhABAQE$BEh/8QAFAEB$AAAf/EABQRAQ$AAD/2gAMAwEAAhEDEQA/ANe1QBf/2Q==","path":"/blog/cheatsheet/"}},{"node":{"name":"programming","description":"Exciting or strange things for programming which worth noting","logo":"/blog/img/3a308f44.png","logoLazy":"AJAAkDASIAAhEBAxEB/8QAFgABAQE$QME/8QAHxAAAQMDBQAAAAAAAAAAAAAAAQACAxEhURITNEGh/8QAFAEB$AAAP/EABQRAQ$AAD/2gAMAwEAAhEDEQA/ABrI9gm9RYDo4UtMOfUs4j1nQf/Z","path":"/blog/programming/"}}]},"allPost":{"edges":[{"node":{"id":"178","title":"腾讯会议 Linux 客户端无法正常结束共享的 Workaround","path":"/post/wemeet-screen-share/","summary":"\n

就改下窗口状态的事儿

\n","createdAt":"2022-02-27T09:02:27.000Z","lastEditedAt":"2022-02-27T09:02:27.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: moments","type":"blog","color":"3dae38","name":"moments","path":"/blog/moments/"}]}},{"node":{"id":"163","title":"A Simple Way to Use More Addons (e.g. Tampermonkey) on Newest Fenix (FireFox Android)","path":"/post/compile-fenix/","summary":"\n

Yes, very simple and... violent. It's about compiling your own copy of Fenix.

\n","createdAt":"2021-03-07T09:34:15.000Z","lastEditedAt":"2022-01-27T12:29:12.000Z","image":null,"imageLazy":"","logo":{"src":"/blog/img/b0991834.png","lazySrc":"AJAAkDASIAAhEBAxEB/8QAFwAAAwE$AIDBP/EABwQAAICAgMAAAAAAAAAAAAAAAABAgMREiIjMf/EABQBAQ$AAD/xAAUEQE$AAA/9oADAMBAAIRAxEAPwCtdcXCHXFrVZ4mDCBeCgf/2Q=="},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: android","type":"tag","color":"3ddc84","name":"android","path":"/tag/android/"}]}},{"node":{"id":"77","title":"换源集合","path":"/post/pkg-source/","summary":"\n

各种换源,我要的都在这里了。针对天朝就不用国际语言了 😄

\n","createdAt":"2020-02-28T14:29:13.000Z","lastEditedAt":"2021-12-09T02:49:50.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: cheatsheet","type":"blog","color":"6655d6","name":"cheatsheet","path":"/blog/cheatsheet/"}]}}]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"allLabel":{"edges":[{"node":{"name":"moments","description":"听说我不发朋友圈?","logo":"/blog/img/fa50d2ff.png","logoLazy":"AJAAkDASIAAhEBAxEB/8QAFwAAAwE$AEDBP/EAB0QAAICAQUAAAAAAAAAAAAAAAACARIDERMhM1H/xAAUAQE$AAA/8QAFREBAQ$AAH/2gAMAwEAAhEDEQA/ACsVWFVduvMmfTF7Il6WJiD/2Q==","path":"/blog/moments/"}},{"node":{"name":"cheatsheet","description":"One sheet to get them all!","logo":"/blog/img/2795820f.jpg","logoLazy":"AJAAkDASIAAhEBAxEB/8QAFgABAQE$AEE/8QAFhABAQE$BEh/8QAFAEB$AAAf/EABQRAQ$AAD/2gAMAwEAAhEDEQA/ANe1QBf/2Q==","path":"/blog/cheatsheet/"}},{"node":{"name":"programming","description":"Exciting or strange things for programming which worth noting","logo":"/blog/img/3a308f44.png","logoLazy":"AJAAkDASIAAhEBAxEB/8QAFgABAQE$QME/8QAHxAAAQMDBQAAAAAAAAAAAAAAAQACAxEhURITNEGh/8QAFAEB$AAAP/EABQRAQ$AAD/2gAMAwEAAhEDEQA/ABrI9gm9RYDo4UtMOfUs4j1nQf/Z","path":"/blog/programming/"}}]},"allPost":{"edges":[{"node":{"id":"174","title":"Docker Cheatsheet","path":"/post/docker-cht/","summary":"\n

Docker commands are easily forgotten

\n

Above image from scmagazine.com

\n","createdAt":"2021-07-31T09:58:17.000Z","lastEditedAt":"2022-05-01T13:49:08.000Z","image":"/blog/img/6c88ced2.jpg","imageLazy":"AFAAkDASIAAhEBAxEB/8QAFQABAQ$AAL/xAAWEAEBAQ$ASL/xAAVAQEB$ACA//EABQRAQ$AAD/2gAMAwEAAhEDEQA/ALt2oDSf/9k=","logo":{"src":"/blog/img/a41c7f22.png","lazySrc":"AJAAkDASIAAhEBAxEB/8QAFgABAQE$gME/8QAHhAAAgAGAwAAAAAAAAAAAAAAAAECAxESITJhgZH/xAAUAQE$AAB/8QAFREBAQ$ACH/2gAMAwEAAhEDEQA/AM6oliLoVsvj0mtQCK//2Q=="},"labels":[{"id":"blog: cheatsheet","type":"blog","color":"6655d6","name":"cheatsheet","path":"/blog/cheatsheet/"},{"id":"tag: docker","type":"tag","color":"71cfff","name":"docker","path":"/tag/docker/"}]}},{"node":{"id":"179","title":"Another Move of My Blog?","path":"/post/moving-blog-to-astro/","summary":"\n

It really needs a serious discussion.

\n","createdAt":"2022-04-06T04:00:10.000Z","lastEditedAt":"2022-05-01T08:55:31.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"}]}},{"node":{"id":"180","title":"免费下网易云会员歌曲 MP3 竟如此简单?","path":"/post/netease-music-bp/","summary":"\n

我坦白,我是标题党

\n","createdAt":"2022-05-01T08:50:29.000Z","lastEditedAt":"2022-05-01T08:50:29.000Z","image":null,"imageLazy":"","logo":null,"labels":[{"id":"blog: moments","type":"blog","color":"3dae38","name":"moments","path":"/blog/moments/"}]}}]}},"context":{}} \ No newline at end of file diff --git a/assets/data/labels/index.json b/assets/data/labels/index.json index dd55a175d..7b65d798a 100644 --- a/assets/data/labels/index.json +++ b/assets/data/labels/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"allLabel":{"edges":[{"node":{"id":"tag: pwa","type":"tag","name":"pwa","color":"5a0fc8","path":"/tag/pwa/","belongsTo":{"totalCount":5}}},{"node":{"id":"tag: android","type":"tag","name":"android","color":"3ddc84","path":"/tag/android/","belongsTo":{"totalCount":3}}},{"node":{"id":"tag: gridsome","type":"tag","name":"gridsome","color":"00a672","path":"/tag/gridsome/","belongsTo":{"totalCount":1}}},{"node":{"id":"tag: flask","type":"tag","name":"flask","color":"000000","path":"/tag/flask/","belongsTo":{"totalCount":1}}},{"node":{"id":"tag: vscode","type":"tag","name":"vscode","color":"0083d0","path":"/tag/vscode/","belongsTo":{"totalCount":1}}},{"node":{"id":"tag: network","type":"tag","name":"network","color":"A5F306","path":"/tag/network/","belongsTo":{"totalCount":1}}},{"node":{"id":"tag: minecraft","type":"tag","name":"minecraft","color":"674a34","path":"/tag/minecraft/","belongsTo":{"totalCount":1}}},{"node":{"id":"tag: tmux","type":"tag","name":"tmux","color":"1bb91f","path":"/tag/tmux/","belongsTo":{"totalCount":2}}},{"node":{"id":"tag: jupyter","type":"tag","name":"jupyter","color":"f37626","path":"/tag/jupyter/","belongsTo":{"totalCount":3}}},{"node":{"id":"tag: wolfram","type":"tag","name":"wolfram","color":"d70026","path":"/tag/wolfram/","belongsTo":{"totalCount":1}}},{"node":{"id":"tag: python","type":"tag","name":"python","color":"3c78a8","path":"/tag/python/","belongsTo":{"totalCount":3}}},{"node":{"id":"series: vue-pwa","type":"series","name":"vue-pwa","color":"41b883","path":"/series/vue-pwa/","belongsTo":{"totalCount":3}}},{"node":{"id":"tag: c","type":"tag","name":"c","color":"aaaaaa","path":"/tag/c/","belongsTo":{"totalCount":3}}},{"node":{"id":"tag: hugo","type":"tag","name":"hugo","color":"ff3db4","path":"/tag/hugo/","belongsTo":{"totalCount":3}}},{"node":{"id":"blog: moments","type":"blog","name":"moments","color":"3dae38","path":"/blog/moments/","belongsTo":{"totalCount":14}}},{"node":{"id":"blog: cheatsheet","type":"blog","name":"cheatsheet","color":"6655d6","path":"/blog/cheatsheet/","belongsTo":{"totalCount":12}}},{"node":{"id":"tag: vim","type":"tag","name":"vim","color":"007f00","path":"/tag/vim/","belongsTo":{"totalCount":3}}},{"node":{"id":"tag: raspi","type":"tag","name":"raspi","color":"c31c4a","path":"/tag/raspi/","belongsTo":{"totalCount":3}}},{"node":{"id":"tag: mysql","type":"tag","name":"mysql","color":"f09011","path":"/tag/mysql/","belongsTo":{"totalCount":3}}},{"node":{"id":"tag: javascript","type":"tag","name":"javascript","color":"f1da4e","path":"/tag/javascript/","belongsTo":{"totalCount":3}}},{"node":{"id":"tag: gh-action","type":"tag","name":"gh-action","color":"006b75","path":"/tag/gh-action/","belongsTo":{"totalCount":1}}},{"node":{"id":"tag: linux","type":"tag","name":"linux","color":"de4815","path":"/tag/linux/","belongsTo":{"totalCount":2}}},{"node":{"id":"tag: vue","type":"tag","name":"vue","color":"41b883","path":"/tag/vue/","belongsTo":{"totalCount":6}}},{"node":{"id":"tag: docker","type":"tag","name":"docker","color":"71cfff","path":"/tag/docker/","belongsTo":{"totalCount":2}}},{"node":{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/","belongsTo":{"totalCount":42}}}]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"allLabel":{"edges":[{"node":{"id":"tag: pwa","type":"tag","name":"pwa","color":"5a0fc8","path":"/tag/pwa/","belongsTo":{"totalCount":5}}},{"node":{"id":"tag: android","type":"tag","name":"android","color":"3ddc84","path":"/tag/android/","belongsTo":{"totalCount":3}}},{"node":{"id":"tag: gridsome","type":"tag","name":"gridsome","color":"00a672","path":"/tag/gridsome/","belongsTo":{"totalCount":1}}},{"node":{"id":"tag: flask","type":"tag","name":"flask","color":"000000","path":"/tag/flask/","belongsTo":{"totalCount":1}}},{"node":{"id":"tag: vscode","type":"tag","name":"vscode","color":"0083d0","path":"/tag/vscode/","belongsTo":{"totalCount":1}}},{"node":{"id":"tag: network","type":"tag","name":"network","color":"A5F306","path":"/tag/network/","belongsTo":{"totalCount":1}}},{"node":{"id":"tag: minecraft","type":"tag","name":"minecraft","color":"674a34","path":"/tag/minecraft/","belongsTo":{"totalCount":1}}},{"node":{"id":"tag: tmux","type":"tag","name":"tmux","color":"1bb91f","path":"/tag/tmux/","belongsTo":{"totalCount":2}}},{"node":{"id":"tag: jupyter","type":"tag","name":"jupyter","color":"f37626","path":"/tag/jupyter/","belongsTo":{"totalCount":3}}},{"node":{"id":"tag: wolfram","type":"tag","name":"wolfram","color":"d70026","path":"/tag/wolfram/","belongsTo":{"totalCount":1}}},{"node":{"id":"tag: python","type":"tag","name":"python","color":"3c78a8","path":"/tag/python/","belongsTo":{"totalCount":3}}},{"node":{"id":"series: vue-pwa","type":"series","name":"vue-pwa","color":"41b883","path":"/series/vue-pwa/","belongsTo":{"totalCount":3}}},{"node":{"id":"tag: c","type":"tag","name":"c","color":"aaaaaa","path":"/tag/c/","belongsTo":{"totalCount":3}}},{"node":{"id":"tag: hugo","type":"tag","name":"hugo","color":"ff3db4","path":"/tag/hugo/","belongsTo":{"totalCount":3}}},{"node":{"id":"blog: moments","type":"blog","name":"moments","color":"3dae38","path":"/blog/moments/","belongsTo":{"totalCount":15}}},{"node":{"id":"blog: cheatsheet","type":"blog","name":"cheatsheet","color":"6655d6","path":"/blog/cheatsheet/","belongsTo":{"totalCount":12}}},{"node":{"id":"tag: vim","type":"tag","name":"vim","color":"007f00","path":"/tag/vim/","belongsTo":{"totalCount":3}}},{"node":{"id":"tag: raspi","type":"tag","name":"raspi","color":"c31c4a","path":"/tag/raspi/","belongsTo":{"totalCount":3}}},{"node":{"id":"tag: mysql","type":"tag","name":"mysql","color":"f09011","path":"/tag/mysql/","belongsTo":{"totalCount":3}}},{"node":{"id":"tag: javascript","type":"tag","name":"javascript","color":"f1da4e","path":"/tag/javascript/","belongsTo":{"totalCount":3}}},{"node":{"id":"tag: gh-action","type":"tag","name":"gh-action","color":"006b75","path":"/tag/gh-action/","belongsTo":{"totalCount":1}}},{"node":{"id":"tag: linux","type":"tag","name":"linux","color":"de4815","path":"/tag/linux/","belongsTo":{"totalCount":2}}},{"node":{"id":"tag: vue","type":"tag","name":"vue","color":"41b883","path":"/tag/vue/","belongsTo":{"totalCount":6}}},{"node":{"id":"tag: docker","type":"tag","name":"docker","color":"71cfff","path":"/tag/docker/","belongsTo":{"totalCount":2}}},{"node":{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/","belongsTo":{"totalCount":43}}}]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/about/index.json b/assets/data/post/about/index.json index e027947e6..967bd92b3 100644 --- a/assets/data/post/about/index.json +++ b/assets/data/post/about/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"121","title":"About This Blog","summary":"","body":"\n

Why \"AC Dustbin\"?

\n

\"AC\" is short for \"Allan Chain\", my GitHub ID. It is not unique, but it's short 😄

\n

\"dustbin\" == \"dust\" + \"bin\" Every blog post, or thought, is just like a piece of dust, which is not a big deal to the whole world. But I try to keep all the dust organized in a bin, and maybe It will be helpful to someone.

\n

What? Why \"Allan Chain\"? That's a long story but I am keeping it short here.

\n

Well, \"Allan\" is the English name I chose 7 years ago. This is a completely valid name while different from common \"Alan\". However, \"Chain\" is a invented last name. It's pronounced like my family name. Besides, you will get the name of my homeland by reordering these five letters.

\n

Why Issues?

\n

I have already disscussed in vue-iblog#why-use-github-issues. Besides, People will see my post if I mention other issues. 正所谓打广告于无形之中(x

\n

Lisence

\n

The code base is licensed under MIT license, while the blog posts are licensed under Creative Commons by-nc-sa. Of course, if special statements are made, the special statements prevail.

\n

Web Page Service

\n

This blog uses GitHub pages to serve the contents, and there are no third-party services. Thus I won't know anything if you visited my site unless you leave a kind ❤️ reaction or comment, which is a great motivation to write.

\n

However, if you want only subscribe to posts, you can use third-party services like RSSHub (e.g. https://rsshub.diy.now.sh/github/issue/allanchain/blog/open/%40post)

\n

Friends Section

\n

Feel free to request adding your link to this section by commenting on issue #148.

\n

Contribute Your Idea

\n

Your issue will never be rendered as a blog post. If you found a bug or have an idea about wonderful features, just send a issue and I will take a good look at it ❤️

\n

Edit: Discussions is alive!

","createdAt":"2020-07-18T14:09:04.000Z","lastEditedAt":"2021-01-23T00:49:16.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"121","title":"About This Blog","summary":"","body":"\n

Why \"AC Dustbin\"?

\n

\"AC\" is short for \"Allan Chain\", my GitHub ID. It is not unique, but it's short 😄

\n

\"dustbin\" == \"dust\" + \"bin\" Every blog post, or thought, is just like a piece of dust, which is not a big deal to the whole world. But I try to keep all the dust organized in a bin, and maybe It will be helpful to someone.

\n

What? Why \"Allan Chain\"? That's a long story but I am keeping it short here.

\n

Well, \"Allan\" is the English name I chose 7 years ago. This is a completely valid name while different from common \"Alan\". However, \"Chain\" is a invented last name. It's pronounced like my family name. Besides, you will get the name of my homeland by reordering these five letters.

\n

Why Issues?

\n

I have already disscussed in vue-iblog#why-use-github-issues. Besides, People will see my post if I mention other issues. 正所谓打广告于无形之中(x

\n

Lisence

\n

The code base is licensed under MIT license, while the blog posts are licensed under Creative Commons by-nc-sa. Of course, if special statements are made, the special statements prevail.

\n

Web Page Service

\n

This blog uses GitHub pages to serve the contents, and there are no third-party services. Thus I won't know anything if you visited my site unless you leave a kind ❤️ reaction or comment, which is a great motivation to write.

\n

However, if you want only subscribe to posts, you can use third-party services like RSSHub (e.g. https://rsshub.diy.now.sh/github/issue/allanchain/blog/open/%40post)

\n

Friends Section

\n

Feel free to request adding your link to this section by commenting on issue #148.

\n

Contribute Your Idea

\n

Your issue will never be rendered as a blog post. If you found a bug or have an idea about wonderful features, just send a issue and I will take a good look at it ❤️

\n

Edit: Discussions is alive!

","createdAt":"2020-07-18T14:09:04.000Z","lastEditedAt":"2021-01-23T00:49:16.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/ahk-numpad/index.json b/assets/data/post/ahk-numpad/index.json index 0285e99de..fdd5b9a06 100644 --- a/assets/data/post/ahk-numpad/index.json +++ b/assets/data/post/ahk-numpad/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"171","title":"Enabling Numpad on a Windows Laptop without Numpad","summary":"\n

Sometimes you really need a numpad. For example when inputing a large quantity of numbers after an insane physics experiment.

\n","body":"\n

It is not very easy to find a good solution, because they are all talking about on screen keyboard. However it is not convinient.

\n

If you have AutoHotkey installed, it is just a problem of writing a proper script to toggle key mapping: https://stackoverflow.com/questions/24389178

\n

Copying the top answer's code:

\n
\n
\n \n ahk\n
mode := 0\n\n^!m::\n    mode:=!mode ;not! toggle\nreturn\n\n#If mode ; All hotkeys below this line will only work if mode is TRUE\n    j::1\n    k::2\n    l::3\n    u::4\n    i::5\n    o::6\n#If
\n

In case you are not familiar with ahk syntax (or I forget in the future):

\n\n

Notice that the script above does not fully simulate numpad, it is sending normal number keys (keys on top) instead of numpad keys. To send numpad keys:

\n
\n
\n \n ahk\n
mode := 0\n\n^!m::\n    mode:=!mode ;not! toggle\nreturn\n\n#If mode ; All hotkeys below this line will only work if mode is TRUE\n    j::Numpad1\n    k::Numpad2\n    l::Numpad3\n    u::Numpad4\n    i::Numpad5\n    o::Numpad6\n    7::Numpad7\n    8::Numpad8\n    9::Numpad9\n    0::Numpad0\n#If
\n

But the use case is rare. The first script is enough for me.

","createdAt":"2021-05-08T08:43:49.000Z","lastEditedAt":"2021-05-08T08:43:49.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: moments","type":"blog","name":"moments","color":"3dae38","path":"/blog/moments/"}],"reactions":[{"emoji":"👍","count":1,"users":["Yixuan-Wang"]}],"comments":[]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"171","title":"Enabling Numpad on a Windows Laptop without Numpad","summary":"\n

Sometimes you really need a numpad. For example when inputing a large quantity of numbers after an insane physics experiment.

\n","body":"\n

It is not very easy to find a good solution, because they are all talking about on screen keyboard. However it is not convinient.

\n

If you have AutoHotkey installed, it is just a problem of writing a proper script to toggle key mapping: https://stackoverflow.com/questions/24389178

\n

Copying the top answer's code:

\n
\n
\n \n ahk\n
mode := 0\n\n^!m::\n    mode:=!mode ;not! toggle\nreturn\n\n#If mode ; All hotkeys below this line will only work if mode is TRUE\n    j::1\n    k::2\n    l::3\n    u::4\n    i::5\n    o::6\n#If
\n

In case you are not familiar with ahk syntax (or I forget in the future):

\n\n

Notice that the script above does not fully simulate numpad, it is sending normal number keys (keys on top) instead of numpad keys. To send numpad keys:

\n
\n
\n \n ahk\n
mode := 0\n\n^!m::\n    mode:=!mode ;not! toggle\nreturn\n\n#If mode ; All hotkeys below this line will only work if mode is TRUE\n    j::Numpad1\n    k::Numpad2\n    l::Numpad3\n    u::Numpad4\n    i::Numpad5\n    o::Numpad6\n    7::Numpad7\n    8::Numpad8\n    9::Numpad9\n    0::Numpad0\n#If
\n

But the use case is rare. The first script is enough for me.

","createdAt":"2021-05-08T08:43:49.000Z","lastEditedAt":"2021-05-08T08:43:49.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: moments","type":"blog","name":"moments","color":"3dae38","path":"/blog/moments/"}],"reactions":[{"emoji":"👍","count":1,"users":["Yixuan-Wang"]}],"comments":[]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/bash-cht/index.json b/assets/data/post/bash-cht/index.json index 123d7ddad..115c637b1 100644 --- a/assets/data/post/bash-cht/index.json +++ b/assets/data/post/bash-cht/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"82","title":"Bash Cheatsheet","summary":"","body":"\n

**/* not recursive?

\n
\n
\n \n shell\n
shopt -s globstar
\n

Recursive chmod

\n

From https://stackoverflow.com/a/11512211/8810271

\n

Want set all files with mode 644 and all sub directories 755?

\n
\n
\n \n shell\n
find /opt/lampp/htdocs -type d -exec chmod 755 {} \\;\nfind /opt/lampp/htdocs -type f -exec chmod 644 {} \\;
\n
\n

chmod 644 {} \\; specifies the command that will be executed by find for each file. {} is replaced by the file path, and the semicolon denotes the end of the command (escaped, otherwise it would be interpreted by the shell instead of find).

\n
\n

If Statement

\n
\n
\n \n shell\n
if [ $1 -gt 100 ]\nthen\n    echo Hey that\\'s a large number.\nfi
\n

Single Square Brackets

\n
\n\nThese are frequently used, and search CONDITIONAL in bash man page for more.\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
OperatorDescription
! EXPRESSIONThe EXPRESSION is false.
-n STRINGThe length of STRING is greater than zero.
-z STRINGThe lengh of STRING is zero (ie it is empty).
STRING1 = STRING2STRING1 is equal to STRING2
STRING1 != STRING2STRING1 is not equal to STRING2
INTEGER1 -eq INTEGER2INTEGER1 is numerically equal to INTEGER2
INTEGER1 -gt INTEGER2INTEGER1 is numerically greater than INTEGER2
INTEGER1 -lt INTEGER2INTEGER1 is numerically less than INTEGER2
-d FILEFILE exists and is a directory.
-e FILEFILE exists.
-r FILEFILE exists and the read permission is granted.
-s FILEFILE exists and it's size is greater than zero (ie. it is not empty).
-w FILEFILE exists and the write permission is granted.
-x FILEFILE exists and the execute permission is granted.
\n
\n
\n

From https://stackoverflow.com/a/31366734/8810271

\n

[ is just a regular command with a weird name.

\n

] is just an argument of [ that prevents further arguments from being used.

\n

Nothing is altered in the way that Bash parses the command. In particular, < is redirection, && and || concatenate multiple commands, ( ) generates subshells unless escaped by \\, and word expansion happens as usual.

\n

[[ X ]] is a single construct that makes X be parsed magically. <, &&, || and () are treated specially, and word splitting rules are different. There are also further differences like = and =~.

\n

[ is a built-in command (compgen -b), and [[ is a keyword (compgen -k)

\n
\n

That's why you need to type spaces after [ and before ]. Furthermore, there is no need to type an entire if statement to debug a condition. Just type [[ 0 > 1 ]] and see your shell prompt (if configured) !

\n

Listing awfully named files in order

\n

Sometimes, you get files like photo_1.jpg, photo_2.jpg, ..., photo_10.jpg, ...

\n

If using plain *.jpg, you will get photo_1.jpg, photo_10.jpg, ..., photo_2.jpg, ...

\n

Quite annoying, isn't it? Solve it by ls -v, namely sort by version.

\n

From man page:

\n
\n

Sort by WORD instead of name: none (-U), size (-S), time (-t), version (-v), extension (-X)

\n
","createdAt":"2020-04-18T04:48:39.000Z","lastEditedAt":"2020-06-30T06:59:32.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: cheatsheet","type":"blog","name":"cheatsheet","color":"6655d6","path":"/blog/cheatsheet/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"82","title":"Bash Cheatsheet","summary":"","body":"\n

**/* not recursive?

\n
\n
\n \n shell\n
shopt -s globstar
\n

Recursive chmod

\n

From https://stackoverflow.com/a/11512211/8810271

\n

Want set all files with mode 644 and all sub directories 755?

\n
\n
\n \n shell\n
find /opt/lampp/htdocs -type d -exec chmod 755 {} \\;\nfind /opt/lampp/htdocs -type f -exec chmod 644 {} \\;
\n
\n

chmod 644 {} \\; specifies the command that will be executed by find for each file. {} is replaced by the file path, and the semicolon denotes the end of the command (escaped, otherwise it would be interpreted by the shell instead of find).

\n
\n

If Statement

\n
\n
\n \n shell\n
if [ $1 -gt 100 ]\nthen\n    echo Hey that\\'s a large number.\nfi
\n

Single Square Brackets

\n
\n\nThese are frequently used, and search CONDITIONAL in bash man page for more.\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
OperatorDescription
! EXPRESSIONThe EXPRESSION is false.
-n STRINGThe length of STRING is greater than zero.
-z STRINGThe lengh of STRING is zero (ie it is empty).
STRING1 = STRING2STRING1 is equal to STRING2
STRING1 != STRING2STRING1 is not equal to STRING2
INTEGER1 -eq INTEGER2INTEGER1 is numerically equal to INTEGER2
INTEGER1 -gt INTEGER2INTEGER1 is numerically greater than INTEGER2
INTEGER1 -lt INTEGER2INTEGER1 is numerically less than INTEGER2
-d FILEFILE exists and is a directory.
-e FILEFILE exists.
-r FILEFILE exists and the read permission is granted.
-s FILEFILE exists and it's size is greater than zero (ie. it is not empty).
-w FILEFILE exists and the write permission is granted.
-x FILEFILE exists and the execute permission is granted.
\n
\n
\n

From https://stackoverflow.com/a/31366734/8810271

\n

[ is just a regular command with a weird name.

\n

] is just an argument of [ that prevents further arguments from being used.

\n

Nothing is altered in the way that Bash parses the command. In particular, < is redirection, && and || concatenate multiple commands, ( ) generates subshells unless escaped by \\, and word expansion happens as usual.

\n

[[ X ]] is a single construct that makes X be parsed magically. <, &&, || and () are treated specially, and word splitting rules are different. There are also further differences like = and =~.

\n

[ is a built-in command (compgen -b), and [[ is a keyword (compgen -k)

\n
\n

That's why you need to type spaces after [ and before ]. Furthermore, there is no need to type an entire if statement to debug a condition. Just type [[ 0 > 1 ]] and see your shell prompt (if configured) !

\n

Listing awfully named files in order

\n

Sometimes, you get files like photo_1.jpg, photo_2.jpg, ..., photo_10.jpg, ...

\n

If using plain *.jpg, you will get photo_1.jpg, photo_10.jpg, ..., photo_2.jpg, ...

\n

Quite annoying, isn't it? Solve it by ls -v, namely sort by version.

\n

From man page:

\n
\n

Sort by WORD instead of name: none (-U), size (-S), time (-t), version (-v), extension (-X)

\n
","createdAt":"2020-04-18T04:48:39.000Z","lastEditedAt":"2020-06-30T06:59:32.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: cheatsheet","type":"blog","name":"cheatsheet","color":"6655d6","path":"/blog/cheatsheet/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/c-review/index.json b/assets/data/post/c-review/index.json index d34fa0d25..3eba8c28c 100644 --- a/assets/data/post/c-review/index.json +++ b/assets/data/post/c-review/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"23","title":"C Review","summary":"\n

计概基础知识复习手记

\n","body":"\n

函数

\n

函数不能嵌套定义

\n

函数改变指针所指变量

\n
\n

调用函数不可能改变实参指针变量的值,但可以改变实参指针变量所指变量的值。

\n
\n
\n
\n \n c\n
swap(int *p1,int *p2)\n{\n    int *p;\n    p=p1; p1=p2; p2=p;\n}
\n

\n
\n
\n \n c\n
swap(int *p1,int *p2)\n{\n    int t;\n    t=*p1; *p1=*p2; *p2=t;\n}
\n

的区别

\n

++

\n
\n

a++错,a是数组首地址,是常量,不能++。

\n
\n
\n

函数p中a++正确,a其实为指针变量
\nmain()中a++错,数组头地址为常数

\n
\n

指针初始化

\n
\n
\n \n c\n
swap(int *p1,int *p2)\n{\n    int *temp;\n    *temp=*p1;\n    *p1=*p2;\n    *p2=temp;\n}
\n

多维数组与指针

\n

一点提示

\n
\n

&a[1]只是地址的一种计算方法,不要简单理解为a[1]的物理地址,因不存在变量a[1]。

\n
\n

理解指针-数组传递

\n
\n
\n \n c\n
void average(float *p, int n);\nvoid search(float (*p)[4], int n);\nmain()\n{\n    float score[3][4] = {{65, 67, 70, 60},\n                         {80, 87, 90, 81},\n                         {90, 99, 100, 98}};\n    average(*score, 12);\n    search(score, 2);\n}
\n

score 看成一维数组,
\n则*score就是获得该数组的第一个元素。
\n由于二维数组,第一个元素还是数组。
\naverage(*score,12)就相当于传一个数组进去。
\n与float *p相符。

\n

float (*p)[4]是 a pointer to array of 4 float,
\nscore本身一维数组就等同于指针,指向第一个数组元素。
\n故这两个是相符的。

\n

指针与字符串

\n

常量区?

\n

有家可归的字符串视为一般变量,直接呆在“家”里(栈 stack),无家可归的字符串常量呆在常量区(全局、静态区 data)。

\n

\"img\"

\n

Image from https://www.geeksforgeeks.org/memory-layout-of-c-program/

\n

有无static相同

\n
\n
\n \n c\n
char *day_name(int n)\n{\n    static char *name[] = {\n        \"Illegal day\", \"Monday\",\n        \"Tuesday\", \"Wednesday\",\n        \"Thursday\", \"Friday\",\n        \"Saturday\", \"Sunday\"};\n    return((n<1||n>7) ? name[0] : name[n]);\n}
\n

有无static不相同,有:可正确打印;无:打印值有时不对

\n
\n
\n \n c\n
char *day_name(int n)\n{\n    static char name[][20] = {\n        \"Illegal day\", \"Monday\",\n        \"Tuesday\", \"Wednesday\",\n        \"Thursday\", \"Friday\",\n        \"Saturday\", \"Sunday\"};\n    return ((n < 1 || n > 7) ? name[0] : name[n]);\n}
\n

对字符指针及字符数组赋初值

\n
\n
\n \n c\n
char *a=\"I love China!\";\n/* == */\nchar *a;\na=\"I love China\";\n\nchar str[14]=\"I love China!\";\n/* != */\nchar str[14];\nstr = \"I love China!\"; \n/* 错误:将‘const char [1]’赋值给‘char [14]’时类型不兼容 */
\n

位运算

\n

交换两个值,不用临时变量

\n
\n
\n \n c\n
a = a ^ b;\nb = b ^ a;\na = a ^ b;\n/*\nb = b ^ (a ^ b) = a ^ b ^ b = a ^ 0 = a\na = (a ^ b) ^ a = a ^ a ^ b = 0 ^ b = b\n*/
","createdAt":"2019-12-29T10:22:51.000Z","lastEditedAt":"2020-06-30T07:52:36.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: c","type":"tag","name":"c","color":"aaaaaa","path":"/tag/c/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"23","title":"C Review","summary":"\n

计概基础知识复习手记

\n","body":"\n

函数

\n

函数不能嵌套定义

\n

函数改变指针所指变量

\n
\n

调用函数不可能改变实参指针变量的值,但可以改变实参指针变量所指变量的值。

\n
\n
\n
\n \n c\n
swap(int *p1,int *p2)\n{\n    int *p;\n    p=p1; p1=p2; p2=p;\n}
\n

\n
\n
\n \n c\n
swap(int *p1,int *p2)\n{\n    int t;\n    t=*p1; *p1=*p2; *p2=t;\n}
\n

的区别

\n

++

\n
\n

a++错,a是数组首地址,是常量,不能++。

\n
\n
\n

函数p中a++正确,a其实为指针变量
\nmain()中a++错,数组头地址为常数

\n
\n

指针初始化

\n
\n
\n \n c\n
swap(int *p1,int *p2)\n{\n    int *temp;\n    *temp=*p1;\n    *p1=*p2;\n    *p2=temp;\n}
\n

多维数组与指针

\n

一点提示

\n
\n

&a[1]只是地址的一种计算方法,不要简单理解为a[1]的物理地址,因不存在变量a[1]。

\n
\n

理解指针-数组传递

\n
\n
\n \n c\n
void average(float *p, int n);\nvoid search(float (*p)[4], int n);\nmain()\n{\n    float score[3][4] = {{65, 67, 70, 60},\n                         {80, 87, 90, 81},\n                         {90, 99, 100, 98}};\n    average(*score, 12);\n    search(score, 2);\n}
\n

score 看成一维数组,
\n则*score就是获得该数组的第一个元素。
\n由于二维数组,第一个元素还是数组。
\naverage(*score,12)就相当于传一个数组进去。
\n与float *p相符。

\n

float (*p)[4]是 a pointer to array of 4 float,
\nscore本身一维数组就等同于指针,指向第一个数组元素。
\n故这两个是相符的。

\n

指针与字符串

\n

常量区?

\n

有家可归的字符串视为一般变量,直接呆在“家”里(栈 stack),无家可归的字符串常量呆在常量区(全局、静态区 data)。

\n

\"img\"

\n

Image from https://www.geeksforgeeks.org/memory-layout-of-c-program/

\n

有无static相同

\n
\n
\n \n c\n
char *day_name(int n)\n{\n    static char *name[] = {\n        \"Illegal day\", \"Monday\",\n        \"Tuesday\", \"Wednesday\",\n        \"Thursday\", \"Friday\",\n        \"Saturday\", \"Sunday\"};\n    return((n<1||n>7) ? name[0] : name[n]);\n}
\n

有无static不相同,有:可正确打印;无:打印值有时不对

\n
\n
\n \n c\n
char *day_name(int n)\n{\n    static char name[][20] = {\n        \"Illegal day\", \"Monday\",\n        \"Tuesday\", \"Wednesday\",\n        \"Thursday\", \"Friday\",\n        \"Saturday\", \"Sunday\"};\n    return ((n < 1 || n > 7) ? name[0] : name[n]);\n}
\n

对字符指针及字符数组赋初值

\n
\n
\n \n c\n
char *a=\"I love China!\";\n/* == */\nchar *a;\na=\"I love China\";\n\nchar str[14]=\"I love China!\";\n/* != */\nchar str[14];\nstr = \"I love China!\"; \n/* 错误:将‘const char [1]’赋值给‘char [14]’时类型不兼容 */
\n

位运算

\n

交换两个值,不用临时变量

\n
\n
\n \n c\n
a = a ^ b;\nb = b ^ a;\na = a ^ b;\n/*\nb = b ^ (a ^ b) = a ^ b ^ b = a ^ 0 = a\na = (a ^ b) ^ a = a ^ a ^ b = 0 ^ b = b\n*/
","createdAt":"2019-12-29T10:22:51.000Z","lastEditedAt":"2020-06-30T07:52:36.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: c","type":"tag","name":"c","color":"aaaaaa","path":"/tag/c/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/compile-fenix/index.json b/assets/data/post/compile-fenix/index.json index 4e70d5c55..1f88c6b0f 100644 --- a/assets/data/post/compile-fenix/index.json +++ b/assets/data/post/compile-fenix/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"163","title":"A Simple Way to Use More Addons (e.g. Tampermonkey) on Newest Fenix (FireFox Android)","summary":"\n

Yes, very simple and... violent. It's about compiling your own copy of Fenix.

\n","body":"\n

Updated Method

\n

Just download Firefox Nightly, go to settings -> about, and tap logo 5 times to enter debug mode. Then you can add your own collection in settings.

\n

Old methods below:

\n
\n

Create Your Own Addon Collection

\n

Go to https://addons.mozilla.org/ and create a collection. Notice your user name and the collection name.

\n

\"Create

\n

Modify app/build.gradle

\n

Modify applicationId and sharedUserId to avoid conflict.

\n
\n
\n \n diff\n
diff --git a/app/build.gradle b/app/build.gradle\nindex 54e1ea7b5..7c56a2f67 100644\n--- a/app/build.gradle\n+++ b/app/build.gradle\n@@ -27,7 +27,7 @@ android {\n     }\n\n     defaultConfig {\n-        applicationId \"org.mozilla\"\n+        applicationId \"org.mozilla.allanchain\"\n         minSdkVersion Config.minSdkVersion\n         targetSdkVersion Config.targetSdkVersion\n         versionCode 1\n@@ -39,8 +39,8 @@ android {\n         buildConfigField \"boolean\", \"USE_RELEASE_VERSIONING\", \"false\"\n         // This should be the \"public\" base URL of AMO.\n         buildConfigField \"String\", \"AMO_BASE_URL\", \"\\\"https://addons.mozilla.org\\\"\"\n-        buildConfigField \"String\", \"AMO_COLLECTION_NAME\", \"\\\"7dfae8669acc4312a65e8ba5553036\\\"\"\n-        buildConfigField \"String\", \"AMO_COLLECTION_USER\", \"\\\"mozilla\\\"\"\n+        buildConfigField \"String\", \"AMO_COLLECTION_NAME\", \"\\\"more-addons\\\"\"\n+        buildConfigField \"String\", \"AMO_COLLECTION_USER\", \"\\\"your user name / id\\\"\"\n         // These add-ons should be excluded for Mozilla Online builds.\n         buildConfigField \"String[]\", \"MOZILLA_ONLINE_ADDON_EXCLUSIONS\",\n                 \"{\" +\n@@ -114,7 +114,7 @@ android {\n                     // fatal consequences. For example see:\n                     //  - https://issuetracker.google.com/issues/36924841\n                     //  - https://issuetracker.google.com/issues/36905922\n-                    \"sharedUserId\": \"org.mozilla.firefox.sharedID\",\n+                    \"sharedUserId\": \"org.mozilla.firefox.allanchain.sharedID\",\n                     \"deepLinkScheme\": deepLinkSchemeValue\n             ]\n         }\n@@ -131,7 +131,7 @@ android {\n                     // fatal consequences. For example see:\n                     //  - https://issuetracker.google.com/issues/36924841\n                     //  - https://issuetracker.google.com/issues/36905922\n-                    \"sharedUserId\": \"org.mozilla.firefox.sharedID\",\n+                    \"sharedUserId\": \"org.mozilla.firefox.allanchain.sharedID\",\n                     \"deepLinkScheme\": deepLinkSchemeValue\n             ]\n         }
\n

Or simply a one-liner:

\n
\n
\n \n shell\n
sed -i -e 's/org\\.mozilla\"/org.mozilla.allanchain\"/' -e 's/org.mozilla.firefox/org.mozilla.firefox.allanchain/g' -e 's/7dfae8669acc4312a65e8ba5553036/more-addons/' -e 's/\\\\\"mozilla\\\\\"/\\\\\"Allan Chain\\\\\"/' app/build.gradle
\n

You may also want to set local.properties and / or other properties according to fenix README. For example:

\n
\n
\n \n shell\n
echo autosignReleaseWithDebugKey > local.properties
\n

Compile

\n
\n
\n \n shell\n
./gradlew app:assembleRelease
\n

You are done! 🎉 The apk is available at app\\build\\outputs\\apk\\release

","createdAt":"2021-03-07T09:34:15.000Z","lastEditedAt":"2022-01-27T12:29:12.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: android","type":"tag","name":"android","color":"3ddc84","path":"/tag/android/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"163","title":"A Simple Way to Use More Addons (e.g. Tampermonkey) on Newest Fenix (FireFox Android)","summary":"\n

Yes, very simple and... violent. It's about compiling your own copy of Fenix.

\n","body":"\n

Updated Method

\n

Just download Firefox Nightly, go to settings -> about, and tap logo 5 times to enter debug mode. Then you can add your own collection in settings.

\n

Old methods below:

\n
\n

Create Your Own Addon Collection

\n

Go to https://addons.mozilla.org/ and create a collection. Notice your user name and the collection name.

\n

\"Create

\n

Modify app/build.gradle

\n

Modify applicationId and sharedUserId to avoid conflict.

\n
\n
\n \n diff\n
diff --git a/app/build.gradle b/app/build.gradle\nindex 54e1ea7b5..7c56a2f67 100644\n--- a/app/build.gradle\n+++ b/app/build.gradle\n@@ -27,7 +27,7 @@ android {\n     }\n\n     defaultConfig {\n-        applicationId \"org.mozilla\"\n+        applicationId \"org.mozilla.allanchain\"\n         minSdkVersion Config.minSdkVersion\n         targetSdkVersion Config.targetSdkVersion\n         versionCode 1\n@@ -39,8 +39,8 @@ android {\n         buildConfigField \"boolean\", \"USE_RELEASE_VERSIONING\", \"false\"\n         // This should be the \"public\" base URL of AMO.\n         buildConfigField \"String\", \"AMO_BASE_URL\", \"\\\"https://addons.mozilla.org\\\"\"\n-        buildConfigField \"String\", \"AMO_COLLECTION_NAME\", \"\\\"7dfae8669acc4312a65e8ba5553036\\\"\"\n-        buildConfigField \"String\", \"AMO_COLLECTION_USER\", \"\\\"mozilla\\\"\"\n+        buildConfigField \"String\", \"AMO_COLLECTION_NAME\", \"\\\"more-addons\\\"\"\n+        buildConfigField \"String\", \"AMO_COLLECTION_USER\", \"\\\"your user name / id\\\"\"\n         // These add-ons should be excluded for Mozilla Online builds.\n         buildConfigField \"String[]\", \"MOZILLA_ONLINE_ADDON_EXCLUSIONS\",\n                 \"{\" +\n@@ -114,7 +114,7 @@ android {\n                     // fatal consequences. For example see:\n                     //  - https://issuetracker.google.com/issues/36924841\n                     //  - https://issuetracker.google.com/issues/36905922\n-                    \"sharedUserId\": \"org.mozilla.firefox.sharedID\",\n+                    \"sharedUserId\": \"org.mozilla.firefox.allanchain.sharedID\",\n                     \"deepLinkScheme\": deepLinkSchemeValue\n             ]\n         }\n@@ -131,7 +131,7 @@ android {\n                     // fatal consequences. For example see:\n                     //  - https://issuetracker.google.com/issues/36924841\n                     //  - https://issuetracker.google.com/issues/36905922\n-                    \"sharedUserId\": \"org.mozilla.firefox.sharedID\",\n+                    \"sharedUserId\": \"org.mozilla.firefox.allanchain.sharedID\",\n                     \"deepLinkScheme\": deepLinkSchemeValue\n             ]\n         }
\n

Or simply a one-liner:

\n
\n
\n \n shell\n
sed -i -e 's/org\\.mozilla\"/org.mozilla.allanchain\"/' -e 's/org.mozilla.firefox/org.mozilla.firefox.allanchain/g' -e 's/7dfae8669acc4312a65e8ba5553036/more-addons/' -e 's/\\\\\"mozilla\\\\\"/\\\\\"Allan Chain\\\\\"/' app/build.gradle
\n

You may also want to set local.properties and / or other properties according to fenix README. For example:

\n
\n
\n \n shell\n
echo autosignReleaseWithDebugKey > local.properties
\n

Compile

\n
\n
\n \n shell\n
./gradlew app:assembleRelease
\n

You are done! 🎉 The apk is available at app\\build\\outputs\\apk\\release

","createdAt":"2021-03-07T09:34:15.000Z","lastEditedAt":"2022-01-27T12:29:12.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: android","type":"tag","name":"android","color":"3ddc84","path":"/tag/android/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/compile-vim-python3/index.json b/assets/data/post/compile-vim-python3/index.json index b6c0d79d2..9f44df61e 100644 --- a/assets/data/post/compile-vim-python3/index.json +++ b/assets/data/post/compile-vim-python3/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"151","title":"How I compile and install Vim with +python3","summary":"\n

It's known to all that compiling is hard. Just record it.

\n","body":"\n

Disclaimer: There may be many errors and unneccesary parts in this post. But the command works, at least on my machine.

\n

Background

\n

Somewhat old system. No sudo. Want awesome Vim8 +Python3.8

\n

Quick Answer

\n
\n
\n \n shell\n
LDFLAGS=-rdynamic ./configure \\\n    --with-features=huge \\\n    --enable-fail-if-missing \\\n    --enable-largefile \\\n    --enable-multibyte \\\n    --enable-python3interp \\\n    --with-python3-command=python3.8 \\\n    --with-python3-config-dir=$HOME/.pyenv/versions/3.8.5/lib/python3.8/config-3.8-x86_64-linux-gnu \\\n    --prefix=$HOME\n\nmake\nmake install
\n

Break Down

\n

Clean before try again

\n
\n

It is not a good news that the second run is very fast.

\n
\n
\n
\n \n shell\n
make clean distclean
\n

Options v.s. Env vars

\n

Both are important. For example, do not pass vi_cv_path_python as an option, although it's in lower case 😄

\n

User install

\n
\n
\n \n shell\n
--prefix=$HOME
\n

What is python3-config-dir

\n

https://vi.stackexchange.com/a/18509

\n

A directory containing config.c

\n

-rdynamic?

\n

It just solves undefined symbol: PyTuple_Type: vim/vim#5509 (comment)

\n

Also, just go dynamic (with +python3/dyn) is fine:

\n
\n
\n \n shell\n
./configure \\\n    --enable-python3interp=dynamic \\\n    --with-python3-command=python3.8 \\\n    --with-python3-config-dir=$HOME/.pyenv/versions/3.8.5/lib/python3.8/config-3.8-x86_64-linux-gnu \\\n    --prefix=$HOME
","createdAt":"2020-11-15T06:36:25.000Z","lastEditedAt":"2020-11-15T06:53:18.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: vim","type":"tag","name":"vim","color":"007f00","path":"/tag/vim/"}],"reactions":[],"comments":[{"resourcePath":"/AllanChain/blog/issues/151#issuecomment-735895347","author":{"id":"karlosp","avatarUrl":"https://avatars.githubusercontent.com/u/5717843?s=64&v=4"},"bodyHTML":"

Thank YOU!

\n

You save me. Compiling Vim on RHEL 7 with python support has been killing me for weeks.
\nLDFLAGS=-rdynamic is my saver!

\n

If someone else will be searching for this:
\n--enable-python3interp=dynamic did not work for me but
\n--enable-python3interp works.

","createdAt":"2020-11-30T16:31:20.000Z","reactions":[{"emoji":"❤","count":1,"users":["AllanChain"]}]}]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"151","title":"How I compile and install Vim with +python3","summary":"\n

It's known to all that compiling is hard. Just record it.

\n","body":"\n

Disclaimer: There may be many errors and unneccesary parts in this post. But the command works, at least on my machine.

\n

Background

\n

Somewhat old system. No sudo. Want awesome Vim8 +Python3.8

\n

Quick Answer

\n
\n
\n \n shell\n
LDFLAGS=-rdynamic ./configure \\\n    --with-features=huge \\\n    --enable-fail-if-missing \\\n    --enable-largefile \\\n    --enable-multibyte \\\n    --enable-python3interp \\\n    --with-python3-command=python3.8 \\\n    --with-python3-config-dir=$HOME/.pyenv/versions/3.8.5/lib/python3.8/config-3.8-x86_64-linux-gnu \\\n    --prefix=$HOME\n\nmake\nmake install
\n

Break Down

\n

Clean before try again

\n
\n

It is not a good news that the second run is very fast.

\n
\n
\n
\n \n shell\n
make clean distclean
\n

Options v.s. Env vars

\n

Both are important. For example, do not pass vi_cv_path_python as an option, although it's in lower case 😄

\n

User install

\n
\n
\n \n shell\n
--prefix=$HOME
\n

What is python3-config-dir

\n

https://vi.stackexchange.com/a/18509

\n

A directory containing config.c

\n

-rdynamic?

\n

It just solves undefined symbol: PyTuple_Type: vim/vim#5509 (comment)

\n

Also, just go dynamic (with +python3/dyn) is fine:

\n
\n
\n \n shell\n
./configure \\\n    --enable-python3interp=dynamic \\\n    --with-python3-command=python3.8 \\\n    --with-python3-config-dir=$HOME/.pyenv/versions/3.8.5/lib/python3.8/config-3.8-x86_64-linux-gnu \\\n    --prefix=$HOME
","createdAt":"2020-11-15T06:36:25.000Z","lastEditedAt":"2020-11-15T06:53:18.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: vim","type":"tag","name":"vim","color":"007f00","path":"/tag/vim/"}],"reactions":[],"comments":[{"resourcePath":"/AllanChain/blog/issues/151#issuecomment-735895347","author":{"id":"karlosp","avatarUrl":"https://avatars.githubusercontent.com/u/5717843?s=64&v=4"},"bodyHTML":"

Thank YOU!

\n

You save me. Compiling Vim on RHEL 7 with python support has been killing me for weeks.
\nLDFLAGS=-rdynamic is my saver!

\n

If someone else will be searching for this:
\n--enable-python3interp=dynamic did not work for me but
\n--enable-python3interp works.

","createdAt":"2020-11-30T16:31:20.000Z","reactions":[{"emoji":"❤","count":1,"users":["AllanChain"]}]}]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/damn-gpg/index.json b/assets/data/post/damn-gpg/index.json index aadec3c0e..fa78c9a8a 100644 --- a/assets/data/post/damn-gpg/index.json +++ b/assets/data/post/damn-gpg/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"55","title":"Damning GPG Key","summary":"\n

Why GPG key keeps annoying me! 整天 Fail 有意思吗?

\n","body":"\n

Read the doc carefully and don't forget to tell git what gpg key to use.

\n
\n

Finally signed commit with success on Windows machine, and I happily did the same on my Ubuntu virtual machine.

\n

However, GitHub said that the commits by my windows machine was unverified but the ones by Ubuntu was verified.

\n

WTH? That's IMPOSSIBLE! I even copied the private keys to windows machine and without luck.

\n

Alright. The email setting was different between two machines and GitHub requires that the email used to commit MUST equals the email (a.k.a. comment) of GPG key.

\n
\n

And today, when I have succeeded in signing many commits in different repos, I failed to sign this repo...

\n

Type:

\n
git config -l\n
\n

And I saw two user.signingkey there... Interesting ...

\n

One is global and one is local, the local one is introduced in the early age when I configure the GPG key generated by windows locally and forgot to remove it...

\n
\n

Alright, damn GPG again.

\n

When I set up gpg keys on WSL today, odd things happend again:

\n
\n
\n \n shell\n
error: gpg failed to sign the data\nfatal: failed to write commit object\n\n> echo \"test\" | gpg2 --clearsign\ngpg: signing failed: Inappropriate ioctl for device\ngpg: [stdin]: clear-sign failed: Inappropriate ioctl for device
\n

GPG NEEDS A FOLLISH TTY?!

\n
export GPG_TTY=$(tty)\n
\n

That solved the problem

\n
\n

Oh, god damn it! The first sign after start up always fail on WSL Ubuntu. Type:

\n
\n
\n \n shell\n
echo \"test\" | gpg2 --clearsign
\n

again and it shows:

\n
gpg: WARNING: unsafe ownership on homedir '/home/ac/.gnupg'\ngpg: can't connect to the agent: IPC connect call failed\ngpg: can't connect to the agent: IPC connect call failed\ngpg: keydb_search failed: No agent running\ngpg: no default secret key: No agent running\ngpg: [stdin]: clear-sign failed: No agent running\n
\n

No agent running. Just need to enable gpg-agent on start up:

\n
\n
\n \n shell\n
echo 'eval $(gpg-agent --daemon 2>/dev/null)' >> ~/.bashrc
\n

To fix unsafe ownership, run:

\n
\n
\n \n shell\n
sudo chown -R $USER:$USER ~/.gnupg\nsudo find ~/.gnupg -type d -exec chmod 700 {} \\;\nsudo find ~/.gnupg -type f -exec chmod 600 {} \\;
\n

Reference:

\n","createdAt":"2020-01-07T12:16:11.000Z","lastEditedAt":"2020-07-17T14:07:41.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"55","title":"Damning GPG Key","summary":"\n

Why GPG key keeps annoying me! 整天 Fail 有意思吗?

\n","body":"\n

Read the doc carefully and don't forget to tell git what gpg key to use.

\n
\n

Finally signed commit with success on Windows machine, and I happily did the same on my Ubuntu virtual machine.

\n

However, GitHub said that the commits by my windows machine was unverified but the ones by Ubuntu was verified.

\n

WTH? That's IMPOSSIBLE! I even copied the private keys to windows machine and without luck.

\n

Alright. The email setting was different between two machines and GitHub requires that the email used to commit MUST equals the email (a.k.a. comment) of GPG key.

\n
\n

And today, when I have succeeded in signing many commits in different repos, I failed to sign this repo...

\n

Type:

\n
git config -l\n
\n

And I saw two user.signingkey there... Interesting ...

\n

One is global and one is local, the local one is introduced in the early age when I configure the GPG key generated by windows locally and forgot to remove it...

\n
\n

Alright, damn GPG again.

\n

When I set up gpg keys on WSL today, odd things happend again:

\n
\n
\n \n shell\n
error: gpg failed to sign the data\nfatal: failed to write commit object\n\n> echo \"test\" | gpg2 --clearsign\ngpg: signing failed: Inappropriate ioctl for device\ngpg: [stdin]: clear-sign failed: Inappropriate ioctl for device
\n

GPG NEEDS A FOLLISH TTY?!

\n
export GPG_TTY=$(tty)\n
\n

That solved the problem

\n
\n

Oh, god damn it! The first sign after start up always fail on WSL Ubuntu. Type:

\n
\n
\n \n shell\n
echo \"test\" | gpg2 --clearsign
\n

again and it shows:

\n
gpg: WARNING: unsafe ownership on homedir '/home/ac/.gnupg'\ngpg: can't connect to the agent: IPC connect call failed\ngpg: can't connect to the agent: IPC connect call failed\ngpg: keydb_search failed: No agent running\ngpg: no default secret key: No agent running\ngpg: [stdin]: clear-sign failed: No agent running\n
\n

No agent running. Just need to enable gpg-agent on start up:

\n
\n
\n \n shell\n
echo 'eval $(gpg-agent --daemon 2>/dev/null)' >> ~/.bashrc
\n

To fix unsafe ownership, run:

\n
\n
\n \n shell\n
sudo chown -R $USER:$USER ~/.gnupg\nsudo find ~/.gnupg -type d -exec chmod 700 {} \\;\nsudo find ~/.gnupg -type f -exec chmod 600 {} \\;
\n

Reference:

\n","createdAt":"2020-01-07T12:16:11.000Z","lastEditedAt":"2020-07-17T14:07:41.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/docker-cht/index.json b/assets/data/post/docker-cht/index.json index 76d3db7b7..6472e0572 100644 --- a/assets/data/post/docker-cht/index.json +++ b/assets/data/post/docker-cht/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"174","title":"Docker Cheatsheet","summary":"\n

Docker commands are easily fogotten

\n","body":"\n

Listing

\n

Images:

\n
\n
\n \n shell\n
docker images
\n

Containers

\n
\n
\n \n shell\n
docker ps [-a]
\n

Cleaning

\n

Remove dangling images

\n
\n
\n \n shell\n
docker image prune
\n

Or remove:

\n\n
\n
\n \n shell\n
docker system prune
\n

Removing

\n
\n
\n \n shell\n
docker rmi [image name]\ndocker rm [container name]
\n

Expose Port

\n
\n
\n \n shell\n
docker run -p [host port]:[container port]
\n

Add IPv6

\n
\n

Disclaimer: may be inaccurate

\n
\n

In /etc/docker/daemon.json:

\n
\n
\n \n json\n
{\n  \"ipv6\": true,\n  \"fixed-cidr-v6\": \"2001:3200:1::/64\"\n}
\n

And run:

\n
\n
\n \n shell\n
sudo systemctl reload docker
\n

This reload is graceful so that you don't need to stop containers.

\n

Then in docker-compose.yml:

\n
\n
\n \n yaml\n
services:\n  app:\n    # ...\n    networks:\n      - app_net\nnetworks:\n  app_net:\n    enable_ipv6: true\n    driver: bridge\n    ipam:\n      driver: default\n      config:\n        - subnet: 2001:db8:1::/64\n          gateway: 2001:db8:1::1
","createdAt":"2021-07-31T09:58:17.000Z","lastEditedAt":"2021-09-11T13:38:15.000Z","image":"/blog/img/f498d489.png","imageLazy":"ACAAkDASIAAhEBAxEB/8QAFgABAQE$AID/8QAGBABAAMB$ECESL/xAAUAQE$AAC/8QAFREBAQ$AAH/2gAMAwEAAhEDEQA/AMLx2nAKjH//2Q==","serializedHeadings":"[]","labels":[{"id":"blog: cheatsheet","type":"blog","name":"cheatsheet","color":"6655d6","path":"/blog/cheatsheet/"},{"id":"tag: docker","type":"tag","name":"docker","color":"71cfff","path":"/tag/docker/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"174","title":"Docker Cheatsheet","summary":"\n

Docker commands are easily forgotten

\n

Above image from scmagazine.com

\n","body":"\n

Listing

\n

Images:

\n
\n
\n \n shell\n
docker images
\n

Containers

\n
\n
\n \n shell\n
docker ps [-a]
\n

Cleaning

\n

Remove dangling images

\n
\n
\n \n shell\n
docker image prune
\n

Or remove:

\n\n
\n
\n \n shell\n
docker system prune
\n

Removing

\n
\n
\n \n shell\n
docker rmi [image name]\ndocker rm [container name]
\n

Expose Port

\n
\n
\n \n shell\n
docker run -p [host port]:[container port]
\n

Add IPv6

\n
\n

Disclaimer: may be inaccurate

\n
\n

In /etc/docker/daemon.json:

\n
\n
\n \n json\n
{\n  \"ipv6\": true,\n  \"fixed-cidr-v6\": \"2001:3200:1::/64\"\n}
\n

And run:

\n
\n
\n \n shell\n
sudo systemctl reload docker
\n

This reload is graceful so that you don't need to stop containers.

\n

Then in docker-compose.yml:

\n
\n
\n \n yaml\n
services:\n  app:\n    # ...\n    networks:\n      - app_net\nnetworks:\n  app_net:\n    enable_ipv6: true\n    driver: bridge\n    ipam:\n      driver: default\n      config:\n        - subnet: 2001:db8:1::/64\n          gateway: 2001:db8:1::1
","createdAt":"2021-07-31T09:58:17.000Z","lastEditedAt":"2022-05-01T13:49:08.000Z","image":"/blog/img/6c88ced2.jpg","imageLazy":"AFAAkDASIAAhEBAxEB/8QAFQABAQ$AAL/xAAWEAEBAQ$ASL/xAAVAQEB$ACA//EABQRAQ$AAD/2gAMAwEAAhEDEQA/ALt2oDSf/9k=","serializedHeadings":"[]","labels":[{"id":"blog: cheatsheet","type":"blog","name":"cheatsheet","color":"6655d6","path":"/blog/cheatsheet/"},{"id":"tag: docker","type":"tag","name":"docker","color":"71cfff","path":"/tag/docker/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/docker-tag/index.json b/assets/data/post/docker-tag/index.json index f5fff4aa8..7ec3d73b5 100644 --- a/assets/data/post/docker-tag/index.json +++ b/assets/data/post/docker-tag/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"75","title":"列出 USTC Docker 镜像的标签","summary":"\n

不再为网络条件发愁!

\n","body":"\n

众所周知,如果网络条件好的话,可以使用官方的 API 获取镜像标签列表。

\n

如 v2 接口:

\n
\n
\n \n shell\n
#!/bin/bash\ni = 0\n\nwhile [ $? == 0 ]\ndo\n   i = $(( i + 1 ))\n   curl https://registry.hub.docker.com/v2/repositories/library/nginx/tags/?page=$i | jq '.\"results\"[][\"name\"]'\n\ndone
\n

但是天朝。。

\n

网易和阿里的官方 API 里都要求有 API Key 签名认证之类,比较繁琐

\n

故先尝试使用 USTC 的镜像,缺点是速度慢、标签不全。。根据一波猜测,得到了 USTC 标签的 API:

\n
https://docker.mirrors.ustc.edu.cn/v2/library/nginx/tags/list\n
\n

同理又意外获得了网易的,标签全!

\n
https://hub-mirror.c.163.com/v2/library/nginx/tags/list\n
\n

于是根据 https://stackoverflow.com/a/39454426/8810271 代码改编:

\n
\n
\n \n shell\n
#!/bin/bash\n\nif [ $# -lt 1 ]\nthen\ncat << HELP\n\ndockertags  --  list all tags for a Docker image on a remote registry.\n\nEXAMPLE:\n    - list all tags for ubuntu:\n       dockertags ubuntu\n\n    - list all php tags containing apache:\n       dockertags php apache\n\nHELP\nexit\nfi\n\nimage = \"$1\"\ntags = `curl https://hub-mirror.c.163.com/v2/library/${image}/tags/list | sed -e 's/.*\\[//g' -e 's/\"//g' -e 's/\\]\\}$//g' -e 's/,/\\n/g'`\n\nif [ -n \"$2\" ]\nthen\n    tags = ` echo \"${tags}\" | grep \"$2\" `\nfi\n\necho \"${tags}\"
","createdAt":"2020-02-28T13:38:39.000Z","lastEditedAt":"2020-06-21T12:56:24.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: docker","type":"tag","name":"docker","color":"71cfff","path":"/tag/docker/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"75","title":"列出 USTC Docker 镜像的标签","summary":"\n

不再为网络条件发愁!

\n","body":"\n

众所周知,如果网络条件好的话,可以使用官方的 API 获取镜像标签列表。

\n

如 v2 接口:

\n
\n
\n \n shell\n
#!/bin/bash\ni = 0\n\nwhile [ $? == 0 ]\ndo\n   i = $(( i + 1 ))\n   curl https://registry.hub.docker.com/v2/repositories/library/nginx/tags/?page=$i | jq '.\"results\"[][\"name\"]'\n\ndone
\n

但是天朝。。

\n

网易和阿里的官方 API 里都要求有 API Key 签名认证之类,比较繁琐

\n

故先尝试使用 USTC 的镜像,缺点是速度慢、标签不全。。根据一波猜测,得到了 USTC 标签的 API:

\n
https://docker.mirrors.ustc.edu.cn/v2/library/nginx/tags/list\n
\n

同理又意外获得了网易的,标签全!

\n
https://hub-mirror.c.163.com/v2/library/nginx/tags/list\n
\n

于是根据 https://stackoverflow.com/a/39454426/8810271 代码改编:

\n
\n
\n \n shell\n
#!/bin/bash\n\nif [ $# -lt 1 ]\nthen\ncat << HELP\n\ndockertags  --  list all tags for a Docker image on a remote registry.\n\nEXAMPLE:\n    - list all tags for ubuntu:\n       dockertags ubuntu\n\n    - list all php tags containing apache:\n       dockertags php apache\n\nHELP\nexit\nfi\n\nimage = \"$1\"\ntags = `curl https://hub-mirror.c.163.com/v2/library/${image}/tags/list | sed -e 's/.*\\[//g' -e 's/\"//g' -e 's/\\]\\}$//g' -e 's/,/\\n/g'`\n\nif [ -n \"$2\" ]\nthen\n    tags = ` echo \"${tags}\" | grep \"$2\" `\nfi\n\necho \"${tags}\"
","createdAt":"2020-02-28T13:38:39.000Z","lastEditedAt":"2020-06-21T12:56:24.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: docker","type":"tag","name":"docker","color":"71cfff","path":"/tag/docker/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/dsa-init/index.json b/assets/data/post/dsa-init/index.json index 247bf809d..2fb896ddd 100644 --- a/assets/data/post/dsa-init/index.json +++ b/assets/data/post/dsa-init/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"73","title":"数算环境准备","summary":"\n

g++ & VS Code 党对《数据结构与算法》的环境准备

\n

有必要解释一下名称的由来:Data Structure and Algorithm

\n","body":"\n

万恶exe

\n

删掉删掉,养虎遗患🐕

\n

文件编码

\n

作为可以追溯到 2008 年的程序们,使用 GBK 之类的编码和 CRLF 的确是正常的,但是看着觉得别扭。

\n

所以使用iconv转换编码,dos2unix转换换行符:

\n
\n
\n \n shell\n
for file in **/*.cpp; do iconv -f GBK -t utf-8 $file -o $file; dos2unix $file; done
\n

转完之后才发现,还有头文件。。改成.h再来一次完事。

\n

WSL + VS Code

\n

使用微软“黑科技”,VS Code 使用 WSL 的环境,就不用担心 MSVC 这玩意了。

\n

安装不多说,参考https://code.visualstudio.com/docs/cpp/config-wsl

\n

配置编译启动的时候有需要注意的地方。VS Code 编译调试 c++ 的时候分两步,配置信息放在两个配置文件中,分别为.vscode/tasks.json, .vscode/launch.json

\n\n

但是新建tasks.json好像并不可靠,有时并没有对应选项。。

\n

由于多为多文件项目,直接默认的编译只会编译当前文件,故对tasks.json改动如下,编译文件所在目录下所有 cpp 文件:

\n
\n
\n \n js\n
{\n    // See https://go.microsoft.com/fwlink/?LinkId=733558 \n    // for the documentation about the tasks.json format\n    \"version\": \"2.0.0\",\n    \"tasks\": [\n        {\n            \"type\": \"shell\",\n            \"label\": \"g++ build active file\",\n            \"command\": \"/usr/bin/g++\",\n            \"args\": [\n                \"-g\",\n                \"${fileDirname}/**.cpp\", // 原为 ${file}\n                \"-o\",\n                \"${fileDirname}/${fileBasenameNoExtension}\"\n            ],\n            \"options\": {\n                \"cwd\": \"/usr/bin\"\n            },\n            \"problemMatcher\": [\n                \"$gcc\"\n            ],\n            \"group\": \"build\"\n        }\n    ]\n}
\n

万恶的gets

\n

虽然该函数已经(2011?)正式退出,但是作为之前的代码,还是有它的身影。

\n

参考 https://stackoverflow.com/q/1694036/8810271

\n

只需将

\n
\n
\n \n c\n
char str[32];\nprintf(\"生成多少随机数:\\n\");\ngets(str);
\n

改为

\n
\n
\n \n c\n
char str[32];\nprintf(\"生成多少随机数:\\n\");\nfgets(str, 32, stdin);
\n

就没什么大问题了。

\n

StdAfx?

\n

https://stackoverflow.com/questions/4726155

\n

用于预编译一些头文件以达到加速效果。但是对于这种小项目,预编译加速效果有限,于是我并没有进行相关配置。

","createdAt":"2020-02-19T08:50:19.000Z","lastEditedAt":"2020-07-01T13:00:05.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: c","type":"tag","name":"c","color":"aaaaaa","path":"/tag/c/"}],"reactions":[{"emoji":"👍","count":1,"users":["Yixuan-Wang"]}],"comments":[]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"73","title":"数算环境准备","summary":"\n

g++ & VS Code 党对《数据结构与算法》的环境准备

\n

有必要解释一下名称的由来:Data Structure and Algorithm

\n","body":"\n

万恶exe

\n

删掉删掉,养虎遗患🐕

\n

文件编码

\n

作为可以追溯到 2008 年的程序们,使用 GBK 之类的编码和 CRLF 的确是正常的,但是看着觉得别扭。

\n

所以使用iconv转换编码,dos2unix转换换行符:

\n
\n
\n \n shell\n
for file in **/*.cpp; do iconv -f GBK -t utf-8 $file -o $file; dos2unix $file; done
\n

转完之后才发现,还有头文件。。改成.h再来一次完事。

\n

WSL + VS Code

\n

使用微软“黑科技”,VS Code 使用 WSL 的环境,就不用担心 MSVC 这玩意了。

\n

安装不多说,参考https://code.visualstudio.com/docs/cpp/config-wsl

\n

配置编译启动的时候有需要注意的地方。VS Code 编译调试 c++ 的时候分两步,配置信息放在两个配置文件中,分别为.vscode/tasks.json, .vscode/launch.json

\n\n

但是新建tasks.json好像并不可靠,有时并没有对应选项。。

\n

由于多为多文件项目,直接默认的编译只会编译当前文件,故对tasks.json改动如下,编译文件所在目录下所有 cpp 文件:

\n
\n
\n \n js\n
{\n    // See https://go.microsoft.com/fwlink/?LinkId=733558 \n    // for the documentation about the tasks.json format\n    \"version\": \"2.0.0\",\n    \"tasks\": [\n        {\n            \"type\": \"shell\",\n            \"label\": \"g++ build active file\",\n            \"command\": \"/usr/bin/g++\",\n            \"args\": [\n                \"-g\",\n                \"${fileDirname}/**.cpp\", // 原为 ${file}\n                \"-o\",\n                \"${fileDirname}/${fileBasenameNoExtension}\"\n            ],\n            \"options\": {\n                \"cwd\": \"/usr/bin\"\n            },\n            \"problemMatcher\": [\n                \"$gcc\"\n            ],\n            \"group\": \"build\"\n        }\n    ]\n}
\n

万恶的gets

\n

虽然该函数已经(2011?)正式退出,但是作为之前的代码,还是有它的身影。

\n

参考 https://stackoverflow.com/q/1694036/8810271

\n

只需将

\n
\n
\n \n c\n
char str[32];\nprintf(\"生成多少随机数:\\n\");\ngets(str);
\n

改为

\n
\n
\n \n c\n
char str[32];\nprintf(\"生成多少随机数:\\n\");\nfgets(str, 32, stdin);
\n

就没什么大问题了。

\n

StdAfx?

\n

https://stackoverflow.com/questions/4726155

\n

用于预编译一些头文件以达到加速效果。但是对于这种小项目,预编译加速效果有限,于是我并没有进行相关配置。

","createdAt":"2020-02-19T08:50:19.000Z","lastEditedAt":"2020-07-01T13:00:05.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: c","type":"tag","name":"c","color":"aaaaaa","path":"/tag/c/"}],"reactions":[{"emoji":"👍","count":1,"users":["Yixuan-Wang"]}],"comments":[]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/fabricmc/index.json b/assets/data/post/fabricmc/index.json index 2223983be..4200d29f6 100644 --- a/assets/data/post/fabricmc/index.json +++ b/assets/data/post/fabricmc/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"140","title":"Getting Started with Minecraft Fabric Without Any Java Knowledge","summary":"\n

Thats challenging but possible

\n","body":"\n

Making a mod in Java is challenging for a hobby programer in Python and JavaScript world. Starting a mod from scratch is even more challenging, and as I am just playing with minecraft mods, I chose to start with a existing mod, Draylar/identity.

\n

Get Java Ready

\n

JDK, JRE

\n

JRE (Java Runtime Environment): essential for Java application to run

\n

JDK (Java Development Kit): Includes JRE, with tools to compile Java source Code

\n

Not Enough?

\n

As a (fresh) Minecraft player, I have already installed jre8. And I also have openjdk8 installed with Android Studio. However thats not enough.

\n

openjdk does not include JavaFX, which is required for HMCL. And jre8 does not include tools to compile. So let's get official Java SE Development Kit 8 .(Most mods use this version.)

\n

Setup Env and Config

\n

For example:

\n
\n
\n \n shell\n
JAVA_HOME=/path/to/jdk\nJAVA_TOOL_OPTIONS=-Dfile.encoding=UTF-8\nPATH=.....%JAVA_HOME%\\bin
\n

Build Fresh Mod!

\n

Clone mod repo and run (varies between OS):

\n
\n
\n \n batchfile\n
gradlew build
\n

This will download correct gradle wrapper version, Minecraft client & server jar, and other dependencies.

\n

It may take some time to finish.

\n

If network connection is poor, you can:

\n\n

Get docs

\n

You will get confused with the code quickly if you just read fabric wiki. Where are docs for all these APIs?

\n

Just go to fabric's own maven repo, navigate to net/fabricmc/yarn/<build-folder>, download and extract javadoc.jar, and you will get docs for net.minecraft.blahblah

\n

And net/fabricmc/sponge-mixin for mixins.

\n

But docs are no very useful, you often need the source code.

\n

Quick Tip: What Commands are Supported?

\n
\n
\n \n batchfile\n
gradlew tasks
\n

to see all the tasks.

\n

Generate Minecraft Source

\n

You will still get confused with mixin injections without Minecraft source: inject to what?

\n

Generate Minecraft source by:

\n
\n
\n \n batchfile\n
gradlew genSources
\n

This will decompile Minecraft and download game assets. Open the project in VSCode, maybe wait a while for project to be imported. You can now right click class or method name and select \"go to implementations\" to see the source.

\n

Debug the Mod

\n

Nobody would like to build the mod, drag the jar to mods directory, restart minecraft game, to see the effect. To debug right inside VSCode, generate launch.json:

\n
\n
\n \n batchfile\n
gradlew vscode
\n

Now Minecraft Client and Minecraft Server option is available in the debug panel!

","createdAt":"2020-08-08T03:38:04.000Z","lastEditedAt":"2021-02-22T08:40:43.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: minecraft","type":"tag","name":"minecraft","color":"674a34","path":"/tag/minecraft/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"140","title":"Getting Started with Minecraft Fabric Without Any Java Knowledge","summary":"\n

Thats challenging but possible

\n","body":"\n

Making a mod in Java is challenging for a hobby programer in Python and JavaScript world. Starting a mod from scratch is even more challenging, and as I am just playing with minecraft mods, I chose to start with a existing mod, Draylar/identity.

\n

Get Java Ready

\n

JDK, JRE

\n

JRE (Java Runtime Environment): essential for Java application to run

\n

JDK (Java Development Kit): Includes JRE, with tools to compile Java source Code

\n

Not Enough?

\n

As a (fresh) Minecraft player, I have already installed jre8. And I also have openjdk8 installed with Android Studio. However thats not enough.

\n

openjdk does not include JavaFX, which is required for HMCL. And jre8 does not include tools to compile. So let's get official Java SE Development Kit 8 .(Most mods use this version.)

\n

Setup Env and Config

\n

For example:

\n
\n
\n \n shell\n
JAVA_HOME=/path/to/jdk\nJAVA_TOOL_OPTIONS=-Dfile.encoding=UTF-8\nPATH=.....%JAVA_HOME%\\bin
\n

Build Fresh Mod!

\n

Clone mod repo and run (varies between OS):

\n
\n
\n \n batchfile\n
gradlew build
\n

This will download correct gradle wrapper version, Minecraft client & server jar, and other dependencies.

\n

It may take some time to finish.

\n

If network connection is poor, you can:

\n\n

Get docs

\n

You will get confused with the code quickly if you just read fabric wiki. Where are docs for all these APIs?

\n

Just go to fabric's own maven repo, navigate to net/fabricmc/yarn/<build-folder>, download and extract javadoc.jar, and you will get docs for net.minecraft.blahblah

\n

And net/fabricmc/sponge-mixin for mixins.

\n

But docs are no very useful, you often need the source code.

\n

Quick Tip: What Commands are Supported?

\n
\n
\n \n batchfile\n
gradlew tasks
\n

to see all the tasks.

\n

Generate Minecraft Source

\n

You will still get confused with mixin injections without Minecraft source: inject to what?

\n

Generate Minecraft source by:

\n
\n
\n \n batchfile\n
gradlew genSources
\n

This will decompile Minecraft and download game assets. Open the project in VSCode, maybe wait a while for project to be imported. You can now right click class or method name and select \"go to implementations\" to see the source.

\n

Debug the Mod

\n

Nobody would like to build the mod, drag the jar to mods directory, restart minecraft game, to see the effect. To debug right inside VSCode, generate launch.json:

\n
\n
\n \n batchfile\n
gradlew vscode
\n

Now Minecraft Client and Minecraft Server option is available in the debug panel!

","createdAt":"2020-08-08T03:38:04.000Z","lastEditedAt":"2021-02-22T08:40:43.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: minecraft","type":"tag","name":"minecraft","color":"674a34","path":"/tag/minecraft/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/ffmpeg-cht/index.json b/assets/data/post/ffmpeg-cht/index.json index b1d6779b2..f2fafaf2a 100644 --- a/assets/data/post/ffmpeg-cht/index.json +++ b/assets/data/post/ffmpeg-cht/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"83","title":"FFmpeg Cheatsheet","summary":"","body":"\n

Basic

\n

Get help from command line

\n
\n
\n \n shell\n
ffmpeg -h
\n
\nSome useful help here\n
Global options (affect whole program instead of just one file:\n-loglevel loglevel  set logging level\n-v loglevel         set logging level\n-y                  overwrite output files\n-n                  never overwrite output files\n\nVideo options:\n-r rate             set frame rate (Hz value, fraction or abbreviation)\n-s size             set frame size (WxH or abbreviation)\n-aspect aspect      set aspect ratio (4:3, 16:9 or 1.3333, 1.7777)\n-vn                 disable video\n-vcodec codec       force video codec ('copy' to copy stream)\n-vf filter_graph    set video filters\n-ab bitrate         audio bitrate (please use -b:a)\n-b bitrate          video bitrate (please use -b:v)\n\nAudio options:\n-aq quality         set audio quality (codec-specific)\n-ar rate            set audio sampling rate (in Hz)\n-ac channels        set number of audio channels\n-an                 disable audio\n-acodec codec       force audio codec ('copy' to copy stream)\n-vol volume         change audio volume (256=normal)\n-af filter_graph    set audio filters\n
\n
\n

Examples

\n

去除和取出声音

\n
\n
\n \n shell\n
ffmpeg -i example.mkv -c copy -an nosound.mkv\nffmpeg -i input.avi -vn -acodec copy output.aac
\n

裁剪

\n
\n
\n \n shell\n
ffmpeg -i input.m4a -ss 0 -t 18 cut.m4a
\n\n

拼接

\n
\n
\n \n shell\n
ffmpeg -safe 0 -f concat -i list.txt -c copy output.mp4
\n

list.txt 内容为:

\n
file 1.mp4\nfile 2.mp4\n
\n

生成静音的音频

\n
\n
\n \n shell\n
ffmpeg -f lavfi -i anullsrc -t 5 -c:a libvorbis output.ogg
\n

把视频和音频合起来

\n
\n
\n \n shell\n
ffmpeg -i all.mp4 -i all.m4a -c:v copy -c:a aac -strict experimental output.mp4
\n

合理转成 GIF

\n

The standard way to use ffmpeg for GIFs is

\n

Generate a palette from the video

\n
\n
\n \n shell\n
ffmpeg -y -i file.mp4 -vf palettegen palette.png
\n

Then,

\n
\n
\n \n shell\n
ffmpeg -y -i file.mp4 -i palette.png -filter_complex paletteuse -r 10 -s 320x480 file.gif
\n

More options documented here.

\n

所以使用我的 ffmpegroup脚本,第二步就是

\n
\n
\n \n shell\n
ffmpegroup mp4/gif -z\"-i palette.png -filter_complex paletteuse -r 10 -s 200x200 -y\"
\n

直接倍速

\n
\n
\n \n shell\n
ffmpeg -i input.mkv -filter:v \"setpts=PTS/60\" output.mkv
\n

或者更快的,但是会有音频问题,而且转出来帧率吓死人,并没有多少压缩作用,请慎用:

\n
\n
\n \n shell\n
ffmpeg -itsscale 0.01666 -i input.mkv -c copy output.mkv
","createdAt":"2020-03-21T23:56:57.000Z","lastEditedAt":"2020-06-30T09:14:31.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: cheatsheet","type":"blog","name":"cheatsheet","color":"6655d6","path":"/blog/cheatsheet/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"83","title":"FFmpeg Cheatsheet","summary":"","body":"\n

Basic

\n

Get help from command line

\n
\n
\n \n shell\n
ffmpeg -h
\n
\nSome useful help here\n
Global options (affect whole program instead of just one file:\n-loglevel loglevel  set logging level\n-v loglevel         set logging level\n-y                  overwrite output files\n-n                  never overwrite output files\n\nVideo options:\n-r rate             set frame rate (Hz value, fraction or abbreviation)\n-s size             set frame size (WxH or abbreviation)\n-aspect aspect      set aspect ratio (4:3, 16:9 or 1.3333, 1.7777)\n-vn                 disable video\n-vcodec codec       force video codec ('copy' to copy stream)\n-vf filter_graph    set video filters\n-ab bitrate         audio bitrate (please use -b:a)\n-b bitrate          video bitrate (please use -b:v)\n\nAudio options:\n-aq quality         set audio quality (codec-specific)\n-ar rate            set audio sampling rate (in Hz)\n-ac channels        set number of audio channels\n-an                 disable audio\n-acodec codec       force audio codec ('copy' to copy stream)\n-vol volume         change audio volume (256=normal)\n-af filter_graph    set audio filters\n
\n
\n

Examples

\n

去除和取出声音

\n
\n
\n \n shell\n
ffmpeg -i example.mkv -c copy -an nosound.mkv\nffmpeg -i input.avi -vn -acodec copy output.aac
\n

裁剪

\n
\n
\n \n shell\n
ffmpeg -i input.m4a -ss 0 -t 18 cut.m4a
\n\n

拼接

\n
\n
\n \n shell\n
ffmpeg -safe 0 -f concat -i list.txt -c copy output.mp4
\n

list.txt 内容为:

\n
file 1.mp4\nfile 2.mp4\n
\n

生成静音的音频

\n
\n
\n \n shell\n
ffmpeg -f lavfi -i anullsrc -t 5 -c:a libvorbis output.ogg
\n

把视频和音频合起来

\n
\n
\n \n shell\n
ffmpeg -i all.mp4 -i all.m4a -c:v copy -c:a aac -strict experimental output.mp4
\n

合理转成 GIF

\n

The standard way to use ffmpeg for GIFs is

\n

Generate a palette from the video

\n
\n
\n \n shell\n
ffmpeg -y -i file.mp4 -vf palettegen palette.png
\n

Then,

\n
\n
\n \n shell\n
ffmpeg -y -i file.mp4 -i palette.png -filter_complex paletteuse -r 10 -s 320x480 file.gif
\n

More options documented here.

\n

所以使用我的 ffmpegroup脚本,第二步就是

\n
\n
\n \n shell\n
ffmpegroup mp4/gif -z\"-i palette.png -filter_complex paletteuse -r 10 -s 200x200 -y\"
\n

直接倍速

\n
\n
\n \n shell\n
ffmpeg -i input.mkv -filter:v \"setpts=PTS/60\" output.mkv
\n

或者更快的,但是会有音频问题,而且转出来帧率吓死人,并没有多少压缩作用,请慎用:

\n
\n
\n \n shell\n
ffmpeg -itsscale 0.01666 -i input.mkv -c copy output.mkv
","createdAt":"2020-03-21T23:56:57.000Z","lastEditedAt":"2020-06-30T09:14:31.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: cheatsheet","type":"blog","name":"cheatsheet","color":"6655d6","path":"/blog/cheatsheet/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/from-python-to-js/index.json b/assets/data/post/from-python-to-js/index.json index 74cac7a7e..18da1427e 100644 --- a/assets/data/post/from-python-to-js/index.json +++ b/assets/data/post/from-python-to-js/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"70","title":"From Python to JavaScript","summary":"\n

Although the title Top Ten Mistakes Python Programmers Make When Learning JavaScript may sound better, I have only noticed a few mistakes now. And I will update this post when I make more mistakes 😄

\n","body":"\n

The new Operator

\n

You can simply create an instance of a class by ClassName(...) in Python. But in JavaScript you need the new operator. It can be easily forgotten!

\n

See also https://stackoverflow.com/q/1646698/8810271, https://stackoverflow.com/q/383402/8810271

\n

But why sometimes it still works when I forget new? For example:

\n
\n
\n \n js\n
> b = new Date()\n2020-02-08T05:20:14.069Z\n> c = Date()\n'Sat Feb 08 2020 13:20:19 GMT+0800 (GMT+08:00)'
\n

As the second link shows, there is a trick:

\n
\n
\n \n js\n
function foo() {\n   // if user accidentally omits the new keyword, this will \n   // silently correct the problem...\n   if ( !(this instanceof foo) )\n      return new foo();\n   // constructor logic follows...\n}
\n

However, you must use new if you are using class:

\n
\n
\n \n js\n
> class Bar {\n... constructor() {\n..... if ( !(this instanceof Bar) ) return new Bar()\n..... this.foo = 1\n..... }\n... }\nundefined\n> Bar()\nThrown:\nTypeError: Class constructor Bar cannot be invoked without 'new'\n> new Bar()\nBar { foo: 1 }
\n

Element in Array or Key in Object / dict

\n

In Python, we have:

\n
\n
\n \n python\n
>>> 3 in [1, 2, 3]\nTrue\n>>> 3 in {1: 2, 3: 4}\nTrue
\n

However, in JavaScript:

\n
\n
\n \n js\n
> 3 in [1, 2, 3]\nfalse\n> 3 in {1: 2, 3: 4}\ntrue
\n

That's because in JavaScript, even Array act like a Object:

\n
\n
\n \n js\n
> let a = [1, 2, 3]\nundefined\n> a.b = 5\n5\n> a\n[ 1, 2, 3, b: 5 ]
\n

So, JS just always detects keys. To determine whether an element in Array, you should do:

\n
\n
\n \n js\n
> a.includes(3)\ntrue
\n

Weird map / Are they Different?

\n
\n
\n \n python\n
>>> list(map(int, ['1', '2', '3']))\n[1, 2, 3]\n>>> list(map(lambda s: int(s), ['1', '2', '3']))\n[1, 2, 3]
\n
\n
\n \n js\n
> ['1', '2', '3'].map(parseInt)\n[ 1, NaN, NaN ]\n> ['1', '2', '3'].map(s => parseInt(s))\n[ 1, 2, 3 ]\n> [1, 2, 3].map(console.log)\n1 0 [ 1, 2, 3 ]\n2 1 [ 1, 2, 3 ]\n3 2 [ 1, 2, 3 ]\n[ undefined, undefined, undefined ]\n> [1, 2, 3].map(s => console.log(s))\n1\n2\n3\n[ undefined, undefined, undefined ]\n> [1, 2, 3].forEach(console.log)\n1 0 [ 1, 2, 3 ]\n2 1 [ 1, 2, 3 ]\n3 2 [ 1, 2, 3 ]\nundefined
\n

mapwill pass 3 parameters: value, index, and the Array itself, as shown in [1, 2, 3].map(console.log)

\n

And, passing too many arguments will not cause error:

\n
\n
\n \n js\n
> const g = s => console.log(s)\nundefined\n> g(1, 2, 3)\n1\nundefined
\n

And as for parseInt, there is a second parameter (see below). That's why parseInt and s => parseInt(s) are different.

\n

parseInt v.s. int

\n
\n

parseInt(string [, radix])

\n

Parameters

\n

string

\n

The value to parse. If this argument is not a string, then it is converted to one using the ToString abstract operation. Leading whitespace in this argument is ignored.

\n

radix Optional

\n

An integer between 2 and 36 that represents the radix (the base in mathematical numeral systems) of the string. Be careful—this does not default to 10!

\n
\n
\n

If radix is undefined, 0, or unspecified, JavaScript assumes the following:

\n
    \n
  1. If the input string begins with \"0x\" or \"0X\" (a zero, followed by lowercase or uppercase X), radix is assumed to be 16 and the rest of the string is parsed as a hexidecimal number.
  2. \n
  3. If the input string begins with \"0\" (a zero), radix is assumed to be 8 (octal) or 10 (decimal). Exactly which radix is chosen is implementation-dependent. ECMAScript 5 clarifies that 10 (decimal) should be used, but not all browsers support this yet. For this reason, always specify a radix when using parseInt.
  4. \n
  5. If the input string begins with any other value, the radix is 10 (decimal).
  6. \n
\n

If the first character cannot be converted to a number, parseInt returns NaN unless the radix is bigger than 10.

\n
\n

Well, that makes sense. And I'm going to specify a radix when using parseInt.

\n

And note that Python is more friendly:

\n
class int(object)\n |  int([x]) -> integer\n |  int(x, base=10) -> integer\n |\n |  Convert a number or string to an integer, or return 0 if no arguments\n |  are given.  If x is a number, return x.__int__().  For floating point\n |  numbers, this truncates towards zero.\n |\n |  If x is not a number or if base is given, then x must be a string,\n |  bytes, or bytearray instance representing an integer literal in the\n |  given base.  The literal can be preceded by '+' or '-' and be surrounded\n |  by whitespace.  The base defaults to 10.  Valid bases are 0 and 2-36.\n |  Base 0 means to interpret the base from the string as an integer literal.\n |  >>> int('0b100', base=0)\n |  4\n
\n

Dead Variable

\n

Based on https://stackoverflow.com/a/54980674/8810271

\n
\n
\n \n js\n
> let a = nothing\nThrown:\nReferenceError: nothing is not defined\n> let a = 1\nThrown:\nSyntaxError: Identifier 'a' has already been declared\n> a = 1\nThrown:\nReferenceError: a is not defined
\n

Once you have a typo when using let in console (such as forgetting new), the variable name will never come back. That's because variable initialization did not complete successfully, and you can't re-declare a variable that's already been declared.

\n

Worse still, you cannot delete the variable declared using let, const or var. Only things like \"global variable\" can be deleted. See also https://stackoverflow.com/q/1596782/8810271

\n

You have to reinvent a good variable name, or reopen the console.

\n

Sure Sorted?

\n
\n
\n \n js\n
> [2, 1].sort()\n[ 1, 2 ]\n> [8, 10].sort()\n[ 10, 8 ]
\n

No, JS sort is just String sorting:

\n
\n

The sort() method sorts the elements of an array in place and returns the sorted array. The default sort order is ascending, built upon converting the elements into strings, then comparing their sequences of UTF-16 code units values.

\n
\n

And if you want number sorting, write:

\n
\n
\n \n js\n
> [8, 10, 6, 19].sort((a, b) => a - b)\n[ 6, 8, 10, 19 ]
\n

How do you split?

\n

Even though expression is the same, the answer does not.

\n
\n
\n \n python\n
>>> 'asdf adsf ak g'.split(' ', 1)\n['asdf', 'adsf ak g']
\n
\n
\n \n js\n
> 'asdf adsf ak g'.split(' ', 1)\n[\"asdf\"]
\n

Hmm, I'd better split, slice and join..

\n

Mutable Default Parameter?

\n

Javascript seems not affected.

\n
\n
\n \n js\n
> const f = (a=[]) => { a.push(1); return a }\nundefined\n> f()\n[ 1 ]\n> f()\n[ 1 ]
\n
\n
\n \n python\n
>>> def f(a=[]):\n...     a.append(1)\n...     return a\n... \n>>> f()\n[1]\n>>> f()\n[1, 1]
\n

Beware of Method / Function Names

\n

Some methods / functions achieve similar, if not the same, effect, but with different names. They are so similar that I often get confused.

\n
\n
\n \n python\n
>>> 'abc'.startswith('a')\nTrue\n>>> ''.join(['a', 'b', 'c'])\n'abc'\n>>> 'abc'.index('a')\n0
\n
\n
\n \n js\n
> 'abc'.startsWith('a')\ntrue\n> ['a', 'b', 'c'].join('')\n'abc'\n> 'abc'.indexOf('a')\n0
","createdAt":"2020-02-07T04:23:51.000Z","lastEditedAt":"2021-02-22T08:52:30.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: javascript","type":"tag","name":"javascript","color":"f1da4e","path":"/tag/javascript/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"70","title":"From Python to JavaScript","summary":"\n

Although the title Top Ten Mistakes Python Programmers Make When Learning JavaScript may sound better, I have only noticed a few mistakes now. And I will update this post when I make more mistakes 😄

\n","body":"\n

The new Operator

\n

You can simply create an instance of a class by ClassName(...) in Python. But in JavaScript you need the new operator. It can be easily forgotten!

\n

See also https://stackoverflow.com/q/1646698/8810271, https://stackoverflow.com/q/383402/8810271

\n

But why sometimes it still works when I forget new? For example:

\n
\n
\n \n js\n
> b = new Date()\n2020-02-08T05:20:14.069Z\n> c = Date()\n'Sat Feb 08 2020 13:20:19 GMT+0800 (GMT+08:00)'
\n

As the second link shows, there is a trick:

\n
\n
\n \n js\n
function foo() {\n   // if user accidentally omits the new keyword, this will \n   // silently correct the problem...\n   if ( !(this instanceof foo) )\n      return new foo();\n   // constructor logic follows...\n}
\n

However, you must use new if you are using class:

\n
\n
\n \n js\n
> class Bar {\n... constructor() {\n..... if ( !(this instanceof Bar) ) return new Bar()\n..... this.foo = 1\n..... }\n... }\nundefined\n> Bar()\nThrown:\nTypeError: Class constructor Bar cannot be invoked without 'new'\n> new Bar()\nBar { foo: 1 }
\n

Element in Array or Key in Object / dict

\n

In Python, we have:

\n
\n
\n \n python\n
>>> 3 in [1, 2, 3]\nTrue\n>>> 3 in {1: 2, 3: 4}\nTrue
\n

However, in JavaScript:

\n
\n
\n \n js\n
> 3 in [1, 2, 3]\nfalse\n> 3 in {1: 2, 3: 4}\ntrue
\n

That's because in JavaScript, even Array act like a Object:

\n
\n
\n \n js\n
> let a = [1, 2, 3]\nundefined\n> a.b = 5\n5\n> a\n[ 1, 2, 3, b: 5 ]
\n

So, JS just always detects keys. To determine whether an element in Array, you should do:

\n
\n
\n \n js\n
> a.includes(3)\ntrue
\n

Weird map / Are they Different?

\n
\n
\n \n python\n
>>> list(map(int, ['1', '2', '3']))\n[1, 2, 3]\n>>> list(map(lambda s: int(s), ['1', '2', '3']))\n[1, 2, 3]
\n
\n
\n \n js\n
> ['1', '2', '3'].map(parseInt)\n[ 1, NaN, NaN ]\n> ['1', '2', '3'].map(s => parseInt(s))\n[ 1, 2, 3 ]\n> [1, 2, 3].map(console.log)\n1 0 [ 1, 2, 3 ]\n2 1 [ 1, 2, 3 ]\n3 2 [ 1, 2, 3 ]\n[ undefined, undefined, undefined ]\n> [1, 2, 3].map(s => console.log(s))\n1\n2\n3\n[ undefined, undefined, undefined ]\n> [1, 2, 3].forEach(console.log)\n1 0 [ 1, 2, 3 ]\n2 1 [ 1, 2, 3 ]\n3 2 [ 1, 2, 3 ]\nundefined
\n

mapwill pass 3 parameters: value, index, and the Array itself, as shown in [1, 2, 3].map(console.log)

\n

And, passing too many arguments will not cause error:

\n
\n
\n \n js\n
> const g = s => console.log(s)\nundefined\n> g(1, 2, 3)\n1\nundefined
\n

And as for parseInt, there is a second parameter (see below). That's why parseInt and s => parseInt(s) are different.

\n

parseInt v.s. int

\n
\n

parseInt(string [, radix])

\n

Parameters

\n

string

\n

The value to parse. If this argument is not a string, then it is converted to one using the ToString abstract operation. Leading whitespace in this argument is ignored.

\n

radix Optional

\n

An integer between 2 and 36 that represents the radix (the base in mathematical numeral systems) of the string. Be careful—this does not default to 10!

\n
\n
\n

If radix is undefined, 0, or unspecified, JavaScript assumes the following:

\n
    \n
  1. If the input string begins with \"0x\" or \"0X\" (a zero, followed by lowercase or uppercase X), radix is assumed to be 16 and the rest of the string is parsed as a hexidecimal number.
  2. \n
  3. If the input string begins with \"0\" (a zero), radix is assumed to be 8 (octal) or 10 (decimal). Exactly which radix is chosen is implementation-dependent. ECMAScript 5 clarifies that 10 (decimal) should be used, but not all browsers support this yet. For this reason, always specify a radix when using parseInt.
  4. \n
  5. If the input string begins with any other value, the radix is 10 (decimal).
  6. \n
\n

If the first character cannot be converted to a number, parseInt returns NaN unless the radix is bigger than 10.

\n
\n

Well, that makes sense. And I'm going to specify a radix when using parseInt.

\n

And note that Python is more friendly:

\n
class int(object)\n |  int([x]) -> integer\n |  int(x, base=10) -> integer\n |\n |  Convert a number or string to an integer, or return 0 if no arguments\n |  are given.  If x is a number, return x.__int__().  For floating point\n |  numbers, this truncates towards zero.\n |\n |  If x is not a number or if base is given, then x must be a string,\n |  bytes, or bytearray instance representing an integer literal in the\n |  given base.  The literal can be preceded by '+' or '-' and be surrounded\n |  by whitespace.  The base defaults to 10.  Valid bases are 0 and 2-36.\n |  Base 0 means to interpret the base from the string as an integer literal.\n |  >>> int('0b100', base=0)\n |  4\n
\n

Dead Variable

\n

Based on https://stackoverflow.com/a/54980674/8810271

\n
\n
\n \n js\n
> let a = nothing\nThrown:\nReferenceError: nothing is not defined\n> let a = 1\nThrown:\nSyntaxError: Identifier 'a' has already been declared\n> a = 1\nThrown:\nReferenceError: a is not defined
\n

Once you have a typo when using let in console (such as forgetting new), the variable name will never come back. That's because variable initialization did not complete successfully, and you can't re-declare a variable that's already been declared.

\n

Worse still, you cannot delete the variable declared using let, const or var. Only things like \"global variable\" can be deleted. See also https://stackoverflow.com/q/1596782/8810271

\n

You have to reinvent a good variable name, or reopen the console.

\n

Sure Sorted?

\n
\n
\n \n js\n
> [2, 1].sort()\n[ 1, 2 ]\n> [8, 10].sort()\n[ 10, 8 ]
\n

No, JS sort is just String sorting:

\n
\n

The sort() method sorts the elements of an array in place and returns the sorted array. The default sort order is ascending, built upon converting the elements into strings, then comparing their sequences of UTF-16 code units values.

\n
\n

And if you want number sorting, write:

\n
\n
\n \n js\n
> [8, 10, 6, 19].sort((a, b) => a - b)\n[ 6, 8, 10, 19 ]
\n

How do you split?

\n

Even though expression is the same, the answer does not.

\n
\n
\n \n python\n
>>> 'asdf adsf ak g'.split(' ', 1)\n['asdf', 'adsf ak g']
\n
\n
\n \n js\n
> 'asdf adsf ak g'.split(' ', 1)\n[\"asdf\"]
\n

Hmm, I'd better split, slice and join..

\n

Mutable Default Parameter?

\n

Javascript seems not affected.

\n
\n
\n \n js\n
> const f = (a=[]) => { a.push(1); return a }\nundefined\n> f()\n[ 1 ]\n> f()\n[ 1 ]
\n
\n
\n \n python\n
>>> def f(a=[]):\n...     a.append(1)\n...     return a\n... \n>>> f()\n[1]\n>>> f()\n[1, 1]
\n

Beware of Method / Function Names

\n

Some methods / functions achieve similar, if not the same, effect, but with different names. They are so similar that I often get confused.

\n
\n
\n \n python\n
>>> 'abc'.startswith('a')\nTrue\n>>> ''.join(['a', 'b', 'c'])\n'abc'\n>>> 'abc'.index('a')\n0
\n
\n
\n \n js\n
> 'abc'.startsWith('a')\ntrue\n> ['a', 'b', 'c'].join('')\n'abc'\n> 'abc'.indexOf('a')\n0
","createdAt":"2020-02-07T04:23:51.000Z","lastEditedAt":"2021-02-22T08:52:30.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: javascript","type":"tag","name":"javascript","color":"f1da4e","path":"/tag/javascript/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/get-wechat-emoji/index.json b/assets/data/post/get-wechat-emoji/index.json index ad7175292..ef81fdb4b 100644 --- a/assets/data/post/get-wechat-emoji/index.json +++ b/assets/data/post/get-wechat-emoji/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"156","title":"我是如何获得微信内置表情的","summary":"\n

授人以鱼,不如授人以渔。虽然百度出来有很多下载资源,但并没有讲怎么获得的(毕竟天朝特色)

\n","body":"\n

在 GitHub 上直接搜微信表情,搜出来的多是 https://res.wx.qq.com/mpres/htmledition/images/icon/emotion/0.gif 系列,但这些表情不仅已经过时,而且不清晰(如 \"emoji),甚至有白底。很不方便。

\n

微信 APK

\n

很自然会想到直接从官方网站下载的 APK 提取。但是会发现 APK 里只有在 assets/newemoji 里有一些新的 emoji。分辨率是 64x64 的,很令人满意。

\n

继续搜索

\n

最后在微信开放社区里发现这个帖子,提到了 miniprogram-component-plus 项目中有微信的 emoji。在该项目中搜索 emoji 就有表情雪碧图的链接,如下图所示:

\n

\"Emoji

\n

光有雪碧图还不够,难道要手动建立对应关系?继续在该项目下搜索就发现 src/emoji/emoji_positon.less 文件里有相应样式。

\n

注意事项

\n

使用表情应当遵守微信相应许可。

","createdAt":"2020-12-28T09:46:53.000Z","lastEditedAt":"2020-12-28T09:58:39.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: android","type":"tag","name":"android","color":"3ddc84","path":"/tag/android/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"156","title":"我是如何获得微信内置表情的","summary":"\n

授人以鱼,不如授人以渔。虽然百度出来有很多下载资源,但并没有讲怎么获得的(毕竟天朝特色)

\n","body":"\n

在 GitHub 上直接搜微信表情,搜出来的多是 https://res.wx.qq.com/mpres/htmledition/images/icon/emotion/0.gif 系列,但这些表情不仅已经过时,而且不清晰(如 \"emoji),甚至有白底。很不方便。

\n

微信 APK

\n

很自然会想到直接从官方网站下载的 APK 提取。但是会发现 APK 里只有在 assets/newemoji 里有一些新的 emoji。分辨率是 64x64 的,很令人满意。

\n

继续搜索

\n

最后在微信开放社区里发现这个帖子,提到了 miniprogram-component-plus 项目中有微信的 emoji。在该项目中搜索 emoji 就有表情雪碧图的链接,如下图所示:

\n

\"Emoji

\n

光有雪碧图还不够,难道要手动建立对应关系?继续在该项目下搜索就发现 src/emoji/emoji_positon.less 文件里有相应样式。

\n

注意事项

\n

使用表情应当遵守微信相应许可。

","createdAt":"2020-12-28T09:46:53.000Z","lastEditedAt":"2020-12-28T09:58:39.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: android","type":"tag","name":"android","color":"3ddc84","path":"/tag/android/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/gh-action-cache/index.json b/assets/data/post/gh-action-cache/index.json index 12800d098..22114a6ec 100644 --- a/assets/data/post/gh-action-cache/index.json +++ b/assets/data/post/gh-action-cache/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"98","title":"Setup GitHub Action Cache the Right Way","summary":"\n

What on earth is happening? Why my cache never hits?

\n","body":"\n

To be clear, I am talking about https://github.com/actions/cache. Better to have a look at the doc first.

\n

What should be hashed?

\n

Make sure the file to hash does not always change on every build. After all, you want the cache hit in some cases.

\n

For example, the project is written in JavaScript and uses npm as package manager. You decided to run GitHub Actions to release the package every time you push a tag. And you choose to hash package-lock.json... Sorry, never hits. Cache is completely useless. That's because the version always changes, even thought the dependencies doesn't, which is what you mean.

\n

Instead, hash rest of the file, use tail -n +4 for example.

\n

Know what you are hashing

\n

Why? Don't I know what I am hashing?

\n

Maybe, when using glob.

\n

Cache action evaluates hash twice, which can cause problems with glob. For example, actions/cache#344

\n
\n

hashFiles('**/yarn.lock') is wrong cause it picks yarn.lock files from node_module (as part of the key) at the end of the build, but tries to restore with empty node_modules which produced different hash.

\n
\n

Be careful with cache scope

\n

GitHub doc

\n
\n

A workflow can access and restore a cache created in the current branch, the base branch (including base branches of forked repositories), or the default branch (usually master)

\n
\n

Caches between two parallel branches are not shared. And tags only have access to caches created in default branch. If you want to share cache between build on tags, you would like to keep cache in master scope. For example, use a update-cache.yml action to keep track of cache.

\n

My Solution

\n

Checkout https://github.com/AllanChain/webnav/tree/3ddd12f707fc374f907f5a2ac26aaf98a7c9a3d2/.github/workflows

","createdAt":"2020-06-21T09:16:37.000Z","lastEditedAt":"2020-07-06T10:52:21.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: gh-action","type":"tag","name":"gh-action","color":"006b75","path":"/tag/gh-action/"}],"reactions":[{"emoji":"👍","count":5,"users":["Yixuan-Wang","felixmosh","allenhsu","utkuozdemir","SimonSiefke"]}],"comments":[{"resourcePath":"/AllanChain/blog/issues/98#issuecomment-660451382","author":{"id":"felixmosh","avatarUrl":"https://avatars.githubusercontent.com/u/9304194?s=64&u=60073e99091e1a7b26736563311e171638bb86a6&v=4"},"bodyHTML":"

Thank you for clarifying this issue.

\n

Quick question, If I need to cache a build result (Next'js cache). I'm building my app using tags pushes.

","createdAt":"2020-07-18T08:48:07.000Z","reactions":[]},{"resourcePath":"/AllanChain/blog/issues/98#issuecomment-660454644","author":{"id":"AllanChain","avatarUrl":"https://avatars.githubusercontent.com/u/36528777?s=64&u=e5821b32c02c77ed038c8c82cfe55e07075911b7&v=4"},"bodyHTML":"

@felixmosh Either refer to my solution to create two workflows: update-cache runs on default branch push and rebuild on hash (cache key) change, so that latest cache is available in gh-pages on tag push.

\n

Or use on: push: branches: master, checkout with fetch-depth: 0, and use git describe --tags or git tag --points-at HEAD to tell whether this is a tag. This way, cache scope is default branch.

","createdAt":"2020-07-18T09:16:43.000Z","reactions":[{"emoji":"👍","count":1,"users":["felixmosh"]},{"emoji":"❤","count":1,"users":["pieczorx"]}]},{"resourcePath":"/AllanChain/blog/issues/98#issuecomment-671185246","author":{"id":"felixmosh","avatarUrl":"https://avatars.githubusercontent.com/u/9304194?s=64&u=60073e99091e1a7b26736563311e171638bb86a6&v=4"},"bodyHTML":"

I've ended with a task triggered by push to \"production\" branch with a check for special commit message.

\n
jobs:\n  build:\n    runs-on: ubuntu-latest\n    if: \"contains(github.event.head_commit.message, 'Release v1.')\"\n    steps:\n      - uses: actions/checkout@v2\n        with:\n          # pulls all commits (needed for lerna / semantic release to correctly version)\n          fetch-depth: \"20\"\n      - name: Fetch tags\n         run: git fetch --depth=1 origin +refs/tags/*:refs/tags/*\n...
\n

Thank you for explaining the limits 🙏🏼

","createdAt":"2020-08-10T06:28:08.000Z","reactions":[{"emoji":"👍","count":1,"users":["utkuozdemir"]},{"emoji":"❤","count":1,"users":["AllanChain"]}]}]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"98","title":"Setup GitHub Action Cache the Right Way","summary":"\n

What on earth is happening? Why my cache never hits?

\n","body":"\n

To be clear, I am talking about https://github.com/actions/cache. Better to have a look at the doc first.

\n

What should be hashed?

\n

Make sure the file to hash does not always change on every build. After all, you want the cache hit in some cases.

\n

For example, the project is written in JavaScript and uses npm as package manager. You decided to run GitHub Actions to release the package every time you push a tag. And you choose to hash package-lock.json... Sorry, never hits. Cache is completely useless. That's because the version always changes, even thought the dependencies doesn't, which is what you mean.

\n

Instead, hash rest of the file, use tail -n +4 for example.

\n

Know what you are hashing

\n

Why? Don't I know what I am hashing?

\n

Maybe, when using glob.

\n

Cache action evaluates hash twice, which can cause problems with glob. For example, actions/cache#344

\n
\n

hashFiles('**/yarn.lock') is wrong cause it picks yarn.lock files from node_module (as part of the key) at the end of the build, but tries to restore with empty node_modules which produced different hash.

\n
\n

Be careful with cache scope

\n

GitHub doc

\n
\n

A workflow can access and restore a cache created in the current branch, the base branch (including base branches of forked repositories), or the default branch (usually master)

\n
\n

Caches between two parallel branches are not shared. And tags only have access to caches created in default branch. If you want to share cache between build on tags, you would like to keep cache in master scope. For example, use a update-cache.yml action to keep track of cache.

\n

My Solution

\n

Checkout https://github.com/AllanChain/webnav/tree/3ddd12f707fc374f907f5a2ac26aaf98a7c9a3d2/.github/workflows

","createdAt":"2020-06-21T09:16:37.000Z","lastEditedAt":"2020-07-06T10:52:21.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: gh-action","type":"tag","name":"gh-action","color":"006b75","path":"/tag/gh-action/"}],"reactions":[{"emoji":"👍","count":5,"users":["Yixuan-Wang","felixmosh","allenhsu","utkuozdemir","SimonSiefke"]}],"comments":[{"resourcePath":"/AllanChain/blog/issues/98#issuecomment-660451382","author":{"id":"felixmosh","avatarUrl":"https://avatars.githubusercontent.com/u/9304194?s=64&u=60073e99091e1a7b26736563311e171638bb86a6&v=4"},"bodyHTML":"

Thank you for clarifying this issue.

\n

Quick question, If I need to cache a build result (Next'js cache). I'm building my app using tags pushes.

","createdAt":"2020-07-18T08:48:07.000Z","reactions":[]},{"resourcePath":"/AllanChain/blog/issues/98#issuecomment-660454644","author":{"id":"AllanChain","avatarUrl":"https://avatars.githubusercontent.com/u/36528777?s=64&u=e5821b32c02c77ed038c8c82cfe55e07075911b7&v=4"},"bodyHTML":"

@felixmosh Either refer to my solution to create two workflows: update-cache runs on default branch push and rebuild on hash (cache key) change, so that latest cache is available in gh-pages on tag push.

\n

Or use on: push: branches: master, checkout with fetch-depth: 0, and use git describe --tags or git tag --points-at HEAD to tell whether this is a tag. This way, cache scope is default branch.

","createdAt":"2020-07-18T09:16:43.000Z","reactions":[{"emoji":"👍","count":1,"users":["felixmosh"]},{"emoji":"❤","count":1,"users":["pieczorx"]}]},{"resourcePath":"/AllanChain/blog/issues/98#issuecomment-671185246","author":{"id":"felixmosh","avatarUrl":"https://avatars.githubusercontent.com/u/9304194?s=64&u=60073e99091e1a7b26736563311e171638bb86a6&v=4"},"bodyHTML":"

I've ended with a task triggered by push to \"production\" branch with a check for special commit message.

\n
jobs:\n  build:\n    runs-on: ubuntu-latest\n    if: \"contains(github.event.head_commit.message, 'Release v1.')\"\n    steps:\n      - uses: actions/checkout@v2\n        with:\n          # pulls all commits (needed for lerna / semantic release to correctly version)\n          fetch-depth: \"20\"\n      - name: Fetch tags\n         run: git fetch --depth=1 origin +refs/tags/*:refs/tags/*\n...
\n

Thank you for explaining the limits 🙏🏼

","createdAt":"2020-08-10T06:28:08.000Z","reactions":[{"emoji":"👍","count":1,"users":["utkuozdemir"]},{"emoji":"❤","count":1,"users":["AllanChain"]}]}]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/git-cht/index.json b/assets/data/post/git-cht/index.json index b7340319e..a04b583db 100644 --- a/assets/data/post/git-cht/index.json +++ b/assets/data/post/git-cht/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"154","title":"Git CheatSheet","summary":"","body":"\n

Force pull

\n
\n
\n \n shell\n
git fetch\ngit reset --hard origin/master
\n

Assume unchanged

\n
\n
\n \n shell\n
git update-index --assume-unchanged exam.ple
\n

Upgrade git for windows

\n
\n
\n \n shell\n
git update-git-for-windows
\n

Delete tag

\n
\n
\n \n shell\n
git tag -d tagname\ngit push --delete origin tagname
\n

Remove submodule

\n

From https://stackoverflow.com/a/1260982/8810271

\n
    \n
  1. Delete the relevant section from the .gitmodules file.
  2. \n
  3. Stage the .gitmodules changes:
    \ngit add .gitmodules
  4. \n
  5. Delete the relevant section from .git/config.
  6. \n
  7. Remove the submodule files from the working tree and index:
    \ngit rm --cached path_to_submodule (no trailing slash).
  8. \n
  9. Remove the submodule's .git directory:
    \nrm -rf .git/modules/path_to_submodule
  10. \n
  11. Commit the changes:
    \ngit commit -m \"Removed submodule <name>\"
  12. \n
  13. Delete the now untracked submodule files:
    \nrm -rf path_to_submodule
  14. \n
\n

Split repo

\n

Single File

\n

From https://stackoverflow.com/questions/39479154/how-can-i-split-a-single-file-from-a-git-repo-into-a-new-repo
\nUse git fast-export.

\n

First you export the history of the file to a fast-import stream. Make sure you do this on the master branch.

\n
\n
\n \n shell\n
cd oldrepo\ngit fast-export HEAD -- MyFile.ext >../myfile.fi
\n

Then you create a new repo and import.

\n
\n
\n \n shell\n
cd ..\nmkdir newrepo\ncd newrepo\ngit init\ngit fast-import <../myfile.fi\ngit checkout
\n

Sub Directory

\n
\n
\n \n shell\n
git filter-branch -f --prune-empty --subdirectory-filter  path/to/module
\n

How to make file +x in Git on Windows?

\n

From https://stackoverflow.com/a/21694391/8810271

\n
\n
\n \n shell\n
git update-index --chmod=+x foo.sh
","createdAt":"2020-12-09T07:32:18.000Z","lastEditedAt":"2021-03-20T05:04:22.000Z","image":"/blog/img/4f1e424e.png","imageLazy":"AGAAkDASIAAhEBAxEB/8QAFgABAQE$AME/8QAGhABAAMAAwAAAAAAAAAAAAAAAQACESEx8P/EABQBAQ$AAD/xAAUEQE$AAA/9oADAMBAAIRAxEAPwDO2rfNEV5fdycRA//Z","serializedHeadings":"[]","labels":[{"id":"blog: cheatsheet","type":"blog","name":"cheatsheet","color":"6655d6","path":"/blog/cheatsheet/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"154","title":"Git CheatSheet","summary":"","body":"\n

Force pull

\n
\n
\n \n shell\n
git fetch\ngit reset --hard origin/master
\n

Assume unchanged

\n
\n
\n \n shell\n
git update-index --assume-unchanged exam.ple
\n

Upgrade git for windows

\n
\n
\n \n shell\n
git update-git-for-windows
\n

Delete tag

\n
\n
\n \n shell\n
git tag -d tagname\ngit push --delete origin tagname
\n

Remove submodule

\n

From https://stackoverflow.com/a/1260982/8810271

\n
    \n
  1. Delete the relevant section from the .gitmodules file.
  2. \n
  3. Stage the .gitmodules changes:
    \ngit add .gitmodules
  4. \n
  5. Delete the relevant section from .git/config.
  6. \n
  7. Remove the submodule files from the working tree and index:
    \ngit rm --cached path_to_submodule (no trailing slash).
  8. \n
  9. Remove the submodule's .git directory:
    \nrm -rf .git/modules/path_to_submodule
  10. \n
  11. Commit the changes:
    \ngit commit -m \"Removed submodule <name>\"
  12. \n
  13. Delete the now untracked submodule files:
    \nrm -rf path_to_submodule
  14. \n
\n

Split repo

\n

Single File

\n

From https://stackoverflow.com/questions/39479154/how-can-i-split-a-single-file-from-a-git-repo-into-a-new-repo
\nUse git fast-export.

\n

First you export the history of the file to a fast-import stream. Make sure you do this on the master branch.

\n
\n
\n \n shell\n
cd oldrepo\ngit fast-export HEAD -- MyFile.ext >../myfile.fi
\n

Then you create a new repo and import.

\n
\n
\n \n shell\n
cd ..\nmkdir newrepo\ncd newrepo\ngit init\ngit fast-import <../myfile.fi\ngit checkout
\n

Sub Directory

\n
\n
\n \n shell\n
git filter-branch -f --prune-empty --subdirectory-filter  path/to/module
\n

How to make file +x in Git on Windows?

\n

From https://stackoverflow.com/a/21694391/8810271

\n
\n
\n \n shell\n
git update-index --chmod=+x foo.sh
","createdAt":"2020-12-09T07:32:18.000Z","lastEditedAt":"2021-03-20T05:04:22.000Z","image":"/blog/img/4f1e424e.png","imageLazy":"AGAAkDASIAAhEBAxEB/8QAFgABAQE$AME/8QAGhABAAMAAwAAAAAAAAAAAAAAAQACESEx8P/EABQBAQ$AAD/xAAUEQE$AAA/9oADAMBAAIRAxEAPwDO2rfNEV5fdycRA//Z","serializedHeadings":"[]","labels":[{"id":"blog: cheatsheet","type":"blog","name":"cheatsheet","color":"6655d6","path":"/blog/cheatsheet/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/gridsome-pwa/index.json b/assets/data/post/gridsome-pwa/index.json index 2ca142b26..5939169c0 100644 --- a/assets/data/post/gridsome-pwa/index.json +++ b/assets/data/post/gridsome-pwa/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"149","title":"Gridsome is not that Ready for PWA","summary":"\n

How I am tring to build a nice gridsome-generated site with PWA support

\n

虽然可以勉强并入“用 Vue 做 PWA”系列,但想着能让更多人看到,就还是用国际语言吧

\n","body":"\n

Preface: Why I Need PWA

\n

More than 300 KB of vendor chunks (vue, vuex, vue-router, vue-meta, vuetify, etc.) with a not-that-fast network is very slow to load. I really don't want it to happen again. Also if the page service is down or unreachable (does happen from time to time, at least for my region), I want the site still to work properly.

\n

First of All: Optimise Assets

\n

If your sites updates frequently, you need to change the default behavior.

\n

Cache Busting

\n

By default, gridsome write hashes to page html and json file. While it helps controlling versions, the hash itself is unreliable. That's becuase the hash is generated by webpack build, which is somewhat random. I am even getting different hashes when building gridsome.org, without changing a single character!

\n

Also, if the hashes do work properly, I don't want to invalidate all cached data just because I change one byte of javascript code.

\n

So I jut turned cache busting off:

\n
\n
\n \n js\n
{\n  cacheBusting: false\n}
\n

But I still don't want browser to load old assets for new HTML. So I turns on assets hashing manually:

\n
\n
\n \n js\n
api.chainWebpack(async (config, { isClient, isProd }) => {\n  if (isProd && isClient) { // if splitting CSS\n    config.plugin('extract-css').tap(() => [{\n      filename: 'assets/css/styles.[contenthash:8].css'\n    }])\n    config.output.filename('assets/js/[name].[contenthash:8].js')\n    config.output.chunkFilename('assets/js/[name].[contenthash:8].js')\n  }\n}
\n

Splitting Chunks

\n

By default, gridsome packs main.js, App.vue, etc., and used node modules into one app.hash.js, which is more than 300 KB in my case, basically vuetify and gridsome with modules it depends on. Changing one byte of App.vue means downloading all 300 KB again, which is a pretty awful UX.

\n

Let's split the chunks:

\n
\n
\n \n js\n
api.chainWebpack(async (config, { isClient, isProd }) => {\n  if (isProd && isClient) {\n    config.optimization.splitChunks({\n      chunks: 'initial',\n      maxInitialRequests: Infinity,\n      cacheGroups: {\n        vueVendor: {\n          test: /[\\\\/]node_modules[\\\\/](vue|vuex|vue-router)[\\\\/]/,\n          name: 'vue-vendors',\n        },\n        gridsome: {\n          test: /[\\\\/]node_modules[\\\\/](gridsome|vue-meta)[\\\\/]/,\n          name: 'gridsome-vendors',\n        },\n        polyfill: {\n          test: /[\\\\/]node_modules[\\\\/]core-js[\\\\/]/,\n          name: 'core-js'\n        },\n        axios: {\n          test: /[\\\\/]node_modules[\\\\/]axios[\\\\/]/,\n          name: 'axios'\n        }\n      }\n    })\n  }\n}
\n

I can only split out 1 chunk if maxInitialRequests is not set.

\n

Note: The above code overwrites gridsome's css: { split: false } config and always splits CSS. To disable CSS splitting, add these lines:

\n
\n
\n \n js\n
        styles: {\n          name: 'styles',\n          test: m => /css\\/mini-extract/.test(m.type),\n          chunks: 'all',\n          enforce: true\n        }
\n

Choosing Cache Strategy

\n

Precache v.s. Runtime Cache

\n

In short, precache caches all files specified in the manifest, which is usually a list of js and css assets, at install time.

\n

Runtime cache is more flexiable and have a lot of strategies to choose.

\n

My Best Practice Now

\n

Precache the assets, use NetworkFirst strategy for HTML pages and post data, and CacheFirst for images. Don't worry if the network is slow and still taking a long time to load, just set networkTimeoutSeconds.

\n

Why not...

\n
StaleWhileRevalidate for JSON Data Files
\n

The user will not receive changes immediately:

\n
    \n
  1. user browse version A of the page
  2. \n
  3. version B published
  4. \n
  5. user browse again, no change (sw returning staled data and revalidating)
  6. \n
  7. user browse again, page changed
  8. \n
\n

Also, caching JSON data means gridsome/gridsome#1032 (comment)

\n
Add Revision to JSON Data Files
\n

Yes, this is posible by using injectManifest and precache some of them, while runtime caching with a handler to add revision to the url. (Detailed implemantation)

\n

However, compiling service worker with webpack is currently limited that I cannot split chunks with it. That means if the post data changed one byte, the user have to redownload the service worker file again, which is about 50 KB. Even if I managed to split the service worker, that's still 13 KB of manifest and will grow overtime. That's quite annoying that the service worker is always updating, slowly.

\n

Of course if you are sure your users' network is fast, you can ignore above drawback.

\n
Just Precache all JSON Data Files
\n

Precaching all data files has the same drawback as above. Plus, precaching all data files means the user is downloading the whole site on first visit, which is a horrible UX, as well as large network overhead. And only after precache is done will the service worker complete installing.

\n

For example, first visit to https://v3.vuejs.org/ takes a long time to complete. ~400 files to precache.

\n

Of course if the users should be able to browse the full site while offline, and you do not care first visit loading and service worker installing time, go ahead with precaching.

\n

Still Problem

\n

Though using NetworkFirst, sw will still use the cached JSON data version if offline. And the cached file may have different versions. For example:

\n
    \n
  1. user browse version A of page I (site version: A, page I version: A)
  2. \n
  3. version B published
  4. \n
  5. user browse version B of page II (site version: B, page I version: A, page II version: B)
  6. \n
  7. user goes offline
  8. \n
  9. user browse page I, broken
  10. \n
\n

Since the user is offline, it is expected that some pages are not available. Better to show freindly offline message instead of getting a wrong version of data and having the broken page. Maybe using the GraphQL query hash as part of JSON filename?

","createdAt":"2020-09-16T03:22:59.000Z","lastEditedAt":"2021-07-03T13:06:53.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: vue","type":"tag","name":"vue","color":"41b883","path":"/tag/vue/"},{"id":"tag: pwa","type":"tag","name":"pwa","color":"5a0fc8","path":"/tag/pwa/"},{"id":"tag: gridsome","type":"tag","name":"gridsome","color":"00a672","path":"/tag/gridsome/"}],"reactions":[{"emoji":"👍","count":4,"users":["askrzypczak","milindsingh","jgar2020","frankfoerster"]}],"comments":[{"resourcePath":"/AllanChain/blog/issues/149#issuecomment-796747239","author":{"id":"milindsingh","avatarUrl":"https://avatars.githubusercontent.com/u/13949703?s=64&u=89a377f6123bdf1b5418a9aa15dfa26bda18f940&v=4"},"bodyHTML":"

Thanks @AllanChain It really helped.

\n

But I have few things, gridsome is still loading all chunks on the index page, but vuejs does page-wise.

\n

Any idea how to only load relevant js bundle on the page ?

","createdAt":"2021-03-11T13:47:48.000Z","reactions":[]},{"resourcePath":"/AllanChain/blog/issues/149#issuecomment-797127643","author":{"id":"AllanChain","avatarUrl":"https://avatars.githubusercontent.com/u/36528777?s=64&u=e5821b32c02c77ed038c8c82cfe55e07075911b7&v=4"},"bodyHTML":"

@milindsingh I am not having this issue 🤷‍♂️ As reference, you can try gridsome branch of this repo and comment out PWA plugin section in gridsome.config.js for simplicity. For example axios is loaded when the page needs it.

","createdAt":"2021-03-11T23:50:57.000Z","reactions":[]}]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"149","title":"Gridsome is not that Ready for PWA","summary":"\n

How I am tring to build a nice gridsome-generated site with PWA support

\n

虽然可以勉强并入“用 Vue 做 PWA”系列,但想着能让更多人看到,就还是用国际语言吧

\n","body":"\n

Preface: Why I Need PWA

\n

More than 300 KB of vendor chunks (vue, vuex, vue-router, vue-meta, vuetify, etc.) with a not-that-fast network is very slow to load. I really don't want it to happen again. Also if the page service is down or unreachable (does happen from time to time, at least for my region), I want the site still to work properly.

\n

First of All: Optimise Assets

\n

If your sites updates frequently, you need to change the default behavior.

\n

Cache Busting

\n

By default, gridsome write hashes to page html and json file. While it helps controlling versions, the hash itself is unreliable. That's becuase the hash is generated by webpack build, which is somewhat random. I am even getting different hashes when building gridsome.org, without changing a single character!

\n

Also, if the hashes do work properly, I don't want to invalidate all cached data just because I change one byte of javascript code.

\n

So I jut turned cache busting off:

\n
\n
\n \n js\n
{\n  cacheBusting: false\n}
\n

But I still don't want browser to load old assets for new HTML. So I turns on assets hashing manually:

\n
\n
\n \n js\n
api.chainWebpack(async (config, { isClient, isProd }) => {\n  if (isProd && isClient) { // if splitting CSS\n    config.plugin('extract-css').tap(() => [{\n      filename: 'assets/css/styles.[contenthash:8].css'\n    }])\n    config.output.filename('assets/js/[name].[contenthash:8].js')\n    config.output.chunkFilename('assets/js/[name].[contenthash:8].js')\n  }\n}
\n

Splitting Chunks

\n

By default, gridsome packs main.js, App.vue, etc., and used node modules into one app.hash.js, which is more than 300 KB in my case, basically vuetify and gridsome with modules it depends on. Changing one byte of App.vue means downloading all 300 KB again, which is a pretty awful UX.

\n

Let's split the chunks:

\n
\n
\n \n js\n
api.chainWebpack(async (config, { isClient, isProd }) => {\n  if (isProd && isClient) {\n    config.optimization.splitChunks({\n      chunks: 'initial',\n      maxInitialRequests: Infinity,\n      cacheGroups: {\n        vueVendor: {\n          test: /[\\\\/]node_modules[\\\\/](vue|vuex|vue-router)[\\\\/]/,\n          name: 'vue-vendors',\n        },\n        gridsome: {\n          test: /[\\\\/]node_modules[\\\\/](gridsome|vue-meta)[\\\\/]/,\n          name: 'gridsome-vendors',\n        },\n        polyfill: {\n          test: /[\\\\/]node_modules[\\\\/]core-js[\\\\/]/,\n          name: 'core-js'\n        },\n        axios: {\n          test: /[\\\\/]node_modules[\\\\/]axios[\\\\/]/,\n          name: 'axios'\n        }\n      }\n    })\n  }\n}
\n

I can only split out 1 chunk if maxInitialRequests is not set.

\n

Note: The above code overwrites gridsome's css: { split: false } config and always splits CSS. To disable CSS splitting, add these lines:

\n
\n
\n \n js\n
        styles: {\n          name: 'styles',\n          test: m => /css\\/mini-extract/.test(m.type),\n          chunks: 'all',\n          enforce: true\n        }
\n

Choosing Cache Strategy

\n

Precache v.s. Runtime Cache

\n

In short, precache caches all files specified in the manifest, which is usually a list of js and css assets, at install time.

\n

Runtime cache is more flexiable and have a lot of strategies to choose.

\n

My Best Practice Now

\n

Precache the assets, use NetworkFirst strategy for HTML pages and post data, and CacheFirst for images. Don't worry if the network is slow and still taking a long time to load, just set networkTimeoutSeconds.

\n

Why not...

\n
StaleWhileRevalidate for JSON Data Files
\n

The user will not receive changes immediately:

\n
    \n
  1. user browse version A of the page
  2. \n
  3. version B published
  4. \n
  5. user browse again, no change (sw returning staled data and revalidating)
  6. \n
  7. user browse again, page changed
  8. \n
\n

Also, caching JSON data means gridsome/gridsome#1032 (comment)

\n
Add Revision to JSON Data Files
\n

Yes, this is posible by using injectManifest and precache some of them, while runtime caching with a handler to add revision to the url. (Detailed implemantation)

\n

However, compiling service worker with webpack is currently limited that I cannot split chunks with it. That means if the post data changed one byte, the user have to redownload the service worker file again, which is about 50 KB. Even if I managed to split the service worker, that's still 13 KB of manifest and will grow overtime. That's quite annoying that the service worker is always updating, slowly.

\n

Of course if you are sure your users' network is fast, you can ignore above drawback.

\n
Just Precache all JSON Data Files
\n

Precaching all data files has the same drawback as above. Plus, precaching all data files means the user is downloading the whole site on first visit, which is a horrible UX, as well as large network overhead. And only after precache is done will the service worker complete installing.

\n

For example, first visit to https://v3.vuejs.org/ takes a long time to complete. ~400 files to precache.

\n

Of course if the users should be able to browse the full site while offline, and you do not care first visit loading and service worker installing time, go ahead with precaching.

\n

Still Problem

\n

Though using NetworkFirst, sw will still use the cached JSON data version if offline. And the cached file may have different versions. For example:

\n
    \n
  1. user browse version A of page I (site version: A, page I version: A)
  2. \n
  3. version B published
  4. \n
  5. user browse version B of page II (site version: B, page I version: A, page II version: B)
  6. \n
  7. user goes offline
  8. \n
  9. user browse page I, broken
  10. \n
\n

Since the user is offline, it is expected that some pages are not available. Better to show freindly offline message instead of getting a wrong version of data and having the broken page. Maybe using the GraphQL query hash as part of JSON filename?

","createdAt":"2020-09-16T03:22:59.000Z","lastEditedAt":"2021-07-03T13:06:53.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: vue","type":"tag","name":"vue","color":"41b883","path":"/tag/vue/"},{"id":"tag: pwa","type":"tag","name":"pwa","color":"5a0fc8","path":"/tag/pwa/"},{"id":"tag: gridsome","type":"tag","name":"gridsome","color":"00a672","path":"/tag/gridsome/"}],"reactions":[{"emoji":"👍","count":4,"users":["askrzypczak","milindsingh","jgar2020","frankfoerster"]}],"comments":[{"resourcePath":"/AllanChain/blog/issues/149#issuecomment-796747239","author":{"id":"milindsingh","avatarUrl":"https://avatars.githubusercontent.com/u/13949703?s=64&u=89a377f6123bdf1b5418a9aa15dfa26bda18f940&v=4"},"bodyHTML":"

Thanks @AllanChain It really helped.

\n

But I have few things, gridsome is still loading all chunks on the index page, but vuejs does page-wise.

\n

Any idea how to only load relevant js bundle on the page ?

","createdAt":"2021-03-11T13:47:48.000Z","reactions":[]},{"resourcePath":"/AllanChain/blog/issues/149#issuecomment-797127643","author":{"id":"AllanChain","avatarUrl":"https://avatars.githubusercontent.com/u/36528777?s=64&u=e5821b32c02c77ed038c8c82cfe55e07075911b7&v=4"},"bodyHTML":"

@milindsingh I am not having this issue 🤷‍♂️ As reference, you can try gridsome branch of this repo and comment out PWA plugin section in gridsome.config.js for simplicity. For example axios is loaded when the page needs it.

","createdAt":"2021-03-11T23:50:57.000Z","reactions":[]}]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/hugo-circle-ci/index.json b/assets/data/post/hugo-circle-ci/index.json index 0b0986e1f..b90a41cd2 100644 --- a/assets/data/post/hugo-circle-ci/index.json +++ b/assets/data/post/hugo-circle-ci/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"51","title":"Deploying Hugo with CircleCI","summary":"\n

Building blog sites manually is tedious and often leads to mistakes, especially when hosting on master/docs

\n","body":"\n

Think about when you happily update the blog post and every thing looks fine by hugo server, you just typed git add, git commit, git push without a second thought, only to find that you forgot to build the site to docs directory. And you git add, git commit, git push again, finding it a pain to reinvent a fancy commit message. Let's get rid of this!

\n

Deploying Hugo with CircleCI: Glance

\n

There is a good article, but is for version 2 and heavily used && which is not beautiful in YAML.

\n

Version 2.1

\n

Here is the snippet, where workflows is at root level:

\n
\n
\n \n yaml\n
workflows:\n  main:\n    jobs:\n      - deploy:\n          filters:\n            branches:\n              only: master
\n

Beautiful YAML

\n

Something like this:

\n
\n
\n \n yaml\n
      - run:\n          name: Update Submodules\n          command: |\n            git submodule sync\n            git submodule update --init\n      - run:\n          name: Set up Repo\n          command: |\n            git worktree add -B gh-pages ../public origin/gh-pages\n            git config user.email \"ci-build@pkuphysu.top\"\n            git config user.name \"ci-build\"
\n

Don't forget to set SSH keys with push access

\n

Just follow the official documentation

\n

Other things to know

\n

Tell CircleCI to do nothing on gh-pages

\n

You probably needs to first manually checkout an orphan branch gh-pages and add the .circleci/config.yml in to let CircleCI know that this branch shall not be deployed.

\n

just:

\n
\n
\n \n shell\n
git checkout --orphan gh-pages\n# And add that config.yml\ngit add .circleci/config.yml\ngit push -u origin gh-pages
\n

Set up timezone

\n

If you want to include time information in the commit, you well probably find that TZ=xx/xx does not work. That's because the image does not contain the necessary package. Just install it:

\n
\n
\n \n yaml\n
      - run:\n          name: Install tzdata\n          command: |\n            apt-get install tzdata
\n

Finally my config file

\n
\n
\n \n yaml\n
version: 2.1\njobs:\n  deploy:\n    working_directory: ~/repo/blog\n    environment:\n      TZ: Asia/Shanghai\n    docker:\n        - image: cibuilds/hugo:latest\n    steps:\n      - checkout\n      - add_ssh_keys:\n          fingerprints:\n            - '8f:11:02:f3:3e:35:37:d7:17:4d:e4:1e:6b:e8:f6:db'\n      - run:\n          name: Update Submodules\n          command: |\n            git submodule sync\n            git submodule update --init\n      - run:\n          name: Set up Repo\n          command: |\n            git worktree add -B gh-pages ../public origin/gh-pages\n            git config user.email \"ci-build@pkuphysu.top\"\n            git config user.name \"ci-build\"\n      - run:\n          name: Build with Hugo\n          command: |\n            HUGO_ENV=production hugo -v -d ../public\n      - run:\n          name: Install tzdata\n          command: |\n            apt-get install tzdata\n      - run:\n          name: Deploy to gh-pages\n          command: |\n            cd ../public\n            git add --all\n            git commit -m \"Site build at $(date '+%F %T')\"\n            git push\nworkflows:\n  main:\n    jobs:\n      - deploy:\n          filters:\n            branches:\n              only: master
","createdAt":"2019-10-27T12:12:30.000Z","lastEditedAt":"2020-06-30T07:28:06.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: hugo","type":"tag","name":"hugo","color":"ff3db4","path":"/tag/hugo/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"51","title":"Deploying Hugo with CircleCI","summary":"\n

Building blog sites manually is tedious and often leads to mistakes, especially when hosting on master/docs

\n","body":"\n

Think about when you happily update the blog post and every thing looks fine by hugo server, you just typed git add, git commit, git push without a second thought, only to find that you forgot to build the site to docs directory. And you git add, git commit, git push again, finding it a pain to reinvent a fancy commit message. Let's get rid of this!

\n

Deploying Hugo with CircleCI: Glance

\n

There is a good article, but is for version 2 and heavily used && which is not beautiful in YAML.

\n

Version 2.1

\n

Here is the snippet, where workflows is at root level:

\n
\n
\n \n yaml\n
workflows:\n  main:\n    jobs:\n      - deploy:\n          filters:\n            branches:\n              only: master
\n

Beautiful YAML

\n

Something like this:

\n
\n
\n \n yaml\n
      - run:\n          name: Update Submodules\n          command: |\n            git submodule sync\n            git submodule update --init\n      - run:\n          name: Set up Repo\n          command: |\n            git worktree add -B gh-pages ../public origin/gh-pages\n            git config user.email \"ci-build@pkuphysu.top\"\n            git config user.name \"ci-build\"
\n

Don't forget to set SSH keys with push access

\n

Just follow the official documentation

\n

Other things to know

\n

Tell CircleCI to do nothing on gh-pages

\n

You probably needs to first manually checkout an orphan branch gh-pages and add the .circleci/config.yml in to let CircleCI know that this branch shall not be deployed.

\n

just:

\n
\n
\n \n shell\n
git checkout --orphan gh-pages\n# And add that config.yml\ngit add .circleci/config.yml\ngit push -u origin gh-pages
\n

Set up timezone

\n

If you want to include time information in the commit, you well probably find that TZ=xx/xx does not work. That's because the image does not contain the necessary package. Just install it:

\n
\n
\n \n yaml\n
      - run:\n          name: Install tzdata\n          command: |\n            apt-get install tzdata
\n

Finally my config file

\n
\n
\n \n yaml\n
version: 2.1\njobs:\n  deploy:\n    working_directory: ~/repo/blog\n    environment:\n      TZ: Asia/Shanghai\n    docker:\n        - image: cibuilds/hugo:latest\n    steps:\n      - checkout\n      - add_ssh_keys:\n          fingerprints:\n            - '8f:11:02:f3:3e:35:37:d7:17:4d:e4:1e:6b:e8:f6:db'\n      - run:\n          name: Update Submodules\n          command: |\n            git submodule sync\n            git submodule update --init\n      - run:\n          name: Set up Repo\n          command: |\n            git worktree add -B gh-pages ../public origin/gh-pages\n            git config user.email \"ci-build@pkuphysu.top\"\n            git config user.name \"ci-build\"\n      - run:\n          name: Build with Hugo\n          command: |\n            HUGO_ENV=production hugo -v -d ../public\n      - run:\n          name: Install tzdata\n          command: |\n            apt-get install tzdata\n      - run:\n          name: Deploy to gh-pages\n          command: |\n            cd ../public\n            git add --all\n            git commit -m \"Site build at $(date '+%F %T')\"\n            git push\nworkflows:\n  main:\n    jobs:\n      - deploy:\n          filters:\n            branches:\n              only: master
","createdAt":"2019-10-27T12:12:30.000Z","lastEditedAt":"2020-06-30T07:28:06.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: hugo","type":"tag","name":"hugo","color":"ff3db4","path":"/tag/hugo/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/hugo-toc/index.json b/assets/data/post/hugo-toc/index.json index 18fb7e3e9..4ca4fda72 100644 --- a/assets/data/post/hugo-toc/index.json +++ b/assets/data/post/hugo-toc/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"25","title":"TOC in Hugo","summary":"\n

What a challenge to build toc in hugo!

\n","body":"\n

As mentioned in previous blog about Hugo #28, The built-in Toc feature is very inconvenient. And recently, I find that the code I found on the Internet is not perfect. It just ensures enough end tags are rendered, And do not support where the first header is not the biggest. And I tried to fix them all.

\n

First Attempt

\n

It is easy to render all the start tags, and the code I copied is correctly dealing with start tags, so it wouldn't be covered here.

\n

I tried to remove some of the unnecessary end tags. The errors by htmlhint was less, but still some: some markdown files has some empty level of headers, which is not handled correctly, such as:

\n
\n\n
\n
\n
\n \n html\n
<ul>\n<li>Header 1\n<ul><ul>\n<li>Header 3\n</li>\n</ul>\n</ul></li>\n</ul>
\n

But normally, it should be:

\n
\n\n
\n
\n
\n \n html\n
<ul>\n<li>Header 1\n<ul>\n<li>Header 2\n<ul>\n<li>Header 3\n</li>\n</ul></li>\n</ul></li>\n</ul>
\n

By looking into it carefully, you would find that when dealing with the end tags, first render </li>, and render </ul></li> per loop fits the usual case, but if some level is skipped, you should just render </ul>

\n

Second Attempt

\n

I use a variable to record how many blank level previous indent made, and render corresponding number of </ul> when dedenting, and rest will be </ul></li>

\n

But this is still not enough, what if your dedent is less than the blank level, or in other words, you don't actually need to close all these blank levels, such as:

\n
\n\n
\n
\n
\n \n html\n
<ul>\n<li>Header 1\n<ul><ul><ul>\n<li>Header 4\n</li>\n</ul>\n<li>Header 3\n</li>\n</ul>\n</ul></li>\n</ul>
\n

If you think that you could use a variable to record number of previous blank levels, and substract some from it in every dedent until it become 0, take a look at this example:

\n
\n\n
\n
\n
\n \n html\n
<ul>\n<li>Header 1\n<ul><ul>\n<li>Header 3\n<ul><ul>\n<li>Header 5\n</li>\n</ul>\n<li>Header 4\n</li>\n</ul></li>\n</ul>\n</ul>\n<li>Header 1\n</li>\n</ul>
\n

Third Attempt

\n

I need a stack to record what levels are left blank, and just render </ul> if closing that level, and pop that level out of the stack. Else, I will render </ul></li>

\n

But unfortunately, Hugo does not have a stack implementation, so I have to build a wheel.

\n

Luckily, Hugo has Scratch, which supports:

\n\n

That's just enough for making a stack, the only tedious part is poping item, and I did this the hard way:

\n
\n
\n \n html\n
{{- $tmp := $.Scratch.Get \"bareul\" -}}\n{{- $.Scratch.Delete \"bareul\" -}}\n{{- $.Scratch.Set \"bareul\" slice}}\n{{- range seq (sub (len $tmp) 1) -}}\n  {{- $.Scratch.Add \"bareul\" (index $tmp (sub . 1)) -}}\n{{- end -}}
\n

Note that in hugo, seq is 1-based, but index is 0-based 😂

\n

Besides, {{ seq $a [1] $b}} only supports auto detect increase or decrease, which means at least one element will be generated, and you cannot force a seq not to be executed by using {{ seq $a 1 $b}} if by chance $b is smaller than $a😭. But bare {{ seq $a }} will do nothing if $a is 0.

\n

As a result, manually add and sub will be inevitable...

\n

Ultimate Solution

\n

The discussion above did not cover the tricks to handle start of the toc and end of it. But it is a simple trick if you understand what I mentioned in the Third Attempt Section.

\n
\n\n
\n

Just make a loop to find the biggest header, and render correct number of <ul>s before and record blank

\n
\n
\n \n html\n
{{- $largest := 6 -}}\n{{- range $headers -}}\n  {{- $headerLevel := index (findRE \"[1-4]\" . 1) 0 -}}\n  {{- $headerLevel := len (seq $headerLevel) -}}\n  {{- if lt $headerLevel $largest -}}\n    {{- $largest = $headerLevel -}}\n  {{- end -}}\n{{- end -}}\n\n{{- $firstHeaderLevel := len (seq (index (findRE \"[1-4]\" (index $headers 0) 1) 0)) -}}\n\n{{- $.Scratch.Set \"bareul\" slice -}}\n<div id=\"TableOfContents\">\n<ul>\n  {{- range seq (sub $firstHeaderLevel $largest) -}}\n    <ul>\n    {{- $.Scratch.Add \"bareul\" (sub (add $largest .) 1) -}}\n  {{- end -}}\n  {{/* ... */}}
\n

As for the end of toc, just do the same closing from last header to the biggest header.

\n

So, here is the ultimate code.

\n

(May be not so ultimate if you see this post long time after it was written 😄)

\n

UPDATE: hugo template code:

\n
\n
\n \n html\n
{{- $headers := findRE \"<h[1-4].*?>(.|\\n])+?</h[1-4]>\" .Content -}}\n{{- $has_headers := ge (len $headers) 1 -}}\n{{- if $has_headers -}}\n\n{{- $largest := 6 -}}\n{{- range $headers -}}\n  {{- $headerLevel := index (findRE \"[1-4]\" . 1) 0 -}}\n  {{- $headerLevel := len (seq $headerLevel) -}}\n  {{- if lt $headerLevel $largest -}}\n    {{- $largest = $headerLevel -}}\n  {{- end -}}\n{{- end -}}\n\n{{- $firstHeaderLevel := len (seq (index (findRE \"[1-4]\" (index $headers 0) 1) 0)) -}}\n\n{{- $.Scratch.Set \"bareul\" slice -}}\n<ul>\n  {{- range seq (sub $firstHeaderLevel $largest) -}}\n    <ul>\n    {{- $.Scratch.Add \"bareul\" (sub (add $largest .) 1) -}}\n  {{- end -}}\n  {{- range $i, $header := $headers -}}\n    {{- $headerLevel := index (findRE \"[1-4]\" . 1) 0 -}}\n    {{- $headerLevel := len (seq $headerLevel) -}}\n\n    {{/* get id=\"xyz\" */}}\n    {{ $id := index (findRE \"(id=\\\"(.*?)\\\")\" $header 9) 0 }}\n\n    {{/* strip id=\"\" to leave xyz (no way to get regex capturing groups in hugo :( */}}\n    {{ $cleanedID := replace (replace $id \"id=\\\"\" \"\") \"\\\"\" \"\" }}\n    {{- $header := replaceRE \"<h[1-4].*?>((.|\\n])+?)</h[1-4]>\" \"$1\" $header -}}\n\n    {{- if ne $i 0 -}}\n      {{- $prevHeaderLevel := index (findRE \"[1-4]\" (index $headers (sub $i 1)) 1) 0 -}}\n      {{- $prevHeaderLevel := len (seq $prevHeaderLevel) -}}\n        {{- if gt $headerLevel $prevHeaderLevel -}}\n          {{- range seq $prevHeaderLevel (sub $headerLevel 1) -}}\n            <ul>\n            {{/* the first should not be recorded */}}\n            {{- if ne $prevHeaderLevel . -}}\n              {{- $.Scratch.Add \"bareul\" . -}}\n            {{- end -}}\n          {{- end -}}\n        {{- else -}}\n          </li>\n          {{- if lt $headerLevel $prevHeaderLevel -}}\n            {{- range seq (sub $prevHeaderLevel 1) -1 $headerLevel -}}\n              {{- if in ($.Scratch.Get \"bareul\") . -}}\n                </ul>\n                {{/* manually do pop item */}}\n                {{- $tmp := $.Scratch.Get \"bareul\" -}}\n                {{- $.Scratch.Delete \"bareul\" -}}\n                {{- $.Scratch.Set \"bareul\" slice}}\n                {{- range seq (sub (len $tmp) 1) -}}\n                  {{- $.Scratch.Add \"bareul\" (index $tmp (sub . 1)) -}}\n                {{- end -}}\n              {{- else -}}\n                </ul></li>\n              {{- end -}}\n            {{- end -}}\n          {{- end -}}\n        {{- end -}}\n        <li>\n          <a href=\"#{{- $cleanedID  -}}\">{{- $header | safeHTML -}}</a>\n    {{- else -}}\n    <li>\n      <a href=\"#{{- $cleanedID -}}\">{{- $header | safeHTML -}}</a>\n    {{- end -}}\n  {{- end -}}\n  {{ $firstHeaderLevel := $largest }}\n  {{- $lastHeaderLevel := len (seq (index (findRE \"[1-4]\" (index $headers (sub (len $headers) 1)) 1) 0)) -}}\n  </li>\n  {{- range seq (sub $lastHeaderLevel $firstHeaderLevel) -}}\n    {{- if in ($.Scratch.Get \"bareul\") (add . $firstHeaderLevel) -}}\n      </ul>\n    {{- else -}}\n      </ul></li>\n    {{- end -}}\n  {{- end -}}\n</ul>\n{{- end -}}
\n

JQuery Solution

\n

And I tried to implement this using JS, that's indeed much simpler. Besides, I can do many cool stuffs using JS!

\n
\n
\n \n js\n
function createToC() {\n  let primaryHeading = 6;\n  let headings = [];\n  $(\"main :header\").each(\n    (index, header) => {\n      let level = header.tagName.slice(-1);\n      if(level < primaryHeading) primaryHeading = level;\n      headings.push({\n        level: level,\n        id: header.id,\n        title: header.innerHTML\n      });\n    }\n  );\n  let root = $(document.createElement('ul'))\n    .appendTo($(\"#toc\"));\n  let parents = [root];\n  let prevLevel = primaryHeading;\n  let parentIndex = 0;\n  headings.forEach(\n    (heading, index) => {\n      if (heading.level < prevLevel)\n        parentIndex -= prevLevel - heading.level;\n      else\n        for (let i=prevLevel; i < heading.level; i++, parentIndex++)\n          parents[parentIndex + 1] = $(document.createElement('ul'))\n            .appendTo(parents[parentIndex]);\n      prevLevel = heading.level;\n      $(document.createElement('a'))\n        .attr(\"href\", \"#\" + heading.id)\n        .html(heading.title)\n        .appendTo($(document.createElement('li'))\n          .appendTo(parents[parentIndex]));\n    }\n  );\n}
","createdAt":"2019-12-31T15:17:22.000Z","lastEditedAt":"2020-06-30T07:33:17.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: hugo","type":"tag","name":"hugo","color":"ff3db4","path":"/tag/hugo/"}],"reactions":[],"comments":[{"resourcePath":"/AllanChain/blog/issues/25#issuecomment-720225730","author":{"id":"adityatelange","avatarUrl":"https://avatars.githubusercontent.com/u/21258296?s=64&u=5885a9283649b03a98fccdd382a505c994758a67&v=4"},"bodyHTML":"

Thank You @AllanChain for hugo template implementaion.
\nIt is being used in hugo-PaperMod :)

\n

adityatelange/hugo-PaperMod@43d1c68

","createdAt":"2020-11-02T04:11:29.000Z","reactions":[{"emoji":"❤","count":1,"users":["AllanChain"]}]}]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"25","title":"TOC in Hugo","summary":"\n

What a challenge to build toc in hugo!

\n","body":"\n

As mentioned in previous blog about Hugo #28, The built-in Toc feature is very inconvenient. And recently, I find that the code I found on the Internet is not perfect. It just ensures enough end tags are rendered, And do not support where the first header is not the biggest. And I tried to fix them all.

\n

First Attempt

\n

It is easy to render all the start tags, and the code I copied is correctly dealing with start tags, so it wouldn't be covered here.

\n

I tried to remove some of the unnecessary end tags. The errors by htmlhint was less, but still some: some markdown files has some empty level of headers, which is not handled correctly, such as:

\n
\n\n
\n
\n
\n \n html\n
<ul>\n<li>Header 1\n<ul><ul>\n<li>Header 3\n</li>\n</ul>\n</ul></li>\n</ul>
\n

But normally, it should be:

\n
\n\n
\n
\n
\n \n html\n
<ul>\n<li>Header 1\n<ul>\n<li>Header 2\n<ul>\n<li>Header 3\n</li>\n</ul></li>\n</ul></li>\n</ul>
\n

By looking into it carefully, you would find that when dealing with the end tags, first render </li>, and render </ul></li> per loop fits the usual case, but if some level is skipped, you should just render </ul>

\n

Second Attempt

\n

I use a variable to record how many blank level previous indent made, and render corresponding number of </ul> when dedenting, and rest will be </ul></li>

\n

But this is still not enough, what if your dedent is less than the blank level, or in other words, you don't actually need to close all these blank levels, such as:

\n
\n\n
\n
\n
\n \n html\n
<ul>\n<li>Header 1\n<ul><ul><ul>\n<li>Header 4\n</li>\n</ul>\n<li>Header 3\n</li>\n</ul>\n</ul></li>\n</ul>
\n

If you think that you could use a variable to record number of previous blank levels, and substract some from it in every dedent until it become 0, take a look at this example:

\n
\n\n
\n
\n
\n \n html\n
<ul>\n<li>Header 1\n<ul><ul>\n<li>Header 3\n<ul><ul>\n<li>Header 5\n</li>\n</ul>\n<li>Header 4\n</li>\n</ul></li>\n</ul>\n</ul>\n<li>Header 1\n</li>\n</ul>
\n

Third Attempt

\n

I need a stack to record what levels are left blank, and just render </ul> if closing that level, and pop that level out of the stack. Else, I will render </ul></li>

\n

But unfortunately, Hugo does not have a stack implementation, so I have to build a wheel.

\n

Luckily, Hugo has Scratch, which supports:

\n\n

That's just enough for making a stack, the only tedious part is poping item, and I did this the hard way:

\n
\n
\n \n html\n
{{- $tmp := $.Scratch.Get \"bareul\" -}}\n{{- $.Scratch.Delete \"bareul\" -}}\n{{- $.Scratch.Set \"bareul\" slice}}\n{{- range seq (sub (len $tmp) 1) -}}\n  {{- $.Scratch.Add \"bareul\" (index $tmp (sub . 1)) -}}\n{{- end -}}
\n

Note that in hugo, seq is 1-based, but index is 0-based 😂

\n

Besides, {{ seq $a [1] $b}} only supports auto detect increase or decrease, which means at least one element will be generated, and you cannot force a seq not to be executed by using {{ seq $a 1 $b}} if by chance $b is smaller than $a😭. But bare {{ seq $a }} will do nothing if $a is 0.

\n

As a result, manually add and sub will be inevitable...

\n

Ultimate Solution

\n

The discussion above did not cover the tricks to handle start of the toc and end of it. But it is a simple trick if you understand what I mentioned in the Third Attempt Section.

\n
\n\n
\n

Just make a loop to find the biggest header, and render correct number of <ul>s before and record blank

\n
\n
\n \n html\n
{{- $largest := 6 -}}\n{{- range $headers -}}\n  {{- $headerLevel := index (findRE \"[1-4]\" . 1) 0 -}}\n  {{- $headerLevel := len (seq $headerLevel) -}}\n  {{- if lt $headerLevel $largest -}}\n    {{- $largest = $headerLevel -}}\n  {{- end -}}\n{{- end -}}\n\n{{- $firstHeaderLevel := len (seq (index (findRE \"[1-4]\" (index $headers 0) 1) 0)) -}}\n\n{{- $.Scratch.Set \"bareul\" slice -}}\n<div id=\"TableOfContents\">\n<ul>\n  {{- range seq (sub $firstHeaderLevel $largest) -}}\n    <ul>\n    {{- $.Scratch.Add \"bareul\" (sub (add $largest .) 1) -}}\n  {{- end -}}\n  {{/* ... */}}
\n

As for the end of toc, just do the same closing from last header to the biggest header.

\n

So, here is the ultimate code.

\n

(May be not so ultimate if you see this post long time after it was written 😄)

\n

UPDATE: hugo template code:

\n
\n
\n \n html\n
{{- $headers := findRE \"<h[1-4].*?>(.|\\n])+?</h[1-4]>\" .Content -}}\n{{- $has_headers := ge (len $headers) 1 -}}\n{{- if $has_headers -}}\n\n{{- $largest := 6 -}}\n{{- range $headers -}}\n  {{- $headerLevel := index (findRE \"[1-4]\" . 1) 0 -}}\n  {{- $headerLevel := len (seq $headerLevel) -}}\n  {{- if lt $headerLevel $largest -}}\n    {{- $largest = $headerLevel -}}\n  {{- end -}}\n{{- end -}}\n\n{{- $firstHeaderLevel := len (seq (index (findRE \"[1-4]\" (index $headers 0) 1) 0)) -}}\n\n{{- $.Scratch.Set \"bareul\" slice -}}\n<ul>\n  {{- range seq (sub $firstHeaderLevel $largest) -}}\n    <ul>\n    {{- $.Scratch.Add \"bareul\" (sub (add $largest .) 1) -}}\n  {{- end -}}\n  {{- range $i, $header := $headers -}}\n    {{- $headerLevel := index (findRE \"[1-4]\" . 1) 0 -}}\n    {{- $headerLevel := len (seq $headerLevel) -}}\n\n    {{/* get id=\"xyz\" */}}\n    {{ $id := index (findRE \"(id=\\\"(.*?)\\\")\" $header 9) 0 }}\n\n    {{/* strip id=\"\" to leave xyz (no way to get regex capturing groups in hugo :( */}}\n    {{ $cleanedID := replace (replace $id \"id=\\\"\" \"\") \"\\\"\" \"\" }}\n    {{- $header := replaceRE \"<h[1-4].*?>((.|\\n])+?)</h[1-4]>\" \"$1\" $header -}}\n\n    {{- if ne $i 0 -}}\n      {{- $prevHeaderLevel := index (findRE \"[1-4]\" (index $headers (sub $i 1)) 1) 0 -}}\n      {{- $prevHeaderLevel := len (seq $prevHeaderLevel) -}}\n        {{- if gt $headerLevel $prevHeaderLevel -}}\n          {{- range seq $prevHeaderLevel (sub $headerLevel 1) -}}\n            <ul>\n            {{/* the first should not be recorded */}}\n            {{- if ne $prevHeaderLevel . -}}\n              {{- $.Scratch.Add \"bareul\" . -}}\n            {{- end -}}\n          {{- end -}}\n        {{- else -}}\n          </li>\n          {{- if lt $headerLevel $prevHeaderLevel -}}\n            {{- range seq (sub $prevHeaderLevel 1) -1 $headerLevel -}}\n              {{- if in ($.Scratch.Get \"bareul\") . -}}\n                </ul>\n                {{/* manually do pop item */}}\n                {{- $tmp := $.Scratch.Get \"bareul\" -}}\n                {{- $.Scratch.Delete \"bareul\" -}}\n                {{- $.Scratch.Set \"bareul\" slice}}\n                {{- range seq (sub (len $tmp) 1) -}}\n                  {{- $.Scratch.Add \"bareul\" (index $tmp (sub . 1)) -}}\n                {{- end -}}\n              {{- else -}}\n                </ul></li>\n              {{- end -}}\n            {{- end -}}\n          {{- end -}}\n        {{- end -}}\n        <li>\n          <a href=\"#{{- $cleanedID  -}}\">{{- $header | safeHTML -}}</a>\n    {{- else -}}\n    <li>\n      <a href=\"#{{- $cleanedID -}}\">{{- $header | safeHTML -}}</a>\n    {{- end -}}\n  {{- end -}}\n  {{ $firstHeaderLevel := $largest }}\n  {{- $lastHeaderLevel := len (seq (index (findRE \"[1-4]\" (index $headers (sub (len $headers) 1)) 1) 0)) -}}\n  </li>\n  {{- range seq (sub $lastHeaderLevel $firstHeaderLevel) -}}\n    {{- if in ($.Scratch.Get \"bareul\") (add . $firstHeaderLevel) -}}\n      </ul>\n    {{- else -}}\n      </ul></li>\n    {{- end -}}\n  {{- end -}}\n</ul>\n{{- end -}}
\n

JQuery Solution

\n

And I tried to implement this using JS, that's indeed much simpler. Besides, I can do many cool stuffs using JS!

\n
\n
\n \n js\n
function createToC() {\n  let primaryHeading = 6;\n  let headings = [];\n  $(\"main :header\").each(\n    (index, header) => {\n      let level = header.tagName.slice(-1);\n      if(level < primaryHeading) primaryHeading = level;\n      headings.push({\n        level: level,\n        id: header.id,\n        title: header.innerHTML\n      });\n    }\n  );\n  let root = $(document.createElement('ul'))\n    .appendTo($(\"#toc\"));\n  let parents = [root];\n  let prevLevel = primaryHeading;\n  let parentIndex = 0;\n  headings.forEach(\n    (heading, index) => {\n      if (heading.level < prevLevel)\n        parentIndex -= prevLevel - heading.level;\n      else\n        for (let i=prevLevel; i < heading.level; i++, parentIndex++)\n          parents[parentIndex + 1] = $(document.createElement('ul'))\n            .appendTo(parents[parentIndex]);\n      prevLevel = heading.level;\n      $(document.createElement('a'))\n        .attr(\"href\", \"#\" + heading.id)\n        .html(heading.title)\n        .appendTo($(document.createElement('li'))\n          .appendTo(parents[parentIndex]));\n    }\n  );\n}
","createdAt":"2019-12-31T15:17:22.000Z","lastEditedAt":"2020-06-30T07:33:17.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: hugo","type":"tag","name":"hugo","color":"ff3db4","path":"/tag/hugo/"}],"reactions":[],"comments":[{"resourcePath":"/AllanChain/blog/issues/25#issuecomment-720225730","author":{"id":"adityatelange","avatarUrl":"https://avatars.githubusercontent.com/u/21258296?s=64&u=5885a9283649b03a98fccdd382a505c994758a67&v=4"},"bodyHTML":"

Thank You @AllanChain for hugo template implementaion.
\nIt is being used in hugo-PaperMod :)

\n

adityatelange/hugo-PaperMod@43d1c68

","createdAt":"2020-11-02T04:11:29.000Z","reactions":[{"emoji":"❤","count":1,"users":["AllanChain"]}]}]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/jupyter-cht/index.json b/assets/data/post/jupyter-cht/index.json index fe39d4685..c4cce9dc2 100644 --- a/assets/data/post/jupyter-cht/index.json +++ b/assets/data/post/jupyter-cht/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"153","title":"Jupyter etc. Cheatsheet","summary":"\n

主要是一些数据与绘图处理相关

\n","body":"\n

JupyterLab

\n

Extensions out of sync?

\n

Go to <python>\\share\\jupyter\\lab\\settings, edit (or delete) those JSON files.

\n

Matplotlib

\n

官方 cheat sheet

\n

\"\"

\n

绘制横线

\n
\n
\n \n python\n
plt.axhline(y, color, xmin, xmax, linestyle)
\n

双 Y 轴

\n

From https://matplotlib.org/stable/gallery/ticks_and_spines/multiple_yaxis_with_spines.html

\n
\n
\n \n python\n
import matplotlib.pyplot as plt\n\n\nfig, ax = plt.subplots()\n\ntwin1 = ax.twinx()\n\np1, = ax.plot([0, 1, 2], [0, 1, 2], \"b-\", label=\"Density\")\np2, = twin1.plot([0, 1, 2], [0, 3, 2], \"r-\", label=\"Temperature\")\n\nax.set_xlim(0, 2)\nax.set_ylim(0, 2)\ntwin1.set_ylim(0, 4)\n\nax.set_xlabel(\"Distance\")\nax.set_ylabel(\"Density\")\ntwin1.set_ylabel(\"Temperature\")\n\nax.legend(handles=[p1, p2])\n\nplt.show()
\n

size

\n

plot 中使用 markersize 或者 ms 来修改点的大小, 而在 scatter 中直接使用 sizes 即可.

\n

Scipy

\n

B-Spline

\n

scipy doc

\n
\n
\n \n python\n
from scipy.interpolate import make_interp_spline\n\nxx = np.linspace(x.min(), x.max(), 400)\nplt.plot(xx, make_interp_spline(x, y, k=2)(xx))
\n

numpy

\n

同阶矩阵逐项相乘

\n

称为 Hadamard Product(哈达玛积),np.multiply() 得到

\n

pandas

\n

Rolling Mean

\n

From https://stackoverflow.com/a/49016377/8810271

\n

类似股票 n 日均线,或者示波器采样平均值

\n
\n
\n \n python\n
df.rolling(100).mean() 
","createdAt":"2020-12-05T01:25:56.000Z","lastEditedAt":"2021-09-20T13:32:35.000Z","image":"/blog/img/8c93085b.png","imageLazy":"AEAAkDASIAAhEBAxEB/8QAFgABAQE$AEE/8QAFRABAQ$AEH/xAAUAQE$AAA/8QAFBEB$AAAP/aAAwDAQACEQMRAD8AxxAB/9k=","serializedHeadings":"[]","labels":[{"id":"blog: cheatsheet","type":"blog","name":"cheatsheet","color":"6655d6","path":"/blog/cheatsheet/"},{"id":"tag: jupyter","type":"tag","name":"jupyter","color":"f37626","path":"/tag/jupyter/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"153","title":"Jupyter etc. Cheatsheet","summary":"\n

主要是一些数据与绘图处理相关

\n","body":"\n

JupyterLab

\n

Extensions out of sync?

\n

Go to <python>\\share\\jupyter\\lab\\settings, edit (or delete) those JSON files.

\n

Matplotlib

\n

官方 cheat sheet

\n

\"\"

\n

绘制横线

\n
\n
\n \n python\n
plt.axhline(y, color, xmin, xmax, linestyle)
\n

双 Y 轴

\n

From https://matplotlib.org/stable/gallery/ticks_and_spines/multiple_yaxis_with_spines.html

\n
\n
\n \n python\n
import matplotlib.pyplot as plt\n\n\nfig, ax = plt.subplots()\n\ntwin1 = ax.twinx()\n\np1, = ax.plot([0, 1, 2], [0, 1, 2], \"b-\", label=\"Density\")\np2, = twin1.plot([0, 1, 2], [0, 3, 2], \"r-\", label=\"Temperature\")\n\nax.set_xlim(0, 2)\nax.set_ylim(0, 2)\ntwin1.set_ylim(0, 4)\n\nax.set_xlabel(\"Distance\")\nax.set_ylabel(\"Density\")\ntwin1.set_ylabel(\"Temperature\")\n\nax.legend(handles=[p1, p2])\n\nplt.show()
\n

size

\n

plot 中使用 markersize 或者 ms 来修改点的大小, 而在 scatter 中直接使用 sizes 即可.

\n

Scipy

\n

B-Spline

\n

scipy doc

\n
\n
\n \n python\n
from scipy.interpolate import make_interp_spline\n\nxx = np.linspace(x.min(), x.max(), 400)\nplt.plot(xx, make_interp_spline(x, y, k=2)(xx))
\n

numpy

\n

同阶矩阵逐项相乘

\n

称为 Hadamard Product(哈达玛积),np.multiply() 得到

\n

pandas

\n

Rolling Mean

\n

From https://stackoverflow.com/a/49016377/8810271

\n

类似股票 n 日均线,或者示波器采样平均值

\n
\n
\n \n python\n
df.rolling(100).mean() 
","createdAt":"2020-12-05T01:25:56.000Z","lastEditedAt":"2021-09-20T13:32:35.000Z","image":"/blog/img/8c93085b.png","imageLazy":"AEAAkDASIAAhEBAxEB/8QAFgABAQE$AEE/8QAFRABAQ$AEH/xAAUAQE$AAA/8QAFBEB$AAAP/aAAwDAQACEQMRAD8AxxAB/9k=","serializedHeadings":"[]","labels":[{"id":"blog: cheatsheet","type":"blog","name":"cheatsheet","color":"6655d6","path":"/blog/cheatsheet/"},{"id":"tag: jupyter","type":"tag","name":"jupyter","color":"f37626","path":"/tag/jupyter/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/jupyter-raspi/index.json b/assets/data/post/jupyter-raspi/index.json index 0ad5ffca5..93db991cc 100644 --- a/assets/data/post/jupyter-raspi/index.json +++ b/assets/data/post/jupyter-raspi/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"64","title":"Jupyter on Raspberry Pi","summary":"","body":"\n

Start Jupyter as service

\n

From https://gist.github.com/whophil/5a2eab328d2f8c16bb31c9ceaf23164f

\n

/etc/systemd/system/jupyter.service:

\n
\n
\n \n ini\n
# After Ubuntu 16.04, Systemd becomes the default.\n# It is simpler than https://gist.github.com/Doowon/38910829898a6624ce4ed554f082c4dd\n\n[Unit]\nDescription=Jupyter Notebook\n\n[Service]\nType=simple\nPIDFile=/run/jupyter.pid\nExecStart=jupyter-notebook --config=/home/pi/.jupyter/jupyter_notebook_config.py\nUser=pi\nGroup=pi\nWorkingDirectory=/home/pi/Notebooks/\nRestart=always\nRestartSec=10\n#KillMode=mixed\n\n[Install]\nWantedBy=multi-user.target
\n

To use, just do it in systemctl way:

\n
\n
\n \n shell\n
sudo systemctl start jupyter\nsudo systemctl stop jupyter\n# To launch while start up\nsudo systemctl enable jupyter
\n
\n

NOTICE

\n

run systemctl daemon-reload often when changing unit file. Or it will silently use the old bad file.

\n

不要问我是怎么知道的

\n
\n

libf77blas.so.3: cannot open shared object file

\n
\n
\n \n shell\n
sudo apt install libatlas-base-dev
\n

Config Jupyter

\n

Follow https://jupyter-notebook.readthedocs.io/en/stable/public_server.html

\n

Typical Way

\n
\n
\n \n shell\n
jupyter notebook --generate-config\n# Get passwd hash\npython3 -c 'from notebook.auth import passwd;print(passwd())'\n# cd to a nice directory\n# where `nodes` means `no-des`, no passwd for private key\nopenssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -sha256 -nodes
\n

And here is the official example for the configuration file:

\n
\n
\n \n python\n
# Set options for certfile, ip, password, and toggle off\n# browser auto-opening\nc.NotebookApp.certfile = '/absolute/path/to/your/certificate/fullchain.pem'\nc.NotebookApp.keyfile = '/absolute/path/to/your/certificate/privkey.pem'\n# Set ip to '*' to bind on all interfaces (ips) for the public server\nc.NotebookApp.ip = '*'\nc.NotebookApp.password = 'sha1:bcd259ccf...<your hashed password here>'\nc.NotebookApp.open_browser = False\n# It is a good idea to set a known, fixed port for server access\nc.NotebookApp.port = 9999
\n
\n

⚠️ ​IMPORTANT

\n

There are so many keys in the file, be sure to modify the correct key.

\n
\n

Listen to ipv6 Only

\n
\n
\n \n python\n
c.NotebookApp.ip = '::'
\n

Jupyter Themes

\n

Installation

\n
\n
\n \n shell\n
pip3 install jupyterthemes
\n

Configuration

\n
\n
\n \n shell\n
jt -t oceans16 -T -N -fs 16 -nfs 16 -cellw 90%
\n

Explanation:

\n
-T, --toolbar         make toolbar visible\n-N, --nbname          nb name/logo visible\n-t THEME              theme name to install\n-fs MONOSIZE          code font-size\n-nfs NBFONTSIZE       notebook fontsize\n-tfs TCFONTSIZE       txtcell fontsize\n-dfs DFFONTSIZE       pandas dataframe fontsize\n-ofs OUTFONTSIZE      output area fontsize\n-mathfs MATHFONTSIZE  mathjax fontsize (in %)\n-cellw CELLWIDTH      set cell width (px or %)\n
\n
\n

Font sizes are in pt

\n
\n

Behind Nginx

\n

Reference: https://gist.github.com/christopherbaek/39e6c432e212ca7a67ffe015fe869664

\n\n
\n
\n \n python\n
c.NotebookApp.base_url = 'jupyter'\nc.NotebookApp.ip = '127.0.0.1'\nc.NotebookApp.open_browser = False\nc.NotebookApp.password = 'sha1:84cb...36f7e6'\nc.NotebookApp.port = 9999
\n\n
\n
\n \n nginx\n
location /jupyter/ {\n    proxy_pass http://127.0.0.1:9999/jupyter/;\n\n    proxy_set_header X-Real-IP $remote_addr;\n    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n    proxy_set_header X-NginX-Proxy true;\n\n    # WebSocket support\n    proxy_http_version 1.1;\n    proxy_set_header Upgrade $http_upgrade;\n    proxy_set_header Connection \"upgrade\";\n    proxy_set_header Origin \"\";\n}
","createdAt":"2020-02-26T12:20:29.000Z","lastEditedAt":"2020-07-01T12:33:55.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: raspi","type":"tag","name":"raspi","color":"c31c4a","path":"/tag/raspi/"},{"id":"tag: jupyter","type":"tag","name":"jupyter","color":"f37626","path":"/tag/jupyter/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"64","title":"Jupyter on Raspberry Pi","summary":"","body":"\n

Start Jupyter as service

\n

From https://gist.github.com/whophil/5a2eab328d2f8c16bb31c9ceaf23164f

\n

/etc/systemd/system/jupyter.service:

\n
\n
\n \n ini\n
# After Ubuntu 16.04, Systemd becomes the default.\n# It is simpler than https://gist.github.com/Doowon/38910829898a6624ce4ed554f082c4dd\n\n[Unit]\nDescription=Jupyter Notebook\n\n[Service]\nType=simple\nPIDFile=/run/jupyter.pid\nExecStart=jupyter-notebook --config=/home/pi/.jupyter/jupyter_notebook_config.py\nUser=pi\nGroup=pi\nWorkingDirectory=/home/pi/Notebooks/\nRestart=always\nRestartSec=10\n#KillMode=mixed\n\n[Install]\nWantedBy=multi-user.target
\n

To use, just do it in systemctl way:

\n
\n
\n \n shell\n
sudo systemctl start jupyter\nsudo systemctl stop jupyter\n# To launch while start up\nsudo systemctl enable jupyter
\n
\n

NOTICE

\n

run systemctl daemon-reload often when changing unit file. Or it will silently use the old bad file.

\n

不要问我是怎么知道的

\n
\n

libf77blas.so.3: cannot open shared object file

\n
\n
\n \n shell\n
sudo apt install libatlas-base-dev
\n

Config Jupyter

\n

Follow https://jupyter-notebook.readthedocs.io/en/stable/public_server.html

\n

Typical Way

\n
\n
\n \n shell\n
jupyter notebook --generate-config\n# Get passwd hash\npython3 -c 'from notebook.auth import passwd;print(passwd())'\n# cd to a nice directory\n# where `nodes` means `no-des`, no passwd for private key\nopenssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -sha256 -nodes
\n

And here is the official example for the configuration file:

\n
\n
\n \n python\n
# Set options for certfile, ip, password, and toggle off\n# browser auto-opening\nc.NotebookApp.certfile = '/absolute/path/to/your/certificate/fullchain.pem'\nc.NotebookApp.keyfile = '/absolute/path/to/your/certificate/privkey.pem'\n# Set ip to '*' to bind on all interfaces (ips) for the public server\nc.NotebookApp.ip = '*'\nc.NotebookApp.password = 'sha1:bcd259ccf...<your hashed password here>'\nc.NotebookApp.open_browser = False\n# It is a good idea to set a known, fixed port for server access\nc.NotebookApp.port = 9999
\n
\n

⚠️ ​IMPORTANT

\n

There are so many keys in the file, be sure to modify the correct key.

\n
\n

Listen to ipv6 Only

\n
\n
\n \n python\n
c.NotebookApp.ip = '::'
\n

Jupyter Themes

\n

Installation

\n
\n
\n \n shell\n
pip3 install jupyterthemes
\n

Configuration

\n
\n
\n \n shell\n
jt -t oceans16 -T -N -fs 16 -nfs 16 -cellw 90%
\n

Explanation:

\n
-T, --toolbar         make toolbar visible\n-N, --nbname          nb name/logo visible\n-t THEME              theme name to install\n-fs MONOSIZE          code font-size\n-nfs NBFONTSIZE       notebook fontsize\n-tfs TCFONTSIZE       txtcell fontsize\n-dfs DFFONTSIZE       pandas dataframe fontsize\n-ofs OUTFONTSIZE      output area fontsize\n-mathfs MATHFONTSIZE  mathjax fontsize (in %)\n-cellw CELLWIDTH      set cell width (px or %)\n
\n
\n

Font sizes are in pt

\n
\n

Behind Nginx

\n

Reference: https://gist.github.com/christopherbaek/39e6c432e212ca7a67ffe015fe869664

\n\n
\n
\n \n python\n
c.NotebookApp.base_url = 'jupyter'\nc.NotebookApp.ip = '127.0.0.1'\nc.NotebookApp.open_browser = False\nc.NotebookApp.password = 'sha1:84cb...36f7e6'\nc.NotebookApp.port = 9999
\n\n
\n
\n \n nginx\n
location /jupyter/ {\n    proxy_pass http://127.0.0.1:9999/jupyter/;\n\n    proxy_set_header X-Real-IP $remote_addr;\n    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n    proxy_set_header X-NginX-Proxy true;\n\n    # WebSocket support\n    proxy_http_version 1.1;\n    proxy_set_header Upgrade $http_upgrade;\n    proxy_set_header Connection \"upgrade\";\n    proxy_set_header Origin \"\";\n}
","createdAt":"2020-02-26T12:20:29.000Z","lastEditedAt":"2020-07-01T12:33:55.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: raspi","type":"tag","name":"raspi","color":"c31c4a","path":"/tag/raspi/"},{"id":"tag: jupyter","type":"tag","name":"jupyter","color":"f37626","path":"/tag/jupyter/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/jupyter-wolfram/index.json b/assets/data/post/jupyter-wolfram/index.json index cf5fbbe9c..e5cd9d21d 100644 --- a/assets/data/post/jupyter-wolfram/index.json +++ b/assets/data/post/jupyter-wolfram/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"76","title":"Install Wolfram With Jupyter","summary":"","body":"\n

From https://github.com/wjxway/PKU-shuakeji, I find that I can install free Wolfram Mathematica on Raspberry Pi.

\n

Go ahead and apt install wolfram-engine wolframscript

\n

Get Wolfram Language For Jupyter Guide here: https://github.com/WolframResearch/WolframLanguageForJupyter/

\n

WTF PATH

\n

If try the first approach:

\n
\n
\n \n shell\n
./configure-jupyter.wls add
\n

Error occurred:

\n
configure-jupyter.wls: Jupyter installation on Environment[\"PATH\"] not found.\n
\n

But jupyter is in PATH indeed:

\n
\n
\n \n shell\n
$ which jupyter\n/usr/local/bin/jupyter\n$ echo $PATH\n/home/pi/.local/bin:/home/pi/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/games:/usr/games
\n

However in wolframscript

\n
\n
\n \n shell\n
$ echo 'Print[Environment[\"PATH\"]]' | wolframscript\nWolfram Language 12.0.1 Engine for Linux ARM (32-bit)\nCopyright 1988-2019 Wolfram Research, Inc.\n\nIn[1]:= /opt/Wolfram/WolframEngine/12.0/Executables:/opt/Wolfram/WolframEngine/12.0/S\\\n\n>   ystemFiles/Graphics/Binaries/Linux-ARM:/home/pi/.local/bin:/usr/bin:/bin
\n

Well, PATH is different...

\n

As said in ./configure-jupyter.wls help, you can specify jupyter path by

\n
configure-jupyter.wls add \"/absolute/path/to/Wolfram-Engine-binary--not-wolframscript\" \"path/to/Jupyter-binary\"\n        adds the provided absolute Wolfram Engine binary path to the provided Jupyter binary path\n
\n

If you try the second, you can avoid providing path to Wolfram Engine binary:

\n
\n
\n \n mathematica\n
ConfigureJupyter[\"Add\", \"JupyterInstallation\" -> \"...\"]
\n

WTF GFW

\n

However, it seems to update the paclet from server, which is very slow here...

\n

Luckily, success.

","createdAt":"2020-02-26T09:50:09.000Z","lastEditedAt":"2020-07-01T12:28:36.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: raspi","type":"tag","name":"raspi","color":"c31c4a","path":"/tag/raspi/"},{"id":"tag: jupyter","type":"tag","name":"jupyter","color":"f37626","path":"/tag/jupyter/"},{"id":"tag: wolfram","type":"tag","name":"wolfram","color":"d70026","path":"/tag/wolfram/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"76","title":"Install Wolfram With Jupyter","summary":"","body":"\n

From https://github.com/wjxway/PKU-shuakeji, I find that I can install free Wolfram Mathematica on Raspberry Pi.

\n

Go ahead and apt install wolfram-engine wolframscript

\n

Get Wolfram Language For Jupyter Guide here: https://github.com/WolframResearch/WolframLanguageForJupyter/

\n

WTF PATH

\n

If try the first approach:

\n
\n
\n \n shell\n
./configure-jupyter.wls add
\n

Error occurred:

\n
configure-jupyter.wls: Jupyter installation on Environment[\"PATH\"] not found.\n
\n

But jupyter is in PATH indeed:

\n
\n
\n \n shell\n
$ which jupyter\n/usr/local/bin/jupyter\n$ echo $PATH\n/home/pi/.local/bin:/home/pi/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/games:/usr/games
\n

However in wolframscript

\n
\n
\n \n shell\n
$ echo 'Print[Environment[\"PATH\"]]' | wolframscript\nWolfram Language 12.0.1 Engine for Linux ARM (32-bit)\nCopyright 1988-2019 Wolfram Research, Inc.\n\nIn[1]:= /opt/Wolfram/WolframEngine/12.0/Executables:/opt/Wolfram/WolframEngine/12.0/S\\\n\n>   ystemFiles/Graphics/Binaries/Linux-ARM:/home/pi/.local/bin:/usr/bin:/bin
\n

Well, PATH is different...

\n

As said in ./configure-jupyter.wls help, you can specify jupyter path by

\n
configure-jupyter.wls add \"/absolute/path/to/Wolfram-Engine-binary--not-wolframscript\" \"path/to/Jupyter-binary\"\n        adds the provided absolute Wolfram Engine binary path to the provided Jupyter binary path\n
\n

If you try the second, you can avoid providing path to Wolfram Engine binary:

\n
\n
\n \n mathematica\n
ConfigureJupyter[\"Add\", \"JupyterInstallation\" -> \"...\"]
\n

WTF GFW

\n

However, it seems to update the paclet from server, which is very slow here...

\n

Luckily, success.

","createdAt":"2020-02-26T09:50:09.000Z","lastEditedAt":"2020-07-01T12:28:36.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: raspi","type":"tag","name":"raspi","color":"c31c4a","path":"/tag/raspi/"},{"id":"tag: jupyter","type":"tag","name":"jupyter","color":"f37626","path":"/tag/jupyter/"},{"id":"tag: wolfram","type":"tag","name":"wolfram","color":"d70026","path":"/tag/wolfram/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/linux-cht/index.json b/assets/data/post/linux-cht/index.json index 76a7fc85a..39faf0346 100644 --- a/assets/data/post/linux-cht/index.json +++ b/assets/data/post/linux-cht/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"81","title":"Linux (Ubuntu) Cheatsheet","summary":"\n

Useful commands and tips about system operation. Things more about scripting is in Bash Cheatsheet

\n","body":"\n

systemctl

\n

Where are service files?

\n
/etc/systemd/system/service-name.service\n
\n

Start at Boot

\n
\n
\n \n shell\n
sudo systemctl enable sshd.service
\n

disable is the opposite.

\n

Use Environment in Service Unit File

\n
[Service]\nEnvironment=PATH=/home/pi/.local/bin:/home/pi/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\n
\n
\n

📔 Note

\n

$PATH do not have its special meaning here.

\n

https://askubuntu.com/q/1014480 for advanced.

\n
\n

Unit File Template

\n
\nClick to expand\n
[Unit]\nDescription=Jupyter Notebook\n\n[Service]\nType=simple\nPIDFile=/run/jupyter.pid\nEnvironment=PATH=/home/pi/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\nExecStart=jupyter lab --config=/home/pi/.jupyter/jupyter_notebook_config.py\nUser=pi\nGroup=pi\nWorkingDirectory=/home/pi/Notebooks/\nRestart=always\nRestartSec=10\n#KillMode=mixed\n\n[Install]\nWantedBy=multi-user.target\n
\n
\n

Exited Without Error Log?

\n

From https://unix.stackexchange.com/a/225407

\n

Use journalctl command. e.g.

\n
\n
\n \n shell\n
journalctl -u service-name.service
\n

-u is short for --unit. More useful options:

\n\n

apt

\n

List All Versions

\n

https://askubuntu.com/q/473886

\n
\n
\n \n shell\n
apt-cache madison chromium-browser\n# or\napt-cache showpkg lyx
\n

Install Specific Version

\n

https://askubuntu.com/q/428772

\n
\n
\n \n shell\n
# get version of installed package\napt-cache policy <package name>\n# install a specific package version\nsudo apt-get install <package name>=<version>
\n

OS Release Info

\n
\n
\n \n shell\n
cat /etc/os-release
\n

List All Users

\n
\n
\n \n shell\n
less /etc/passwd
\n

lsof

\n

LiSt Open Files. A very powerful tool.

\n

For network checking:

\n
ls -i [46][protocol][@hostname|hostaddr][:service|port]\n
\n

e.g.

\n
\n
\n \n shell\n
sudo lsof -i :80
\n

find

\n
\n
\n \n shell\n
find -iname 'qwerty'\nfind dir/ -iname 'qwerty'\nfind -name 'Qwerty'
\n

No Wireless Connection After Sleep

\n
\n
\n \n shell\n
sudo service network-manager restart
","createdAt":"2020-04-18T04:48:27.000Z","lastEditedAt":"2020-06-30T06:59:29.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: cheatsheet","type":"blog","name":"cheatsheet","color":"6655d6","path":"/blog/cheatsheet/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"81","title":"Linux (Ubuntu) Cheatsheet","summary":"\n

Useful commands and tips about system operation. Things more about scripting is in Bash Cheatsheet

\n","body":"\n

systemctl

\n

Where are service files?

\n
/etc/systemd/system/service-name.service\n
\n

Start at Boot

\n
\n
\n \n shell\n
sudo systemctl enable sshd.service
\n

disable is the opposite.

\n

Use Environment in Service Unit File

\n
[Service]\nEnvironment=PATH=/home/pi/.local/bin:/home/pi/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\n
\n
\n

📔 Note

\n

$PATH do not have its special meaning here.

\n

https://askubuntu.com/q/1014480 for advanced.

\n
\n

Unit File Template

\n
\nClick to expand\n
[Unit]\nDescription=Jupyter Notebook\n\n[Service]\nType=simple\nPIDFile=/run/jupyter.pid\nEnvironment=PATH=/home/pi/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\nExecStart=jupyter lab --config=/home/pi/.jupyter/jupyter_notebook_config.py\nUser=pi\nGroup=pi\nWorkingDirectory=/home/pi/Notebooks/\nRestart=always\nRestartSec=10\n#KillMode=mixed\n\n[Install]\nWantedBy=multi-user.target\n
\n
\n

Exited Without Error Log?

\n

From https://unix.stackexchange.com/a/225407

\n

Use journalctl command. e.g.

\n
\n
\n \n shell\n
journalctl -u service-name.service
\n

-u is short for --unit. More useful options:

\n\n

apt

\n

List All Versions

\n

https://askubuntu.com/q/473886

\n
\n
\n \n shell\n
apt-cache madison chromium-browser\n# or\napt-cache showpkg lyx
\n

Install Specific Version

\n

https://askubuntu.com/q/428772

\n
\n
\n \n shell\n
# get version of installed package\napt-cache policy <package name>\n# install a specific package version\nsudo apt-get install <package name>=<version>
\n

OS Release Info

\n
\n
\n \n shell\n
cat /etc/os-release
\n

List All Users

\n
\n
\n \n shell\n
less /etc/passwd
\n

lsof

\n

LiSt Open Files. A very powerful tool.

\n

For network checking:

\n
ls -i [46][protocol][@hostname|hostaddr][:service|port]\n
\n

e.g.

\n
\n
\n \n shell\n
sudo lsof -i :80
\n

find

\n
\n
\n \n shell\n
find -iname 'qwerty'\nfind dir/ -iname 'qwerty'\nfind -name 'Qwerty'
\n

No Wireless Connection After Sleep

\n
\n
\n \n shell\n
sudo service network-manager restart
","createdAt":"2020-04-18T04:48:27.000Z","lastEditedAt":"2020-06-30T06:59:29.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: cheatsheet","type":"blog","name":"cheatsheet","color":"6655d6","path":"/blog/cheatsheet/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/manjaro-first-glance/index.json b/assets/data/post/manjaro-first-glance/index.json index b7fd4988d..3011cb0e0 100644 --- a/assets/data/post/manjaro-first-glance/index.json +++ b/assets/data/post/manjaro-first-glance/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"170","title":"Manjaro Linux 初体验","summary":"\n

偏随笔性质的文章,也没什么新东西,就随便谈谈,直接用母语好了

\n","body":"\n

为什么双系统?

\n

更多的是想真正体验一下 Linux 的桌面环境吧,说白了就是玩玩。反正现在很多程序对 Windows 支持也都还可以,也不是说非得一台 Linux 系统才能完成。就算是跳出 Windows 见见世面也是好的。

\n

双系统的坏处想过吗?

\n

确实想过,也正是这些弊端延后了我折腾双系统的进程 😄

\n

总的来说还是老生常谈的两大弊端:开机启动时间变长、个人信息管理的碎片化

\n

开机启动时间的话确实会变慢 5 - 10 秒的,主要还是进 grub 再进系统的时间耽误。你可以把选择时间改成一秒,默认系统改成 Windows,但不能选择不进 grub ...

\n

碎片化这一点确实比较难解,主要的文件通过同步、Git 的方式可以比较好的解决,毕竟副系统大多数时候也不用管不常用的文件。但是软件也不好搞。浏览器编辑器这种不装不行,那多媒体工具也来一套?算了,毕竟是副系统,管那么多呢!

\n

为什么选择 Manjaro?

\n

其实也是巧合,在我决定装一个双系统玩一玩之前都没怎么听说过 Manjaro 的大名 😂 主要还是看到在看到一位博主由 Ubuntu 粉转 Manjaro 粉之后,又因为以前用过 Ubuntu 的虚拟机,决定试一试 Manjaro。

\n

其实还有原因。Manjaro 是 Arch 系的,可以蹭社区蹭 WIki,是版本控的乐园,不必经历一年两度的换系统时间(没错就是内涵的 Ubuntu)。同时由可以说是专攻个人电脑的系统,没有 Arch 那么折腾。想想就很期待呢!

\n

至于 XFCE, KDE 和 GNOME 的差别我也分不清,毕竟是冲着体验桌面环境来的。那就从号称 eliable and fast 的 XFCE 开始吧!

\n

Ventoy 装双系统?

\n

正如 Ventoy 文档所说的,在 Windows 下制作 Ventoy 盘成功概率玄学,尤其是被折腾过的 U盘。试了好几次都是 Boot Failed, 最后使用 rufus 把 Ventoy LiveCD 弄进去也不行。但是这么一烧,传统的方法又制作成功了 😕

\n

接下来就是把镜像下到 U 盘里,就能安装了。唯一要留心的就是分区,多看几篇文章就差不多了。

\n

配置、使用系统的体验?

\n

其实更多的还是 Linux 的共同的体验 😅

\n

无非就是装一些软件包嘛,顺便说 AUR 是真的香,就可惜在垃圾校园网下 git clone 有点……

\n

主要还是中文的字体、输入法有一点点麻烦,看上去有点怪怪的,可能是微软雅黑看多了吧……

\n

触摸板的支持确实不如 Windows 下丝滑,就很奇怪 Windows 下的浏览器触摸板可以实现与 Ctrl + + 不同的缩放,双指滑动前进后退的功能也找不到……

\n

还有指纹读取器的问题,也是醉了。这个 Goodix 厂商就没打算让人来写这个驱动,好家伙要靠逆向工程才能让 libfprint 支持,那估计(至少该电脑的)有生之年是不太可能了……

\n

以及刚刚知道“模板”文件夹的神奇功能:What is the \"Templates\" folder in the home directory for? 直接完爆注册表!

\n

还有 XFCE 的自定义确实够强,尤其是面板(任务栏),控件确实不错。

\n

哦对了,说了双系统怎么能不提时间问题呢?Windows 在主板里存的是本地时间,而 Linux 存的是 UTC 导致切换系统后时间不对。解决方法也很简单,百度一下就有了:sudo timedatectl set-local-rtc true

\n

Linux 的桌面环境资源占用率确实低,跑起 MC 不在话下

\n

其他?再说吧。东西多且杂就不拟小标题了。

","createdAt":"2021-04-19T13:50:07.000Z","lastEditedAt":"2021-04-19T14:31:41.000Z","image":"/blog/img/d75c565e.jpg","imageLazy":"AGAAkDASIAAhEBAxEB/8QAFgABAQE$AID/8QAGhAAAgID$ECEgMRUf/EABQBAQ$AAH/xAAUEQE$AAA/9oADAMBAAIRAxEAPwDHI1FKNVoi0ugAX//Z","serializedHeadings":"[]","labels":[{"id":"blog: moments","type":"blog","name":"moments","color":"3dae38","path":"/blog/moments/"},{"id":"tag: linux","type":"tag","name":"linux","color":"de4815","path":"/tag/linux/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"170","title":"Manjaro Linux 初体验","summary":"\n

偏随笔性质的文章,也没什么新东西,就随便谈谈,直接用母语好了

\n","body":"\n

为什么双系统?

\n

更多的是想真正体验一下 Linux 的桌面环境吧,说白了就是玩玩。反正现在很多程序对 Windows 支持也都还可以,也不是说非得一台 Linux 系统才能完成。就算是跳出 Windows 见见世面也是好的。

\n

双系统的坏处想过吗?

\n

确实想过,也正是这些弊端延后了我折腾双系统的进程 😄

\n

总的来说还是老生常谈的两大弊端:开机启动时间变长、个人信息管理的碎片化

\n

开机启动时间的话确实会变慢 5 - 10 秒的,主要还是进 grub 再进系统的时间耽误。你可以把选择时间改成一秒,默认系统改成 Windows,但不能选择不进 grub ...

\n

碎片化这一点确实比较难解,主要的文件通过同步、Git 的方式可以比较好的解决,毕竟副系统大多数时候也不用管不常用的文件。但是软件也不好搞。浏览器编辑器这种不装不行,那多媒体工具也来一套?算了,毕竟是副系统,管那么多呢!

\n

为什么选择 Manjaro?

\n

其实也是巧合,在我决定装一个双系统玩一玩之前都没怎么听说过 Manjaro 的大名 😂 主要还是看到在看到一位博主由 Ubuntu 粉转 Manjaro 粉之后,又因为以前用过 Ubuntu 的虚拟机,决定试一试 Manjaro。

\n

其实还有原因。Manjaro 是 Arch 系的,可以蹭社区蹭 WIki,是版本控的乐园,不必经历一年两度的换系统时间(没错就是内涵的 Ubuntu)。同时由可以说是专攻个人电脑的系统,没有 Arch 那么折腾。想想就很期待呢!

\n

至于 XFCE, KDE 和 GNOME 的差别我也分不清,毕竟是冲着体验桌面环境来的。那就从号称 eliable and fast 的 XFCE 开始吧!

\n

Ventoy 装双系统?

\n

正如 Ventoy 文档所说的,在 Windows 下制作 Ventoy 盘成功概率玄学,尤其是被折腾过的 U盘。试了好几次都是 Boot Failed, 最后使用 rufus 把 Ventoy LiveCD 弄进去也不行。但是这么一烧,传统的方法又制作成功了 😕

\n

接下来就是把镜像下到 U 盘里,就能安装了。唯一要留心的就是分区,多看几篇文章就差不多了。

\n

配置、使用系统的体验?

\n

其实更多的还是 Linux 的共同的体验 😅

\n

无非就是装一些软件包嘛,顺便说 AUR 是真的香,就可惜在垃圾校园网下 git clone 有点……

\n

主要还是中文的字体、输入法有一点点麻烦,看上去有点怪怪的,可能是微软雅黑看多了吧……

\n

触摸板的支持确实不如 Windows 下丝滑,就很奇怪 Windows 下的浏览器触摸板可以实现与 Ctrl + + 不同的缩放,双指滑动前进后退的功能也找不到……

\n

还有指纹读取器的问题,也是醉了。这个 Goodix 厂商就没打算让人来写这个驱动,好家伙要靠逆向工程才能让 libfprint 支持,那估计(至少该电脑的)有生之年是不太可能了……

\n

以及刚刚知道“模板”文件夹的神奇功能:What is the \"Templates\" folder in the home directory for? 直接完爆注册表!

\n

还有 XFCE 的自定义确实够强,尤其是面板(任务栏),控件确实不错。

\n

哦对了,说了双系统怎么能不提时间问题呢?Windows 在主板里存的是本地时间,而 Linux 存的是 UTC 导致切换系统后时间不对。解决方法也很简单,百度一下就有了:sudo timedatectl set-local-rtc true

\n

Linux 的桌面环境资源占用率确实低,跑起 MC 不在话下

\n

其他?再说吧。东西多且杂就不拟小标题了。

","createdAt":"2021-04-19T13:50:07.000Z","lastEditedAt":"2021-04-19T14:31:41.000Z","image":"/blog/img/d75c565e.jpg","imageLazy":"AGAAkDASIAAhEBAxEB/8QAFgABAQE$AID/8QAGhAAAgID$ECEgMRUf/EABQBAQ$AAH/xAAUEQE$AAA/9oADAMBAAIRAxEAPwDHI1FKNVoi0ugAX//Z","serializedHeadings":"[]","labels":[{"id":"blog: moments","type":"blog","name":"moments","color":"3dae38","path":"/blog/moments/"},{"id":"tag: linux","type":"tag","name":"linux","color":"de4815","path":"/tag/linux/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/markdown-cht/index.json b/assets/data/post/markdown-cht/index.json index 9c42fbde2..519256492 100644 --- a/assets/data/post/markdown-cht/index.json +++ b/assets/data/post/markdown-cht/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"84","title":"Markdown Cheatsheet","summary":"\n

Tricks which are less known to markdown starters

\n","body":"\n

Escaping `

\n

The general principle is, escape ` with more `s.

\n

For example, if you want to escape a single `, just wrap it with two on each side, and add spaces, that is, `` ` ``.

\n

If you want to escape three back quotes in a code block, use 4 for the code fence:

\n
````\n```python\nprint('hello')\n```\n````\n
\n

Produces:

\n
```python\nprint('hello')\n```\n
\n

Awesome Dropdown!

\n
\nWrite Markdown!\n\n

Python Code

\n
\n
\n \n python\n
from __future__ import print_function\nprint('Hello world!')
\n
\n
\nRaw code\n
\n
\n \n html\n
<details open>\n<summary>Write Markdown!</summary>\n<!--All you need is a blank line-->\n\n**Python** *Code*\n````python\nfrom __future__ import print_function\nprint('Hello world!')\n```\n</details>
\n
\n

Reference: https://github.github.com/gfm/#html-block

\n

The trick is, the blank line terminates the html block

\n

Center a Image in GFM

\n

Use HTML tag p with align to wrap img

\n
\n
\n \n html\n
<p align=\"center\">\n  <img src=\"https://github.com/favicon.ico\">\n</p>
\n

","createdAt":"2020-04-18T04:48:51.000Z","lastEditedAt":"2020-07-17T15:18:28.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: cheatsheet","type":"blog","name":"cheatsheet","color":"6655d6","path":"/blog/cheatsheet/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"84","title":"Markdown Cheatsheet","summary":"\n

Tricks which are less known to markdown starters

\n","body":"\n

Escaping `

\n

The general principle is, escape ` with more `s.

\n

For example, if you want to escape a single `, just wrap it with two on each side, and add spaces, that is, `` ` ``.

\n

If you want to escape three back quotes in a code block, use 4 for the code fence:

\n
````\n```python\nprint('hello')\n```\n````\n
\n

Produces:

\n
```python\nprint('hello')\n```\n
\n

Awesome Dropdown!

\n
\nWrite Markdown!\n\n

Python Code

\n
\n
\n \n python\n
from __future__ import print_function\nprint('Hello world!')
\n
\n
\nRaw code\n
\n
\n \n html\n
<details open>\n<summary>Write Markdown!</summary>\n<!--All you need is a blank line-->\n\n**Python** *Code*\n````python\nfrom __future__ import print_function\nprint('Hello world!')\n```\n</details>
\n
\n

Reference: https://github.github.com/gfm/#html-block

\n

The trick is, the blank line terminates the html block

\n

Center a Image in GFM

\n

Use HTML tag p with align to wrap img

\n
\n
\n \n html\n
<p align=\"center\">\n  <img src=\"https://github.com/favicon.ico\">\n</p>
\n

","createdAt":"2020-04-18T04:48:51.000Z","lastEditedAt":"2020-07-17T15:18:28.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: cheatsheet","type":"blog","name":"cheatsheet","color":"6655d6","path":"/blog/cheatsheet/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/md-cn-bold/index.json b/assets/data/post/md-cn-bold/index.json index 7568ce17a..66ef778e6 100644 --- a/assets/data/post/md-cn-bold/index.json +++ b/assets/data/post/md-cn-bold/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"143","title":"手机端 GitHub Markdown 中文粗体显示的问题","summary":"\n

这鬼问题应该只发生在安卓机上

\n","body":"\n

问题现象

\n

明明电脑端显示很正常的粗体,到了华为手机的浏览器里就只对英文加粗,中文的被忽略,仍然显示正常字体。

\n

本质原因

\n

安卓机的字体并不如苹果机的细腻,即 font-weight 的变化很有限。

\n

可查看 https://allanchain.gitee.io/html/font-weight.html 了解你的手机对字体粗细的尊重情况。比如我的机子:

\n

\"Screenshot_2020-08-22-08-53-32\"

\n

可以看到就在 600 的位置,也就是 GitHub <strong> 指定的 font-weight,英文/数字被加粗了但是中文没有。。

\n

扩展资料

\n

禾几的微信小程序踩坑*2 中也有类似叙述,并附有多种机型的截图,可惜未标注具体机型。

\n

解决方法?

\n

要么换字体,要么换浏览器用插件、脚本 😂

\n

如果是网站开发者,一定要做好主流机子的字体兼容性测试。看起来 700 到 900 或者 bold 是比较合适的。

","createdAt":"2020-08-22T00:53:07.000Z","lastEditedAt":"2020-08-22T01:07:36.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: android","type":"tag","name":"android","color":"3ddc84","path":"/tag/android/"}],"reactions":[],"comments":[{"resourcePath":"/AllanChain/blog/issues/143#issuecomment-678587544","author":{"id":"Yixuan-Wang","avatarUrl":"https://avatars.githubusercontent.com/u/21151119?s=64&u=ca1f5a8752cd0dcd6ac2046bf3684ef7eb223c72&v=4"},"bodyHTML":"

自带字体字重不全,应该是只有 normal 和 bold……Noto 系列字重很全(Noto 字体死忠粉发言)。

\n

https://developer.mozilla.org/zh-CN/docs/Web/CSS/font-weight
\nnormal = 400
\nbold = 700

","createdAt":"2020-08-22T03:31:50.000Z","reactions":[]},{"resourcePath":"/AllanChain/blog/issues/143#issuecomment-678595400","author":{"id":"AllanChain","avatarUrl":"https://avatars.githubusercontent.com/u/36528777?s=64&u=e5821b32c02c77ed038c8c82cfe55e07075911b7&v=4"},"bodyHTML":"
\n

自带字体字重不全,应该是只有 normal 和 bold……

\n
\n

是的,刚刚试了自带的默认、安卓、华为字体,在 600 处都有这个迷惑行为😭 Noto 值得考虑

","createdAt":"2020-08-22T05:03:54.000Z","reactions":[]}]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"143","title":"手机端 GitHub Markdown 中文粗体显示的问题","summary":"\n

这鬼问题应该只发生在安卓机上

\n","body":"\n

问题现象

\n

明明电脑端显示很正常的粗体,到了华为手机的浏览器里就只对英文加粗,中文的被忽略,仍然显示正常字体。

\n

本质原因

\n

安卓机的字体并不如苹果机的细腻,即 font-weight 的变化很有限。

\n

可查看 https://allanchain.gitee.io/html/font-weight.html 了解你的手机对字体粗细的尊重情况。比如我的机子:

\n

\"Screenshot_2020-08-22-08-53-32\"

\n

可以看到就在 600 的位置,也就是 GitHub <strong> 指定的 font-weight,英文/数字被加粗了但是中文没有。。

\n

扩展资料

\n

禾几的微信小程序踩坑*2 中也有类似叙述,并附有多种机型的截图,可惜未标注具体机型。

\n

解决方法?

\n

要么换字体,要么换浏览器用插件、脚本 😂

\n

如果是网站开发者,一定要做好主流机子的字体兼容性测试。看起来 700 到 900 或者 bold 是比较合适的。

","createdAt":"2020-08-22T00:53:07.000Z","lastEditedAt":"2020-08-22T01:07:36.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: android","type":"tag","name":"android","color":"3ddc84","path":"/tag/android/"}],"reactions":[],"comments":[{"resourcePath":"/AllanChain/blog/issues/143#issuecomment-678587544","author":{"id":"Yixuan-Wang","avatarUrl":"https://avatars.githubusercontent.com/u/21151119?s=64&u=ca1f5a8752cd0dcd6ac2046bf3684ef7eb223c72&v=4"},"bodyHTML":"

自带字体字重不全,应该是只有 normal 和 bold……Noto 系列字重很全(Noto 字体死忠粉发言)。

\n

https://developer.mozilla.org/zh-CN/docs/Web/CSS/font-weight
\nnormal = 400
\nbold = 700

","createdAt":"2020-08-22T03:31:50.000Z","reactions":[]},{"resourcePath":"/AllanChain/blog/issues/143#issuecomment-678595400","author":{"id":"AllanChain","avatarUrl":"https://avatars.githubusercontent.com/u/36528777?s=64&u=e5821b32c02c77ed038c8c82cfe55e07075911b7&v=4"},"bodyHTML":"
\n

自带字体字重不全,应该是只有 normal 和 bold……

\n
\n

是的,刚刚试了自带的默认、安卓、华为字体,在 600 处都有这个迷惑行为😭 Noto 值得考虑

","createdAt":"2020-08-22T05:03:54.000Z","reactions":[]}]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/moving-blog-to-astro/index.json b/assets/data/post/moving-blog-to-astro/index.json new file mode 100644 index 000000000..f5a3bea7e --- /dev/null +++ b/assets/data/post/moving-blog-to-astro/index.json @@ -0,0 +1 @@ +{"hash":"gridsome","data":{"post":{"id":"179","title":"Another Move of My Blog?","summary":"\n

It really needs a serious discussion.

\n","body":"\n

Well, yes. I'm planning another move.

\n

It's not only because that the front-end ecosystem is moving fast, but also about all the unsolved problems.

\n

Brief History

\n

Before I presenting the motivation and possibilities of moving to astro, let me briefly introduce the history.

\n

The oldest version of blog is ProgrammingNotes, which has been archived. It was built with MkDocs. It is a great tool for documentation, but not for blog. It's more like a knowledge base than a actual blog.

\n

Then I switched to Hugo. I chose Hugo mainly because its wide adoption in simple personal websites. There are hundreds of awesome templates for displaying your experience and publications. And I made my own theme from a great theme called XMag. During the process of making and maintaining the theme, I found that Hugo's template language is really counter-intuitive and hard to do it right. Also the comment on GitHub system is hard. The OAuth and \"behave on your behalf\" approach is not elegant. So I chose to blog directly with GitHub issues. And I was learning Vue, so Gridsome is a natural choice.

\n

The Motivation

\n

But Gridsome has it's own problem. It's updating slowly. Very slow. The Vue ecosystem has changed a lot in recent years, and Gridsome fails to keep up.

\n

Alright, if that's the only reason, I'll just stick to Gridsome. But there are more.

\n

It was a wrong choice to use Vuetify for a blog. Vuetify provides a lot of useful components, and is a fantastic framework for apps. It was the only great UI framework for Vue I knew at that time. However, it just make a log site bloated. There is too much JavaScript and CSS for a site deployed on GitHub Pages and visited mainly in my region. Although I'm using PWA (Progressive Web App) tech to ease the pain, the first page load is still slow, and PWA itself is introducing a lot of inconsistent and buggy behaviors. Besides, Vuetify's design is not suitable for blog, or maybe it's my fault not doing this in a right way. People are just tired of material designs, probably because it looks like a cheap app.

\n

Things will be different if I migrate the blog's UI system to Tailwind / WindiCSS / UnoCSS. The bundle size will be small, and it will be easy if there is another move in the future. I have already used both WindiCSS and UnoCSS in a couple of projects, and they are awesome, especially the attributify mode.

\n

Also, with the new micro-blog, the old 3-part division (moments, cheat sheets, programming) is no longer needed. The category can just act like a normal tag. Also I'm planning to adopt a common blog index page, namely focusing on the recent posts, and hopefully making it more attractive.

\n

Possibilities of Using Astro

\n

Why not Vite SSG?

\n

One already have great SSG (Static Site Generation) in Vite, why bother using another framework? The answer is that the SSG Vite community provides is not suitable for a blog. I really don't like the idea that every page is a component. In the context of blogging, the blog content is more data than components.

\n

Advantages of Using Astro

\n

Astro fetches data ones at build time just like Gridsome, while Nuxt still fetches data when switching pages. So the logic of fetching data from GitHub Issues can just be reused.

\n

Besides, when it comes to making blog content interactive (i.e. copying code block, making interactive table of contents), the amount of required vanilla JavaScript is no less than actual Vue code. So it might be better just embracing vanilla JavaScript and create Vue components when necessary.

\n

Moving Blog too Often?

\n

Yes, it's a bad practice to constantly move blogs without writing many posts in between. However, the blog is not just a website to log something, it's also a place we explore and play around with new web technologies!

\n

Besides, during this move, adopting Astro is just a small part of it. It's just a little more than a \"regular\" update.

","createdAt":"2022-04-06T04:00:10.000Z","lastEditedAt":"2022-05-01T08:55:31.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"}],"reactions":[{"emoji":"🚀","count":1,"users":["Yixuan-Wang"]}],"comments":[{"resourcePath":"/AllanChain/blog/issues/179#issuecomment-1089981801","author":{"id":"Yixuan-Wang","avatarUrl":"https://avatars.githubusercontent.com/u/21151119?s=64&u=ca1f5a8752cd0dcd6ac2046bf3684ef7eb223c72&v=4"},"bodyHTML":"

blogging
\n blog-rewriting

","createdAt":"2022-04-06T08:20:37.000Z","reactions":[]},{"resourcePath":"/AllanChain/blog/issues/179#issuecomment-1090088303","author":{"id":"AllanChain","avatarUrl":"https://avatars.githubusercontent.com/u/36528777?s=64&u=e5821b32c02c77ed038c8c82cfe55e07075911b7&v=4"},"bodyHTML":"
\n

blogging
\n blog-rewriting

\n
\n

To put it in a nicer way:

\n

A website to log something
\n A testing field for new techs

","createdAt":"2022-04-06T10:04:26.000Z","reactions":[{"emoji":"👍","count":1,"users":["Yixuan-Wang"]}]}]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/mysql-emoji/index.json b/assets/data/post/mysql-emoji/index.json index 526ef7302..7495bbe27 100644 --- a/assets/data/post/mysql-emoji/index.json +++ b/assets/data/post/mysql-emoji/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"22","title":"MySQL 存储 Emoji","summary":"\n

最近需要使用 SQLAlchemy 存弹幕的内容,但是遇到了存 emoji 的问题。

\n","body":"\n

UPDATE: See https://docs.sqlalchemy.org/en/13/dialects/mysql.html#unicode for more info

\n

utf8_bin?

\n

一开始就套用存储中文姓名的那一套,使用utf8_bin的 collation,觉得 utf8 这种万能的东西直接用就行了。可谁知给我报错:

\n
mysql.connector.errors.DatabaseError: 1366 (HY000): Incorrect string value: '\\xE8\\x86\\x9C' for column 'text' at row 1\n
\n

蛤?竟有如此操作?

\n

utf8mb4_unicode_ci?

\n

一波搜索之后就看到了使用utf8mb4_unicode_ci的 collation。于是就写:

\n
\n
\n \n python\n
text = db.Column(db.Unicode(256, collation='utf8mb4_unicode_ci'))
\n

可惜并没有实质性效果

\n

暴力二进制

\n

utf8 解决不了,二进制存储总行了吧?

\n

果然,存进去并没有问题。但是当要读取出来的时候又报了奇怪的错误:

\n
\n
\n \n python\n
TypeError: string argument without an encoding
\n

明明是二进制怎么给我搞出来一个编码问题?

\n

后期查阅资料发现,SQL 似乎有一个动态类型,每列的数据类型是建议值,并不强制。其结果就是我存一个二进制编码的字符串,他就真的以为是字符类型,并且数据库表里没有存编码,导致了问题出现。

\n

疾病设计😠!!!

\n

直接escape

\n

二进制也不行,全 escape 总可以了吧!

\n
\n
\n \n python\n
>>> text.encode('unicode_escape').decode()\n>>> text.decode('unicode_escape').encode()
\n

总算成功存储和读取了!

\n

人人都说utf8mb4_unicode_ci

\n

为什么就是不行呢?

\n

哦,还要改数据库 charset, collation

\n
\n
\n \n sql\n
ALTER DATABASE databasename CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;\nALTER TABLE tablename CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
\n

还要给 SQLAlchemy 加 ?charset=utf8mb4

\n
\n

Edit: 使用 SQLALCHEMY_ENGINE_OPTIONS.connect_args 不应该加 ?charset=utf8mb4,加完之后会优先认 ?charset=utf8mb4,导致 collation 默认 utf8mb4_0900_ai_ci (which is MySQL 8.0's default but not implemented in MySQL 5.7)

\n
\n

可是为什么还是不行?

\n
\n
\n \n python\n
mysql.connector.errors.DatabaseError: 1273 (HY000): Unknown collation: 'utf8mb4_0900_ai_ci'
\n

我几几年用过这编码?

\n

版本大坑

\n

我找到了mysql-connector-python的官方文档:https://dev.mysql.com/doc/connector-python/en/connector-python-connectargs.html

\n

collation 一栏中:

\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Argument NameDefaultDescription
collationutf8mb4_general_ai_ci (is utf8_general_ci in 2.xWhich MySQL collation to use. The 8.x default values are generated from the latest MySQL Server 8.0 defaults.
\n
\n

什么?8.0 的默认值?我们只有 5.7 呢!

\n

虽然并不是utf8mb4_0900_ai_ci这种东西,我还是试着把链接数据库参数修改成utf8mb4_unicode_ci

\n
\n
\n \n yaml\n
SQLALCHEMY_ENGINE_OPTIONS:\n    connect_args:\n        collation: utf8mb4_unicode_ci
\n

噫!成功了!

\n

事实证明utf8mb4_0900_ai_ciutf8mb4_general_ai_ci应该是同义词一样的存在。

\n

可是:

\n
\n

MySQL Connector/Python 8.0 is highly recommended for use with MySQL Server 8.0, 5.7, 5.6, and 5.5. Please upgrade to MySQL Connector/Python 8.0.

\n
\n

好一个recommended,就给我挖这种坑?版本问题害死人!

","createdAt":"2019-12-23T14:21:24.000Z","lastEditedAt":"2020-09-26T07:41:43.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: mysql","type":"tag","name":"mysql","color":"f09011","path":"/tag/mysql/"}],"reactions":[],"comments":[{"resourcePath":"/AllanChain/blog/issues/22#issuecomment-600969690","author":{"id":"AllanChain","avatarUrl":"https://avatars.githubusercontent.com/u/36528777?s=64&u=e5821b32c02c77ed038c8c82cfe55e07075911b7&v=4"},"bodyHTML":"
SQLALCHEMY_ENGINE_OPTIONS:\n    connect_args:\n        charset: utf8mb4\n        collation: utf8mb4_unicode_ci
\n

这里指定编码。

","createdAt":"2020-03-19T03:33:14.000Z","reactions":[]}]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"22","title":"MySQL 存储 Emoji","summary":"\n

最近需要使用 SQLAlchemy 存弹幕的内容,但是遇到了存 emoji 的问题。

\n","body":"\n

UPDATE: See https://docs.sqlalchemy.org/en/13/dialects/mysql.html#unicode for more info

\n

utf8_bin?

\n

一开始就套用存储中文姓名的那一套,使用utf8_bin的 collation,觉得 utf8 这种万能的东西直接用就行了。可谁知给我报错:

\n
mysql.connector.errors.DatabaseError: 1366 (HY000): Incorrect string value: '\\xE8\\x86\\x9C' for column 'text' at row 1\n
\n

蛤?竟有如此操作?

\n

utf8mb4_unicode_ci?

\n

一波搜索之后就看到了使用utf8mb4_unicode_ci的 collation。于是就写:

\n
\n
\n \n python\n
text = db.Column(db.Unicode(256, collation='utf8mb4_unicode_ci'))
\n

可惜并没有实质性效果

\n

暴力二进制

\n

utf8 解决不了,二进制存储总行了吧?

\n

果然,存进去并没有问题。但是当要读取出来的时候又报了奇怪的错误:

\n
\n
\n \n python\n
TypeError: string argument without an encoding
\n

明明是二进制怎么给我搞出来一个编码问题?

\n

后期查阅资料发现,SQL 似乎有一个动态类型,每列的数据类型是建议值,并不强制。其结果就是我存一个二进制编码的字符串,他就真的以为是字符类型,并且数据库表里没有存编码,导致了问题出现。

\n

疾病设计😠!!!

\n

直接escape

\n

二进制也不行,全 escape 总可以了吧!

\n
\n
\n \n python\n
>>> text.encode('unicode_escape').decode()\n>>> text.decode('unicode_escape').encode()
\n

总算成功存储和读取了!

\n

人人都说utf8mb4_unicode_ci

\n

为什么就是不行呢?

\n

哦,还要改数据库 charset, collation

\n
\n
\n \n sql\n
ALTER DATABASE databasename CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;\nALTER TABLE tablename CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
\n

还要给 SQLAlchemy 加 ?charset=utf8mb4

\n
\n

Edit: 使用 SQLALCHEMY_ENGINE_OPTIONS.connect_args 不应该加 ?charset=utf8mb4,加完之后会优先认 ?charset=utf8mb4,导致 collation 默认 utf8mb4_0900_ai_ci (which is MySQL 8.0's default but not implemented in MySQL 5.7)

\n
\n

可是为什么还是不行?

\n
\n
\n \n python\n
mysql.connector.errors.DatabaseError: 1273 (HY000): Unknown collation: 'utf8mb4_0900_ai_ci'
\n

我几几年用过这编码?

\n

版本大坑

\n

我找到了mysql-connector-python的官方文档:https://dev.mysql.com/doc/connector-python/en/connector-python-connectargs.html

\n

collation 一栏中:

\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Argument NameDefaultDescription
collationutf8mb4_general_ai_ci (is utf8_general_ci in 2.xWhich MySQL collation to use. The 8.x default values are generated from the latest MySQL Server 8.0 defaults.
\n
\n

什么?8.0 的默认值?我们只有 5.7 呢!

\n

虽然并不是utf8mb4_0900_ai_ci这种东西,我还是试着把链接数据库参数修改成utf8mb4_unicode_ci

\n
\n
\n \n yaml\n
SQLALCHEMY_ENGINE_OPTIONS:\n    connect_args:\n        collation: utf8mb4_unicode_ci
\n

噫!成功了!

\n

事实证明utf8mb4_0900_ai_ciutf8mb4_general_ai_ci应该是同义词一样的存在。

\n

可是:

\n
\n

MySQL Connector/Python 8.0 is highly recommended for use with MySQL Server 8.0, 5.7, 5.6, and 5.5. Please upgrade to MySQL Connector/Python 8.0.

\n
\n

好一个recommended,就给我挖这种坑?版本问题害死人!

","createdAt":"2019-12-23T14:21:24.000Z","lastEditedAt":"2020-09-26T07:41:43.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: mysql","type":"tag","name":"mysql","color":"f09011","path":"/tag/mysql/"}],"reactions":[],"comments":[{"resourcePath":"/AllanChain/blog/issues/22#issuecomment-600969690","author":{"id":"AllanChain","avatarUrl":"https://avatars.githubusercontent.com/u/36528777?s=64&u=e5821b32c02c77ed038c8c82cfe55e07075911b7&v=4"},"bodyHTML":"
SQLALCHEMY_ENGINE_OPTIONS:\n    connect_args:\n        charset: utf8mb4\n        collation: utf8mb4_unicode_ci
\n

这里指定编码。

","createdAt":"2020-03-19T03:33:14.000Z","reactions":[]}]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/mysql-in-wsl/index.json b/assets/data/post/mysql-in-wsl/index.json index 6ecdfaaf9..2c8d0ebe8 100644 --- a/assets/data/post/mysql-in-wsl/index.json +++ b/assets/data/post/mysql-in-wsl/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"71","title":"Mysql in WSL","summary":"\n

😜 TL;DR

\n

查看日志/var/log/mysql/error.log,上互联网搜

\n","body":"\n

在使用 Windows Subsystem for Linux 时,可能因为缺少对 MySQL 是预置安装,会遇到一些问题

\n

如果说

\n
ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2)\n
\n

可以试试建立 /var/run/mysqld 目录,并给 mysql 权限

\n

如果说

\n
No directory, logging in with HOME=/\n
\n

可以试试 sudo usermod -d /var/lib/mysql/ mysql

\n
\n

更新了一次系统后,MySQL 又无法启动了。刚开始还以为是更新的时候之前的配置被抹掉了,可并不是。。

\n

UPDATE: 不要听下面这个部分

\n

根据microsoft/WSL#3631 (comment),是 MySQL 版本太高了。。自动升到了 8.x。还要按照步骤回退。。😡

\n

但是但是,由于我作死升到了 eoan,那个仓库并不支持。所以去 https://dev.mysql.com/downloads/repo/apt/ 下载了最新的。然而,并没有 5.7 的选项😭

\n

所以只能强行安装 Ubuntu 19.04 的包了,下载对应的 deb 文件:

\n
\n
\n \n shell\n
wget https://dev.mysql.com/get/mysql-apt-config_0.8.13-1_all.deb
\n
\n

或者。。直接装 Windows 上,说不定也可。

\n
\n

UPDATE: 事实证明,是 MySQL 未正确升级所致,只需要完全删除 MySQL,再重新安装,就可以了(只要备份了重要文件)

\n
\n
\n \n shell\n
sudo apt remove --purge *mysql*\nsudo apt remove --purge *mariadb*\nsudo rm -rf /etc/mysql /var/lib/mysql\nsudo apt autoremove\nsudo apt autoclean
","createdAt":"2020-02-07T08:59:02.000Z","lastEditedAt":"2020-06-24T05:55:14.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: mysql","type":"tag","name":"mysql","color":"f09011","path":"/tag/mysql/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"71","title":"Mysql in WSL","summary":"\n

😜 TL;DR

\n

查看日志/var/log/mysql/error.log,上互联网搜

\n","body":"\n

在使用 Windows Subsystem for Linux 时,可能因为缺少对 MySQL 是预置安装,会遇到一些问题

\n

如果说

\n
ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2)\n
\n

可以试试建立 /var/run/mysqld 目录,并给 mysql 权限

\n

如果说

\n
No directory, logging in with HOME=/\n
\n

可以试试 sudo usermod -d /var/lib/mysql/ mysql

\n
\n

更新了一次系统后,MySQL 又无法启动了。刚开始还以为是更新的时候之前的配置被抹掉了,可并不是。。

\n

UPDATE: 不要听下面这个部分

\n

根据microsoft/WSL#3631 (comment),是 MySQL 版本太高了。。自动升到了 8.x。还要按照步骤回退。。😡

\n

但是但是,由于我作死升到了 eoan,那个仓库并不支持。所以去 https://dev.mysql.com/downloads/repo/apt/ 下载了最新的。然而,并没有 5.7 的选项😭

\n

所以只能强行安装 Ubuntu 19.04 的包了,下载对应的 deb 文件:

\n
\n
\n \n shell\n
wget https://dev.mysql.com/get/mysql-apt-config_0.8.13-1_all.deb
\n
\n

或者。。直接装 Windows 上,说不定也可。

\n
\n

UPDATE: 事实证明,是 MySQL 未正确升级所致,只需要完全删除 MySQL,再重新安装,就可以了(只要备份了重要文件)

\n
\n
\n \n shell\n
sudo apt remove --purge *mysql*\nsudo apt remove --purge *mariadb*\nsudo rm -rf /etc/mysql /var/lib/mysql\nsudo apt autoremove\nsudo apt autoclean
","createdAt":"2020-02-07T08:59:02.000Z","lastEditedAt":"2020-06-24T05:55:14.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: mysql","type":"tag","name":"mysql","color":"f09011","path":"/tag/mysql/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/netease-music-bp/index.json b/assets/data/post/netease-music-bp/index.json new file mode 100644 index 000000000..e6a71ca8f --- /dev/null +++ b/assets/data/post/netease-music-bp/index.json @@ -0,0 +1 @@ +{"hash":"gridsome","data":{"post":{"id":"180","title":"免费下网易云会员歌曲 MP3 竟如此简单?","summary":"\n

我坦白,我是标题党

\n","body":"\n

背景:出于一些正经的教学目的,我要从网易云上下载一个会员专享的专辑

\n

条件:近日某校内社交平台友很多送会员的帖子

\n

方案:说一句“谢谢洞主”即可白拿一个 7 日会员体验。然后现下一个网易云音乐客户端,下好曲子拷贝到电脑之后就可以卸载了

\n

手段:万能的 GitHub 一搜就有 ncm 格式转 mp3 的脚本,也不长,直接复制下来运行

\n

小贴士:因为脚本需要用到 AES 加解密,原本的 pycrypto 库已经停止维护,在 Python 3.10 上无法正常运行,所以需要安装 pycryptodome 库,它是前者的 drop-in replacement.

","createdAt":"2022-05-01T08:50:29.000Z","lastEditedAt":"2022-05-01T08:50:29.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: moments","type":"blog","name":"moments","color":"3dae38","path":"/blog/moments/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/npm-reg/index.json b/assets/data/post/npm-reg/index.json index fd54dc516..7963bd301 100644 --- a/assets/data/post/npm-reg/index.json +++ b/assets/data/post/npm-reg/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"160","title":"NPM Registry: What If I Cannot Reach It?","summary":"\n

The story of mirrors, ipv6, and yarn 2

\n","body":"\n

Above image background from https://travelandleisureindia.in/great-wall-of-china-visitors-cap/

\n

Well, you might say, npm config set registry xxx does the trick. I know how to set npm registry mirror of course. I just have another story to tell.

\n

Registries are Shown in Lock File

\n

Npm or yarn classic, no matter which you are using, recommends you to commit the lock file (at least in several situations) and writes registry into lock file. What if you want to speed up development using one registry while speeding up CI with another registry, due to differrent network environments? What if the registry committed in version control is simply not reachable by others, which cannot be easily solved without deleting lock file? For more situations, see yarnpkg/yarn/issues/3330.

\n

Say Hello to Yarn 2

\n

Although PnP mode introducedn in yarn 2 has many capability issues, you can simply turn it of by setting nodeLinker: node-modules in .yarnrc.yml. Enjoy registry agnostic lock file... to some degree. https://registry.npm.taobao.org is not the case, because it doesn't follow the standard url patterns from the npm registry. See yarnpkg/berry#2192 (comment) for the explanation. But good news, https://repo.huaweicloud.com/repository/npm/ works great.

\n

Why Registry Works Fine in Browser but Fails in Terminal?

\n

This is a little bit confusing. I pinged registry.npmjs.org and found it was an ipv6 address. Then I tried to ping resolved ipv4 address (dig registry.npmjs.org A) but all of them are timed out. Maybe both npm and yarn do not support ipv6, in year 2021?

\n

Nico Schottelius' blog post The Nodejs in IPv6 only networks problem is to the point. It's basically a NodeJS bug (nodejs/node/pull/31567). NodeJS reorders the DNS result so that IPv4 addresses come before IPv6 addresses by default, making it unfriendly to ipv6 network environments.

\n

Workaround

\n

Set hosts to an ipv6 address to overwrite DNS result.

","createdAt":"2021-02-01T08:18:23.000Z","lastEditedAt":"2021-02-01T08:18:23.000Z","image":"/blog/img/b056d3fb.jpg","imageLazy":"AEAAkDASIAAhEBAxEB/8QAFQABAQ$AAL/xAAXEAADAQ$AREC/8QAFQEBAQ$AAH/xAAVEQEB$AAEf/aAAwDAQACEQMRAD8AvLkiRdAIV//Z","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: network","type":"tag","name":"network","color":"A5F306","path":"/tag/network/"}],"reactions":[{"emoji":"👍","count":2,"users":["miyurusankalpa","loleg"]}],"comments":[]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"160","title":"NPM Registry: What If I Cannot Reach It?","summary":"\n

The story of mirrors, ipv6, and yarn 2

\n","body":"\n

Above image background from https://travelandleisureindia.in/great-wall-of-china-visitors-cap/

\n

Well, you might say, npm config set registry xxx does the trick. I know how to set npm registry mirror of course. I just have another story to tell.

\n

Registries are Shown in Lock File

\n

Npm or yarn classic, no matter which you are using, recommends you to commit the lock file (at least in several situations) and writes registry into lock file. What if you want to speed up development using one registry while speeding up CI with another registry, due to differrent network environments? What if the registry committed in version control is simply not reachable by others, which cannot be easily solved without deleting lock file? For more situations, see yarnpkg/yarn/issues/3330.

\n

Say Hello to Yarn 2

\n

Although PnP mode introducedn in yarn 2 has many capability issues, you can simply turn it of by setting nodeLinker: node-modules in .yarnrc.yml. Enjoy registry agnostic lock file... to some degree. https://registry.npm.taobao.org is not the case, because it doesn't follow the standard url patterns from the npm registry. See yarnpkg/berry#2192 (comment) for the explanation. But good news, https://repo.huaweicloud.com/repository/npm/ works great.

\n

Why Registry Works Fine in Browser but Fails in Terminal?

\n

This is a little bit confusing. I pinged registry.npmjs.org and found it was an ipv6 address. Then I tried to ping resolved ipv4 address (dig registry.npmjs.org A) but all of them are timed out. Maybe both npm and yarn do not support ipv6, in year 2021?

\n

Nico Schottelius' blog post The Nodejs in IPv6 only networks problem is to the point. It's basically a NodeJS bug (nodejs/node/pull/31567). NodeJS reorders the DNS result so that IPv4 addresses come before IPv6 addresses by default, making it unfriendly to ipv6 network environments.

\n

Workaround

\n

Set hosts to an ipv6 address to overwrite DNS result.

","createdAt":"2021-02-01T08:18:23.000Z","lastEditedAt":"2021-02-01T08:18:23.000Z","image":"/blog/img/b056d3fb.jpg","imageLazy":"AEAAkDASIAAhEBAxEB/8QAFQABAQ$AAL/xAAXEAADAQ$AREC/8QAFQEBAQ$AAH/xAAVEQEB$AAEf/aAAwDAQACEQMRAD8AvLkiRdAIV//Z","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: network","type":"tag","name":"network","color":"A5F306","path":"/tag/network/"}],"reactions":[{"emoji":"👍","count":2,"users":["miyurusankalpa","loleg"]}],"comments":[]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/pipenv-and-poetry/index.json b/assets/data/post/pipenv-and-poetry/index.json index 994dae1ca..8c2b12638 100644 --- a/assets/data/post/pipenv-and-poetry/index.json +++ b/assets/data/post/pipenv-and-poetry/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"145","title":"Use pipenv and poetry in a way that works","summary":"\n

Strange as the title is, they just don't work conveniently \"out of box\"

\n","body":"\n

Preface

\n
\n

I am not an expert. Wrote this section just for completion.

\n
\n

Why not simply pip

\n

pip install is simple and naive. You will get crazy if you come from JavaScript world where both npm and yarn are excellent package managers. It even takes effort to manage production and development dependecies in two separate list. A tool for handling all the dependencies and virtual env works is handy, and can simplify workflows.

\n

What do they actuallly do

\n

Pretty much like mentioned above. Quoting from pipenv's doc for some detailed feature:

\n
\n\n
\n

Major Problem: Speed

\n

Resolving and locking dependencies are too slow. A typical jupyterlab workspace takes hours to finish.

\n

Why this happens

\n

In node package ecosystem a project has hundreds of dependencies to resolve, install and lock, but still faster than pipenv or poetry. That's because in python package ecosystem, many packages are not properly formed and it's impossible to get it's dependencies without downloading it.

\n

See also https://python-poetry.org/docs/faq/#why-is-the-dependency-resolution-process-slow

\n

How to solve in pipenv

\n

pipenv is slow at locking, so you may want use --skip-lock flag or set PIPENV_SKIP_LOCK env when the network is poor, then pipenv lock when network connection is good.

\n

How to solve in poetry

\n

Poetry is slow at dependency resolution. Quoting from poetry doc:

\n
\n

At the moment there is no way around it.

\n
\n

Other pipenv problems

\n

Lock file not cross platform

\n

Ironically, deterministic builds actually leads to wrong packages installed on different platforms.

\n

Lock files may be version-dependent. Take pytest for example, if you install and lock on python 3.8, the importlib_metadata is not installed because py3.8 already have importlib.metadata included. But when you install based on the lock file on py3.6, importlib_metadata is not found. This is not happening when directly install pytest on py3.6

\n

How to solve

\n
    \n
  1. Use markers for subdependencies: importlib_metadata = {version=\"~=1.7.0\", python_version=\"<'3.8'\"}
  2. \n
  3. Or use pyenv / pyenv-win
  4. \n
\n

apt install may be outdated

\n

Better install via pip(x)

\n

Script shortcut cannot combine multi commands

\n

pypa/pipenv#2283

\n

You can write command A && command B in package.json, but not Pipfile. All you can do is write in a shell script.

\n

No python version range

\n

pypa/pipenv#1050

\n

If you are sure that your program and Pipfile.lock works for python > 3.5.2, remove python requirement 😏

\n

No extra denpendencies

\n

This can be frustrating if you want uWSGI to be installed only on production machine, but not on your PC.

\n

Other poetry problems

\n

Prefer python

\n

Use poetry with pyenv if python -V outputs 2.7.x. Or it will be an awful develop experience.

\n

Or if you are sure you just need to create venv with system's python3, you can choose pip(x) install it.

\n

No built-in dot-env and handy scripts

\n

But you can make use of the power of pyproject.toml and use tools like taskipy and poethepoet

\n

Not supported by github dependency graph

\n

https://docs.github.com/en/github/visualizing-repository-data-with-graphs/about-the-dependency-graph#supported-package-ecosystems

\n

But dependabot does support it.

\n
\n

More info and solutions welcome!

","createdAt":"2020-08-23T07:29:31.000Z","lastEditedAt":"2020-09-02T01:59:37.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: python","type":"tag","name":"python","color":"3c78a8","path":"/tag/python/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"145","title":"Use pipenv and poetry in a way that works","summary":"\n

Strange as the title is, they just don't work conveniently \"out of box\"

\n","body":"\n

Preface

\n
\n

I am not an expert. Wrote this section just for completion.

\n
\n

Why not simply pip

\n

pip install is simple and naive. You will get crazy if you come from JavaScript world where both npm and yarn are excellent package managers. It even takes effort to manage production and development dependecies in two separate list. A tool for handling all the dependencies and virtual env works is handy, and can simplify workflows.

\n

What do they actuallly do

\n

Pretty much like mentioned above. Quoting from pipenv's doc for some detailed feature:

\n
\n\n
\n

Major Problem: Speed

\n

Resolving and locking dependencies are too slow. A typical jupyterlab workspace takes hours to finish.

\n

Why this happens

\n

In node package ecosystem a project has hundreds of dependencies to resolve, install and lock, but still faster than pipenv or poetry. That's because in python package ecosystem, many packages are not properly formed and it's impossible to get it's dependencies without downloading it.

\n

See also https://python-poetry.org/docs/faq/#why-is-the-dependency-resolution-process-slow

\n

How to solve in pipenv

\n

pipenv is slow at locking, so you may want use --skip-lock flag or set PIPENV_SKIP_LOCK env when the network is poor, then pipenv lock when network connection is good.

\n

How to solve in poetry

\n

Poetry is slow at dependency resolution. Quoting from poetry doc:

\n
\n

At the moment there is no way around it.

\n
\n

Other pipenv problems

\n

Lock file not cross platform

\n

Ironically, deterministic builds actually leads to wrong packages installed on different platforms.

\n

Lock files may be version-dependent. Take pytest for example, if you install and lock on python 3.8, the importlib_metadata is not installed because py3.8 already have importlib.metadata included. But when you install based on the lock file on py3.6, importlib_metadata is not found. This is not happening when directly install pytest on py3.6

\n

How to solve

\n
    \n
  1. Use markers for subdependencies: importlib_metadata = {version=\"~=1.7.0\", python_version=\"<'3.8'\"}
  2. \n
  3. Or use pyenv / pyenv-win
  4. \n
\n

apt install may be outdated

\n

Better install via pip(x)

\n

Script shortcut cannot combine multi commands

\n

pypa/pipenv#2283

\n

You can write command A && command B in package.json, but not Pipfile. All you can do is write in a shell script.

\n

No python version range

\n

pypa/pipenv#1050

\n

If you are sure that your program and Pipfile.lock works for python > 3.5.2, remove python requirement 😏

\n

No extra denpendencies

\n

This can be frustrating if you want uWSGI to be installed only on production machine, but not on your PC.

\n

Other poetry problems

\n

Prefer python

\n

Use poetry with pyenv if python -V outputs 2.7.x. Or it will be an awful develop experience.

\n

Or if you are sure you just need to create venv with system's python3, you can choose pip(x) install it.

\n

No built-in dot-env and handy scripts

\n

But you can make use of the power of pyproject.toml and use tools like taskipy and poethepoet

\n

Not supported by github dependency graph

\n

https://docs.github.com/en/github/visualizing-repository-data-with-graphs/about-the-dependency-graph#supported-package-ecosystems

\n

But dependabot does support it.

\n
\n

More info and solutions welcome!

","createdAt":"2020-08-23T07:29:31.000Z","lastEditedAt":"2020-09-02T01:59:37.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: python","type":"tag","name":"python","color":"3c78a8","path":"/tag/python/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/pkg-source/index.json b/assets/data/post/pkg-source/index.json index 60819f9b5..a6bca9b59 100644 --- a/assets/data/post/pkg-source/index.json +++ b/assets/data/post/pkg-source/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"77","title":"换源集合","summary":"\n

各种换源,我要的都在这里了。针对天朝就不用国际语言了 😄

\n","body":"\n

众所周知,天朝网络环境需要换源来支撑,闲言少叙:

\n

https://mirrors.tuna.tsinghua.edu.cn/

\n

pip

\n

https://mirrors.tuna.tsinghua.edu.cn/help/pypi/

\n
\n
\n \n shell\n
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
\n

npm

\n
\n
\n \n shell\n
npm config set registry https://registry.npm.taobao.org\nnpm config set registry https://repo.huaweicloud.com/repository/npm/
\n

sharp 源

\n
\n
\n \n shell\n
yarn config set sharp_binary_host \"https://npm.taobao.org/mirrors/sharp\"
\n

electron 源

\n
npm config set electron_mirror https://npm.taobao.org/mirrors/electron/\n
\n

docker

\n

/etc/docker/daemon.json 或者直接 Docker Desktop 的 GUI 界面里修改:

\n
\n
\n \n json\n
\"registry-mirrors\": [\n  \"https://hub-mirror.c.163.com\",\n]
\n

maven

\n

$HOME/.gradle/init.gradle

\n
\n
\n \n groovy\n
allprojects {\n  repositories {\n    maven {\n      url 'https://maven.aliyun.com/repository/public/'\n    }\n    mavenLocal()\n    mavenCentral()\n  }\n}
","createdAt":"2020-02-28T14:29:13.000Z","lastEditedAt":"2021-12-09T02:49:50.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: cheatsheet","type":"blog","name":"cheatsheet","color":"6655d6","path":"/blog/cheatsheet/"}],"reactions":[{"emoji":"❤","count":1,"users":["Yixuan-Wang"]}],"comments":[]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"77","title":"换源集合","summary":"\n

各种换源,我要的都在这里了。针对天朝就不用国际语言了 😄

\n","body":"\n

众所周知,天朝网络环境需要换源来支撑,闲言少叙:

\n

https://mirrors.tuna.tsinghua.edu.cn/

\n

pip

\n

https://mirrors.tuna.tsinghua.edu.cn/help/pypi/

\n
\n
\n \n shell\n
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
\n

npm

\n
\n
\n \n shell\n
npm config set registry https://registry.npm.taobao.org\nnpm config set registry https://repo.huaweicloud.com/repository/npm/
\n

sharp 源

\n
\n
\n \n shell\n
yarn config set sharp_binary_host \"https://npm.taobao.org/mirrors/sharp\"
\n

electron 源

\n
npm config set electron_mirror https://npm.taobao.org/mirrors/electron/\n
\n

docker

\n

/etc/docker/daemon.json 或者直接 Docker Desktop 的 GUI 界面里修改:

\n
\n
\n \n json\n
\"registry-mirrors\": [\n  \"https://hub-mirror.c.163.com\",\n]
\n

maven

\n

$HOME/.gradle/init.gradle

\n
\n
\n \n groovy\n
allprojects {\n  repositories {\n    maven {\n      url 'https://maven.aliyun.com/repository/public/'\n    }\n    mavenLocal()\n    mavenCentral()\n  }\n}
","createdAt":"2020-02-28T14:29:13.000Z","lastEditedAt":"2021-12-09T02:49:50.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: cheatsheet","type":"blog","name":"cheatsheet","color":"6655d6","path":"/blog/cheatsheet/"}],"reactions":[{"emoji":"❤","count":1,"users":["Yixuan-Wang"]}],"comments":[]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/pwa-skipwaiting/index.json b/assets/data/post/pwa-skipwaiting/index.json index 6c7aaedcf..e56bcff61 100644 --- a/assets/data/post/pwa-skipwaiting/index.json +++ b/assets/data/post/pwa-skipwaiting/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"173","title":"`skipWaiting()` with `StaleWhileRevalidate` the right way","summary":"\n

It is common to use workbox StaleWhileRevalidate strategy to cache resources which may take some time to fetch. Usually the resource needs to be updated but not immediately. However if the resource request takes too much time to complete, service worker's life cycle and some functionality may be impacted, especially self.skipWaiting().

\n","body":"\n

Why skipWaiting is important?

\n

By default, a service worker takes over the page from start to end, even if new service worker is discovered and installed. This behavior ensures consistency. However if the update is important and does not have conflict with the old one, you may want the new one to activate as soon as installed.

\n

Another use case is click to refresh feature. Remember the old service worker is still in charge even though refreshing the page. To update the PWA app without leaving it, skip the waiting phase of new service worker is needed. Thus after location.reload(), new service worker with new precached assets are there. We will focus on this use case in this post.

\n

How to skipWaiting without StaleWhileRevalidate strategy

\n

As you may seen in many documentations and tutorials, it is quite straightforward:

\n
\n
\n \n js\n
// normal/service-worker.js\n\n// Change this line to make service worker different\n\nself.addEventListener('message', (event) => {\n  const message = event.data\n  if (!message) return\n  if (message.type === 'skip-waiting') {\n    console.log('trigger skipWaiting at', +new Date())\n    self.skipWaiting()\n  }\n}
\n

You may need to change the comment line to test skip waiting again.

\n

Complete code for demonstration

\n

Let's add a simple server and some HTML to complete the PWA and simulate a slow request.

\n
\n

📓 Note:

\n

You can find complete code in my GitHub repo

\n
\n
\n
\n \n html\n
<!-- body part of normal/index.html -->\n\n<p><span id=\"action\">No</span> long running request</p>\n<button id=\"upgrade\" disabled>Click to skip waiting</button>\n<script>\n  let newWorker\n  const button = document.getElementById('upgrade')\n  const listenStage = () => {\n    console.log(newWorker.state, +new Date())\n    if (newWorker.state === 'installed') {\n      button.removeAttribute('disabled')\n    } else if (newWorker.state === 'activated') {\n      location.reload()\n    }\n  }\n  navigator.serviceWorker.register('./service-worker.js').then((swr) => {\n    if (swr.waiting) {\n      newWorker = swr.waiting\n      newWorker.onstatechange = listenStage\n      listenStage() // Trigger installed\n    }\n    swr.onupdatefound = () => {\n      newWorker = swr.installing\n      newWorker.onstatechange = listenStage\n    }\n  })\n  button.addEventListener('click', function () {\n    // If there is a slow request when upgrading\n    const actionElement = document.getElementById('action')\n    actionElement.innerText = 'Doing'\n    fetch('slow.json').then(() => (actionElement.innerText = 'Done'))\n\n    // Ensure slow request is alive\n    setTimeout(() => newWorker.postMessage({ type: 'skip-waiting' }), 100)\n    this.setAttribute('disabled', true)\n  })\n</script>
\n

The above code might be a little bit long. It does 3 things:

\n\n
\n
\n \n js\n
// index.js\n\nconst http = require('http')\nconst statik = require('node-static')\n\nconst file = new statik.Server('.')\n\nhttp.createServer(function (req, res) {\n  // Simulate slow request\n  if (req.url.endsWith('/slow.json')) {\n    setTimeout(() => {\n      res.writeHead(200)\n      res.end('{}')\n    }, 4000)\n    return\n  }\n  file.serve(req, res)\n}\n).listen(8345)\n\nconsole.log('Listening on http://localhost:8345')
\n

The server is pretty straightforward, using node-static to serve files.

\n

A favicon.ico is also needed to avoid favicon.ico not found error.

\n

Now run index.js and:

\n
    \n
  1. open http://localhost:8345/normal/
  2. \n
  3. wait service worker installed and close page
  4. \n
  5. change service-worker.js
  6. \n
  7. open the page again
  8. \n
  9. open the console, check \"Preserve log\"
  10. \n
  11. click the \"Click to skip waiting\" button
  12. \n
  13. change service-worker.js, manually reload page and test again
  14. \n
\n

You may found something like this in console:

\n
(index):16 installed 1624852106984\nservice-worker.js:7 trigger skipWaiting at 1624852110391\n(index):16 activating 1624852110391\nactivated 1624852110393\n
\n

Then page reloads, the button stay disabled because the new service worker is active and no service worker waiting.

\n

Open Network tab, you can see slow.json is canceled because of page refresh. Or in Firefox it's directly logged into console:

\n
installed 1624852139119 normal:16:17\ntrigger skipWaiting at 1624852141791 service-worker.js:7:13\nactivating 1624852141795 normal:16:17\nactivated 1624852141797 normal:16:17\nUncaught (in promise) TypeError: NetworkError when attempting to fetch resource.\n
\n

This is intended because you almost always want to cancel fetching when refreshing the page. But that's where the problem lies when using StaleWhileRevalidate strategy

\n

Still waiting after skipWaiting, when StaleWhileRevalidate

\n

Now let's add StaleWhileRevalidate strategy:

\n
\n
\n \n diff\n
diff --color -u normal/index.html stuck/index.html\n--- normal/index.html   2021-06-28 11:46:20.464842200 +0800\n+++ stuck/index.html    2021-06-28 11:44:19.298915100 +0800\n@@ -4,7 +4,7 @@\n     <meta charset=\"UTF-8\" />\n     <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />\n     <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n-    <title>Normal</title>\n+    <title>Stuck</title>\n   </head>\n   <body>\n     <p><span id=\"action\">No</span> long running request</p>\ndiff --color -u normal/service-worker.js stuck/service-worker.js\n--- normal/service-worker.js    2021-06-28 11:48:22.461331300 +0800\n+++ stuck/service-worker.js     2021-06-28 11:44:30.814991700 +0800\n@@ -1,4 +1,14 @@\n-// Change this line to make service worker differenta\n+/* global workbox, importScripts */\n+\n+// Change this line to make service worker different\n+\n+importScripts('https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js')\n+\n+workbox.routing.registerRoute(\n+  /\\.json/,\n+  new workbox.strategies.StaleWhileRevalidate(),\n+  'GET'\n+)\n\n self.addEventListener('message', (event) => {\n   const message = event.data
\n

open http://localhost:8345/stuck/, repeat the above steps. Here is the example output:

\n
(index):16 installed 1624853106784\nworkbox-core.dev.js:45 workbox Router is responding to: /stuck/slow.json\nservice-worker.js:17 trigger skipWaiting at 1624853112301\nworkbox-core.dev.js:45 workbox Using StaleWhileRevalidate to respond to '/stuck/slow.json'\nworkbox-core.dev.js:45 No response found in the 'workbox-runtime-http://localhost:8345/stuck/' cache. Will wait for the network response.\nworkbox-core.dev.js:45 View the final response here.\n(index):16 activating 1624853116470\n(index):16 activated 1624853117482\nNavigated to http://localhost:8345/stuck/\n
\n

The new service worker is not activated until the request completes. Even if resource is cached:

\n
(index):16 installed 1624853346141\nworkbox-core.dev.js:45 workbox Router is responding to: /stuck/slow.json\nworkbox-core.dev.js:45 workbox Using StaleWhileRevalidate to respond to '/stuck/slow.json'\nservice-worker.js:17 trigger skipWaiting at 1624853348949\n(index):16 activating 1624853352867\n(index):16 activated 1624853353871\n
\n

Though cached, StaleWhileRevalidate strategy still revalidates the resource in the background. Thus the old service worker is unable to stop until the request finishes.

\n

The solution

\n

It's natural to think of aborting the request in the old service worker. Let's add AbortController. Notice that you need to let new service worker to skip waiting, and let old service worker to abort fetches. Don't get confused.

\n
\n
\n \n diff\n
diff --color -u stuck/index.html solution/index.html\n--- stuck/index.html    2021-06-28 11:44:19.298915100 +0800\n+++ solution/index.html 2021-06-28 12:18:54.089259800 +0800\n@@ -4,13 +4,13 @@\n     <meta charset=\"UTF-8\" />\n     <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />\n     <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n-    <title>Stuck</title>\n+    <title>Solution</title>\n   </head>\n   <body>\n     <p><span id=\"action\">No</span> long running request</p>\n     <button id=\"upgrade\" disabled>Click to skip waiting</button>\n     <script>\n-      let newWorker\n+      let newWorker, oldWorker\n       const button = document.getElementById('upgrade')\n       const listenStage = () => {\n         console.log(newWorker.state, +new Date())\n@@ -21,6 +21,7 @@\n         }\n       }\n       navigator.serviceWorker.register('./service-worker.js').then((swr) => {\n+        oldWorker = swr.active\n         if (swr.waiting) {\n           console.log('Waiting...')\n           newWorker = swr.waiting\n@@ -38,7 +39,10 @@\n         fetch('slow.json').then(() => (actionElement.innerText = 'Done'))\n\n         // Ensure slow request is alive\n-        setTimeout(() => newWorker.postMessage({ type: 'skip-waiting' }), 100)\n+        setTimeout(() => {\n+          oldWorker.postMessage({ type: 'abort-connections' })\n+          newWorker.postMessage({ type: 'skip-waiting' })\n+        }, 100)\n         this.setAttribute('disabled', true)\n       })\n     </script>\ndiff --color -u stuck/service-worker.js solution/service-worker.js\n--- stuck/service-worker.js     2021-06-28 12:08:58.811435500 +0800\n+++ solution/service-worker.js  2021-06-28 12:19:48.204055800 +0800\n@@ -1,12 +1,16 @@\n /* global workbox, importScripts */\n\n-// Change this line to make service worker different\n+// Change this line to make service worker differenta\n\n importScripts('https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js')\n\n+const controller = new AbortController()\n+\n workbox.routing.registerRoute(\n-  /\\.json/,\n-  new workbox.strategies.StaleWhileRevalidate(),\n+  /.*/,\n+  new workbox.strategies.StaleWhileRevalidate({\n+    fetchOptions: { signal: controller.signal }\n+  }),\n   'GET'\n )\n\n@@ -16,5 +20,7 @@\n   if (message.type === 'skip-waiting') {\n     console.log('trigger skipWaiting at', +new Date())\n     self.skipWaiting()\n+  } else if (message.type === 'abort-connections') {\n+    controller.abort()\n   }\n })
\n

And the (truncated) output:

\n
(index):16 installed 1624853994162\nworkbox-core.dev.js:45 workbox Router is responding to: /solution/slow.json\nservice-worker.js:21 trigger skipWaiting at 1624854001606\nworkbox-strategies.dev.js:1005 Uncaught (in promise) no-response: The strategy could not generate a response for 'http://localhost:8345/solution/slow.json'. The underlying error is AbortError: The user aborted a request..\n    at StaleWhileRevalidate.makeRequest (https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-strategies.dev.js:1005:15)\n(index):16 activating 1624854001616\n(index):16 activated 1624854002627\nNavigated to http://localhost:8345/solution/\n
\n

The new service worker is immediately activated after aborting the request 🎉

\n

Bonus

\n

If you open multiple tabs at the same time, and click the button, all of them are refreshed, avoiding potential conflict between new service worker and old page. If this is not the befavior you want, you can always change the condition for page reload.

","createdAt":"2021-06-28T04:31:10.000Z","lastEditedAt":"2021-06-28T10:11:41.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: pwa","type":"tag","name":"pwa","color":"5a0fc8","path":"/tag/pwa/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"173","title":"`skipWaiting()` with `StaleWhileRevalidate` the right way","summary":"\n

It is common to use workbox StaleWhileRevalidate strategy to cache resources which may take some time to fetch. Usually the resource needs to be updated but not immediately. However if the resource request takes too much time to complete, service worker's life cycle and some functionality may be impacted, especially self.skipWaiting().

\n","body":"\n

Why skipWaiting is important?

\n

By default, a service worker takes over the page from start to end, even if new service worker is discovered and installed. This behavior ensures consistency. However if the update is important and does not have conflict with the old one, you may want the new one to activate as soon as installed.

\n

Another use case is click to refresh feature. Remember the old service worker is still in charge even though refreshing the page. To update the PWA app without leaving it, skip the waiting phase of new service worker is needed. Thus after location.reload(), new service worker with new precached assets are there. We will focus on this use case in this post.

\n

How to skipWaiting without StaleWhileRevalidate strategy

\n

As you may seen in many documentations and tutorials, it is quite straightforward:

\n
\n
\n \n js\n
// normal/service-worker.js\n\n// Change this line to make service worker different\n\nself.addEventListener('message', (event) => {\n  const message = event.data\n  if (!message) return\n  if (message.type === 'skip-waiting') {\n    console.log('trigger skipWaiting at', +new Date())\n    self.skipWaiting()\n  }\n}
\n

You may need to change the comment line to test skip waiting again.

\n

Complete code for demonstration

\n

Let's add a simple server and some HTML to complete the PWA and simulate a slow request.

\n
\n

📓 Note:

\n

You can find complete code in my GitHub repo

\n
\n
\n
\n \n html\n
<!-- body part of normal/index.html -->\n\n<p><span id=\"action\">No</span> long running request</p>\n<button id=\"upgrade\" disabled>Click to skip waiting</button>\n<script>\n  let newWorker\n  const button = document.getElementById('upgrade')\n  const listenStage = () => {\n    console.log(newWorker.state, +new Date())\n    if (newWorker.state === 'installed') {\n      button.removeAttribute('disabled')\n    } else if (newWorker.state === 'activated') {\n      location.reload()\n    }\n  }\n  navigator.serviceWorker.register('./service-worker.js').then((swr) => {\n    if (swr.waiting) {\n      newWorker = swr.waiting\n      newWorker.onstatechange = listenStage\n      listenStage() // Trigger installed\n    }\n    swr.onupdatefound = () => {\n      newWorker = swr.installing\n      newWorker.onstatechange = listenStage\n    }\n  })\n  button.addEventListener('click', function () {\n    // If there is a slow request when upgrading\n    const actionElement = document.getElementById('action')\n    actionElement.innerText = 'Doing'\n    fetch('slow.json').then(() => (actionElement.innerText = 'Done'))\n\n    // Ensure slow request is alive\n    setTimeout(() => newWorker.postMessage({ type: 'skip-waiting' }), 100)\n    this.setAttribute('disabled', true)\n  })\n</script>
\n

The above code might be a little bit long. It does 3 things:

\n\n
\n
\n \n js\n
// index.js\n\nconst http = require('http')\nconst statik = require('node-static')\n\nconst file = new statik.Server('.')\n\nhttp.createServer(function (req, res) {\n  // Simulate slow request\n  if (req.url.endsWith('/slow.json')) {\n    setTimeout(() => {\n      res.writeHead(200)\n      res.end('{}')\n    }, 4000)\n    return\n  }\n  file.serve(req, res)\n}\n).listen(8345)\n\nconsole.log('Listening on http://localhost:8345')
\n

The server is pretty straightforward, using node-static to serve files.

\n

A favicon.ico is also needed to avoid favicon.ico not found error.

\n

Now run index.js and:

\n
    \n
  1. open http://localhost:8345/normal/
  2. \n
  3. wait service worker installed and close page
  4. \n
  5. change service-worker.js
  6. \n
  7. open the page again
  8. \n
  9. open the console, check \"Preserve log\"
  10. \n
  11. click the \"Click to skip waiting\" button
  12. \n
  13. change service-worker.js, manually reload page and test again
  14. \n
\n

You may found something like this in console:

\n
(index):16 installed 1624852106984\nservice-worker.js:7 trigger skipWaiting at 1624852110391\n(index):16 activating 1624852110391\nactivated 1624852110393\n
\n

Then page reloads, the button stay disabled because the new service worker is active and no service worker waiting.

\n

Open Network tab, you can see slow.json is canceled because of page refresh. Or in Firefox it's directly logged into console:

\n
installed 1624852139119 normal:16:17\ntrigger skipWaiting at 1624852141791 service-worker.js:7:13\nactivating 1624852141795 normal:16:17\nactivated 1624852141797 normal:16:17\nUncaught (in promise) TypeError: NetworkError when attempting to fetch resource.\n
\n

This is intended because you almost always want to cancel fetching when refreshing the page. But that's where the problem lies when using StaleWhileRevalidate strategy

\n

Still waiting after skipWaiting, when StaleWhileRevalidate

\n

Now let's add StaleWhileRevalidate strategy:

\n
\n
\n \n diff\n
diff --color -u normal/index.html stuck/index.html\n--- normal/index.html   2021-06-28 11:46:20.464842200 +0800\n+++ stuck/index.html    2021-06-28 11:44:19.298915100 +0800\n@@ -4,7 +4,7 @@\n     <meta charset=\"UTF-8\" />\n     <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />\n     <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n-    <title>Normal</title>\n+    <title>Stuck</title>\n   </head>\n   <body>\n     <p><span id=\"action\">No</span> long running request</p>\ndiff --color -u normal/service-worker.js stuck/service-worker.js\n--- normal/service-worker.js    2021-06-28 11:48:22.461331300 +0800\n+++ stuck/service-worker.js     2021-06-28 11:44:30.814991700 +0800\n@@ -1,4 +1,14 @@\n-// Change this line to make service worker differenta\n+/* global workbox, importScripts */\n+\n+// Change this line to make service worker different\n+\n+importScripts('https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js')\n+\n+workbox.routing.registerRoute(\n+  /\\.json/,\n+  new workbox.strategies.StaleWhileRevalidate(),\n+  'GET'\n+)\n\n self.addEventListener('message', (event) => {\n   const message = event.data
\n

open http://localhost:8345/stuck/, repeat the above steps. Here is the example output:

\n
(index):16 installed 1624853106784\nworkbox-core.dev.js:45 workbox Router is responding to: /stuck/slow.json\nservice-worker.js:17 trigger skipWaiting at 1624853112301\nworkbox-core.dev.js:45 workbox Using StaleWhileRevalidate to respond to '/stuck/slow.json'\nworkbox-core.dev.js:45 No response found in the 'workbox-runtime-http://localhost:8345/stuck/' cache. Will wait for the network response.\nworkbox-core.dev.js:45 View the final response here.\n(index):16 activating 1624853116470\n(index):16 activated 1624853117482\nNavigated to http://localhost:8345/stuck/\n
\n

The new service worker is not activated until the request completes. Even if resource is cached:

\n
(index):16 installed 1624853346141\nworkbox-core.dev.js:45 workbox Router is responding to: /stuck/slow.json\nworkbox-core.dev.js:45 workbox Using StaleWhileRevalidate to respond to '/stuck/slow.json'\nservice-worker.js:17 trigger skipWaiting at 1624853348949\n(index):16 activating 1624853352867\n(index):16 activated 1624853353871\n
\n

Though cached, StaleWhileRevalidate strategy still revalidates the resource in the background. Thus the old service worker is unable to stop until the request finishes.

\n

The solution

\n

It's natural to think of aborting the request in the old service worker. Let's add AbortController. Notice that you need to let new service worker to skip waiting, and let old service worker to abort fetches. Don't get confused.

\n
\n
\n \n diff\n
diff --color -u stuck/index.html solution/index.html\n--- stuck/index.html    2021-06-28 11:44:19.298915100 +0800\n+++ solution/index.html 2021-06-28 12:18:54.089259800 +0800\n@@ -4,13 +4,13 @@\n     <meta charset=\"UTF-8\" />\n     <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />\n     <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n-    <title>Stuck</title>\n+    <title>Solution</title>\n   </head>\n   <body>\n     <p><span id=\"action\">No</span> long running request</p>\n     <button id=\"upgrade\" disabled>Click to skip waiting</button>\n     <script>\n-      let newWorker\n+      let newWorker, oldWorker\n       const button = document.getElementById('upgrade')\n       const listenStage = () => {\n         console.log(newWorker.state, +new Date())\n@@ -21,6 +21,7 @@\n         }\n       }\n       navigator.serviceWorker.register('./service-worker.js').then((swr) => {\n+        oldWorker = swr.active\n         if (swr.waiting) {\n           console.log('Waiting...')\n           newWorker = swr.waiting\n@@ -38,7 +39,10 @@\n         fetch('slow.json').then(() => (actionElement.innerText = 'Done'))\n\n         // Ensure slow request is alive\n-        setTimeout(() => newWorker.postMessage({ type: 'skip-waiting' }), 100)\n+        setTimeout(() => {\n+          oldWorker.postMessage({ type: 'abort-connections' })\n+          newWorker.postMessage({ type: 'skip-waiting' })\n+        }, 100)\n         this.setAttribute('disabled', true)\n       })\n     </script>\ndiff --color -u stuck/service-worker.js solution/service-worker.js\n--- stuck/service-worker.js     2021-06-28 12:08:58.811435500 +0800\n+++ solution/service-worker.js  2021-06-28 12:19:48.204055800 +0800\n@@ -1,12 +1,16 @@\n /* global workbox, importScripts */\n\n-// Change this line to make service worker different\n+// Change this line to make service worker differenta\n\n importScripts('https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js')\n\n+const controller = new AbortController()\n+\n workbox.routing.registerRoute(\n-  /\\.json/,\n-  new workbox.strategies.StaleWhileRevalidate(),\n+  /.*/,\n+  new workbox.strategies.StaleWhileRevalidate({\n+    fetchOptions: { signal: controller.signal }\n+  }),\n   'GET'\n )\n\n@@ -16,5 +20,7 @@\n   if (message.type === 'skip-waiting') {\n     console.log('trigger skipWaiting at', +new Date())\n     self.skipWaiting()\n+  } else if (message.type === 'abort-connections') {\n+    controller.abort()\n   }\n })
\n

And the (truncated) output:

\n
(index):16 installed 1624853994162\nworkbox-core.dev.js:45 workbox Router is responding to: /solution/slow.json\nservice-worker.js:21 trigger skipWaiting at 1624854001606\nworkbox-strategies.dev.js:1005 Uncaught (in promise) no-response: The strategy could not generate a response for 'http://localhost:8345/solution/slow.json'. The underlying error is AbortError: The user aborted a request..\n    at StaleWhileRevalidate.makeRequest (https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-strategies.dev.js:1005:15)\n(index):16 activating 1624854001616\n(index):16 activated 1624854002627\nNavigated to http://localhost:8345/solution/\n
\n

The new service worker is immediately activated after aborting the request 🎉

\n

Bonus

\n

If you open multiple tabs at the same time, and click the button, all of them are refreshed, avoiding potential conflict between new service worker and old page. If this is not the befavior you want, you can always change the condition for page reload.

","createdAt":"2021-06-28T04:31:10.000Z","lastEditedAt":"2021-06-28T10:11:41.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: pwa","type":"tag","name":"pwa","color":"5a0fc8","path":"/tag/pwa/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/python-async-subprocess/index.json b/assets/data/post/python-async-subprocess/index.json index aacb7d119..f987b1d7e 100644 --- a/assets/data/post/python-async-subprocess/index.json +++ b/assets/data/post/python-async-subprocess/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"69","title":"异步 & 异步获取命令输出","summary":"\n

与 JavaScript 的异步对比,初步了解 Python 的异步,并通过异步获取命令输出“实战”

\n","body":"\n

Tested with Python 3.7

\n
\n

😜 TL;DR

\n

直达:异步获取命令输出

\n
\n

异步

\n

异步和多线程比较而言,就是在应该停的地方停下,让给另一个任务。这样就使任务之间的交替变得更可控,不像多线程一样会动不动出现资源竞争的操作。

\n

简单例子

\n

From https://www.tornadoweb.org/en/stable/guide/async.html

\n

synchronous:

\n
\n
\n \n python\n
from tornado.httpclient import HTTPClient\n\ndef synchronous_fetch(url):\n    http_client = HTTPClient()\n    response = http_client.fetch(url)\n    return response.body
\n

asynchronous:

\n
\n
\n \n python\n
from tornado.httpclient import AsyncHTTPClient\n\nasync def asynchronous_fetch(url):\n    http_client = AsyncHTTPClient()\n    response = await http_client.fetch(url)\n    return response.body
\n

虽然看起来除了async await之外和同步的没有什么区别,但是阻塞的函数并不能直接改成异步的函数,因为异步函数会立即返回,而且写程序的时候要明确告知什么时候停下。接下来详细讨论一下。

\n

Get Your Hands Dirty!

\n

Based on https://realpython.com/async-io-python/

\n
\n
\n \n python\n
import asyncio\nimport time\n\n\nasync def count():\n    print(\"One\", end=\" \")\n    await asyncio.sleep(1)\n    # time.sleep(1)\n    print(\"Two\", end=\" \")\n\n\nasync def main():\n    await asyncio.gather(count(), count(), count())\n\nif __name__ == \"__main__\":\n    s = time.perf_counter()\n    asyncio.run(main())\n    elapsed = time.perf_counter() - s\n    print(f\"{__file__} executed in {elapsed:0.2f} seconds.\")
\n
One One One Two Two Two aioplay.py executed in 1.00 seconds.\n
\n

只是直接调用count(),会返回<coroutine object count at ...>,而并不会立即执行,这和 JS 不一样。插播 JS:

\n
\n
\n \n js\n
const a = async () => console.log(1)\na()
\n
1\nPromise {\n  undefined,\n  domain:\n   ... }\n
\n

但是在一个异步函数里调用而不用await变现,会直接报错:

\n
\n
\n \n python\n
In [5]: async def count():\n   ...:     print(\"One\")\n   ...:     await asyncio.sleep(1)\n   ...:     print(\"Two\")\n   ...:\n\nIn [6]: async def run():\n   ...:     count()\n   ...:\n\nIn [7]: asyncio.run(run())\nipython:2: RuntimeWarning: coroutine 'count' was never awaited\nRuntimeWarning: Enable tracemalloc to get the object allocation traceback
\n

这又和 JS 不一样。

\n
\n

如果不使用await asyncio.sleep,而是使用time.sleep来模拟一个阻塞的操作:

\n
One Two One Two One Two aioplay.py executed in 3.00 seconds.\n
\n

没有切入点,只好顺序执行

\n
\n

如果修改main,成为一个循环:

\n
\n
\n \n python\n
async def main():\n    for _ in range(3):\n        await count()
\n
One Two One Two One Two aioplay.py executed in 3.00 seconds.\n
\n

因为await会等到函数结束后才进行下一步操作。asyncio.gather(count(), count(), count())正是为了让它们一起运行。

\n

获取命令输出

\n

注意 Windows 下要指定路径

\n
\n
\n \n python\n
import subprocess\n\np = subprocess.Popen(['/bin/ping', '-c', '4', 'www.baidu.com'],\n                     shell=False, stdout=subprocess.PIPE)\nfor line in iter(p.stdout.readline, b''):\n    print(line.rstrip().decode())
\n

这里的 iter 为不常见用法,指一直调用此函数,直至返回b''

\n

或者使用with

\n
\n
\n \n python\n
with subprocess.Popen(['/bin/ping', '-c', '4', 'www.baidu.com'],\n                      shell=False, stdout=subprocess.PIPE) as p:\n    for line in iter(p.stdout.readline, b''):\n        print(line.rstrip().decode())
\n

或者

\n
\n
\n \n python\n
with subprocess.Popen(['/bin/ping', '-c', '4', 'www.baidu.com'],\n                      shell=False, stdout=subprocess.PIPE) as p:\n    for line in p.stdout:\n        print(line.rstrip().decode())
\n

异步获取命令输出

\n

可惜上述是阻塞的,不能用于异步。这就需要asyncio.subprocess出场了。

\n
\n
\n \n python\n
import asyncio\n\n\nasync def ping(prefix):\n    # Create the subprocess; redirect the standard output\n    # into a pipe.\n    proc = await asyncio.create_subprocess_exec(\n        'ping', '-c', '4', 'www.baidu.com',\n        stdout=asyncio.subprocess.PIPE)\n\n    async for data in proc.stdout:\n        line = data.decode('ascii').rstrip()\n        print(prefix + line)\n\n    # Wait for the subprocess exit.\n    await proc.wait()\n\n\nasync def main():\n    await asyncio.gather(ping('1> '), ping('2> '))\n\n# For Windows\n# https://stackoverflow.com/a/53146484/8810271\n# loop = asyncio.ProactorEventLoop()\n# asyncio.set_event_loop(loop)\n# loop.run_until_complete(get_date())\nasyncio.run(main())
\n
1> PING www.a.shifen.com (183.232.231.174) 56(84) bytes of data.\n1> 64 bytes from 183.232.231.174 (183.232.231.174): icmp_seq=1 ttl=55 time=34.2 ms\n2> PING www.a.shifen.com (183.232.231.174) 56(84) bytes of data.\n2> 64 bytes from 183.232.231.174 (183.232.231.174): icmp_seq=1 ttl=55 time=46.6 ms\n1> 64 bytes from 183.232.231.174 (183.232.231.174): icmp_seq=2 ttl=55 time=38.6 ms\n2> 64 bytes from 183.232.231.174 (183.232.231.174): icmp_seq=2 ttl=55 time=32.5 ms\n1> 64 bytes from 183.232.231.174 (183.232.231.174): icmp_seq=3 ttl=55 time=34.2 ms\n2> 64 bytes from 183.232.231.174 (183.232.231.174): icmp_seq=3 ttl=55 time=31.7 ms\n1> 64 bytes from 183.232.231.174 (183.232.231.174): icmp_seq=4 ttl=55 time=35.9 ms\n1>\n1> --- www.a.shifen.com ping statistics ---\n1> 4 packets transmitted, 4 received, 0% packet loss, time 3003ms\n1> rtt min/avg/max/mdev = 34.212/35.731/38.574/1.781 ms\n2> 64 bytes from 183.232.231.174 (183.232.231.174): icmp_seq=4 ttl=55 time=32.7 ms\n2>\n2> --- www.a.shifen.com ping statistics ---\n2> 4 packets transmitted, 4 received, 0% packet loss, time 3004ms\n2> rtt min/avg/max/mdev = 31.721/35.892/46.580/6.182 ms\n
\n

async for?

\n
\n
\n \n python\n
async for TARGET in ITER:\n    BLOCK\nelse:\n    BLOCK2
\n

is equivalent to

\n
\n
\n \n python\n
iter = (ITER)\niter = type(iter).__aiter__(iter)\nrunning = True\nwhile running:\n    try:\n        TARGET = await type(iter).__anext__(iter)\n    except StopAsyncIteration:\n        running = False\n    else:\n        BLOCK\nelse:\n    BLOCK2
\n

如果不用类呢,就会是这个样子:

\n
\n
\n \n python\n
import asyncio\nimport time\n\n\nasync def count():\n    print(\"One\", end=\" \")\n    await asyncio.sleep(1)\n    print(\"Two\", end=\" \")\n    return 'Yeah!'\n\n\nasync def generate_workers():\n    for _ in range(3):\n        yield await count()\n\n\nasync def main():\n    async for line in generate_workers():\n        print(line)\n\nif __name__ == \"__main__\":\n    s = time.perf_counter()\n    asyncio.run(main())\n    elapsed = time.perf_counter() - s\n    print(f\"{__file__} executed in {elapsed:0.2f} seconds.\")
\n
One Two Yeah!\nOne Two Yeah!\nOne Two Yeah!\naioplay.py executed in 3.00 seconds.\n
","createdAt":"2020-02-05T10:53:16.000Z","lastEditedAt":"2020-06-30T08:58:31.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: python","type":"tag","name":"python","color":"3c78a8","path":"/tag/python/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"69","title":"异步 & 异步获取命令输出","summary":"\n

与 JavaScript 的异步对比,初步了解 Python 的异步,并通过异步获取命令输出“实战”

\n","body":"\n

Tested with Python 3.7

\n
\n

😜 TL;DR

\n

直达:异步获取命令输出

\n
\n

异步

\n

异步和多线程比较而言,就是在应该停的地方停下,让给另一个任务。这样就使任务之间的交替变得更可控,不像多线程一样会动不动出现资源竞争的操作。

\n

简单例子

\n

From https://www.tornadoweb.org/en/stable/guide/async.html

\n

synchronous:

\n
\n
\n \n python\n
from tornado.httpclient import HTTPClient\n\ndef synchronous_fetch(url):\n    http_client = HTTPClient()\n    response = http_client.fetch(url)\n    return response.body
\n

asynchronous:

\n
\n
\n \n python\n
from tornado.httpclient import AsyncHTTPClient\n\nasync def asynchronous_fetch(url):\n    http_client = AsyncHTTPClient()\n    response = await http_client.fetch(url)\n    return response.body
\n

虽然看起来除了async await之外和同步的没有什么区别,但是阻塞的函数并不能直接改成异步的函数,因为异步函数会立即返回,而且写程序的时候要明确告知什么时候停下。接下来详细讨论一下。

\n

Get Your Hands Dirty!

\n

Based on https://realpython.com/async-io-python/

\n
\n
\n \n python\n
import asyncio\nimport time\n\n\nasync def count():\n    print(\"One\", end=\" \")\n    await asyncio.sleep(1)\n    # time.sleep(1)\n    print(\"Two\", end=\" \")\n\n\nasync def main():\n    await asyncio.gather(count(), count(), count())\n\nif __name__ == \"__main__\":\n    s = time.perf_counter()\n    asyncio.run(main())\n    elapsed = time.perf_counter() - s\n    print(f\"{__file__} executed in {elapsed:0.2f} seconds.\")
\n
One One One Two Two Two aioplay.py executed in 1.00 seconds.\n
\n

只是直接调用count(),会返回<coroutine object count at ...>,而并不会立即执行,这和 JS 不一样。插播 JS:

\n
\n
\n \n js\n
const a = async () => console.log(1)\na()
\n
1\nPromise {\n  undefined,\n  domain:\n   ... }\n
\n

但是在一个异步函数里调用而不用await变现,会直接报错:

\n
\n
\n \n python\n
In [5]: async def count():\n   ...:     print(\"One\")\n   ...:     await asyncio.sleep(1)\n   ...:     print(\"Two\")\n   ...:\n\nIn [6]: async def run():\n   ...:     count()\n   ...:\n\nIn [7]: asyncio.run(run())\nipython:2: RuntimeWarning: coroutine 'count' was never awaited\nRuntimeWarning: Enable tracemalloc to get the object allocation traceback
\n

这又和 JS 不一样。

\n
\n

如果不使用await asyncio.sleep,而是使用time.sleep来模拟一个阻塞的操作:

\n
One Two One Two One Two aioplay.py executed in 3.00 seconds.\n
\n

没有切入点,只好顺序执行

\n
\n

如果修改main,成为一个循环:

\n
\n
\n \n python\n
async def main():\n    for _ in range(3):\n        await count()
\n
One Two One Two One Two aioplay.py executed in 3.00 seconds.\n
\n

因为await会等到函数结束后才进行下一步操作。asyncio.gather(count(), count(), count())正是为了让它们一起运行。

\n

获取命令输出

\n

注意 Windows 下要指定路径

\n
\n
\n \n python\n
import subprocess\n\np = subprocess.Popen(['/bin/ping', '-c', '4', 'www.baidu.com'],\n                     shell=False, stdout=subprocess.PIPE)\nfor line in iter(p.stdout.readline, b''):\n    print(line.rstrip().decode())
\n

这里的 iter 为不常见用法,指一直调用此函数,直至返回b''

\n

或者使用with

\n
\n
\n \n python\n
with subprocess.Popen(['/bin/ping', '-c', '4', 'www.baidu.com'],\n                      shell=False, stdout=subprocess.PIPE) as p:\n    for line in iter(p.stdout.readline, b''):\n        print(line.rstrip().decode())
\n

或者

\n
\n
\n \n python\n
with subprocess.Popen(['/bin/ping', '-c', '4', 'www.baidu.com'],\n                      shell=False, stdout=subprocess.PIPE) as p:\n    for line in p.stdout:\n        print(line.rstrip().decode())
\n

异步获取命令输出

\n

可惜上述是阻塞的,不能用于异步。这就需要asyncio.subprocess出场了。

\n
\n
\n \n python\n
import asyncio\n\n\nasync def ping(prefix):\n    # Create the subprocess; redirect the standard output\n    # into a pipe.\n    proc = await asyncio.create_subprocess_exec(\n        'ping', '-c', '4', 'www.baidu.com',\n        stdout=asyncio.subprocess.PIPE)\n\n    async for data in proc.stdout:\n        line = data.decode('ascii').rstrip()\n        print(prefix + line)\n\n    # Wait for the subprocess exit.\n    await proc.wait()\n\n\nasync def main():\n    await asyncio.gather(ping('1> '), ping('2> '))\n\n# For Windows\n# https://stackoverflow.com/a/53146484/8810271\n# loop = asyncio.ProactorEventLoop()\n# asyncio.set_event_loop(loop)\n# loop.run_until_complete(get_date())\nasyncio.run(main())
\n
1> PING www.a.shifen.com (183.232.231.174) 56(84) bytes of data.\n1> 64 bytes from 183.232.231.174 (183.232.231.174): icmp_seq=1 ttl=55 time=34.2 ms\n2> PING www.a.shifen.com (183.232.231.174) 56(84) bytes of data.\n2> 64 bytes from 183.232.231.174 (183.232.231.174): icmp_seq=1 ttl=55 time=46.6 ms\n1> 64 bytes from 183.232.231.174 (183.232.231.174): icmp_seq=2 ttl=55 time=38.6 ms\n2> 64 bytes from 183.232.231.174 (183.232.231.174): icmp_seq=2 ttl=55 time=32.5 ms\n1> 64 bytes from 183.232.231.174 (183.232.231.174): icmp_seq=3 ttl=55 time=34.2 ms\n2> 64 bytes from 183.232.231.174 (183.232.231.174): icmp_seq=3 ttl=55 time=31.7 ms\n1> 64 bytes from 183.232.231.174 (183.232.231.174): icmp_seq=4 ttl=55 time=35.9 ms\n1>\n1> --- www.a.shifen.com ping statistics ---\n1> 4 packets transmitted, 4 received, 0% packet loss, time 3003ms\n1> rtt min/avg/max/mdev = 34.212/35.731/38.574/1.781 ms\n2> 64 bytes from 183.232.231.174 (183.232.231.174): icmp_seq=4 ttl=55 time=32.7 ms\n2>\n2> --- www.a.shifen.com ping statistics ---\n2> 4 packets transmitted, 4 received, 0% packet loss, time 3004ms\n2> rtt min/avg/max/mdev = 31.721/35.892/46.580/6.182 ms\n
\n

async for?

\n
\n
\n \n python\n
async for TARGET in ITER:\n    BLOCK\nelse:\n    BLOCK2
\n

is equivalent to

\n
\n
\n \n python\n
iter = (ITER)\niter = type(iter).__aiter__(iter)\nrunning = True\nwhile running:\n    try:\n        TARGET = await type(iter).__anext__(iter)\n    except StopAsyncIteration:\n        running = False\n    else:\n        BLOCK\nelse:\n    BLOCK2
\n

如果不用类呢,就会是这个样子:

\n
\n
\n \n python\n
import asyncio\nimport time\n\n\nasync def count():\n    print(\"One\", end=\" \")\n    await asyncio.sleep(1)\n    print(\"Two\", end=\" \")\n    return 'Yeah!'\n\n\nasync def generate_workers():\n    for _ in range(3):\n        yield await count()\n\n\nasync def main():\n    async for line in generate_workers():\n        print(line)\n\nif __name__ == \"__main__\":\n    s = time.perf_counter()\n    asyncio.run(main())\n    elapsed = time.perf_counter() - s\n    print(f\"{__file__} executed in {elapsed:0.2f} seconds.\")
\n
One Two Yeah!\nOne Two Yeah!\nOne Two Yeah!\naioplay.py executed in 3.00 seconds.\n
","createdAt":"2020-02-05T10:53:16.000Z","lastEditedAt":"2020-06-30T08:58:31.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: python","type":"tag","name":"python","color":"3c78a8","path":"/tag/python/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/raspi-tricks/index.json b/assets/data/post/raspi-tricks/index.json index dfa63dc1b..800e82a7c 100644 --- a/assets/data/post/raspi-tricks/index.json +++ b/assets/data/post/raspi-tricks/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"65","title":"Tricks about Raspberry Pi","summary":"\n

Handy tricks when playing with raspberry pi. You should know them.

\n","body":"\n

Get the Temperature

\n
\n
\n \n shell\n
cat /sys/class/thermal/thermal_zone0/temp
\n

Outputs:

\n
29482\n
\n

And here is bash script to create temperature PS1

\n
\n
\n \n shell\n
if [ -e /sys/class/thermal/thermal_zone0/temp ]; then\n    temp=$(cat /sys/class/thermal/thermal_zone0/temp)\n    if [ $temp -lt 30000 ]; then color=2\n    elif [ $temp -lt 50000 ]; then color=3\n    else color=1\n    fi\n    prompt_section $color \"${temp::${#temp}-3}.${temp:${#temp}-3}\"\nfi
\n

Adding a dot is just mixing bash substrings (${string:offset[:length]}) with bash string lengths (${#string})

\n

vcgencmd

\n

From https://www.raspberrypi.org/documentation/raspbian/applications/vcgencmd.md

\n

To get all commands, use:

\n
\n
\n \n shell\n
vcgencmd commands
\n

Voltage

\n
\n
\n \n shell\n
vcgencmd get_throttled
\n

If voltage is okay, you will get 0x0

\n

Else, say 0x50000, convert it to binary, and see each bit:

\n
19 18 17 16 15 14 13 12 11 10  9  8  7  6  5  4  3  2  1  0\n 0  1  0  1  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
BitMeaning
0Under-voltage detected
1Arm frequency capped
2Currently throttled
3Soft temperature limit active
16Under-voltage has occurred
17Arm frequency capping has occurred
18Throttling has occurred
19Soft temperature limit has occurred
\n

Generally, the 3 0s in the middle do not have specific meaning. Just need to care about the first 5 and the last 0.

\n

Screen on / off

\n

When you start the Raspberry Pi with display / monitor / screen on, it will automatically turn on display power. If not, display power will never turn on even if you later connect it to a screen. So it is necessary to control the display power.

\n
\n
\n \n shell\n
vcgencmd display_power 1
\n

1 for on and 0 for off. bare vcgencmd display_power shows current state.

\n

It is possible to specify a certain display ID. For more info, visit the doc above.

","createdAt":"2020-01-21T08:29:47.000Z","lastEditedAt":"2020-06-24T06:04:38.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: raspi","type":"tag","name":"raspi","color":"c31c4a","path":"/tag/raspi/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"65","title":"Tricks about Raspberry Pi","summary":"\n

Handy tricks when playing with raspberry pi. You should know them.

\n","body":"\n

Get the Temperature

\n
\n
\n \n shell\n
cat /sys/class/thermal/thermal_zone0/temp
\n

Outputs:

\n
29482\n
\n

And here is bash script to create temperature PS1

\n
\n
\n \n shell\n
if [ -e /sys/class/thermal/thermal_zone0/temp ]; then\n    temp=$(cat /sys/class/thermal/thermal_zone0/temp)\n    if [ $temp -lt 30000 ]; then color=2\n    elif [ $temp -lt 50000 ]; then color=3\n    else color=1\n    fi\n    prompt_section $color \"${temp::${#temp}-3}.${temp:${#temp}-3}\"\nfi
\n

Adding a dot is just mixing bash substrings (${string:offset[:length]}) with bash string lengths (${#string})

\n

vcgencmd

\n

From https://www.raspberrypi.org/documentation/raspbian/applications/vcgencmd.md

\n

To get all commands, use:

\n
\n
\n \n shell\n
vcgencmd commands
\n

Voltage

\n
\n
\n \n shell\n
vcgencmd get_throttled
\n

If voltage is okay, you will get 0x0

\n

Else, say 0x50000, convert it to binary, and see each bit:

\n
19 18 17 16 15 14 13 12 11 10  9  8  7  6  5  4  3  2  1  0\n 0  1  0  1  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
BitMeaning
0Under-voltage detected
1Arm frequency capped
2Currently throttled
3Soft temperature limit active
16Under-voltage has occurred
17Arm frequency capping has occurred
18Throttling has occurred
19Soft temperature limit has occurred
\n

Generally, the 3 0s in the middle do not have specific meaning. Just need to care about the first 5 and the last 0.

\n

Screen on / off

\n

When you start the Raspberry Pi with display / monitor / screen on, it will automatically turn on display power. If not, display power will never turn on even if you later connect it to a screen. So it is necessary to control the display power.

\n
\n
\n \n shell\n
vcgencmd display_power 1
\n

1 for on and 0 for off. bare vcgencmd display_power shows current state.

\n

It is possible to specify a certain display ID. For more info, visit the doc above.

","createdAt":"2020-01-21T08:29:47.000Z","lastEditedAt":"2020-06-24T06:04:38.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: raspi","type":"tag","name":"raspi","color":"c31c4a","path":"/tag/raspi/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/react-virtual-scroll/index.json b/assets/data/post/react-virtual-scroll/index.json index 5eae76431..3dc970a9c 100644 --- a/assets/data/post/react-virtual-scroll/index.json +++ b/assets/data/post/react-virtual-scroll/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"109","title":"聊一聊 React 的 virtual scroll","summary":"\n

也没啥高见,重复一下网上现有的资料而已

\n","body":"\n

Virtual scroll 解决了什么问题

\n

Virtual scroll 是绘制超大型列表的性能解决方案。众所周知当 DOM 元素变得很多的时候,渲染元素将消耗可观的时间。即使元素是分配获取、绘制的,浏览器的内存消耗、计算元素位置的 CPU 消耗等也会上升,列表滚动及其他操作的延迟就会增加。

\n

Virtual scroll 是怎么做的

\n

正如某相关模块 react-window 的名字,你是通过一扇窗户来看这个大列表,也只有窗户里的东西才会绘制(当然可以设置余量 overscan)。

\n

其他解决方案的问题

\n

(没错,我只知道 LazyLoad)

\n

Lazyload

\n

对于 React 而言,react-lazyload 中需要 lazyload 的元素呆在同一个列表中,这意味着如果一个页面有两个列表,一个的滚动将引起另一个列表内元素的重新计算。而且如果你有一个 overflow: scroll 的列表和一个随页面滚动的列表,将引起事件处理的混乱。

\n

一般来说,LazyLoad 并没有解决 DOM 元素数量级的问题,只是把复杂的元素变得简单,到时候再绘制。如果有大规模筛选列表的操作,Unmount LazyLoad component 也是一笔不小的开支。

\n

如果滚动到很下方,对上面的元素如何处置又是一个问题。Unmount 吧,滚动位置会突变;hide 吧,实质作用并不大,仍然参与计算,内存也占用着。

\n

Virtual scroll 的难点及 React 现有模块简评

\n

这里主要针对列表元素高度未知(dynamic,动态,与随时间变化不同),画出来才知道的情况。固定高度的可以简单搞定。

\n

react-window 也是一个好模块,但是动态功能难产,就一笔带过了。

\n

基本问题

\n

如何确定每个元素的位置是个难题,如果要求处理 resize (滚回去元素高度会变化)就更难了。

\n

如果你还要求滚过的元素记住状态,对不起,自己实现或者弃坑。

\n

没有浏览器加持,布局受限

\n

平常做点网页的效果只需要 CSS 什么的就可以搞定。但是这里全靠手动,还不一定能成功 🤷‍♂️

\n

复杂算法,体积庞大

\n

如果还要装作列表是随页面滚动的呢?什么?还要一起滚动的表头和表尾?表头还不能 unmount?……一堆需求下来,就有了体型庞大的 react-virtualized (35 KB min+gzipped) (虽然支持一定程度的 tree-shaking)

\n

性能和懒难以兼得

\n

本来就是为了性能才使用 virtual scroll 的(大雾

\n

比如 react-virtuoso 虽然可以支持大小随时间变化,但是估计是加了元素大小变化事件监听,快速滚动下来明显不如 react-virtual 顺畅,甚至可以看到表尾不呆在应该有的底部。

\n

bug 和问题频出

\n

(看了 Issue 区就有点劝退了)

\n

简单讲一个 react-virtual 碰到的问题。因为元素高度会变化,在图片加载后让 react-virtual 重新计算。但是重新计算意味着把所有元素的高度都看做是估计值。将会出现多次尝试绘制,再次触发的重新计算甚至引起绘制的死循环。同时滚动会出现跳跃。。😭

","createdAt":"2020-07-10T09:58:04.000Z","lastEditedAt":"2020-07-11T01:08:48.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: javascript","type":"tag","name":"javascript","color":"f1da4e","path":"/tag/javascript/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"109","title":"聊一聊 React 的 virtual scroll","summary":"\n

也没啥高见,重复一下网上现有的资料而已

\n","body":"\n

Virtual scroll 解决了什么问题

\n

Virtual scroll 是绘制超大型列表的性能解决方案。众所周知当 DOM 元素变得很多的时候,渲染元素将消耗可观的时间。即使元素是分配获取、绘制的,浏览器的内存消耗、计算元素位置的 CPU 消耗等也会上升,列表滚动及其他操作的延迟就会增加。

\n

Virtual scroll 是怎么做的

\n

正如某相关模块 react-window 的名字,你是通过一扇窗户来看这个大列表,也只有窗户里的东西才会绘制(当然可以设置余量 overscan)。

\n

其他解决方案的问题

\n

(没错,我只知道 LazyLoad)

\n

Lazyload

\n

对于 React 而言,react-lazyload 中需要 lazyload 的元素呆在同一个列表中,这意味着如果一个页面有两个列表,一个的滚动将引起另一个列表内元素的重新计算。而且如果你有一个 overflow: scroll 的列表和一个随页面滚动的列表,将引起事件处理的混乱。

\n

一般来说,LazyLoad 并没有解决 DOM 元素数量级的问题,只是把复杂的元素变得简单,到时候再绘制。如果有大规模筛选列表的操作,Unmount LazyLoad component 也是一笔不小的开支。

\n

如果滚动到很下方,对上面的元素如何处置又是一个问题。Unmount 吧,滚动位置会突变;hide 吧,实质作用并不大,仍然参与计算,内存也占用着。

\n

Virtual scroll 的难点及 React 现有模块简评

\n

这里主要针对列表元素高度未知(dynamic,动态,与随时间变化不同),画出来才知道的情况。固定高度的可以简单搞定。

\n

react-window 也是一个好模块,但是动态功能难产,就一笔带过了。

\n

基本问题

\n

如何确定每个元素的位置是个难题,如果要求处理 resize (滚回去元素高度会变化)就更难了。

\n

如果你还要求滚过的元素记住状态,对不起,自己实现或者弃坑。

\n

没有浏览器加持,布局受限

\n

平常做点网页的效果只需要 CSS 什么的就可以搞定。但是这里全靠手动,还不一定能成功 🤷‍♂️

\n

复杂算法,体积庞大

\n

如果还要装作列表是随页面滚动的呢?什么?还要一起滚动的表头和表尾?表头还不能 unmount?……一堆需求下来,就有了体型庞大的 react-virtualized (35 KB min+gzipped) (虽然支持一定程度的 tree-shaking)

\n

性能和懒难以兼得

\n

本来就是为了性能才使用 virtual scroll 的(大雾

\n

比如 react-virtuoso 虽然可以支持大小随时间变化,但是估计是加了元素大小变化事件监听,快速滚动下来明显不如 react-virtual 顺畅,甚至可以看到表尾不呆在应该有的底部。

\n

bug 和问题频出

\n

(看了 Issue 区就有点劝退了)

\n

简单讲一个 react-virtual 碰到的问题。因为元素高度会变化,在图片加载后让 react-virtual 重新计算。但是重新计算意味着把所有元素的高度都看做是估计值。将会出现多次尝试绘制,再次触发的重新计算甚至引起绘制的死循环。同时滚动会出现跳跃。。😭

","createdAt":"2020-07-10T09:58:04.000Z","lastEditedAt":"2020-07-11T01:08:48.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: javascript","type":"tag","name":"javascript","color":"f1da4e","path":"/tag/javascript/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/showcase/index.json b/assets/data/post/showcase/index.json index d315bb70a..4efcacd2e 100644 --- a/assets/data/post/showcase/index.json +++ b/assets/data/post/showcase/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"18","title":"Showcase","summary":"\n

测试一下 Markdown 渲染的效果 网页的 CSS

\n","body":"\n

Hello!

\n
Yes!\n

😂 🐶

\n
\n
\n \n python\n
print('hello world!')
\n
\n

really awesome!

\n
    \n
  • Can
  • \n
  • I
  • \n
  • nest
  • \n
\n
\n
\n \n js\n
even('write').code ? ah : ha
\n
\n
\n

code in head of course!

\n
code with unspecified language\n
\n
or code with indent\n
\n

More complex heading level!

\n
A very very very very very very very very very very very very very very very very very very very very very very very very very very very very very long code\n
\n

And more!

\n

Paragraph

\n
Small Headers
\n

Paragraph

\n
Should Be Visible
\n

Another paragraph

\n

Inline $\\frac{1}{2 \\pi i}$

\n

$$
\n\\int_{- \\infty}^{\\infty} \\frac{1}{x} \\mathrm d x
\n$$

","createdAt":"2019-12-28T09:22:09.000Z","lastEditedAt":"2020-07-22T07:54:13.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"}],"reactions":[{"emoji":"😄","count":1,"users":["AllanChain"]},{"emoji":"🎉","count":1,"users":["AllanChain"]},{"emoji":"❤","count":1,"users":["AllanChain"]},{"emoji":"🚀","count":1,"users":["AllanChain"]}],"comments":[{"resourcePath":"/AllanChain/blog/issues/18#issuecomment-639432138","author":{"id":"AllanChain","avatarUrl":"https://avatars.githubusercontent.com/u/36528777?s=64&u=e5821b32c02c77ed038c8c82cfe55e07075911b7&v=4"},"bodyHTML":"

Awesome!

","createdAt":"2020-06-05T11:47:13.000Z","reactions":[{"emoji":"👍","count":1,"users":["fangtu"]},{"emoji":"😄","count":2,"users":["AllanChain","fangtu"]},{"emoji":"❤","count":2,"users":["AllanChain","fangtu"]}]},{"resourcePath":"/AllanChain/blog/issues/18#issuecomment-648726240","author":{"id":"AllanChain","avatarUrl":"https://avatars.githubusercontent.com/u/36528777?s=64&u=e5821b32c02c77ed038c8c82cfe55e07075911b7&v=4"},"bodyHTML":"

More

\n
\n

comment

\n
\n
with\n
\n

rich style

","createdAt":"2020-06-24T10:04:23.000Z","reactions":[{"emoji":"🚀","count":1,"users":["AllanChain"]}]},{"resourcePath":"/AllanChain/blog/issues/18#issuecomment-649517566","author":{"id":"fangtu","avatarUrl":"https://avatars.githubusercontent.com/u/18296978?s=64&v=4"},"bodyHTML":"

nice

","createdAt":"2020-06-25T12:44:32.000Z","reactions":[{"emoji":"😄","count":1,"users":["AllanChain"]}]},{"resourcePath":"/AllanChain/blog/issues/18#issuecomment-657232119","author":{"id":"AllanChain","avatarUrl":"https://avatars.githubusercontent.com/u/36528777?s=64&u=e5821b32c02c77ed038c8c82cfe55e07075911b7&v=4"},"bodyHTML":"

\n
\n

\n blog/github.data.js\n

\n

\n Lines 15 to 27\n in\n cdaf1f5\n

\n
\n
\n \n\n \n \n \n \n\n \n \n \n \n\n \n \n \n \n\n \n \n \n \n\n \n \n \n \n\n \n \n \n \n\n \n \n \n \n\n \n \n \n \n\n \n \n \n \n\n \n \n \n \n\n \n \n \n \n\n \n \n \n \n\n \n \n \n \n
/**
* Add slug to HTML headers
* @param {string} html html to add slug
*/
const slugPlugin = html => {
const slugger = new GithubSlugger()
return html.replace(/<h(\\d)>(.*?)<\\/h\\1>/gs, (_, level, title) => {
const slug = slugger.slug(title.replace(/<.*?>/g, ''))
return `<h${level}>
<a id=\"article-${slug}\" class=\"anchor-hover\" href=\"#${slug}\">
#</a> ${title}</h${level}>`
})
}
\n
\n
\n

","createdAt":"2020-07-12T14:49:54.000Z","reactions":[]},{"resourcePath":"/AllanChain/blog/issues/18#issuecomment-662301785","author":{"id":"AllanChain","avatarUrl":"https://avatars.githubusercontent.com/u/36528777?s=64&u=e5821b32c02c77ed038c8c82cfe55e07075911b7&v=4"},"bodyHTML":"

Link to other post like this vue-pwa-1#本地测试 vue-pwa-2

\n

Write math! $\\frac{1}{2}$

\n

Inline display $\\dfrac 1 2$, $\\displaystyle \\int x \\mathrm d x$

\n

Custom macro $\\ds \\int x \\rm d x$

","createdAt":"2020-07-22T07:51:03.000Z","reactions":[]}]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"18","title":"Showcase","summary":"\n

测试一下 Markdown 渲染的效果 网页的 CSS

\n","body":"\n

Hello!

\n
Yes!\n

😂 🐶

\n
\n
\n \n python\n
print('hello world!')
\n
\n

really awesome!

\n
    \n
  • Can
  • \n
  • I
  • \n
  • nest
  • \n
\n
\n
\n \n js\n
even('write').code ? ah : ha
\n
\n
\n

code in head of course!

\n
code with unspecified language\n
\n
or code with indent\n
\n

More complex heading level!

\n
A very very very very very very very very very very very very very very very very very very very very very very very very very very very very very long code\n
\n

And more!

\n

Paragraph

\n
Small Headers
\n

Paragraph

\n
Should Be Visible
\n

Another paragraph

\n

Inline $\\frac{1}{2 \\pi i}$

\n

$$
\n\\int_{- \\infty}^{\\infty} \\frac{1}{x} \\mathrm d x
\n$$

","createdAt":"2019-12-28T09:22:09.000Z","lastEditedAt":"2020-07-22T07:54:13.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"}],"reactions":[{"emoji":"😄","count":1,"users":["AllanChain"]},{"emoji":"🎉","count":1,"users":["AllanChain"]},{"emoji":"❤","count":1,"users":["AllanChain"]},{"emoji":"🚀","count":1,"users":["AllanChain"]}],"comments":[{"resourcePath":"/AllanChain/blog/issues/18#issuecomment-639432138","author":{"id":"AllanChain","avatarUrl":"https://avatars.githubusercontent.com/u/36528777?s=64&u=e5821b32c02c77ed038c8c82cfe55e07075911b7&v=4"},"bodyHTML":"

Awesome!

","createdAt":"2020-06-05T11:47:13.000Z","reactions":[{"emoji":"👍","count":1,"users":["fangtu"]},{"emoji":"😄","count":2,"users":["AllanChain","fangtu"]},{"emoji":"❤","count":2,"users":["AllanChain","fangtu"]}]},{"resourcePath":"/AllanChain/blog/issues/18#issuecomment-648726240","author":{"id":"AllanChain","avatarUrl":"https://avatars.githubusercontent.com/u/36528777?s=64&u=e5821b32c02c77ed038c8c82cfe55e07075911b7&v=4"},"bodyHTML":"

More

\n
\n

comment

\n
\n
with\n
\n

rich style

","createdAt":"2020-06-24T10:04:23.000Z","reactions":[{"emoji":"🚀","count":1,"users":["AllanChain"]}]},{"resourcePath":"/AllanChain/blog/issues/18#issuecomment-649517566","author":{"id":"fangtu","avatarUrl":"https://avatars.githubusercontent.com/u/18296978?s=64&v=4"},"bodyHTML":"

nice

","createdAt":"2020-06-25T12:44:32.000Z","reactions":[{"emoji":"😄","count":1,"users":["AllanChain"]}]},{"resourcePath":"/AllanChain/blog/issues/18#issuecomment-657232119","author":{"id":"AllanChain","avatarUrl":"https://avatars.githubusercontent.com/u/36528777?s=64&u=e5821b32c02c77ed038c8c82cfe55e07075911b7&v=4"},"bodyHTML":"

\n
\n

\n blog/github.data.js\n

\n

\n Lines 15 to 27\n in\n cdaf1f5\n

\n
\n
\n \n\n \n \n \n \n\n \n \n \n \n\n \n \n \n \n\n \n \n \n \n\n \n \n \n \n\n \n \n \n \n\n \n \n \n \n\n \n \n \n \n\n \n \n \n \n\n \n \n \n \n\n \n \n \n \n\n \n \n \n \n\n \n \n \n \n
/**
* Add slug to HTML headers
* @param {string} html html to add slug
*/
const slugPlugin = html => {
const slugger = new GithubSlugger()
return html.replace(/<h(\\d)>(.*?)<\\/h\\1>/gs, (_, level, title) => {
const slug = slugger.slug(title.replace(/<.*?>/g, ''))
return `<h${level}>
<a id=\"article-${slug}\" class=\"anchor-hover\" href=\"#${slug}\">
#</a> ${title}</h${level}>`
})
}
\n
\n
\n

","createdAt":"2020-07-12T14:49:54.000Z","reactions":[]},{"resourcePath":"/AllanChain/blog/issues/18#issuecomment-662301785","author":{"id":"AllanChain","avatarUrl":"https://avatars.githubusercontent.com/u/36528777?s=64&u=e5821b32c02c77ed038c8c82cfe55e07075911b7&v=4"},"bodyHTML":"

Link to other post like this vue-pwa-1#本地测试 vue-pwa-2

\n

Write math! $\\frac{1}{2}$

\n

Inline display $\\dfrac 1 2$, $\\displaystyle \\int x \\mathrm d x$

\n

Custom macro $\\ds \\int x \\rm d x$

","createdAt":"2020-07-22T07:51:03.000Z","reactions":[]}]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/sql-query-perf/index.json b/assets/data/post/sql-query-perf/index.json index bebbc3317..7170fefa7 100644 --- a/assets/data/post/sql-query-perf/index.json +++ b/assets/data/post/sql-query-perf/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"164","title":"数据库查询性能优化手记","summary":"\n

简单的手记,没有什么系统性。相关的变量名是经过简单替换的,以免关注点偏离(bushi

\n","body":"\n

尝试 1:使用 SQLAlchemy 的 Dynamic Relationship Loaders 功能:

\n
\n
\n \n python\n
class BasicInfo(Base):\n    __tablename__ = \"basic_info\"\n    day_infos = relationship(\n        \"DayInfo\", order_by=\"desc(DayInfo.date)\", lazy=\"dynamic\"\n    )\n\n\nclass DayInfo(Base):\n    __tablename__ = \"day_info\"\n    code = Column(String(8), ForeignKey(\"basic_info.code\"), primary_key=True)
\n

并在读取中使用 limit 控制读取的个数。处理 3 天的数据查找指定条件,用时约 1 分钟。

\n

尝试 2:一次性读取所有的记录

\n

按照尝试 1 的做法数据库请求的个数会很多,数千次请求不会很快。如果改用所有数据合并到一次请求当中,则大大加快。用时达到 8 秒。

\n

问题 1:启动时间

\n

由于需要导入大量的库,启动时间约有 4 秒钟,不容乐观。

\n

使用 PYTHONPROFILEIMPORTTIME=1 运行,并将 stderr 导出至文件,使用 tuna 查看,发现大头是 pandas 和 SQLAlchemy。由于 pandas 并不是每次运行都需要使用,故将其从 top level import 挪到的相应的函数中。时间减少了 2 秒。

\n

此时用时达到 6 秒。

\n

尝试 3:使用 List Comprehension 运行匹配算法

\n

时间几乎没有变化。后来知道是匹配算法本来运行时间就短的原因。

\n

尝试 4:使用 SQLAlchemy 自带的分页功能读取数据

\n
\n
\n \n python\n
for data in session.execute(\n    session.query(BasicInfo, DayInfo)\n    .join(BasicInfo.day_infos)\n    .where(DayInfo.date > date_after)\n    .execution_options(yield_per=1000)\n).partitions():
\n

时间并没有显著变化,但是内存的占用从大约 9% 降至 2%。可以肯定是 Python 的对象数量减少带来的。

\n

尝试 5:更换 Python 的 MySQL 连接库

\n

使用 python -m cProfile -o program.prof -m myprog ,再使用 snakeviz 可视化,发现很多时间被用在了 parse string, int 等上。而且使用 top 命令也可以看到 CPU 占满了一个核。将 PyMySQL 更换为 mysqlclient 后,处理 40 天数据从 22 秒降至 12 到 13 秒。

\n

问题 2:分页

\n

由于股票数据很多,尽管尝试 4 中使用了分页,但是在 MySQL 中,数据仍是一次读取。

\n

参考了网上的资料:

\n\n

看到应该结合所选的条件进行分页而不是暴力地 LIMIT OFFSET. 如:

\n
\n
\n \n sql\n
SELECT day_info.*, basic_info.data FROM basic_info\nJOIN day_info ON basic_info.code=day_info.code\nWHERE DATE > '2021-03-01'\nAND (\n    basic_info.code > 'sh600011'\n    OR (basic_info.code='sh600011' AND day_info.date > '2021-03-03')\n)\nLIMIT 30
\n

尝试 6:不使用 SQLAlchemy

\n

进一步 profile 看到有很多运行时间是花在 SQLAlchemy 上的。直接跳过 ORM 层操作,时间减少了约 1 秒(可能就是不 import SQLAlchemy 带来的),但是在运行时间中,CPU 占用比率((user + sys)/real) 从 96% 降至 50% - 60%。这意味着性能瓶颈由原来的 CPU 转化为 CPU 和 IO

\n

问题 3:Query Time

\n

过程中发现 MariaDB 突然表现出了缓存功能,再次运行后 IO 时间显著减小至可以忽略。所以 MySQL 的 query 速度还有很大的优化空间。

\n

使用

\n
\n
\n \n sql\n
SHOW GlOBAL VARIABLES LIKE 'slow%log%'
\n

发现并没有开启对慢查询的日志,使用以下命令开启:

\n
\n
\n \n sql\n
SET GLOBAL slow_query_log='on';\nSET GLOBAL slow_query_log_file='/var/log/mysql/sql-slow.log';\nSET GLOBAL long_query_time = 1;
\n

查阅日志可以看到,一条一万数据的 select 花了 7 秒钟才完成。

\n
Query_time: 6.917748  Lock_time: 0.000308  Rows_sent: 10000  Rows_examined: 799\n
\n

为方便调试性能,需要关闭缓存功能,可使用 SELECT SQL_NO_CACHE ... 实现。

\n

如问题 2 中的 query:

\n
\n
\n \n sql\n
SELECT sql_no_cache day_info.*, basic_info.data\nFROM basic_info\nJOIN day_info ON basic_info.code=day_info.code\nWHERE DATE > '2021-01-25' AND (\n basic_info.code > 'specificode' OR (\n basic_info.code='specificode' AND day_info.date > '2021-02-09'\n)\n)\nLIMIT 5000;
\n

单独运行需要 3 秒钟。但是如果拆成 2 个:

\n
\n
\n \n sql\n
SELECT sql_no_cache day_info.*, basic_info.data\nFROM basic_info\nJOIN day_info ON basic_info.code=day_info.code\nWHERE DATE > '2021-01-25' AND  basic_info.code > 'specificode'\nLIMIT 5000;
\n
\n
\n \n sql\n
SELECT sql_no_cache day_info.*, basic_info.data\nFROM basic_info\nJOIN day_info ON basic_info.code=day_info.code\nWHERE basic_info.code='specificode' AND day_info.date > '2021-02-09';
\n

则半秒钟内即可完成。

\n

有没有将两个合成一个但又不影响性能的方法呢?是有的。使用 UNION 合并两个 query,并注意要用 UNION ALL 跳过去重步骤,并用括号第把二个语句括起来。如果不使用括号,则 LIMIT 将被识别为整个大语句的,严重影响速度。

\n
\n
\n \n sql\n
SELECT SQL_NO_CACHE day_info.*, basic_info.data\nFROM basic_info\nJOIN day_info ON basic_info.code=day_info.code\nWHERE basic_info.code='specificode' AND day_info.date > '2021-02-09'\nUNION ALL\n(\n  SELECT day_info.*, basic_info.data\n  FROM basic_info\n  JOIN day_info ON basic_info.code=day_info.code\n  WHERE DATE > '2021-01-25' AND (\n   basic_info.code > 'specificode'\n  )\n  LIMIT 5000\n);
\n

处理 40 天数据降至 7 秒。

\n

尝试 7:use_result vs store_result

\n

在 mysqlclient 中,默认的 cursor 类在底层使用 store_result,即将每一次查询的结果一股脑存下来备用。相比之下 use_result 就人性化很多,先放在 server 里有需要再取,也符合 fetchmany 的初衷。

\n

使用默认 Cursor 类的 profile 结果:

\n

\"before\"

\n

使用 SSCursor 类的 profile 结果(橘色那块是 execute,剩下那块是另一个与 SQL 无关的函数,也就是上图较深灰的部分):

\n

\"after\"

","createdAt":"2021-03-09T10:08:35.000Z","lastEditedAt":"2021-08-12T11:59:56.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: mysql","type":"tag","name":"mysql","color":"f09011","path":"/tag/mysql/"}],"reactions":[{"emoji":"👍","count":1,"users":["Yixuan-Wang"]}],"comments":[]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"164","title":"数据库查询性能优化手记","summary":"\n

简单的手记,没有什么系统性。相关的变量名是经过简单替换的,以免关注点偏离(bushi

\n","body":"\n

尝试 1:使用 SQLAlchemy 的 Dynamic Relationship Loaders 功能:

\n
\n
\n \n python\n
class BasicInfo(Base):\n    __tablename__ = \"basic_info\"\n    day_infos = relationship(\n        \"DayInfo\", order_by=\"desc(DayInfo.date)\", lazy=\"dynamic\"\n    )\n\n\nclass DayInfo(Base):\n    __tablename__ = \"day_info\"\n    code = Column(String(8), ForeignKey(\"basic_info.code\"), primary_key=True)
\n

并在读取中使用 limit 控制读取的个数。处理 3 天的数据查找指定条件,用时约 1 分钟。

\n

尝试 2:一次性读取所有的记录

\n

按照尝试 1 的做法数据库请求的个数会很多,数千次请求不会很快。如果改用所有数据合并到一次请求当中,则大大加快。用时达到 8 秒。

\n

问题 1:启动时间

\n

由于需要导入大量的库,启动时间约有 4 秒钟,不容乐观。

\n

使用 PYTHONPROFILEIMPORTTIME=1 运行,并将 stderr 导出至文件,使用 tuna 查看,发现大头是 pandas 和 SQLAlchemy。由于 pandas 并不是每次运行都需要使用,故将其从 top level import 挪到的相应的函数中。时间减少了 2 秒。

\n

此时用时达到 6 秒。

\n

尝试 3:使用 List Comprehension 运行匹配算法

\n

时间几乎没有变化。后来知道是匹配算法本来运行时间就短的原因。

\n

尝试 4:使用 SQLAlchemy 自带的分页功能读取数据

\n
\n
\n \n python\n
for data in session.execute(\n    session.query(BasicInfo, DayInfo)\n    .join(BasicInfo.day_infos)\n    .where(DayInfo.date > date_after)\n    .execution_options(yield_per=1000)\n).partitions():
\n

时间并没有显著变化,但是内存的占用从大约 9% 降至 2%。可以肯定是 Python 的对象数量减少带来的。

\n

尝试 5:更换 Python 的 MySQL 连接库

\n

使用 python -m cProfile -o program.prof -m myprog ,再使用 snakeviz 可视化,发现很多时间被用在了 parse string, int 等上。而且使用 top 命令也可以看到 CPU 占满了一个核。将 PyMySQL 更换为 mysqlclient 后,处理 40 天数据从 22 秒降至 12 到 13 秒。

\n

问题 2:分页

\n

由于股票数据很多,尽管尝试 4 中使用了分页,但是在 MySQL 中,数据仍是一次读取。

\n

参考了网上的资料:

\n\n

看到应该结合所选的条件进行分页而不是暴力地 LIMIT OFFSET. 如:

\n
\n
\n \n sql\n
SELECT day_info.*, basic_info.data FROM basic_info\nJOIN day_info ON basic_info.code=day_info.code\nWHERE DATE > '2021-03-01'\nAND (\n    basic_info.code > 'sh600011'\n    OR (basic_info.code='sh600011' AND day_info.date > '2021-03-03')\n)\nLIMIT 30
\n

尝试 6:不使用 SQLAlchemy

\n

进一步 profile 看到有很多运行时间是花在 SQLAlchemy 上的。直接跳过 ORM 层操作,时间减少了约 1 秒(可能就是不 import SQLAlchemy 带来的),但是在运行时间中,CPU 占用比率((user + sys)/real) 从 96% 降至 50% - 60%。这意味着性能瓶颈由原来的 CPU 转化为 CPU 和 IO

\n

问题 3:Query Time

\n

过程中发现 MariaDB 突然表现出了缓存功能,再次运行后 IO 时间显著减小至可以忽略。所以 MySQL 的 query 速度还有很大的优化空间。

\n

使用

\n
\n
\n \n sql\n
SHOW GlOBAL VARIABLES LIKE 'slow%log%'
\n

发现并没有开启对慢查询的日志,使用以下命令开启:

\n
\n
\n \n sql\n
SET GLOBAL slow_query_log='on';\nSET GLOBAL slow_query_log_file='/var/log/mysql/sql-slow.log';\nSET GLOBAL long_query_time = 1;
\n

查阅日志可以看到,一条一万数据的 select 花了 7 秒钟才完成。

\n
Query_time: 6.917748  Lock_time: 0.000308  Rows_sent: 10000  Rows_examined: 799\n
\n

为方便调试性能,需要关闭缓存功能,可使用 SELECT SQL_NO_CACHE ... 实现。

\n

如问题 2 中的 query:

\n
\n
\n \n sql\n
SELECT sql_no_cache day_info.*, basic_info.data\nFROM basic_info\nJOIN day_info ON basic_info.code=day_info.code\nWHERE DATE > '2021-01-25' AND (\n basic_info.code > 'specificode' OR (\n basic_info.code='specificode' AND day_info.date > '2021-02-09'\n)\n)\nLIMIT 5000;
\n

单独运行需要 3 秒钟。但是如果拆成 2 个:

\n
\n
\n \n sql\n
SELECT sql_no_cache day_info.*, basic_info.data\nFROM basic_info\nJOIN day_info ON basic_info.code=day_info.code\nWHERE DATE > '2021-01-25' AND  basic_info.code > 'specificode'\nLIMIT 5000;
\n
\n
\n \n sql\n
SELECT sql_no_cache day_info.*, basic_info.data\nFROM basic_info\nJOIN day_info ON basic_info.code=day_info.code\nWHERE basic_info.code='specificode' AND day_info.date > '2021-02-09';
\n

则半秒钟内即可完成。

\n

有没有将两个合成一个但又不影响性能的方法呢?是有的。使用 UNION 合并两个 query,并注意要用 UNION ALL 跳过去重步骤,并用括号第把二个语句括起来。如果不使用括号,则 LIMIT 将被识别为整个大语句的,严重影响速度。

\n
\n
\n \n sql\n
SELECT SQL_NO_CACHE day_info.*, basic_info.data\nFROM basic_info\nJOIN day_info ON basic_info.code=day_info.code\nWHERE basic_info.code='specificode' AND day_info.date > '2021-02-09'\nUNION ALL\n(\n  SELECT day_info.*, basic_info.data\n  FROM basic_info\n  JOIN day_info ON basic_info.code=day_info.code\n  WHERE DATE > '2021-01-25' AND (\n   basic_info.code > 'specificode'\n  )\n  LIMIT 5000\n);
\n

处理 40 天数据降至 7 秒。

\n

尝试 7:use_result vs store_result

\n

在 mysqlclient 中,默认的 cursor 类在底层使用 store_result,即将每一次查询的结果一股脑存下来备用。相比之下 use_result 就人性化很多,先放在 server 里有需要再取,也符合 fetchmany 的初衷。

\n

使用默认 Cursor 类的 profile 结果:

\n

\"before\"

\n

使用 SSCursor 类的 profile 结果(橘色那块是 execute,剩下那块是另一个与 SQL 无关的函数,也就是上图较深灰的部分):

\n

\"after\"

","createdAt":"2021-03-09T10:08:35.000Z","lastEditedAt":"2021-08-12T11:59:56.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: mysql","type":"tag","name":"mysql","color":"f09011","path":"/tag/mysql/"}],"reactions":[{"emoji":"👍","count":1,"users":["Yixuan-Wang"]}],"comments":[]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/tmux-setup/index.json b/assets/data/post/tmux-setup/index.json index 3c57c4a45..756548082 100644 --- a/assets/data/post/tmux-setup/index.json +++ b/assets/data/post/tmux-setup/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"67","title":"Introduce and Setup Tmux","summary":"\n

Setup and introduce Tmux so that I can use it

\n","body":"\n

Cheat Sheet

\n

\"\"

\n

Image from https://linuxacademy.com

\n

Enable 256 color vim

\n

From https://superuser.com/a/1459904

\n

First, outside tmux, you need:

\n
\n
\n \n shell\n
export TERM=xterm-256color
\n

in .tmux.conf:

\n
\n
\n \n shell\n
set -g default-terminal \"screen-256color\"
\n

And don't forget to source config manually:

\n
\n
\n \n shell\n
tmux source-file ~/.tmux.conf
\n

All above make sure tmux have full 256 colors support.

\n

And in your vimrc, add:

\n
\n
\n \n viml\n
if exists(\"$TMUX\")\n    set t_Co=256\n    set notermguicolors\nelse\n    set termguicolors\nendif
\n

Enable True Color for vim

\n

From tmux/tmux#1246 and https://github.com/lifepillar/vim-solarized8#troubleshooting

\n

Basically,

\n
\n
\n \n viml\n
let &t_8f = \"\\<Esc>[38;2;%lu;%lu;%lum\"\nlet &t_8b = \"\\<Esc>[48;2;%lu;%lu;%lum\"\nset termguicolors
\n

will do the trick.

\n

Resize Panes on Keyboard

\n
\n

You may just use mouse though

\n
\n

This assumes that you've hit ctrl + b and : to get to the command prompt

\n
:resize-pane -D (Resizes the current pane down)\n:resize-pane -U (Resizes the current pane upward)\n:resize-pane -L (Resizes the current pane left)\n:resize-pane -R (Resizes the current pane right)\n:resize-pane -D 10 (Resizes the current pane down by 10 cells)\n:resize-pane -U 10 (Resizes the current pane upward by 10 cells)\n:resize-pane -L 10 (Resizes the current pane left by 10 cells)\n:resize-pane -R 10 (Resizes the current pane right by 10 cells)\n
\n

Border Chaos

\n

If I split the panes vertically, the first column of the right pane is overwritten by the border.

\n

\"chaos\"

\n

It turns out to be the old problem with MinTTY. As you can see on the top-middle of the screenshot, the border is displayed in full width (2 chars), with introduced the chaos.

\n

To solve it, inspired by mintty/mintty#615, all you need to do is Options → Text → Locale: C (Or any language with sane character width). And the broken vim airline is solved too!

\n

\"fixed\"

","createdAt":"2020-01-21T13:55:42.000Z","lastEditedAt":"2020-07-10T12:46:29.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: tmux","type":"tag","name":"tmux","color":"1bb91f","path":"/tag/tmux/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"67","title":"Introduce and Setup Tmux","summary":"\n

Setup and introduce Tmux so that I can use it

\n","body":"\n

Cheat Sheet

\n

\"\"

\n

Image from https://linuxacademy.com

\n

Enable 256 color vim

\n

From https://superuser.com/a/1459904

\n

First, outside tmux, you need:

\n
\n
\n \n shell\n
export TERM=xterm-256color
\n

in .tmux.conf:

\n
\n
\n \n shell\n
set -g default-terminal \"screen-256color\"
\n

And don't forget to source config manually:

\n
\n
\n \n shell\n
tmux source-file ~/.tmux.conf
\n

All above make sure tmux have full 256 colors support.

\n

And in your vimrc, add:

\n
\n
\n \n viml\n
if exists(\"$TMUX\")\n    set t_Co=256\n    set notermguicolors\nelse\n    set termguicolors\nendif
\n

Enable True Color for vim

\n

From tmux/tmux#1246 and https://github.com/lifepillar/vim-solarized8#troubleshooting

\n

Basically,

\n
\n
\n \n viml\n
let &t_8f = \"\\<Esc>[38;2;%lu;%lu;%lum\"\nlet &t_8b = \"\\<Esc>[48;2;%lu;%lu;%lum\"\nset termguicolors
\n

will do the trick.

\n

Resize Panes on Keyboard

\n
\n

You may just use mouse though

\n
\n

This assumes that you've hit ctrl + b and : to get to the command prompt

\n
:resize-pane -D (Resizes the current pane down)\n:resize-pane -U (Resizes the current pane upward)\n:resize-pane -L (Resizes the current pane left)\n:resize-pane -R (Resizes the current pane right)\n:resize-pane -D 10 (Resizes the current pane down by 10 cells)\n:resize-pane -U 10 (Resizes the current pane upward by 10 cells)\n:resize-pane -L 10 (Resizes the current pane left by 10 cells)\n:resize-pane -R 10 (Resizes the current pane right by 10 cells)\n
\n

Border Chaos

\n

If I split the panes vertically, the first column of the right pane is overwritten by the border.

\n

\"chaos\"

\n

It turns out to be the old problem with MinTTY. As you can see on the top-middle of the screenshot, the border is displayed in full width (2 chars), with introduced the chaos.

\n

To solve it, inspired by mintty/mintty#615, all you need to do is Options → Text → Locale: C (Or any language with sane character width). And the broken vim airline is solved too!

\n

\"fixed\"

","createdAt":"2020-01-21T13:55:42.000Z","lastEditedAt":"2020-07-10T12:46:29.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: tmux","type":"tag","name":"tmux","color":"1bb91f","path":"/tag/tmux/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/tmux-ssh/index.json b/assets/data/post/tmux-ssh/index.json index 03190665a..72a720f4e 100644 --- a/assets/data/post/tmux-ssh/index.json +++ b/assets/data/post/tmux-ssh/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"66","title":"Working with tmux and SSH","summary":"\n

Useful tips when SSH in remote device which uses tmux

\n","body":"\n

How to automatically start tmux on SSH session?

\n

From https://stackoverflow.com/a/40192494/8810271

\n

Just put it in server-side .bashrc

\n
\n
\n \n shell\n
if [[ -n \"$PS1\" ]] && [[ -z \"$TMUX\" ]] && [[ -n \"$SSH_CONNECTION\" ]]; then\n  tmux attach-session -t ssh_$USER || tmux new-session -s ssh_$USER\nfi
\n

Detach from tmux session and close SSH session with 1 command

\n

From https://unix.stackexchange.com/a/546831

\n

Just type

\n
\n
\n \n shell\n
tmux detach -P
\n

Copy Content Inside tmux

\n
    \n
  1. enter copy mode using c-b [
  2. \n
  3. navigate to beginning of text, you want to select and hit C-Space
  4. \n
  5. move around using arrow keys to select region
  6. \n
  7. when you reach end of region simply hit M-w to copy the region
  8. \n
  9. now c-b ] will paste the selection
  10. \n
\n

Copy over SSH with MinTTY

\n

Options → Selection → Allow setting selection

\n

And copy as usual in tmux.

\n

If inside vim, enter copy mode first by hitting c-b [

","createdAt":"2020-01-21T13:55:36.000Z","lastEditedAt":"2020-07-03T01:54:18.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: tmux","type":"tag","name":"tmux","color":"1bb91f","path":"/tag/tmux/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"66","title":"Working with tmux and SSH","summary":"\n

Useful tips when SSH in remote device which uses tmux

\n","body":"\n

How to automatically start tmux on SSH session?

\n

From https://stackoverflow.com/a/40192494/8810271

\n

Just put it in server-side .bashrc

\n
\n
\n \n shell\n
if [[ -n \"$PS1\" ]] && [[ -z \"$TMUX\" ]] && [[ -n \"$SSH_CONNECTION\" ]]; then\n  tmux attach-session -t ssh_$USER || tmux new-session -s ssh_$USER\nfi
\n

Detach from tmux session and close SSH session with 1 command

\n

From https://unix.stackexchange.com/a/546831

\n

Just type

\n
\n
\n \n shell\n
tmux detach -P
\n

Copy Content Inside tmux

\n
    \n
  1. enter copy mode using c-b [
  2. \n
  3. navigate to beginning of text, you want to select and hit C-Space
  4. \n
  5. move around using arrow keys to select region
  6. \n
  7. when you reach end of region simply hit M-w to copy the region
  8. \n
  9. now c-b ] will paste the selection
  10. \n
\n

Copy over SSH with MinTTY

\n

Options → Selection → Allow setting selection

\n

And copy as usual in tmux.

\n

If inside vim, enter copy mode first by hitting c-b [

","createdAt":"2020-01-21T13:55:36.000Z","lastEditedAt":"2020-07-03T01:54:18.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: tmux","type":"tag","name":"tmux","color":"1bb91f","path":"/tag/tmux/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/upgrade-ubuntu/index.json b/assets/data/post/upgrade-ubuntu/index.json index 6e5605191..a3a9d5294 100644 --- a/assets/data/post/upgrade-ubuntu/index.json +++ b/assets/data/post/upgrade-ubuntu/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"97","title":"Two Points to Notice when Upgrading Ubuntu","summary":"\n

Ever feel frustated when a new release is available but it keeps saying no release found?

\n","body":"\n

The upgrade strategy

\n

The configuration file lies in /etc/update-manager/release-upgrades. Change to normal if not upgrating to a LTS release.

\n

Upgrate to devel release

\n

By default, you can only upgrade after the first point release, say 20.04.1. If you can't wait, run do-release-upgrade -d to upgrade to the devel release.

","createdAt":"2020-04-26T00:00:00.000Z","lastEditedAt":"2020-06-21T12:55:54.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: linux","type":"tag","name":"linux","color":"de4815","path":"/tag/linux/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"97","title":"Two Points to Notice when Upgrading Ubuntu","summary":"\n

Ever feel frustated when a new release is available but it keeps saying no release found?

\n","body":"\n

The upgrade strategy

\n

The configuration file lies in /etc/update-manager/release-upgrades. Change to normal if not upgrating to a LTS release.

\n

Upgrate to devel release

\n

By default, you can only upgrade after the first point release, say 20.04.1. If you can't wait, run do-release-upgrade -d to upgrade to the devel release.

","createdAt":"2020-04-26T00:00:00.000Z","lastEditedAt":"2020-06-21T12:55:54.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: linux","type":"tag","name":"linux","color":"de4815","path":"/tag/linux/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/vim-ariline/index.json b/assets/data/post/vim-ariline/index.json index 25ca6c2fd..2d17cf221 100644 --- a/assets/data/post/vim-ariline/index.json +++ b/assets/data/post/vim-ariline/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"37","title":"Setup Vim Airline (in Termux)","summary":"","body":"\n

Vim airline is a tool to enhance the looking of vim. And it is a little tricky to setup especially in termux.

\n

As I did not install any package manager for vim (in fact, I failed to install and setup Vundle), I need to clone the repo to ~/.vim/pack/dist/start/ to let vim automatically load the package. And I see the airline when I opened vim.

\n
\n

By the way, all packages I tested (NERDTree, indentLine, gitgutter) all support this kind of operation.

\n
\n

Trouble Shooting

\n

Only mode and filename is displayed!

\n

\"0\"

\n

For termux disable the auto truncate of section A,B,C and Z is perfect. That is, let other sections truncate at a larger width than your screen.

\n
\n
\n \n viml\n
let g:airline#extensions#default#section_truncate_width = {\n    \\ 'warning': 80,\n    \\ 'error': 80,\n    \\ 'x': 80,\n    \\ 'y': 80}
\n
\n

Note: Only when nocompatible set can you use \\ to continue line.

\n
\n

\"1\"

\n

No fancy > s there!

\n

Enable fancy fonts!

\n
\n
\n \n viml\n
let g:airline_powerline_fonts = 1
\n

\"2\"

\n

There are dots following the mode!

\n

Set the name of the modes!

\n
\n
\n \n viml\n
let g:airline_mode_map = {\n    \\ 'c': 'C',\n    \\ 'n': 'N',\n    \\ 'V': 'V',\n    \\ 'i':'I'}
\n

\"3\"

\n

I do not need the section Z so long!

\n

Set the custom section z!

\n
\n
\n \n viml\n
let g:airline_section_z = '%2l/%L☰%2v'
\n

\"4\"

\n

What if I need a tab bar?

\n

Enable it and set the good style!

\n
\n
\n \n viml\n
let g:airline#extensions#tabline#enabled=1\nlet g:airline#extensions#tabline#formatter = 'unique_tail'
\n

\"5\"

\n

The word count is annoying!

\n

Disable it!

\n
\n
\n \n viml\n
let g:airline#extensions#wordcount#enabled = 0
\n

\"6\"

\n

Fancy style?

\n

Yes Badwolf!

\n
\n
\n \n viml\n
let g:airline_theme = 'badwolf'
\n

\"7\"
\n\"8\"

\n

Do not want extra > s?

\n

Disable it!

\n
\n
\n \n viml\n
let g:airline_skip_empty_sections = 1
\n

\"9\"

","createdAt":"2019-02-09T00:00:00.000Z","lastEditedAt":"2021-02-22T01:59:03.000Z","image":"/blog/img/5a6f75bf.png","imageLazy":"ACAAkDASIAAhEBAxEB/8QAFQABAQ$AAP/xAAZEAEAAgM$RFDUYH/xAAUAQE$AAB/8QAFREBAQ$AAH/2gAMAwEAAhEDEQA/AJZur3OwFL//2Q==","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: vim","type":"tag","name":"vim","color":"007f00","path":"/tag/vim/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"37","title":"Setup Vim Airline (in Termux)","summary":"","body":"\n

Vim airline is a tool to enhance the looking of vim. And it is a little tricky to setup especially in termux.

\n

As I did not install any package manager for vim (in fact, I failed to install and setup Vundle), I need to clone the repo to ~/.vim/pack/dist/start/ to let vim automatically load the package. And I see the airline when I opened vim.

\n
\n

By the way, all packages I tested (NERDTree, indentLine, gitgutter) all support this kind of operation.

\n
\n

Trouble Shooting

\n

Only mode and filename is displayed!

\n

\"0\"

\n

For termux disable the auto truncate of section A,B,C and Z is perfect. That is, let other sections truncate at a larger width than your screen.

\n
\n
\n \n viml\n
let g:airline#extensions#default#section_truncate_width = {\n    \\ 'warning': 80,\n    \\ 'error': 80,\n    \\ 'x': 80,\n    \\ 'y': 80}
\n
\n

Note: Only when nocompatible set can you use \\ to continue line.

\n
\n

\"1\"

\n

No fancy > s there!

\n

Enable fancy fonts!

\n
\n
\n \n viml\n
let g:airline_powerline_fonts = 1
\n

\"2\"

\n

There are dots following the mode!

\n

Set the name of the modes!

\n
\n
\n \n viml\n
let g:airline_mode_map = {\n    \\ 'c': 'C',\n    \\ 'n': 'N',\n    \\ 'V': 'V',\n    \\ 'i':'I'}
\n

\"3\"

\n

I do not need the section Z so long!

\n

Set the custom section z!

\n
\n
\n \n viml\n
let g:airline_section_z = '%2l/%L☰%2v'
\n

\"4\"

\n

What if I need a tab bar?

\n

Enable it and set the good style!

\n
\n
\n \n viml\n
let g:airline#extensions#tabline#enabled=1\nlet g:airline#extensions#tabline#formatter = 'unique_tail'
\n

\"5\"

\n

The word count is annoying!

\n

Disable it!

\n
\n
\n \n viml\n
let g:airline#extensions#wordcount#enabled = 0
\n

\"6\"

\n

Fancy style?

\n

Yes Badwolf!

\n
\n
\n \n viml\n
let g:airline_theme = 'badwolf'
\n

\"7\"
\n\"8\"

\n

Do not want extra > s?

\n

Disable it!

\n
\n
\n \n viml\n
let g:airline_skip_empty_sections = 1
\n

\"9\"

","createdAt":"2019-02-09T00:00:00.000Z","lastEditedAt":"2021-02-22T01:59:03.000Z","image":"/blog/img/5a6f75bf.png","imageLazy":"ACAAkDASIAAhEBAxEB/8QAFQABAQ$AAP/xAAZEAEAAgM$RFDUYH/xAAUAQE$AAB/8QAFREBAQ$AAH/2gAMAwEAAhEDEQA/AJZur3OwFL//2Q==","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: vim","type":"tag","name":"vim","color":"007f00","path":"/tag/vim/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/vim-cht/index.json b/assets/data/post/vim-cht/index.json index ee3e2346a..1fb86406e 100644 --- a/assets/data/post/vim-cht/index.json +++ b/assets/data/post/vim-cht/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"85","title":"Vim Cheatsheet","summary":"","body":"\n

Getting help

\n

ctrl+] to follow link, ctrl+t to trace back.

\n

Quit all

\n
\n
\n \n viml\n
:qa
\n

Scroll the terminal

\n

From https://stackoverflow.com/a/50545253/8810271

\n

ctrl+w N (notice the capital N) to enter terminal normal mode. You can even search in the terminal output!

\n

And hit either i or a to enter insert mode.

\n

Open terminal / help page verically

\n

Use the :vert[ical] command modifier:

\n
\n
\n \n viml\n
:vert term\n:vert help ex
\n

Enter normal mode for command history

\n

CTRL-F q: q/ q?

\n

Paste yanked text into the Vim command line

\n

From https://stackoverflow.com/a/3997110/8810271

\n

Hit Ctrl-R then \". If you have literal control characters in what you have yanked, use Ctrl-R, Ctrl-O, \".

\n

PS: this Stack Overflow answer is excellent, maybe I will translate it into Chinese later.

\n

Excute command on matched line

\n

:h global for more information

\n
:[range]g[lobal]/{pattern}/[cmd]\n
\n

Execute the Ex command [cmd] (default \":p\") on the lines within [range] where {pattern} matches.

\n

For pattern not match, use :g! or :v instead. You can use another charater as delimiter or even nest g and v.

\n

If you want to excute normal commands, just :g/This line/norm 3dd.

\n

What is Ex mode?

\n

Switch to \"Ex\" mode. This is a bit like typing \":\" commands one after another, except:

\n\n

Therefore, Ex command can simply be considered as command.

\n

Measure startup time

\n
\n
\n \n shell\n
vim --startuptime vim.log
","createdAt":"2020-04-18T04:48:56.000Z","lastEditedAt":"2020-07-01T13:01:11.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: cheatsheet","type":"blog","name":"cheatsheet","color":"6655d6","path":"/blog/cheatsheet/"},{"id":"tag: vim","type":"tag","name":"vim","color":"007f00","path":"/tag/vim/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"85","title":"Vim Cheatsheet","summary":"","body":"\n

Getting help

\n

ctrl+] to follow link, ctrl+t to trace back.

\n

Quit all

\n
\n
\n \n viml\n
:qa
\n

Scroll the terminal

\n

From https://stackoverflow.com/a/50545253/8810271

\n

ctrl+w N (notice the capital N) to enter terminal normal mode. You can even search in the terminal output!

\n

And hit either i or a to enter insert mode.

\n

Open terminal / help page verically

\n

Use the :vert[ical] command modifier:

\n
\n
\n \n viml\n
:vert term\n:vert help ex
\n

Enter normal mode for command history

\n

CTRL-F q: q/ q?

\n

Paste yanked text into the Vim command line

\n

From https://stackoverflow.com/a/3997110/8810271

\n

Hit Ctrl-R then \". If you have literal control characters in what you have yanked, use Ctrl-R, Ctrl-O, \".

\n

PS: this Stack Overflow answer is excellent, maybe I will translate it into Chinese later.

\n

Excute command on matched line

\n

:h global for more information

\n
:[range]g[lobal]/{pattern}/[cmd]\n
\n

Execute the Ex command [cmd] (default \":p\") on the lines within [range] where {pattern} matches.

\n

For pattern not match, use :g! or :v instead. You can use another charater as delimiter or even nest g and v.

\n

If you want to excute normal commands, just :g/This line/norm 3dd.

\n

What is Ex mode?

\n

Switch to \"Ex\" mode. This is a bit like typing \":\" commands one after another, except:

\n\n

Therefore, Ex command can simply be considered as command.

\n

Measure startup time

\n
\n
\n \n shell\n
vim --startuptime vim.log
","createdAt":"2020-04-18T04:48:56.000Z","lastEditedAt":"2020-07-01T13:01:11.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: cheatsheet","type":"blog","name":"cheatsheet","color":"6655d6","path":"/blog/cheatsheet/"},{"id":"tag: vim","type":"tag","name":"vim","color":"007f00","path":"/tag/vim/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/vscode-setup/index.json b/assets/data/post/vscode-setup/index.json index 76283ddd7..3285c95db 100644 --- a/assets/data/post/vscode-setup/index.json +++ b/assets/data/post/vscode-setup/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"162","title":"VSCode 与如何配置 VSCode","summary":"\n

这是一篇(尽量)新手向的文章,以 VSCode, Python, JavaScript (Vue) 为例,介绍编辑器的功能和配置。

\n

当然,没说必须选择 VSCode,大可选择 JetBrains 那一套。但是由于笔者平日折腾跨越的语言、领域较多(且前端居多),VSCode 是更好的选择,故只对 VSCode 有少量研究。

\n","body":"\n

鉴于部分同学不会配置 VSCode,也不知道一个编辑器应当怎么配置,导致代码不美观,包含大量低级错误等问题,故有必要说明。

\n

简单而言,一个代码编辑器应当具有的功能,即区别于自带记事本等的特性包括:

\n\n

为什么选择 VSCode

\n

可参考 https://code.visualstudio.com/Docs/editor/whyvscode

\n

基于插件 (Extension) 的模式

\n

与 JetBrains (IntelliJ IDEA, PyCharm, WebStorm, PhpStorm, CLion......) 等编辑器不同的是,VSCode 是为了支持各种语言而生的,只要安装对应的插件,但是支持程度得看相应插件的水平了。与 VSCode 相类似的还有 Sublime Text, Vim, Atom, Emacs 等。

\n

比如,Python 就有 Python 插件(对,就叫这名),Vue 就有 Vetur,Docker 就有 Docker...

\n

当然,一些语言 (C / C++, JavaScript, JSON, CSS...) 的插件已经随 VSCode 默认安装,就像手机出厂的时候就不是一块板砖。具体列表可以在侧边栏扩展 (Extensions) 标签页下,输入 @builtin 查看。如果自带的插件功能上没有欠缺的话,就不必另行安装插件。

\n

大家的选择

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
语言来源第一名第二名第三名第四名第五名
PythonPython Developers Survey 2019PyCharm
33%
VSCode
24%
Vim
9%
Sublime Text
6%
Jupyter NoteBook
5%
JavaScriptState of JS 2020VSCode
86%
Vim
20%
WebStorm
18.5%
SublimeText
12.3%
Notepad++
8.2%
GoGo Developer Survey 2019VSCode
41%
GoLand / IntelliJ
34%
Vim
14%
Emacs
4%
Sublime Text
2%
C++The State of Developer Ecosystem in 2020Visual Studio
26%
Clion
22%
VSCode
22%
Vim
7%
QtCreator
4%
JavaThe State of Developer Ecosystem in 2020IntelliJ IDEA
72%
Eclipse
13%
Android Studio
6%
VSCode
4%
NetBeans
3%
RustThe State of Developer Ecosystem in 2020VSCode
41%
CLion
22%
IntelliJ IDEA
16%
Vim
11%
Emacs
3%
\n

注:JetBrains 主导的调查是不是存在护犊子的情况就不得而知了

\n

其实在这些调查结果中也可以看到,JetBrains 的编辑器也不止是可以支持一种语言,但相比 VSCode 而言还是更加专一门。

\n

所以说在这些流行的语言中,尤其是新兴的语言中,VSCode 地位还是很靠前的。

\n

上手 VSCode

\n

菜单 Help -> Interactive Playground

\n

菜单 Help -> Tips and Tricks

\n

高颜值 / 好看的主题 (Awesome Theme)

\n

主题 (Theme) 一般是一种配色方案。在 VSCode 下,主题可以是传统意义上的配色主题,也可指图标主题,即 VSCode 中涉及文件的视图,文件(夹)名前面的图标。

\n

已经安装的主题可以通过快捷键 Ctrl + K Ctrl + T 查看和切换。同时,在扩展 (Extensions) 标签页下,输入 @category:\"themes\" 可以查找所有的主题。而 https://vscodethemes.com/ 也提供了不同主题的预览,更加便于发现好主题。

\n

此处安利 One Dark

\n

语法高亮 (Syntax Highlighting)

\n

谁也不想打开一段代码像记事本一样通篇一个颜色,不分主次、不分类别,看上去很吃力。

\n

要启用也很简单,只要安装了对应语言 / 框架的插件即可。有的语言甚至默认就支持。语法高亮的实现原理上也很简单,直接正则表达式匹配就能有不错的结果,当然也可以通过各种分析对变量、函数、类进行不同的高亮。

\n

当然也有一些专做语法高亮的插件,比如 Rainbow CSV, MagicPython 等等。

\n

需要注意的是语法高亮只负责标记不同部分,这些部分显示什么颜色还是要看主题。

\n

智能补全 (Auto Completion, IntelliSense)

\n

普通的变量名补全,可以让你少打几个字,让编辑器完成剩下的,不仅可以加快速度,让你从琐碎的劳动中解脱出来,还可以让你不再因为打字麻烦而不规范地命名变量和函数名称。

\n

就像使用拼音输入法,用久了之后谁也不想每个字都拼出来才能把词语打出吧。手机上的输入法还会根据前面的输入建议接下来的词语,或者调整下一个拼音中不同候选词的位置,(不管人工智能还是人工智障,总之)提供了很大的便利。

\n

同时,在变量和函数的补全中,也省去了许多(不是全部)查阅文档的工作,也避免了变量名和函数名打错的情况。比如在码了多年 Python 和多年 JavaScript 之后,分不清 .startsWith, .starts_with, .startswith, 自动补全可以省去因记不清每次都上网查文档的烦恼。又比如有的时候 team work,由于习惯一直以为队友打的是过去式 created_blahblah, 但是实际上是 create_blahblah, 这时使用智能补全可以很好的避免变量误写。在下面的代码检查中可以看到另一种避免方式,但是它对一些 dynamic 的语言, implicit 的写法,(比如 SQLAlchemy,)也几乎束手无策。

\n

与语法高亮类似,补全功能也基本都集成在插件当中。一般情况下在你输入的时候就会出现提示,如果不出现,可以通过 Ctrl + Space 唤出。实现上一般是通过后台开一个语言服务器 (Language Server), 通过协议 (Language Server Protocol, LSP) 与编辑器前端通讯,由服务器负责分析代码,给出建议。比如 Python 的语言服务器有 Jedi, Pylance, 而 JavaScript / TypeScript 有 TSServer, 虽然 Vue 只是一个框架,也有 VLS (Vue Language Server).

\n

注意对于 Python,如果要对虚拟环境里的库进行补全,则必须要配置 Python 的解释器路径。Ctrl + Shift + P 显示命令菜单,输入 Python: Select Interpreter 设置正确的解释器路径。

\n

代码格式化 (Formatting)

\n

在解释格式化之前,应当解释什么是代码风格 (Code Style). 代码风格就是代码看起来长什么样,拆开来说,何时使用空格,何时换行,如何缩进,左大括号是否另起一行,有多种写法时选择哪一种,等等。那么代码格式化,就是将每个人写出来的代码规范为统一的格式。这在团队合作中显得更加重要,谁也不想代码乱成一锅粥,这个函数是这个风格,那个函数又是另外一个风格。

\n

代码格式化的任务往往不是插件本身完成的,插件只是和现有的代码格式化工具 (Formatter) 集成。每个语言都有自己的格式化工具,比如 Python 的 Black, YAPF, 和 JavaScript 的 Prettier. 使用时只需快捷键 Shift + Alt + F 或右键菜单 -> Format Document( With...)

\n

代码检查 (Linting)

\n

代码检查一大功能就是在不运行你的代码的情况下,分析代码中存在的问题,或不符合最佳实践 (Best Practice) 的地方。与代码格式化类似,插件只是和现有的代码检查器 (Linter) 集成,比如根据 linter 的输出,在编辑器中有问题的地方显示红色黄色下划线。

\n

但是有的代码检查器又兼具检查代码风格甚至代码格式化的功能,比如 Python 的 Flake8,JavaScript 的 ESLint 等。但是相比于专门的 formatter 来说,linter 的格式要求与格式化能力往往要弱一些。

\n

编辑器的代码检查功能需要额外配置。比如 Python 需要 Ctrl + Shift + P 显示所有命令,输入 Python: Select Linter 选择 linter。对于 JavaScript 就要简单得多,在项目使用并且配置好 ESLint 的前提下,安装 ESLint 插件就可以进行检查。

\n

以 ESLint 为例, getter-return (enforce return statements in getters) 就是发现错误,eqeqeq (require the use of === and !==) 就是最佳实践,no-mixed-spaces-and-tabs(disallow mixed spaces and tabs for indentation) 就是对格式的要求。

\n

自动缩进 (Auto Indentation)

\n

代码没有缩进就没有层次。自动缩进顾名思义就是减少手动调整缩进的繁琐。自动缩进对于 Python 来说比较重要。当然 Python 是对缩进敏感的,基础的自动缩进已经由 Python 插件完成了。但是根据 PEP8 的风格规范,Python 断行、括号内换行有特殊的规范,而 Python 插件对比如括号内回车换行的自动缩进并没有很好的支持,这时需要插件 Python Indent 的帮忙。

\n

快捷键 (Keyboard Shortcut)

\n

当前设置的快捷键可以通过 Ctrl + K Ctrl + S 查看。同时,VSCode 还很贴心的准备了快捷键的 Cheatsheet,一页 PDF:Ctrl + K Ctrl + R ,或菜单 Help -> Keyboard Shortcur Reference 即可打开。

\n

同时,安装 keymap 类别的扩展可以在 VSCode 上轻松使用其他编辑器的快捷键。

\n

代码片段 (Code Snippets)

\n

写代码时有很多常用的、模板式的语句,比如 JavaScript 的:

\n
\n
\n \n js\n
try {\n  // ...\n} catch (error) {\n  // ...\n}
\n

代码片段的功能就是帮你打完这些模板式的语句(块)。仍然以 try...catch 为例。在 JavaScript 代码中,输入 try,则会自动提示 trycatch,如果不出现,可以通过 Ctrl + Space 唤出。这时在建议菜单中选中 trycatch (点击、回车或 Tab),就会补全。具体可自行尝试。

\n
\n

其他的以后再说吧(逃

","createdAt":"2021-02-14T02:30:06.000Z","lastEditedAt":"2021-02-14T02:45:24.000Z","image":"/blog/img/be39e231.png","imageLazy":"ADAAkDASIAAhEBAxEB/8QAFQABAQ$AAH/xAAUEAE$AAA/8QAFQEBAQ$AAH/xAAUEQE$AAA/9oADAMBAAIRAxEAPwCAKP/Z","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: vscode","type":"tag","name":"vscode","color":"0083d0","path":"/tag/vscode/"}],"reactions":[{"emoji":"👍","count":1,"users":["Yixuan-Wang"]}],"comments":[]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"162","title":"VSCode 与如何配置 VSCode","summary":"\n

这是一篇(尽量)新手向的文章,以 VSCode, Python, JavaScript (Vue) 为例,介绍编辑器的功能和配置。

\n

当然,没说必须选择 VSCode,大可选择 JetBrains 那一套。但是由于笔者平日折腾跨越的语言、领域较多(且前端居多),VSCode 是更好的选择,故只对 VSCode 有少量研究。

\n","body":"\n

鉴于部分同学不会配置 VSCode,也不知道一个编辑器应当怎么配置,导致代码不美观,包含大量低级错误等问题,故有必要说明。

\n

简单而言,一个代码编辑器应当具有的功能,即区别于自带记事本等的特性包括:

\n\n

为什么选择 VSCode

\n

可参考 https://code.visualstudio.com/Docs/editor/whyvscode

\n

基于插件 (Extension) 的模式

\n

与 JetBrains (IntelliJ IDEA, PyCharm, WebStorm, PhpStorm, CLion......) 等编辑器不同的是,VSCode 是为了支持各种语言而生的,只要安装对应的插件,但是支持程度得看相应插件的水平了。与 VSCode 相类似的还有 Sublime Text, Vim, Atom, Emacs 等。

\n

比如,Python 就有 Python 插件(对,就叫这名),Vue 就有 Vetur,Docker 就有 Docker...

\n

当然,一些语言 (C / C++, JavaScript, JSON, CSS...) 的插件已经随 VSCode 默认安装,就像手机出厂的时候就不是一块板砖。具体列表可以在侧边栏扩展 (Extensions) 标签页下,输入 @builtin 查看。如果自带的插件功能上没有欠缺的话,就不必另行安装插件。

\n

大家的选择

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
语言来源第一名第二名第三名第四名第五名
PythonPython Developers Survey 2019PyCharm
33%
VSCode
24%
Vim
9%
Sublime Text
6%
Jupyter NoteBook
5%
JavaScriptState of JS 2020VSCode
86%
Vim
20%
WebStorm
18.5%
SublimeText
12.3%
Notepad++
8.2%
GoGo Developer Survey 2019VSCode
41%
GoLand / IntelliJ
34%
Vim
14%
Emacs
4%
Sublime Text
2%
C++The State of Developer Ecosystem in 2020Visual Studio
26%
Clion
22%
VSCode
22%
Vim
7%
QtCreator
4%
JavaThe State of Developer Ecosystem in 2020IntelliJ IDEA
72%
Eclipse
13%
Android Studio
6%
VSCode
4%
NetBeans
3%
RustThe State of Developer Ecosystem in 2020VSCode
41%
CLion
22%
IntelliJ IDEA
16%
Vim
11%
Emacs
3%
\n

注:JetBrains 主导的调查是不是存在护犊子的情况就不得而知了

\n

其实在这些调查结果中也可以看到,JetBrains 的编辑器也不止是可以支持一种语言,但相比 VSCode 而言还是更加专一门。

\n

所以说在这些流行的语言中,尤其是新兴的语言中,VSCode 地位还是很靠前的。

\n

上手 VSCode

\n

菜单 Help -> Interactive Playground

\n

菜单 Help -> Tips and Tricks

\n

高颜值 / 好看的主题 (Awesome Theme)

\n

主题 (Theme) 一般是一种配色方案。在 VSCode 下,主题可以是传统意义上的配色主题,也可指图标主题,即 VSCode 中涉及文件的视图,文件(夹)名前面的图标。

\n

已经安装的主题可以通过快捷键 Ctrl + K Ctrl + T 查看和切换。同时,在扩展 (Extensions) 标签页下,输入 @category:\"themes\" 可以查找所有的主题。而 https://vscodethemes.com/ 也提供了不同主题的预览,更加便于发现好主题。

\n

此处安利 One Dark

\n

语法高亮 (Syntax Highlighting)

\n

谁也不想打开一段代码像记事本一样通篇一个颜色,不分主次、不分类别,看上去很吃力。

\n

要启用也很简单,只要安装了对应语言 / 框架的插件即可。有的语言甚至默认就支持。语法高亮的实现原理上也很简单,直接正则表达式匹配就能有不错的结果,当然也可以通过各种分析对变量、函数、类进行不同的高亮。

\n

当然也有一些专做语法高亮的插件,比如 Rainbow CSV, MagicPython 等等。

\n

需要注意的是语法高亮只负责标记不同部分,这些部分显示什么颜色还是要看主题。

\n

智能补全 (Auto Completion, IntelliSense)

\n

普通的变量名补全,可以让你少打几个字,让编辑器完成剩下的,不仅可以加快速度,让你从琐碎的劳动中解脱出来,还可以让你不再因为打字麻烦而不规范地命名变量和函数名称。

\n

就像使用拼音输入法,用久了之后谁也不想每个字都拼出来才能把词语打出吧。手机上的输入法还会根据前面的输入建议接下来的词语,或者调整下一个拼音中不同候选词的位置,(不管人工智能还是人工智障,总之)提供了很大的便利。

\n

同时,在变量和函数的补全中,也省去了许多(不是全部)查阅文档的工作,也避免了变量名和函数名打错的情况。比如在码了多年 Python 和多年 JavaScript 之后,分不清 .startsWith, .starts_with, .startswith, 自动补全可以省去因记不清每次都上网查文档的烦恼。又比如有的时候 team work,由于习惯一直以为队友打的是过去式 created_blahblah, 但是实际上是 create_blahblah, 这时使用智能补全可以很好的避免变量误写。在下面的代码检查中可以看到另一种避免方式,但是它对一些 dynamic 的语言, implicit 的写法,(比如 SQLAlchemy,)也几乎束手无策。

\n

与语法高亮类似,补全功能也基本都集成在插件当中。一般情况下在你输入的时候就会出现提示,如果不出现,可以通过 Ctrl + Space 唤出。实现上一般是通过后台开一个语言服务器 (Language Server), 通过协议 (Language Server Protocol, LSP) 与编辑器前端通讯,由服务器负责分析代码,给出建议。比如 Python 的语言服务器有 Jedi, Pylance, 而 JavaScript / TypeScript 有 TSServer, 虽然 Vue 只是一个框架,也有 VLS (Vue Language Server).

\n

注意对于 Python,如果要对虚拟环境里的库进行补全,则必须要配置 Python 的解释器路径。Ctrl + Shift + P 显示命令菜单,输入 Python: Select Interpreter 设置正确的解释器路径。

\n

代码格式化 (Formatting)

\n

在解释格式化之前,应当解释什么是代码风格 (Code Style). 代码风格就是代码看起来长什么样,拆开来说,何时使用空格,何时换行,如何缩进,左大括号是否另起一行,有多种写法时选择哪一种,等等。那么代码格式化,就是将每个人写出来的代码规范为统一的格式。这在团队合作中显得更加重要,谁也不想代码乱成一锅粥,这个函数是这个风格,那个函数又是另外一个风格。

\n

代码格式化的任务往往不是插件本身完成的,插件只是和现有的代码格式化工具 (Formatter) 集成。每个语言都有自己的格式化工具,比如 Python 的 Black, YAPF, 和 JavaScript 的 Prettier. 使用时只需快捷键 Shift + Alt + F 或右键菜单 -> Format Document( With...)

\n

代码检查 (Linting)

\n

代码检查一大功能就是在不运行你的代码的情况下,分析代码中存在的问题,或不符合最佳实践 (Best Practice) 的地方。与代码格式化类似,插件只是和现有的代码检查器 (Linter) 集成,比如根据 linter 的输出,在编辑器中有问题的地方显示红色黄色下划线。

\n

但是有的代码检查器又兼具检查代码风格甚至代码格式化的功能,比如 Python 的 Flake8,JavaScript 的 ESLint 等。但是相比于专门的 formatter 来说,linter 的格式要求与格式化能力往往要弱一些。

\n

编辑器的代码检查功能需要额外配置。比如 Python 需要 Ctrl + Shift + P 显示所有命令,输入 Python: Select Linter 选择 linter。对于 JavaScript 就要简单得多,在项目使用并且配置好 ESLint 的前提下,安装 ESLint 插件就可以进行检查。

\n

以 ESLint 为例, getter-return (enforce return statements in getters) 就是发现错误,eqeqeq (require the use of === and !==) 就是最佳实践,no-mixed-spaces-and-tabs(disallow mixed spaces and tabs for indentation) 就是对格式的要求。

\n

自动缩进 (Auto Indentation)

\n

代码没有缩进就没有层次。自动缩进顾名思义就是减少手动调整缩进的繁琐。自动缩进对于 Python 来说比较重要。当然 Python 是对缩进敏感的,基础的自动缩进已经由 Python 插件完成了。但是根据 PEP8 的风格规范,Python 断行、括号内换行有特殊的规范,而 Python 插件对比如括号内回车换行的自动缩进并没有很好的支持,这时需要插件 Python Indent 的帮忙。

\n

快捷键 (Keyboard Shortcut)

\n

当前设置的快捷键可以通过 Ctrl + K Ctrl + S 查看。同时,VSCode 还很贴心的准备了快捷键的 Cheatsheet,一页 PDF:Ctrl + K Ctrl + R ,或菜单 Help -> Keyboard Shortcur Reference 即可打开。

\n

同时,安装 keymap 类别的扩展可以在 VSCode 上轻松使用其他编辑器的快捷键。

\n

代码片段 (Code Snippets)

\n

写代码时有很多常用的、模板式的语句,比如 JavaScript 的:

\n
\n
\n \n js\n
try {\n  // ...\n} catch (error) {\n  // ...\n}
\n

代码片段的功能就是帮你打完这些模板式的语句(块)。仍然以 try...catch 为例。在 JavaScript 代码中,输入 try,则会自动提示 trycatch,如果不出现,可以通过 Ctrl + Space 唤出。这时在建议菜单中选中 trycatch (点击、回车或 Tab),就会补全。具体可自行尝试。

\n
\n

其他的以后再说吧(逃

","createdAt":"2021-02-14T02:30:06.000Z","lastEditedAt":"2021-02-14T02:45:24.000Z","image":"/blog/img/be39e231.png","imageLazy":"ADAAkDASIAAhEBAxEB/8QAFQABAQ$AAH/xAAUEAE$AAA/8QAFQEBAQ$AAH/xAAUEQE$AAA/9oADAMBAAIRAxEAPwCAKP/Z","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: vscode","type":"tag","name":"vscode","color":"0083d0","path":"/tag/vscode/"}],"reactions":[{"emoji":"👍","count":1,"users":["Yixuan-Wang"]}],"comments":[]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/vue-hammer/index.json b/assets/data/post/vue-hammer/index.json index 7dabb841e..cb4f896ce 100644 --- a/assets/data/post/vue-hammer/index.json +++ b/assets/data/post/vue-hammer/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"80","title":"Vue 加 Hammer 处理手势","summary":"\n

接近原生 App 体验,手势是少不了的。但是无论是 Vue 还是 Vuetify 对手势的支持并不好,于是需要 Hammer.js 支持。

\n","body":"\n

Hammer.js

\n

hammerjs 在拙劣的搜索之后,才发现了这个库。虽然有 一个 Vue 的封装库 vue-touch,但是它年久失修,而且其组件名称v-touch与 Vuetify 的某组件重名。。。

\n

使用

\n

http://hammerjs.github.io/

\n

文档非常简单,提几点需要注意的地方

\n

Vue 引用 DOM 元素

\n

配合$ref食用更佳

\n
\n
\n \n html\n
<template>\n  <div ref=\"container\">\n  </div>\n</template>\n\n<script>\nexport default {\n  mounted() {\n    let ele = this.$refs.container\n  }\n}\n</script>
\n

做主人

\n

通过new Hammer(myElement, myOptions)创建的事件监听默认开启了tap, doubletap, press, horizontal panswipe,并且加上了禁用了的pinchrotate

\n

所以使用new Hammer.Manager(myElement, myOptions)更加舒服。

\n

理解get

\n

为什么用Hammer创建的对象可以随便get然后设置enable: false,而Hammer.Manager出来是null呢?

\n

如上所述,Hammer创建的默认加上了全套事件监听,而Hammer.Manager出来是白板一块,自然不能get没有加入的事件了。

\n

大坑一个

\n

文档里的确说了pinchrotate会阻塞元素的事件响应,但没想到有点愚蠢和暴力:启用了pinch之后,scroll就不行了?!一个手指的滑动和两个手指的缩放有关系?

\n

还好找到了解决方案:https://stackoverflow.com/a/27550784/8810271

\n

由于hammerjs2.x 版本不允许绑定touchstart and touchend,可以直接绑 DOM 元素上啊!

\n

于是解决如下:

\n
\n
\n \n html\n
<template>\n  <div ref=\"container\" class=\"container\" @touchstart=\"touchStart\" @touchend=\"touchEnd\">\n  </div>\n</template>\n\n<script>\nimport Hammer from 'hammerjs'\nlet originZoom\nexport default {\n  mounted() {\n    hammer = new Hammer.Manager(this.$refs.container)\n    hammer.add(new Hammer.Swipe({ direction: Hammer.DIRECTION_HORIZONTAL }))\n    hammer.add(new Hammer.Pinch({ enable: false }))\n    hammer.on('pinchstart', this.pinchStart)\n    hammer.on('pinchmove', this.pinchMove)\n  },\n  methods: {\n    pinchStart(e) {\n      originZoom = this.zoom\n    },\n    pinchMove(e) {\n      this.zoom = originZoom * e.scale\n    },\n    touchStart(e) {\n      if (e.touches.length > 1) {\n        hammer.get('pinch').set({ enable: true })\n      }\n    },\n    touchEnd(e) {\n      hammer.get('pinch').set({ enable: false })\n    }\n  }\n}\n</script>
","createdAt":"2020-01-27T08:33:10.000Z","lastEditedAt":"2020-07-03T02:20:23.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: vue","type":"tag","name":"vue","color":"41b883","path":"/tag/vue/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"80","title":"Vue 加 Hammer 处理手势","summary":"\n

接近原生 App 体验,手势是少不了的。但是无论是 Vue 还是 Vuetify 对手势的支持并不好,于是需要 Hammer.js 支持。

\n","body":"\n

Hammer.js

\n

hammerjs 在拙劣的搜索之后,才发现了这个库。虽然有 一个 Vue 的封装库 vue-touch,但是它年久失修,而且其组件名称v-touch与 Vuetify 的某组件重名。。。

\n

使用

\n

http://hammerjs.github.io/

\n

文档非常简单,提几点需要注意的地方

\n

Vue 引用 DOM 元素

\n

配合$ref食用更佳

\n
\n
\n \n html\n
<template>\n  <div ref=\"container\">\n  </div>\n</template>\n\n<script>\nexport default {\n  mounted() {\n    let ele = this.$refs.container\n  }\n}\n</script>
\n

做主人

\n

通过new Hammer(myElement, myOptions)创建的事件监听默认开启了tap, doubletap, press, horizontal panswipe,并且加上了禁用了的pinchrotate

\n

所以使用new Hammer.Manager(myElement, myOptions)更加舒服。

\n

理解get

\n

为什么用Hammer创建的对象可以随便get然后设置enable: false,而Hammer.Manager出来是null呢?

\n

如上所述,Hammer创建的默认加上了全套事件监听,而Hammer.Manager出来是白板一块,自然不能get没有加入的事件了。

\n

大坑一个

\n

文档里的确说了pinchrotate会阻塞元素的事件响应,但没想到有点愚蠢和暴力:启用了pinch之后,scroll就不行了?!一个手指的滑动和两个手指的缩放有关系?

\n

还好找到了解决方案:https://stackoverflow.com/a/27550784/8810271

\n

由于hammerjs2.x 版本不允许绑定touchstart and touchend,可以直接绑 DOM 元素上啊!

\n

于是解决如下:

\n
\n
\n \n html\n
<template>\n  <div ref=\"container\" class=\"container\" @touchstart=\"touchStart\" @touchend=\"touchEnd\">\n  </div>\n</template>\n\n<script>\nimport Hammer from 'hammerjs'\nlet originZoom\nexport default {\n  mounted() {\n    hammer = new Hammer.Manager(this.$refs.container)\n    hammer.add(new Hammer.Swipe({ direction: Hammer.DIRECTION_HORIZONTAL }))\n    hammer.add(new Hammer.Pinch({ enable: false }))\n    hammer.on('pinchstart', this.pinchStart)\n    hammer.on('pinchmove', this.pinchMove)\n  },\n  methods: {\n    pinchStart(e) {\n      originZoom = this.zoom\n    },\n    pinchMove(e) {\n      this.zoom = originZoom * e.scale\n    },\n    touchStart(e) {\n      if (e.touches.length > 1) {\n        hammer.get('pinch').set({ enable: true })\n      }\n    },\n    touchEnd(e) {\n      hammer.get('pinch').set({ enable: false })\n    }\n  }\n}\n</script>
","createdAt":"2020-01-27T08:33:10.000Z","lastEditedAt":"2020-07-03T02:20:23.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: vue","type":"tag","name":"vue","color":"41b883","path":"/tag/vue/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/vue-iblog/index.json b/assets/data/post/vue-iblog/index.json index 45668a8da..512d801c7 100644 --- a/assets/data/post/vue-iblog/index.json +++ b/assets/data/post/vue-iblog/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"96","title":"Writing Blog with GitHub Issue and Vue Apollo","summary":"\n

Some tricks when using vue-apollo to communicate with GitHub API v4

\n","body":"\n

Reference:

\n\n

Preface

\n

Why use GitHub Issues?

\n

It sucks to provide comment and reaction feature. GitHub Issues is the only choice without third-party services. However, OAuth just on the client side is not so secure and you need OAuth the blog before comment! That will drive people away.

\n

Besides, when witing issues, you can just drag your images to upload. That's surely more comvenient than committing images to git repo.

\n

And with GitHub issue templates, it is very easy to draft a new post on a mobile device.

\n

You even don't need a RSS, just watch the repo. (If you don't care about blog source code, you can subscribe to issue's RSS, see comment.)

\n

Why not just GitHub Issue?

\n

One benefit is that I can use my own theme and make the blog more interactive.

\n

Besides, though GitHub does provide \"sort:updated-desc\" feature, searching, navigating, issue numbering and filtering is not so friendly to a blog.

\n

Aim of the new blog

\n

I will provide 2 version of the blog, the web version and GitHub Issues version.

\n

About Migrating

\n

Migrating will cause problems. Neither the created time nor the modified time is the actual time. An extra field is needed.

\n

Get Hands Dirty

\n

Generate a Personal Access Token

\n

Here (https://github.com/settings/tokens/new) to generate.

\n

You can actually uncheck all scope, and this token will only hae access to public resources.

\n

More info for the scope, refer to https://developer.github.com/apps/building-oauth-apps/understanding-scopes-for-oauth-apps/

\n

Let's play with vue-apollo !

\n
\n

⚠️ Heads up!

\n

Do not use vue-cli-plugin-apollo, it sucks: Akryum/vue-cli-plugin-apollo#28

\n

I want to manage production dependencies myself, not vue-cli-plugin-apollo!

\n
\n

As GitHub API v4 needs Authorization header, the basic configuration is not enough. Use the full configuration install, and follow this apollo-client issue to setup.

\n
\n

If you import ApolloClient from 'apollo-boost', ApolloClient will not honor headers in fetchOption

\n
\n

Basically, src/plugins/vue-apollo.js will look like this:

\n
\n
\n \n js\n
import Vue from 'vue'\nimport VueApollo from 'vue-apollo'\nimport { InMemoryCache } from 'apollo-cache-inmemory'\nimport { ApolloClient } from 'apollo-client'\nimport { HttpLink } from 'apollo-link-http'\n\nVue.use(VueApollo)\n\nconst apolloClient = new ApolloClient({\n  cache: new InMemoryCache(),\n  link: new HttpLink({\n    uri: 'https://api.github.com/graphql',\n    headers: {\n      Authorization: 'bearer <your token here>'\n    }\n  })\n})\n\nexport default new VueApollo({\n  defaultClient: apolloClient\n})
\n

And import it into src/main.js:

\n
\n
\n \n js\n
import Vue from 'vue'\nimport App from './App.vue'\nimport apolloProvider from './plugins/vue-apollo'\n\nnew Vue({\n  apolloProvider,\n  render: h => h(App)\n}).$mount('#app')
","createdAt":"2020-04-24T00:00:00.000Z","lastEditedAt":"2020-09-04T07:47:20.000Z","image":"/blog/img/e4670a56.png","imageLazy":"ADAAkDASIAAhEBAxEB/8QAFgABAQE$AEE/8QAFRABAQ$ABH/xAAUAQE$AAA/8QAFBEB$AAAP/aAAwDAQACEQMRAD8A2RQB/9k=","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: vue","type":"tag","name":"vue","color":"41b883","path":"/tag/vue/"}],"reactions":[],"comments":[{"resourcePath":"/AllanChain/blog/issues/96#issuecomment-660487900","author":{"id":"AllanChain","avatarUrl":"https://avatars.githubusercontent.com/u/36528777?s=64&u=e5821b32c02c77ed038c8c82cfe55e07075911b7&v=4"},"bodyHTML":"

Hey! I am not using vue-apollo now. Gridsome and simple graphql requests meet my needs.

\n

To be honest, gridsome is very friendly to blogger, but it's young and you must do things on your own.

","createdAt":"2020-07-18T14:05:27.000Z","reactions":[]},{"resourcePath":"/AllanChain/blog/issues/96#issuecomment-676979554","author":{"id":"Yixuan-Wang","avatarUrl":"https://avatars.githubusercontent.com/u/21151119?s=64&u=ca1f5a8752cd0dcd6ac2046bf3684ef7eb223c72&v=4"},"bodyHTML":"

You can use RSSHub route /github/issue/allanchain/blog/open/%40post to subscribe the RSS feed of all posts, or you can replace /%40post with /blog%3A%20someType /tag%3A%20someTag to subscribe a certain type of posts(programming, cheatsheet, moments, etc.) or a tag.

","createdAt":"2020-08-20T03:55:08.000Z","reactions":[]},{"resourcePath":"/AllanChain/blog/issues/96#issuecomment-677031516","author":{"id":"AllanChain","avatarUrl":"https://avatars.githubusercontent.com/u/36528777?s=64&u=e5821b32c02c77ed038c8c82cfe55e07075911b7&v=4"},"bodyHTML":"

@Yixuan-Wang Great idea! Added to about#web-page-service

","createdAt":"2020-08-20T04:30:44.000Z","reactions":[]}]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"96","title":"Writing Blog with GitHub Issue and Vue Apollo","summary":"\n

Some tricks when using vue-apollo to communicate with GitHub API v4

\n","body":"\n

Reference:

\n\n

Preface

\n

Why use GitHub Issues?

\n

It sucks to provide comment and reaction feature. GitHub Issues is the only choice without third-party services. However, OAuth just on the client side is not so secure and you need OAuth the blog before comment! That will drive people away.

\n

Besides, when witing issues, you can just drag your images to upload. That's surely more comvenient than committing images to git repo.

\n

And with GitHub issue templates, it is very easy to draft a new post on a mobile device.

\n

You even don't need a RSS, just watch the repo. (If you don't care about blog source code, you can subscribe to issue's RSS, see comment.)

\n

Why not just GitHub Issue?

\n

One benefit is that I can use my own theme and make the blog more interactive.

\n

Besides, though GitHub does provide \"sort:updated-desc\" feature, searching, navigating, issue numbering and filtering is not so friendly to a blog.

\n

Aim of the new blog

\n

I will provide 2 version of the blog, the web version and GitHub Issues version.

\n

About Migrating

\n

Migrating will cause problems. Neither the created time nor the modified time is the actual time. An extra field is needed.

\n

Get Hands Dirty

\n

Generate a Personal Access Token

\n

Here (https://github.com/settings/tokens/new) to generate.

\n

You can actually uncheck all scope, and this token will only hae access to public resources.

\n

More info for the scope, refer to https://developer.github.com/apps/building-oauth-apps/understanding-scopes-for-oauth-apps/

\n

Let's play with vue-apollo !

\n
\n

⚠️ Heads up!

\n

Do not use vue-cli-plugin-apollo, it sucks: Akryum/vue-cli-plugin-apollo#28

\n

I want to manage production dependencies myself, not vue-cli-plugin-apollo!

\n
\n

As GitHub API v4 needs Authorization header, the basic configuration is not enough. Use the full configuration install, and follow this apollo-client issue to setup.

\n
\n

If you import ApolloClient from 'apollo-boost', ApolloClient will not honor headers in fetchOption

\n
\n

Basically, src/plugins/vue-apollo.js will look like this:

\n
\n
\n \n js\n
import Vue from 'vue'\nimport VueApollo from 'vue-apollo'\nimport { InMemoryCache } from 'apollo-cache-inmemory'\nimport { ApolloClient } from 'apollo-client'\nimport { HttpLink } from 'apollo-link-http'\n\nVue.use(VueApollo)\n\nconst apolloClient = new ApolloClient({\n  cache: new InMemoryCache(),\n  link: new HttpLink({\n    uri: 'https://api.github.com/graphql',\n    headers: {\n      Authorization: 'bearer <your token here>'\n    }\n  })\n})\n\nexport default new VueApollo({\n  defaultClient: apolloClient\n})
\n

And import it into src/main.js:

\n
\n
\n \n js\n
import Vue from 'vue'\nimport App from './App.vue'\nimport apolloProvider from './plugins/vue-apollo'\n\nnew Vue({\n  apolloProvider,\n  render: h => h(App)\n}).$mount('#app')
","createdAt":"2020-04-24T00:00:00.000Z","lastEditedAt":"2020-09-04T07:47:20.000Z","image":"/blog/img/e4670a56.png","imageLazy":"ADAAkDASIAAhEBAxEB/8QAFgABAQE$AEE/8QAFRABAQ$ABH/xAAUAQE$AAA/8QAFBEB$AAAP/aAAwDAQACEQMRAD8A2RQB/9k=","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: vue","type":"tag","name":"vue","color":"41b883","path":"/tag/vue/"}],"reactions":[],"comments":[{"resourcePath":"/AllanChain/blog/issues/96#issuecomment-660487900","author":{"id":"AllanChain","avatarUrl":"https://avatars.githubusercontent.com/u/36528777?s=64&u=e5821b32c02c77ed038c8c82cfe55e07075911b7&v=4"},"bodyHTML":"

Hey! I am not using vue-apollo now. Gridsome and simple graphql requests meet my needs.

\n

To be honest, gridsome is very friendly to blogger, but it's young and you must do things on your own.

","createdAt":"2020-07-18T14:05:27.000Z","reactions":[]},{"resourcePath":"/AllanChain/blog/issues/96#issuecomment-676979554","author":{"id":"Yixuan-Wang","avatarUrl":"https://avatars.githubusercontent.com/u/21151119?s=64&u=ca1f5a8752cd0dcd6ac2046bf3684ef7eb223c72&v=4"},"bodyHTML":"

You can use RSSHub route /github/issue/allanchain/blog/open/%40post to subscribe the RSS feed of all posts, or you can replace /%40post with /blog%3A%20someType /tag%3A%20someTag to subscribe a certain type of posts(programming, cheatsheet, moments, etc.) or a tag.

","createdAt":"2020-08-20T03:55:08.000Z","reactions":[]},{"resourcePath":"/AllanChain/blog/issues/96#issuecomment-677031516","author":{"id":"AllanChain","avatarUrl":"https://avatars.githubusercontent.com/u/36528777?s=64&u=e5821b32c02c77ed038c8c82cfe55e07075911b7&v=4"},"bodyHTML":"

@Yixuan-Wang Great idea! Added to about#web-page-service

","createdAt":"2020-08-20T04:30:44.000Z","reactions":[]}]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/vue-pwa-1/index.json b/assets/data/post/vue-pwa-1/index.json index 25c62b330..93a408d70 100644 --- a/assets/data/post/vue-pwa-1/index.json +++ b/assets/data/post/vue-pwa-1/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"68","title":"用 Vue 做 PWA(一):开始","summary":"\n

Vue 党快速上手 PWA

\n","body":"\n

什么是 PWA

\n

其全称为 Progressive Web Apps,可赋予网页原生 App 的各种优点。本人水平有限,不再赘述。对我而言最重要的是本地存储 + 离线可看,还有消息推送。

\n

为什么 PWA

\n

当然是不想学 Android 和 iOS 啦!学会前端一下搞定桌面、Android、iOS 三端,岂不美哉!

\n

如何开始

\n

这里就不说怎么安装 vue-cli 了。这里以 vue-cli-4 为例。

\n

如果是已有的项目,如下,记得先 commit,Vue 会改动代码

\n
\n
\n \n shell\n
# 创建 名为 test 的项目,一般默认即可\nvue create test\n\n# 添加 PWA 功能\nvue add pwa
\n

直接新建 PWA 项目(该方法已过时):

\n
\n
\n \n shell\n
npm install -g @vue/cli-init # 使用 init 需要安装\nvue init pwa test
\n

看看加了什么

\n

运行vue add pwa后,会输出修改的文件。如果直接新建项目并且 Git 配置正确,Vue 会自动初始化提交。这时再添加 PWA,可使用git status查看。输出略有不同

\n

输出:

\n
The following files have been updated / added:\n\n public/img/icons/android-chrome-192x192.png\n public/img/icons/android-chrome-512x512.png\n public/img/icons/apple-touch-icon-120x120.png\n ...\n public/img/icons/favicon-32x32.png\n public/img/icons/msapplication-icon-144x144.png\n public/img/icons/mstile-150x150.png\n public/img/icons/safari-pinned-tab.svg\n public/robots.txt\n src/registerServiceWorker.js\n package-lock.json\n package.json\n src/main.js\n
\n

git status输出:

\n
On branch master\nChanges not staged for commit:\n  (use \"git add <file>...\" to update what will be committed)\n  (use \"git checkout -- <file>...\" to discard changes in working directory)\n\n        modified:   package-lock.json\n        modified:   package.json\n        modified:   src/main.js\n\nUntracked files:\n  (use \"git add <file>...\" to include in what will be committed)\n\n        public/img/\n        public/robots.txt\n        src/registerServiceWorker.js\n\nno changes added to commit (use \"git add\" and/or \"git commit -a\")\n
\n\n

配置 PWA

\n
\n

😜 TL;DR

\n

一切默认也可,你可以不用做任何配置。

\n
\n

在项目根目录下新建vue.config.js进行配置

\n

配置以文档为准

\n\n

最简单的情况下,可考虑如下配置:

\n
\n
\n \n js\n
module.exports = {\n  pwa: {\n    name: 'test',\n    themeColor: '#4c89fe',\n    msTileColor: '#4c89fe',\n    manifestOptions: {\n      start_url: '.',\n      background_color: '#4c89fe'\n    },\n    workboxPluginMode: 'GenerateSW',\n    workboxOptions: {\n    }\n  }\n}
\n

名字和颜色涉及添加至桌面的应用名,及桌面进入的启动页面的长相。

\n

workboxPluginMode: 'GenerateSW'就是自动生成 Service Worker,也是默认操作。具体要求就如workboxOptions。这里根据默认,一股脑 precache 了所有东西,可以达到离线可看的目的。

\n

如果部署环境不在网站根目录,还需加上:

\n
\n
\n \n js\n
publicPath: process.env.PUB_PATH || '/'
\n
\n

部署时要:

\n
\n
\n \n batchfile\n
set PUB_PATH=\"/test/\"\nnpm run build
\n
\n
\n \n shell\n
PUB_PATH='/test/' npm run build
\n
\n

比下面的更优(马上看到为什么):

\n
\n
\n \n js\n
publicPath: process.env.NODE_ENV === 'production' ? '/URLPrefix/' : '/'
\n

本地测试

\n
\n

⚠️ 避免踩坑

\n

使用 devserver 是看不到想要的结果的,Service Worker 也显示不能正常工作。因为 dev 环境下使用的是“傻子” Service Worker,啥都不存(不然开发是时候不得心态崩)。

\n

但是npm run build再开本地服务器,或使用 GitHub Pages 部署后,Android Chrome 成功跳出安装至桌面的提示。

\n
\n
\n
\n \n shell\n
npm run build
\n
\n

提示

\n

如果如上第二种配置了publicPath,此时要设置NODE_ENV 不为production,但是非production不能成功注册 Service Worker。所以 publicPath依赖的其实不是NODE_ENV

\n
\n

可以使用browser-sync作为本地服务器。

\n
\n
\n \n shell\n
npm install -g browser-sync\nbrowser-sync dist
\n

如果成功配置,Chrome 导航栏会出现$\\oplus$字样,console 会输出成功缓存。注意仅在 localhost 会有哦

\n
Service worker has been registered.\nregisterServiceWorker.js:20 New content is downloading.\nlogger.mjs:44 workbox Precaching 7 files.\nlogger.mjs:44 View newly precached URLs.\nregisterServiceWorker.js:17 Content has been cached for offline use.\nregisterServiceWorker.js:8 App is being served from cache by a service worker.\nFor more details, visit https://goo.gl/AFskqB\n
\n

再次打开,会输出成功加载

\n
workbox Precaching is responding to: /css/app.b706d8fd.css\nlogger.mjs:44 workbox Precaching is responding to: /css/chunk-vendors.bb30aab5.css\nlogger.mjs:44 workbox Precaching is responding to: /js/app.23a81c05.js\nlogger.mjs:44 workbox Precaching is responding to: /js/chunk-vendors.57affefc.js\napp.23a81c05.js?__WB_REVISION__=1542843adc3336277dad:1 App is being served from cache by a service worker.\nFor more details, visit https://goo.gl/AFskqB\nlogger.mjs:44 workbox Precaching is responding to: /manifest.json\napp.23a81c05.js?__WB_REVISION__=1542843adc3336277dad:1 Service worker has been registered.\n
\n

然后呢

\n

恭喜你有了 PWA 的基本配置,像正常一样开发 Vue 应用,初级阶段也没什么问题。

","createdAt":"2020-01-30T07:59:20.000Z","lastEditedAt":"2020-09-04T23:59:48.000Z","image":"/blog/img/bda492f1.png","imageLazy":"ADAAkDASIAAhEBAxEB/8QAFgABAQE$AEE/8QAGRAAAgMB$ECEVGR/8QAFAEB$AAAv/EABYRAQEB$ABAv/aAAwDAQACEQMRAD8A0SVQdaToAaWX/9k=","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: vue","type":"tag","name":"vue","color":"41b883","path":"/tag/vue/"},{"id":"tag: pwa","type":"tag","name":"pwa","color":"5a0fc8","path":"/tag/pwa/"},{"id":"series: vue-pwa","type":"series","name":"vue-pwa","color":"41b883","path":"/series/vue-pwa/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"68","title":"用 Vue 做 PWA(一):开始","summary":"\n

Vue 党快速上手 PWA

\n","body":"\n

什么是 PWA

\n

其全称为 Progressive Web Apps,可赋予网页原生 App 的各种优点。本人水平有限,不再赘述。对我而言最重要的是本地存储 + 离线可看,还有消息推送。

\n

为什么 PWA

\n

当然是不想学 Android 和 iOS 啦!学会前端一下搞定桌面、Android、iOS 三端,岂不美哉!

\n

如何开始

\n

这里就不说怎么安装 vue-cli 了。这里以 vue-cli-4 为例。

\n

如果是已有的项目,如下,记得先 commit,Vue 会改动代码

\n
\n
\n \n shell\n
# 创建 名为 test 的项目,一般默认即可\nvue create test\n\n# 添加 PWA 功能\nvue add pwa
\n

直接新建 PWA 项目(该方法已过时):

\n
\n
\n \n shell\n
npm install -g @vue/cli-init # 使用 init 需要安装\nvue init pwa test
\n

看看加了什么

\n

运行vue add pwa后,会输出修改的文件。如果直接新建项目并且 Git 配置正确,Vue 会自动初始化提交。这时再添加 PWA,可使用git status查看。输出略有不同

\n

输出:

\n
The following files have been updated / added:\n\n public/img/icons/android-chrome-192x192.png\n public/img/icons/android-chrome-512x512.png\n public/img/icons/apple-touch-icon-120x120.png\n ...\n public/img/icons/favicon-32x32.png\n public/img/icons/msapplication-icon-144x144.png\n public/img/icons/mstile-150x150.png\n public/img/icons/safari-pinned-tab.svg\n public/robots.txt\n src/registerServiceWorker.js\n package-lock.json\n package.json\n src/main.js\n
\n

git status输出:

\n
On branch master\nChanges not staged for commit:\n  (use \"git add <file>...\" to update what will be committed)\n  (use \"git checkout -- <file>...\" to discard changes in working directory)\n\n        modified:   package-lock.json\n        modified:   package.json\n        modified:   src/main.js\n\nUntracked files:\n  (use \"git add <file>...\" to include in what will be committed)\n\n        public/img/\n        public/robots.txt\n        src/registerServiceWorker.js\n\nno changes added to commit (use \"git add\" and/or \"git commit -a\")\n
\n\n

配置 PWA

\n
\n

😜 TL;DR

\n

一切默认也可,你可以不用做任何配置。

\n
\n

在项目根目录下新建vue.config.js进行配置

\n

配置以文档为准

\n\n

最简单的情况下,可考虑如下配置:

\n
\n
\n \n js\n
module.exports = {\n  pwa: {\n    name: 'test',\n    themeColor: '#4c89fe',\n    msTileColor: '#4c89fe',\n    manifestOptions: {\n      start_url: '.',\n      background_color: '#4c89fe'\n    },\n    workboxPluginMode: 'GenerateSW',\n    workboxOptions: {\n    }\n  }\n}
\n

名字和颜色涉及添加至桌面的应用名,及桌面进入的启动页面的长相。

\n

workboxPluginMode: 'GenerateSW'就是自动生成 Service Worker,也是默认操作。具体要求就如workboxOptions。这里根据默认,一股脑 precache 了所有东西,可以达到离线可看的目的。

\n

如果部署环境不在网站根目录,还需加上:

\n
\n
\n \n js\n
publicPath: process.env.PUB_PATH || '/'
\n
\n

部署时要:

\n
\n
\n \n batchfile\n
set PUB_PATH=\"/test/\"\nnpm run build
\n
\n
\n \n shell\n
PUB_PATH='/test/' npm run build
\n
\n

比下面的更优(马上看到为什么):

\n
\n
\n \n js\n
publicPath: process.env.NODE_ENV === 'production' ? '/URLPrefix/' : '/'
\n

本地测试

\n
\n

⚠️ 避免踩坑

\n

使用 devserver 是看不到想要的结果的,Service Worker 也显示不能正常工作。因为 dev 环境下使用的是“傻子” Service Worker,啥都不存(不然开发是时候不得心态崩)。

\n

但是npm run build再开本地服务器,或使用 GitHub Pages 部署后,Android Chrome 成功跳出安装至桌面的提示。

\n
\n
\n
\n \n shell\n
npm run build
\n
\n

提示

\n

如果如上第二种配置了publicPath,此时要设置NODE_ENV 不为production,但是非production不能成功注册 Service Worker。所以 publicPath依赖的其实不是NODE_ENV

\n
\n

可以使用browser-sync作为本地服务器。

\n
\n
\n \n shell\n
npm install -g browser-sync\nbrowser-sync dist
\n

如果成功配置,Chrome 导航栏会出现$\\oplus$字样,console 会输出成功缓存。注意仅在 localhost 会有哦

\n
Service worker has been registered.\nregisterServiceWorker.js:20 New content is downloading.\nlogger.mjs:44 workbox Precaching 7 files.\nlogger.mjs:44 View newly precached URLs.\nregisterServiceWorker.js:17 Content has been cached for offline use.\nregisterServiceWorker.js:8 App is being served from cache by a service worker.\nFor more details, visit https://goo.gl/AFskqB\n
\n

再次打开,会输出成功加载

\n
workbox Precaching is responding to: /css/app.b706d8fd.css\nlogger.mjs:44 workbox Precaching is responding to: /css/chunk-vendors.bb30aab5.css\nlogger.mjs:44 workbox Precaching is responding to: /js/app.23a81c05.js\nlogger.mjs:44 workbox Precaching is responding to: /js/chunk-vendors.57affefc.js\napp.23a81c05.js?__WB_REVISION__=1542843adc3336277dad:1 App is being served from cache by a service worker.\nFor more details, visit https://goo.gl/AFskqB\nlogger.mjs:44 workbox Precaching is responding to: /manifest.json\napp.23a81c05.js?__WB_REVISION__=1542843adc3336277dad:1 Service worker has been registered.\n
\n

然后呢

\n

恭喜你有了 PWA 的基本配置,像正常一样开发 Vue 应用,初级阶段也没什么问题。

","createdAt":"2020-01-30T07:59:20.000Z","lastEditedAt":"2020-09-04T23:59:48.000Z","image":"/blog/img/bda492f1.png","imageLazy":"ADAAkDASIAAhEBAxEB/8QAFgABAQE$AEE/8QAGRAAAgMB$ECEVGR/8QAFAEB$AAAv/EABYRAQEB$ABAv/aAAwDAQACEQMRAD8A0SVQdaToAaWX/9k=","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: vue","type":"tag","name":"vue","color":"41b883","path":"/tag/vue/"},{"id":"tag: pwa","type":"tag","name":"pwa","color":"5a0fc8","path":"/tag/pwa/"},{"id":"series: vue-pwa","type":"series","name":"vue-pwa","color":"41b883","path":"/series/vue-pwa/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/vue-pwa-2/index.json b/assets/data/post/vue-pwa-2/index.json index 99ce31949..095ebaa76 100644 --- a/assets/data/post/vue-pwa-2/index.json +++ b/assets/data/post/vue-pwa-2/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"87","title":"用 Vue 做 PWA(二):Runtime Caching","summary":"\n

本系列已经很久没更新了,在此讨论实践过程中遇到的 Runtime Caching 的一些问题。主要注意点是在处理跨域请求上。

\n","body":"\n

注意整理博客时将原来滥竽充数的 (二) 改名, 这篇原来的 (三) 变成 (二)

\n

Runtime Caching 是指除了网页的 js 和 css 资源之类的以外,对运行时请求的资源进行缓存。接下来讨论如下配置:

\n
\n
\n \n js\n
runtimeCaching: [\n  {\n    urlPattern: /.*/,\n    handler: 'StaleWhileRevalidate',\n    options: {\n      cacheName: 'Example',\n      expiration: {\n        maxAgeSeconds: 86400 * 15\n      },\n      cacheableResponse: {\n        statuses: [0, 200]\n      }\n    }\n  }\n]
\n

urlPattern

\n

需要缓存的请求 URL,可以是一个字符串、正则表达式,甚至一个回调函数。对于跨域请求,必须从头到尾匹配正则表达式。如果喜欢暴力,那么就可以使用/.*/来缓存所有请求。

\n

不透明响应 (Opaque Response)

\n

Reference https://developers.google.com/web/tools/workbox/guides/handle-third-party-requests

\n

如果跨域请求不是 CORS,或者发起的姿势不对(比如没有使用 crossorigin=\"anonymous\" )导致原本支持 CORS 的变成了 no-cors,那么你就会得到 Opaque Response。对于 Opaque Response,你不能以任何方式得到返回的信息,甚至连返回代码都不知道,永远是 0。只能将其作为图片资源等。对于 staleWhileRevalidate 和 networkFirst 策略会默认缓存,但是对于 cacheFirst 策略默认不缓存。所以需要使用

\n
\n
\n \n js\n
cacheableResponse: {\n  statuses: [0, 200]\n}
\n

来强行缓存 Opaque Response。注意如果请求发生错误,也会被缓存!请谨慎选择缓存策略,比如首次出错的 cacheFirst 将导致以后一直拿不到正确结果。

\n

Opaque Response 的大小也是不能读取的,所以 Chrome 会虚报大小,比如几 K 的图片会占用几 M 的空间!于是你会更快地达到空间限制。显然 Chrome 不会傻到真的占用了那么多磁盘空间。

\n

handler 缓存策略

\n\n

expiration

\n

有的时候缓存会变动,有点资源变动后以后都用不着了,得清除出去,所以可以设置过期时间。

","createdAt":"2020-04-18T04:49:20.000Z","lastEditedAt":"2021-03-13T00:27:46.000Z","image":"/blog/img/bda492f1.png","imageLazy":"ADAAkDASIAAhEBAxEB/8QAFgABAQE$AEE/8QAGRAAAgMB$ECEVGR/8QAFAEB$AAAv/EABYRAQEB$ABAv/aAAwDAQACEQMRAD8A0SVQdaToAaWX/9k=","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: vue","type":"tag","name":"vue","color":"41b883","path":"/tag/vue/"},{"id":"tag: pwa","type":"tag","name":"pwa","color":"5a0fc8","path":"/tag/pwa/"},{"id":"series: vue-pwa","type":"series","name":"vue-pwa","color":"41b883","path":"/series/vue-pwa/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"87","title":"用 Vue 做 PWA(二):Runtime Caching","summary":"\n

本系列已经很久没更新了,在此讨论实践过程中遇到的 Runtime Caching 的一些问题。主要注意点是在处理跨域请求上。

\n","body":"\n

注意整理博客时将原来滥竽充数的 (二) 改名, 这篇原来的 (三) 变成 (二)

\n

Runtime Caching 是指除了网页的 js 和 css 资源之类的以外,对运行时请求的资源进行缓存。接下来讨论如下配置:

\n
\n
\n \n js\n
runtimeCaching: [\n  {\n    urlPattern: /.*/,\n    handler: 'StaleWhileRevalidate',\n    options: {\n      cacheName: 'Example',\n      expiration: {\n        maxAgeSeconds: 86400 * 15\n      },\n      cacheableResponse: {\n        statuses: [0, 200]\n      }\n    }\n  }\n]
\n

urlPattern

\n

需要缓存的请求 URL,可以是一个字符串、正则表达式,甚至一个回调函数。对于跨域请求,必须从头到尾匹配正则表达式。如果喜欢暴力,那么就可以使用/.*/来缓存所有请求。

\n

不透明响应 (Opaque Response)

\n

Reference https://developers.google.com/web/tools/workbox/guides/handle-third-party-requests

\n

如果跨域请求不是 CORS,或者发起的姿势不对(比如没有使用 crossorigin=\"anonymous\" )导致原本支持 CORS 的变成了 no-cors,那么你就会得到 Opaque Response。对于 Opaque Response,你不能以任何方式得到返回的信息,甚至连返回代码都不知道,永远是 0。只能将其作为图片资源等。对于 staleWhileRevalidate 和 networkFirst 策略会默认缓存,但是对于 cacheFirst 策略默认不缓存。所以需要使用

\n
\n
\n \n js\n
cacheableResponse: {\n  statuses: [0, 200]\n}
\n

来强行缓存 Opaque Response。注意如果请求发生错误,也会被缓存!请谨慎选择缓存策略,比如首次出错的 cacheFirst 将导致以后一直拿不到正确结果。

\n

Opaque Response 的大小也是不能读取的,所以 Chrome 会虚报大小,比如几 K 的图片会占用几 M 的空间!于是你会更快地达到空间限制。显然 Chrome 不会傻到真的占用了那么多磁盘空间。

\n

handler 缓存策略

\n\n

expiration

\n

有的时候缓存会变动,有点资源变动后以后都用不着了,得清除出去,所以可以设置过期时间。

","createdAt":"2020-04-18T04:49:20.000Z","lastEditedAt":"2021-03-13T00:27:46.000Z","image":"/blog/img/bda492f1.png","imageLazy":"ADAAkDASIAAhEBAxEB/8QAFgABAQE$AEE/8QAGRAAAgMB$ECEVGR/8QAFAEB$AAAv/EABYRAQEB$ABAv/aAAwDAQACEQMRAD8A0SVQdaToAaWX/9k=","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: vue","type":"tag","name":"vue","color":"41b883","path":"/tag/vue/"},{"id":"tag: pwa","type":"tag","name":"pwa","color":"5a0fc8","path":"/tag/pwa/"},{"id":"series: vue-pwa","type":"series","name":"vue-pwa","color":"41b883","path":"/series/vue-pwa/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/vue-pwa-3/index.json b/assets/data/post/vue-pwa-3/index.json index 7174b9b51..cf7b1758c 100644 --- a/assets/data/post/vue-pwa-3/index.json +++ b/assets/data/post/vue-pwa-3/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"147","title":"用 Vue 做 PWA (三):理解生命周期","summary":"\n

虽然前两节的内容足以写出能用的 service worker,但是如果深入细节,仍然会有一些“匪夷所思”的现象发生。

\n","body":"\n

理解 service worker 的生命周期,可以帮助掌握其行为,以达到更好的体验。

\n

本文主要概括 https://developers.google.com/web/fundamentals/primers/service-workers/lifecycle

\n

理解 service worker 的“哲学”

\n

概括来讲就是离线优先和一致性。离线优先没说的,一致性体现在:

\n
    \n
  1. 页面打开是什么(用不用 service worker,用哪个版本的)就一直是什么
  2. \n
  3. 所有(同一个作用域内的)标签页用同一个版本的 service worker
  4. \n
  5. 当然新的不能影响正在运行的旧的 service worker
  6. \n
\n

知识补充:precache 机制

\n

Precache 简单讲就是在 service worker 安装的时候预先下载需要的文件( JS, CSS 之类),保证安装成功的时候这些文件都已经缓存完毕,一旦 service worker 激活即可直接从 cache 中获取。

\n

cli-plugin-pwa 用到了 workbox-webpack-plugin。之所以成为 webpack-plugin,就是为了自动将打包出来的文件 precache。

\n

安装成功不代表可用

\n

按照第一条,由于第一次打开时是无 service worker 的,所以即使安装成功了,激活了,请求也不会走 service worker。必须刷新页面才行。

\n

\"sw-install\"

\n

不过可以用选项 clientsClaim 可以让 service worker 激活后立马接管页面,如果当前无人接管的话。需要注意的是,如果请求早于 service worker 安装并激活,显然即使激活后立马接管页面也无能为力了。而且如果 precache 的东西太多的话会严重减慢 service worker 的安装进度,不利于快速接管。

\n

Scope

\n

当然安装成功了但是没法用还有一种可能,作用域不对。如果是注册 /assets/js/sw.js,那默认作用域就是 /assets/js/。解决方法有二:

\n
    \n
  1. sw.js 放到应用根目录下
  2. \n
  3. 注册的时候说明 register('/assets/js/sw.js', { scope: '/' })\n
    \n
    \n \n js\n
    // 或者翻译成 Vue 的\nregister('/service-worker.js', {\n  registrationOptions: { scope: './' },\n   // ...\n})
    \n
  4. \n
\n

注意如果你的应用在 /app/ 下,注册了 /app/sw.js,但是某一次以 /app (没有末尾斜杠)访问主页,那么 service worker 是不会起作用的。

\n

什么时候更新

\n

这个简单情况很简单,只要访问到作用域内的页面即可触发浏览器查看更新。

\n

不过 pushsync 的事件在一定条件下也可触发,但这是直接写 InjectManifest 的大佬的事情了。

\n

还可以手动更新:

\n
\n
\n \n js\n
navigator.serviceWorker.register('/sw.js').then(reg => {\n  // sometime later…\n  reg.update();\n})
\n

或者翻译成 Vue 的:

\n
\n
\n \n js\n
import { register } from 'register-service-worker'\n\nregister('/service-worker.js', {\n  registered (registration) {\n    console.log('Service worker has been registered.')\n\n    registration.update()\n    // Or\n    setTimeout(registration.update.bind(registration), 10000)\n  }\n})
\n

更新完要重开才有效果

\n

为什么安装是刷新就可以,但是更新是重开?因为在刷新的时候,旧页面和新页面是有交叠的,也就是说旧的 service worker 没法放手,要一直抓着,直到页面关闭。

\n

\"sw-update\"

\n

所以结合起来,要(正常)更新 service worker(也就是更新应用版本,因为 precache 机制),需要打开页面,稍等一会,等待新的 service worker 安装完成后

\n

不过(在新的 service worker 上)用 skipWaiting 选项可以让新的 service worker 在安装成功后直接激活。但是也有可能使页面混乱,谨慎使用。

\n

更新这么麻烦,开发测试的时候等不起啊

\n

勾上 Update on reload

\n

这样每次刷新或访问新的页面都会触发更新,且无论 service worker 是否有变化都会重新安装并立即生效。

\n

\"update-on-reload\"

\n

手动 Skip waiting

\n

\"skip-waiting\"

\n

强制刷新

\n

不仅在开发时有用,还可以作为一个选项提供给用户,方便解决设计出错或用不上新功能给用户带来的烦恼。

\n
\n
\n \n js\n
location.reload(true)
","createdAt":"2020-09-03T02:40:57.000Z","lastEditedAt":"2020-09-08T03:09:07.000Z","image":"/blog/img/bda492f1.png","imageLazy":"ADAAkDASIAAhEBAxEB/8QAFgABAQE$AEE/8QAGRAAAgMB$ECEVGR/8QAFAEB$AAAv/EABYRAQEB$ABAv/aAAwDAQACEQMRAD8A0SVQdaToAaWX/9k=","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: vue","type":"tag","name":"vue","color":"41b883","path":"/tag/vue/"},{"id":"tag: pwa","type":"tag","name":"pwa","color":"5a0fc8","path":"/tag/pwa/"},{"id":"series: vue-pwa","type":"series","name":"vue-pwa","color":"41b883","path":"/series/vue-pwa/"}],"reactions":[{"emoji":"👍","count":1,"users":["Yixuan-Wang"]}],"comments":[]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"147","title":"用 Vue 做 PWA (三):理解生命周期","summary":"\n

虽然前两节的内容足以写出能用的 service worker,但是如果深入细节,仍然会有一些“匪夷所思”的现象发生。

\n","body":"\n

理解 service worker 的生命周期,可以帮助掌握其行为,以达到更好的体验。

\n

本文主要概括 https://developers.google.com/web/fundamentals/primers/service-workers/lifecycle

\n

理解 service worker 的“哲学”

\n

概括来讲就是离线优先和一致性。离线优先没说的,一致性体现在:

\n
    \n
  1. 页面打开是什么(用不用 service worker,用哪个版本的)就一直是什么
  2. \n
  3. 所有(同一个作用域内的)标签页用同一个版本的 service worker
  4. \n
  5. 当然新的不能影响正在运行的旧的 service worker
  6. \n
\n

知识补充:precache 机制

\n

Precache 简单讲就是在 service worker 安装的时候预先下载需要的文件( JS, CSS 之类),保证安装成功的时候这些文件都已经缓存完毕,一旦 service worker 激活即可直接从 cache 中获取。

\n

cli-plugin-pwa 用到了 workbox-webpack-plugin。之所以成为 webpack-plugin,就是为了自动将打包出来的文件 precache。

\n

安装成功不代表可用

\n

按照第一条,由于第一次打开时是无 service worker 的,所以即使安装成功了,激活了,请求也不会走 service worker。必须刷新页面才行。

\n

\"sw-install\"

\n

不过可以用选项 clientsClaim 可以让 service worker 激活后立马接管页面,如果当前无人接管的话。需要注意的是,如果请求早于 service worker 安装并激活,显然即使激活后立马接管页面也无能为力了。而且如果 precache 的东西太多的话会严重减慢 service worker 的安装进度,不利于快速接管。

\n

Scope

\n

当然安装成功了但是没法用还有一种可能,作用域不对。如果是注册 /assets/js/sw.js,那默认作用域就是 /assets/js/。解决方法有二:

\n
    \n
  1. sw.js 放到应用根目录下
  2. \n
  3. 注册的时候说明 register('/assets/js/sw.js', { scope: '/' })\n
    \n
    \n \n js\n
    // 或者翻译成 Vue 的\nregister('/service-worker.js', {\n  registrationOptions: { scope: './' },\n   // ...\n})
    \n
  4. \n
\n

注意如果你的应用在 /app/ 下,注册了 /app/sw.js,但是某一次以 /app (没有末尾斜杠)访问主页,那么 service worker 是不会起作用的。

\n

什么时候更新

\n

这个简单情况很简单,只要访问到作用域内的页面即可触发浏览器查看更新。

\n

不过 pushsync 的事件在一定条件下也可触发,但这是直接写 InjectManifest 的大佬的事情了。

\n

还可以手动更新:

\n
\n
\n \n js\n
navigator.serviceWorker.register('/sw.js').then(reg => {\n  // sometime later…\n  reg.update();\n})
\n

或者翻译成 Vue 的:

\n
\n
\n \n js\n
import { register } from 'register-service-worker'\n\nregister('/service-worker.js', {\n  registered (registration) {\n    console.log('Service worker has been registered.')\n\n    registration.update()\n    // Or\n    setTimeout(registration.update.bind(registration), 10000)\n  }\n})
\n

更新完要重开才有效果

\n

为什么安装是刷新就可以,但是更新是重开?因为在刷新的时候,旧页面和新页面是有交叠的,也就是说旧的 service worker 没法放手,要一直抓着,直到页面关闭。

\n

\"sw-update\"

\n

所以结合起来,要(正常)更新 service worker(也就是更新应用版本,因为 precache 机制),需要打开页面,稍等一会,等待新的 service worker 安装完成后

\n

不过(在新的 service worker 上)用 skipWaiting 选项可以让新的 service worker 在安装成功后直接激活。但是也有可能使页面混乱,谨慎使用。

\n

更新这么麻烦,开发测试的时候等不起啊

\n

勾上 Update on reload

\n

这样每次刷新或访问新的页面都会触发更新,且无论 service worker 是否有变化都会重新安装并立即生效。

\n

\"update-on-reload\"

\n

手动 Skip waiting

\n

\"skip-waiting\"

\n

强制刷新

\n

不仅在开发时有用,还可以作为一个选项提供给用户,方便解决设计出错或用不上新功能给用户带来的烦恼。

\n
\n
\n \n js\n
location.reload(true)
","createdAt":"2020-09-03T02:40:57.000Z","lastEditedAt":"2020-09-08T03:09:07.000Z","image":"/blog/img/bda492f1.png","imageLazy":"ADAAkDASIAAhEBAxEB/8QAFgABAQE$AEE/8QAGRAAAgMB$ECEVGR/8QAFAEB$AAAv/EABYRAQEB$ABAv/aAAwDAQACEQMRAD8A0SVQdaToAaWX/9k=","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: vue","type":"tag","name":"vue","color":"41b883","path":"/tag/vue/"},{"id":"tag: pwa","type":"tag","name":"pwa","color":"5a0fc8","path":"/tag/pwa/"},{"id":"series: vue-pwa","type":"series","name":"vue-pwa","color":"41b883","path":"/series/vue-pwa/"}],"reactions":[{"emoji":"👍","count":1,"users":["Yixuan-Wang"]}],"comments":[]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/why-flask/index.json b/assets/data/post/why-flask/index.json index bfa647c91..0299a158c 100644 --- a/assets/data/post/why-flask/index.json +++ b/assets/data/post/why-flask/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"29","title":"我为什么要选 Flask?","summary":"\n

注意,这里不是说 Flask 有多好,而是。。用 Flask 用到怀疑人生!

\n","body":"\n

不错,我又开始后悔了 😳

\n\n

但是django怎么样呢?(以前用过的感受)

\n\n

既然选择了 Flask,那就要一直做下去。于是,被逼之下,解锁新技能——面向 GitHub 编程

\n\n

UPDATE

\n

用了一段时间后的感受又不一样了。

\n

正如西方文化选读的老师讲的,基督教认为,上帝给了你自由意志,让你用自由意志去服从上帝。

\n

Flask 和 Django 关系大抵类似。Flask 给了你很自由的编程方式,允许你任意组织项目结构、决定使用什么库。但是呢,Django 的模式还是一样的香,ORM 就是好用,没有像样的 template 引擎就是不行,表单验证就是需要简化,一个功能分一个模块,模块下 route 和 database 分开也很舒服。总之,Django 选择这样的模式还是很有道理的(不然也不会有那么多星对吧)

\n

但是呢?你还是不会选择完全模仿 Django。Flask 有自己的装饰器实现的 route,因为业务需求,也会把所有涉及数据库的函数全部封装放到一个类里。而且 Django 使用的类的黑魔法也有些反人类。使用 Flask 还是有着更多的掌控感。

\n

也许所谓上帝自由意志也是如此,虽然给了你自由意志,但是也像 Flask 一样,总归有新教徒,面向同一件事,各自有着自己的实现方式。

\n

所以,Flask or Django 的实质还是那句话,你是想要一把榔头,从五金市场拿到所需的材料,构建合适的项目,就像自己理解圣经并不断探zhe索teng呢,还是要一个开箱即用的工具箱,拥有 drop-in experience,就像直接接受流行教派的思想搭上救赎的快车呢?

\n
\n

Maybe, solutions for problems in Flask are open to the whole world, but solutions for problems Django are confined in the Cathollic church world.

\n

However, Django's documentation and community are mature enough.

\n

All in all, Flask or Django is your own choice.

\n
\n

声明:本文扯上基督教只是因为课上讲到并有感而发,并非黑,而且对基督教思想还停留在比较浅薄的地步,如有错误轻喷

","createdAt":"2020-01-03T13:00:00.000Z","lastEditedAt":"2021-02-22T08:02:38.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: python","type":"tag","name":"python","color":"3c78a8","path":"/tag/python/"},{"id":"tag: flask","type":"tag","name":"flask","color":"000000","path":"/tag/flask/"}],"reactions":[],"comments":[{"resourcePath":"/AllanChain/blog/issues/29#issuecomment-571567919","author":{"id":"AllanChain","avatarUrl":"https://avatars.githubusercontent.com/u/36528777?s=64&u=e5821b32c02c77ed038c8c82cfe55e07075911b7&v=4"},"bodyHTML":"

其实,写 hugo 主题也要面向 github 编程😭

","createdAt":"2020-01-07T12:32:30.000Z","reactions":[]},{"resourcePath":"/AllanChain/blog/issues/29#issuecomment-783179211","author":{"id":"AllanChain","avatarUrl":"https://avatars.githubusercontent.com/u/36528777?s=64&u=e5821b32c02c77ed038c8c82cfe55e07075911b7&v=4"},"bodyHTML":"

有多少东西不用面向 GitHub 编程?不让你读源码算好的 🐶

","createdAt":"2021-02-22T08:09:40.000Z","reactions":[{"emoji":"👍","count":1,"users":["Yixuan-Wang"]}]}]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"29","title":"我为什么要选 Flask?","summary":"\n

注意,这里不是说 Flask 有多好,而是。。用 Flask 用到怀疑人生!

\n","body":"\n

不错,我又开始后悔了 😳

\n\n

但是django怎么样呢?(以前用过的感受)

\n\n

既然选择了 Flask,那就要一直做下去。于是,被逼之下,解锁新技能——面向 GitHub 编程

\n\n

UPDATE

\n

用了一段时间后的感受又不一样了。

\n

正如西方文化选读的老师讲的,基督教认为,上帝给了你自由意志,让你用自由意志去服从上帝。

\n

Flask 和 Django 关系大抵类似。Flask 给了你很自由的编程方式,允许你任意组织项目结构、决定使用什么库。但是呢,Django 的模式还是一样的香,ORM 就是好用,没有像样的 template 引擎就是不行,表单验证就是需要简化,一个功能分一个模块,模块下 route 和 database 分开也很舒服。总之,Django 选择这样的模式还是很有道理的(不然也不会有那么多星对吧)

\n

但是呢?你还是不会选择完全模仿 Django。Flask 有自己的装饰器实现的 route,因为业务需求,也会把所有涉及数据库的函数全部封装放到一个类里。而且 Django 使用的类的黑魔法也有些反人类。使用 Flask 还是有着更多的掌控感。

\n

也许所谓上帝自由意志也是如此,虽然给了你自由意志,但是也像 Flask 一样,总归有新教徒,面向同一件事,各自有着自己的实现方式。

\n

所以,Flask or Django 的实质还是那句话,你是想要一把榔头,从五金市场拿到所需的材料,构建合适的项目,就像自己理解圣经并不断探zhe索teng呢,还是要一个开箱即用的工具箱,拥有 drop-in experience,就像直接接受流行教派的思想搭上救赎的快车呢?

\n
\n

Maybe, solutions for problems in Flask are open to the whole world, but solutions for problems Django are confined in the Cathollic church world.

\n

However, Django's documentation and community are mature enough.

\n

All in all, Flask or Django is your own choice.

\n
\n

声明:本文扯上基督教只是因为课上讲到并有感而发,并非黑,而且对基督教思想还停留在比较浅薄的地步,如有错误轻喷

","createdAt":"2020-01-03T13:00:00.000Z","lastEditedAt":"2021-02-22T08:02:38.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: python","type":"tag","name":"python","color":"3c78a8","path":"/tag/python/"},{"id":"tag: flask","type":"tag","name":"flask","color":"000000","path":"/tag/flask/"}],"reactions":[],"comments":[{"resourcePath":"/AllanChain/blog/issues/29#issuecomment-571567919","author":{"id":"AllanChain","avatarUrl":"https://avatars.githubusercontent.com/u/36528777?s=64&u=e5821b32c02c77ed038c8c82cfe55e07075911b7&v=4"},"bodyHTML":"

其实,写 hugo 主题也要面向 github 编程😭

","createdAt":"2020-01-07T12:32:30.000Z","reactions":[]},{"resourcePath":"/AllanChain/blog/issues/29#issuecomment-783179211","author":{"id":"AllanChain","avatarUrl":"https://avatars.githubusercontent.com/u/36528777?s=64&u=e5821b32c02c77ed038c8c82cfe55e07075911b7&v=4"},"bodyHTML":"

有多少东西不用面向 GitHub 编程?不让你读源码算好的 🐶

","createdAt":"2021-02-22T08:09:40.000Z","reactions":[{"emoji":"👍","count":1,"users":["Yixuan-Wang"]}]}]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/win-cmd-cht/index.json b/assets/data/post/win-cmd-cht/index.json index daf69d1b5..933f83678 100644 --- a/assets/data/post/win-cmd-cht/index.json +++ b/assets/data/post/win-cmd-cht/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"86","title":"Windows CMD Cheatsheet","summary":"","body":"\n

Create Soft / Hard link

\n

Using ln in WSL does not create Windows capable symlinks. To do so, open CMD with administartor priviledge, and use mklink command. Notice that mklink has an odd argument order.

\n

To make soft link for directory:

\n
\n
\n \n batchfile\n
mklink /D Link Target
\n

Target is the directory which already exist.

\n

Type mklink for more information.

\n

Checkout battery info

\n
\n
\n \n batchfile\n
powerconfig /batteryreport
\n

Checkout html generated at $HOME/battery_report.html

","createdAt":"2020-04-18T04:49:03.000Z","lastEditedAt":"2020-08-06T13:16:42.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: cheatsheet","type":"blog","name":"cheatsheet","color":"6655d6","path":"/blog/cheatsheet/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"86","title":"Windows CMD Cheatsheet","summary":"","body":"\n

Create Soft / Hard link

\n

Using ln in WSL does not create Windows capable symlinks. To do so, open CMD with administartor priviledge, and use mklink command. Notice that mklink has an odd argument order.

\n

To make soft link for directory:

\n
\n
\n \n batchfile\n
mklink /D Link Target
\n

Target is the directory which already exist.

\n

Type mklink for more information.

\n

Checkout battery info

\n
\n
\n \n batchfile\n
powerconfig /batteryreport
\n

Checkout html generated at $HOME/battery_report.html

","createdAt":"2020-04-18T04:49:03.000Z","lastEditedAt":"2020-08-06T13:16:42.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: cheatsheet","type":"blog","name":"cheatsheet","color":"6655d6","path":"/blog/cheatsheet/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/win-cmd-font/index.json b/assets/data/post/win-cmd-font/index.json index 6abad8de4..149fb086e 100644 --- a/assets/data/post/win-cmd-font/index.json +++ b/assets/data/post/win-cmd-font/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"30","title":"Change Win10 CMD Fonts","summary":"\n

It took me quite a lot time to try to solve this...

\n

Note: this answer only works in non-English regions

\n","body":"\n

Based on: https://answers.microsoft.com/en-us/windows/forum/windows_10-start/how-to-customize-consoles-fonts-in-windows-10/f257d215-66db-4186-86cc-2e0b5056b50c

\n

Introduction to the Problem

\n

I have tried for a long time to change to nice font in CMD. After following the tutorials ( GUI version and non-GUI version) on the web and enabling the fonts in the registry, strange things happened:

\n\n

Root of the Problem

\n

That's the Code Page. I have not heard of this before, but I do know cp-936 encoding stuff when configuring gVim, that is short for Code Page 936 (ANSI/OEM - 简体中文 GBK).

\n

Check it Out

\n

When you right click and click \"Option\" (选项) tab, it will show you the current code page.

\n

In CMD, you can use chcp 65001 to change Unicode code page. Now right click again and go to \"Properties\", fonts enabled should be there.

\n

Solutions

\n

Here are two ways to go:

\n
\n

It looks like you have installed the Simplified Chinese version of Windows. Do you need to display Chinese text in the console?

\n

If not, and if you don't run legacy Chinese apps that use code page 936 text encoding instead of Unicode, then one option for you is to change the \"Language for non-Unicode programs\" setting to any language other than Chinese, Japanese or Korean.

\n

To change that setting:

\n\n
\n

But unfortunately:

\n
\n

I am sorry that I have to run some legacy Chinese apps which still use non-Unicode encoding.

\n
\n

So here is another way, choose Beta: Use Unicode UTF-8 for i18n, in the prompt said above, and click to reboot.

\n

\"intl.cpl\"

\n

You will see that CMD is using UFT-8 now, and CMD recognizes those fonts! Enjoy programing!

\n

Still Problem

\n

PowerShell still changes to cp 936 when executing commands or running programs 🤔...

","createdAt":"2020-01-03T07:39:10.000Z","lastEditedAt":"2020-07-03T01:31:22.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"30","title":"Change Win10 CMD Fonts","summary":"\n

It took me quite a lot time to try to solve this...

\n

Note: this answer only works in non-English regions

\n","body":"\n

Based on: https://answers.microsoft.com/en-us/windows/forum/windows_10-start/how-to-customize-consoles-fonts-in-windows-10/f257d215-66db-4186-86cc-2e0b5056b50c

\n

Introduction to the Problem

\n

I have tried for a long time to change to nice font in CMD. After following the tutorials ( GUI version and non-GUI version) on the web and enabling the fonts in the registry, strange things happened:

\n\n

Root of the Problem

\n

That's the Code Page. I have not heard of this before, but I do know cp-936 encoding stuff when configuring gVim, that is short for Code Page 936 (ANSI/OEM - 简体中文 GBK).

\n

Check it Out

\n

When you right click and click \"Option\" (选项) tab, it will show you the current code page.

\n

In CMD, you can use chcp 65001 to change Unicode code page. Now right click again and go to \"Properties\", fonts enabled should be there.

\n

Solutions

\n

Here are two ways to go:

\n
\n

It looks like you have installed the Simplified Chinese version of Windows. Do you need to display Chinese text in the console?

\n

If not, and if you don't run legacy Chinese apps that use code page 936 text encoding instead of Unicode, then one option for you is to change the \"Language for non-Unicode programs\" setting to any language other than Chinese, Japanese or Korean.

\n

To change that setting:

\n\n
\n

But unfortunately:

\n
\n

I am sorry that I have to run some legacy Chinese apps which still use non-Unicode encoding.

\n
\n

So here is another way, choose Beta: Use Unicode UTF-8 for i18n, in the prompt said above, and click to reboot.

\n

\"intl.cpl\"

\n

You will see that CMD is using UFT-8 now, and CMD recognizes those fonts! Enjoy programing!

\n

Still Problem

\n

PowerShell still changes to cp 936 when executing commands or running programs 🤔...

","createdAt":"2020-01-03T07:39:10.000Z","lastEditedAt":"2020-07-03T01:31:22.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/win11-explorer-patcher/index.json b/assets/data/post/win11-explorer-patcher/index.json index 28cb0af34..f3dde6a79 100644 --- a/assets/data/post/win11-explorer-patcher/index.json +++ b/assets/data/post/win11-explorer-patcher/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"176","title":"Keep Old-fashioned Taskbar on the Left While Using Windows 11","summary":"\n

Windows 11 comes with awesome new features for developers as well as insane taskbar settings. Don't let the awful taskbar design stop you from upgrading!

\n","body":"\n
\n

Not having Windows11 yet? Get it here!

\n
\n

The process is surprisingly simple but hard to find. You just need to know https://github.com/valinet/ExplorerPatcher .

\n

Install ExplorerPatcher

\n
\n

For latest instructions, follow README and release notes.

\n
\n

ExplorerPatcher brings old Windows 10 taskbar back, together with some other tweaks. I am on 22000.194.25 and the installation is pretty simple:

\n
    \n
  1. Download dxgi.dll here
  2. \n
  3. Copy it to C:\\Windows (needs administrator permission grant)
  4. \n
  5. Restart explorer.exe from task manager
  6. \n
  7. Wait explorer.exe downloading ~40M of Microsoft files (can take a while, can be monitored in network section of taskmanager)
  8. \n
\n

Follow README for other customization. You probably need Win + X and \"Enable missing system tray icons\".

\n

Then you will get something like ExplorerPatcher's demo:

\n

\"ExplorerPatcher's

\n

Moving taskbar to left

\n

As you can see in the first image, I prefer taskbar docked on the left side.

\n

Open regedit, go to HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\StuckRects3, and edit Settings. Change the following part to 00:

\n

\"image\"

\n

And here is the meaning of the numbers:

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
NumberPosition
00Left
01Top
02Right
03Bottom
\n

\n

And don't forget to restart explorer.exe to make changes take effect.

\n

Make smaller icons

\n

Large icons look ugly on a vertical taskbar →_→ Let's make them smaller.

\n

Again in regedit, go to HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Advanced, change TaskbarSmallIcons to 1.

\n

\n

Again, restart explorer.exe.

\n

Done

\n

If everything goes well, you will get a old-fashioned taskbar like mine!

","createdAt":"2021-10-11T07:21:25.000Z","lastEditedAt":"2021-10-11T10:41:16.000Z","image":"/blog/img/4748dfe8.png","imageLazy":"AHAAkDASIAAhEBAxEB/8QAFgABAQE$AME/8QAGBABAAMB$ECEVH/xAAVAQEB$ABAv/EABURAQE$AAB/9oADAMBAAIRAxEAPwDPCu24AXH/2Q==","serializedHeadings":"[]","labels":[{"id":"blog: moments","type":"blog","name":"moments","color":"3dae38","path":"/blog/moments/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"176","title":"Keep Old-fashioned Taskbar on the Left While Using Windows 11","summary":"\n

Windows 11 comes with awesome new features for developers as well as insane taskbar settings. Don't let the awful taskbar design stop you from upgrading!

\n","body":"\n
\n

Not having Windows11 yet? Get it here!

\n
\n

The process is surprisingly simple but hard to find. You just need to know https://github.com/valinet/ExplorerPatcher .

\n

Install ExplorerPatcher

\n
\n

For latest instructions, follow README and release notes.

\n
\n

ExplorerPatcher brings old Windows 10 taskbar back, together with some other tweaks. I am on 22000.194.25 and the installation is pretty simple:

\n
    \n
  1. Download dxgi.dll here
  2. \n
  3. Copy it to C:\\Windows (needs administrator permission grant)
  4. \n
  5. Restart explorer.exe from task manager
  6. \n
  7. Wait explorer.exe downloading ~40M of Microsoft files (can take a while, can be monitored in network section of taskmanager)
  8. \n
\n

Follow README for other customization. You probably need Win + X and \"Enable missing system tray icons\".

\n

Then you will get something like ExplorerPatcher's demo:

\n

\"ExplorerPatcher's

\n

Moving taskbar to left

\n

As you can see in the first image, I prefer taskbar docked on the left side.

\n

Open regedit, go to HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\StuckRects3, and edit Settings. Change the following part to 00:

\n

\"image\"

\n

And here is the meaning of the numbers:

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
NumberPosition
00Left
01Top
02Right
03Bottom
\n

\n

And don't forget to restart explorer.exe to make changes take effect.

\n

Make smaller icons

\n

Large icons look ugly on a vertical taskbar →_→ Let's make them smaller.

\n

Again in regedit, go to HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Advanced, change TaskbarSmallIcons to 1.

\n

\n

Again, restart explorer.exe.

\n

Done

\n

If everything goes well, you will get a old-fashioned taskbar like mine!

","createdAt":"2021-10-11T07:21:25.000Z","lastEditedAt":"2021-10-11T10:41:16.000Z","image":"/blog/img/4748dfe8.png","imageLazy":"AHAAkDASIAAhEBAxEB/8QAFgABAQE$AME/8QAGBABAAMB$ECEVH/xAAVAQEB$ABAv/EABURAQE$AAB/9oADAMBAAIRAxEAPwDPCu24AXH/2Q==","serializedHeadings":"[]","labels":[{"id":"blog: moments","type":"blog","name":"moments","color":"3dae38","path":"/blog/moments/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/wsl-cht/index.json b/assets/data/post/wsl-cht/index.json index d24bf14ad..01666f1e5 100644 --- a/assets/data/post/wsl-cht/index.json +++ b/assets/data/post/wsl-cht/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"175","title":"WSL Cheatsheet","summary":"\n

WSL issues or tricks I had to search for again and again

\n","body":"\n

No Network

\n
ping: socket: Operation not permitted\nTemporary failure in name resolution\n
\n

Solution 1: resolv.conf

\n

microsoft/WSL#6404 (comment)

\n
\n

It seems lounching VSCode daemon messes things up

\n

Setup below helped me, thanks guys:

\n
    \n
  1. /etc/wsl.conf
  2. \n
\n
[network]\ngenerateResolvConf = false\n
\n
    \n
  1. Shutdown wsl
  2. \n
\n
wsl --shutdown\n
\n
    \n
  1. Start wsl, delete /etc/resolv.conf symbolic link and create a file instead:
    \n/etc/resolv.conf
  2. \n
\n
nameserver 8.8.8.8\n
\n
\n

But this solution ruins mDNS.

\n

Solution 2: Check Firewall

\n

If the DNS works after disabling Firewall, try allowing 172.16.0.0/12 inbound in Windows Defender.

\n

Run Linux GUI apps on the Windows Subsystem for Linux

\n

https://docs.microsoft.com/en-us/windows/wsl/tutorials/gui-apps

\n

Hyper-V & Reserved Ports

\n

https://hungyi.net/posts/wsl2-reserved-ports/

\n

Run netsh int ipv4 show excludedportrange protocol=tcp to show reserved ports.

\n
    \n
  1. Run netsh int ipv4 set dynamic tcp start=51001 num=5000 to reset the dynamic port range.
  2. \n
  3. Run reg add HKLM\\SYSTEM\\CurrentControlSet\\Services\\hns\\State /v EnableExcludedPortRange /d 0 /f to disable the HNS port exclusion behavior.
  4. \n
  5. Reboot
  6. \n
","createdAt":"2021-10-05T10:49:23.000Z","lastEditedAt":"2021-10-14T02:17:43.000Z","image":"/blog/img/f6b72683.png","imageLazy":"ADAAkDASIAAhEBAxEB/8QAFQABAQ$AAT/xAAZEAADAAM$RECFGH/xAAVAQEB$AAAf/EABURAQE$AAB/9oADAMBAAIRAxEAPwBXsYvhRWAFr//Z","serializedHeadings":"[]","labels":[{"id":"blog: cheatsheet","type":"blog","name":"cheatsheet","color":"6655d6","path":"/blog/cheatsheet/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"175","title":"WSL Cheatsheet","summary":"\n

WSL issues or tricks I had to search for again and again

\n","body":"\n

No Network

\n
ping: socket: Operation not permitted\nTemporary failure in name resolution\n
\n

Solution 1: resolv.conf

\n

microsoft/WSL#6404 (comment)

\n
\n

It seems lounching VSCode daemon messes things up

\n

Setup below helped me, thanks guys:

\n
    \n
  1. /etc/wsl.conf
  2. \n
\n
[network]\ngenerateResolvConf = false\n
\n
    \n
  1. Shutdown wsl
  2. \n
\n
wsl --shutdown\n
\n
    \n
  1. Start wsl, delete /etc/resolv.conf symbolic link and create a file instead:
    \n/etc/resolv.conf
  2. \n
\n
nameserver 8.8.8.8\n
\n
\n

But this solution ruins mDNS.

\n

Solution 2: Check Firewall

\n

If the DNS works after disabling Firewall, try allowing 172.16.0.0/12 inbound in Windows Defender.

\n

Run Linux GUI apps on the Windows Subsystem for Linux

\n

https://docs.microsoft.com/en-us/windows/wsl/tutorials/gui-apps

\n

Hyper-V & Reserved Ports

\n

https://hungyi.net/posts/wsl2-reserved-ports/

\n

Run netsh int ipv4 show excludedportrange protocol=tcp to show reserved ports.

\n
    \n
  1. Run netsh int ipv4 set dynamic tcp start=51001 num=5000 to reset the dynamic port range.
  2. \n
  3. Run reg add HKLM\\SYSTEM\\CurrentControlSet\\Services\\hns\\State /v EnableExcludedPortRange /d 0 /f to disable the HNS port exclusion behavior.
  4. \n
  5. Reboot
  6. \n
","createdAt":"2021-10-05T10:49:23.000Z","lastEditedAt":"2021-10-14T02:17:43.000Z","image":"/blog/img/f6b72683.png","imageLazy":"ADAAkDASIAAhEBAxEB/8QAFQABAQ$AAT/xAAZEAADAAM$RECFGH/xAAVAQEB$AAAf/EABURAQE$AAB/9oADAMBAAIRAxEAPwBXsYvhRWAFr//Z","serializedHeadings":"[]","labels":[{"id":"blog: cheatsheet","type":"blog","name":"cheatsheet","color":"6655d6","path":"/blog/cheatsheet/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/wtf-c/index.json b/assets/data/post/wtf-c/index.json index 11f6282ee..cda5e76d3 100644 --- a/assets/data/post/wtf-c/index.json +++ b/assets/data/post/wtf-c/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"26","title":"WTFs in C","summary":"\n

为了备战变态的计算概论考试, 不得已要研究一下 C 的奇奇怪怪的东西

\n","body":"\n

UPDATE: 看起来今年的计概考试并没有类似往年题一样的未定义行为出现,可以说今年收敛了一些。故这里说,zy 和 chj 比起来,还是排雷 chj 😁

\n

除非有说明,均在 gcc 下测试

\n

Nice websites

\n\n

往年题?

\n
\n
\n \n c\n
#include <stdio.h>\n#define f(x) -x*x+\nint main()\n{\n    int i,j,x,n,k;\n    x=1;\n    n=2;\n    k=3;\n    i=-f(-x+n)-k;\n    j=--x+n*-x+n+-k;\n    \n    printf(\"%d\\n%d\\n\",i,j);\n}
\n

其中,gcc -E认为的是

\n
\n
\n \n c\n
i=- - -x+n*-x+n+-k;
\n

注意到 C 的运算符处理和空格有关,故结果为

\n
-4\n-1\n
\n

而 VC 认为的是

\n
\n
\n \n c\n
i=- --x+n*-x+n+-k;
\n

注意到第二句时 x 已自减,故结果为

\n
-1\n0\n
\n

h(x) != g(x) ?

\n
\n
\n \n c\n
#include <stdio.h>\n#define f(a,b) a##b\n#define g(a)   #a\n#define h(a) g(a)\n\nint main()\n{\n  printf(\"%s\\n\",h(f(1,2)));\n  printf(\"%s\\n\",g(f(1,2)));\n  return 0;\n}
\n
\n

https://stackoverflow.com/a/4368983

\n

A single '#' will create a string from the given argument, regardless of what that argument contains, while the double '##' will create a new token by concatenating the arguments.

\n
\n

An occurrence of a parameter in a function-like macro, unless it is the operand of # or ##, is expanded before substituting it and rescanning the whole for further expansion. Because g's parameter is the operand of #, the argument is not expanded but instead immediately stringified (\"f(1,2)\"). Because h's parameter is not the operand of # nor ##, the argument is first expanded (12), then substituted (g(12)), then rescanning and further expansion occurs (\"12\").

\n
\n

也就是说,碰到###后,宏就不会递归展开

\n

No print?

\n
\n
\n \n c\n
#include<stdio.h>\n\n#define TOTAL_ELEMENTS (sizeof(array) / sizeof(array[0]))\nint array[] = {23,34,12,17,204,99,16};\n\nint main()\n{\n  int d;\n\n  for(d=-1;d <= (TOTAL_ELEMENTS-2);d++)\n      printf(\"%d\\n\",array[d+1]);\n\n  return 0;\n}
\n

这是非常神奇的, 但是和宏没什么关系。

\n

首先,sizeof返回的是 unsigned long int, dint 类型,这两个比较的时候会比较迷,现行的是 unsigned preserving 策略,即把 int 变成 unsigned int,再行比较。

\n

于是TOTAL_ELEMENTS-2还是unsigned long int,而 -1 就变成巨大无比,条件自然就不可能成立了。

\n

所以呢,把TOTAL_ELEMENTS换成由unsigned int a=7;定义的a,还是一样的。

\n

还有一点有意思的是:

\n
\n

https://stackoverflow.com/questions/2084949

\n

On a platform with 32bit int with e.g.

\n
\n
\n \n c\n
int x = -1;\nunsigned y = 0xffffffff;
\n

the expression x == y would yield 1 because through the \"usual arithmetic conversions\" the value of x is converted to unsigned and thus to 0xffffffff.

\n

The expression (unsigned int)x == y is 1 as well. The only difference is that you do the conversion explicitly with a cast.

\n

The expression x == (int)y will most likely be 1 as well because converting 0xffffffff to int yields -1 on most platforms (two's complement negatives). Strictly speaking this is implementation-defined behavior and thus might vary on different platforms.

\n
\n

Safest is to check that the number is in range before casting:

\n
\n
\n \n c\n
if (x >= 0 && ((unsigned int)x) == y)
\n
\n

continue in do...while

\n
\n
\n \n c\n
#include<stdio.h>\n\nenum {false,true};\n\nint main()\n{\n    int i=1;\n    do {\n        printf(\"%d\\n\",i);\n        i++;\n        if(i < 15)\n            continue;\n    }while(false);\n    return 0;\n}
\n

跟在后面的while也是循环控制的一种,所以continue不会跳过while

\n

拓展知识:how for equals while?

\n
\n

Why does it tend to get into an infinite loop if I use continue in a while loop, but works fine in a for loop?
\nThe loop-counter increment i++ gets ignored in while loop if I use it after continue, but it works if it is in for loop.

\n

If continue ignores subsequent statements, then why doesn't it ignore the third statement of the for loop then, which contains the counter increment i++? Isn't the third statement of for loop subsequent to continue as well and should be ignored, given the third statement of for loop is executed after the loop body?

\n
\n
\n \n c\n
while(i<10)   //causes infinite loop\n{\n    ...\n    continue\n    i++\n    ...\n}\n\nfor(i=0;i<10;i++)  //works fine and exits after 10 iterations\n{\n    ...\n    continue\n    ...\n}
\n
\n

The reason is because the continue statement will short-circuit the statements that follow it in the loop body. Since the way you wrote the while loop has the increment statement following the continue statement, it gets short-circuited. You can solve this by changing your while loop.

\n

A lot of text books claim that:

\n
\n
\n \n c\n
for (i = 0; i < N; ++i) {\n    /*...*/\n}
\n

is equivalent to:

\n
\n
\n \n c\n
i = 0;\nwhile (i < N) {\n    /*...*/\n    ++i;\n}
\n

But, in reality, it is really like:

\n
\n
\n \n c\n
j = 0;\nwhile ((i = j++) < N) {\n    /*...*/\n}
\n

Or, to be a little more pedantic:

\n
\n
\n \n c\n
i = 0;\nif (i < 10) do {\n    /*...*/\n} while (++i, (i < 10));
\n

These are more equivalent, since now if the body of the while has a continue, the increment still occurs, just like in a for. The latter alternative only executes the increment after the iteration has completed, just like for (the former executes the increment before the iteration, deferring to save it in i until after the iteration).

\n
\n

著名的变态题

\n
\n
\n \n c\n
#include<stdio.h>\n\nint main() {\n    int i = 1;\n    printf(\"%d\", ++i+ ++i+ ++i);\n}
\n

著名的 ALE(bushi) 如是说:

\n
\n

operation on 'i' may be undefined

\n
\n

Stackoverflow 如是说

\n
\n

https://stackoverflow.com/a/2989771/8810271

\n

Modifying the value of i more than once without a sequence point in between the modifications results in undefined behavior. So, the results of your code are undefined.

\n
\n

简单翻译成人话就是,想一口气多次改变变量值的行为是未定义的。所以考试考一个未定义的东西有什么用呢?

\n

所以“往年题”部分提到的例子也是未定义的。

\n

讲道理说,不同主流编译器在同样的计算机上,使用相同的合法的程序,产生不同的结果,要么是编译器的附加功能,要么就是未定义的运算了吧!

\n

这只是普通的标签

\n
\n
\n \n c\n
#include<stdio.h>\nint main()\n{\n    int a=10;\n    switch(a)\n    {\n        case '1':\n            printf(\"ONE\\n\");\n            break;\n        case '2':\n            printf(\"TWO\\n\");\n            break;\n        defau1t:\n            printf(\"NONE\\n\");\n    }\n    return 0;\n}
\n

坑就在deefaultl打成了1😂

\n

居然编译器给过了!?

\n

原来假的defau1t被处理成了一个标签供goto使用,casedefault也类似于 C 里的标签,共用类似的语法,毕竟case本身就可以直接跳到任何地方,包括if里面。

\n

case真的可以随便跳吗?

\n
\n
\n \n c\n
#include<stdio.h>\nint main()\n{\n    int a=1;\n    switch(a)\n    {   int b=20;\n        case 1: printf(\"b is %d\\n\",b);\n                break;\n        default:printf(\"b is %d\\n\",b);\n                break;\n    }\n    return 0;\n}
\n

跳过了变量初始化就会报错了,编译都过不了

","createdAt":"2020-01-01T08:59:08.000Z","lastEditedAt":"2020-06-30T07:43:45.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: c","type":"tag","name":"c","color":"aaaaaa","path":"/tag/c/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"26","title":"WTFs in C","summary":"\n

为了备战变态的计算概论考试, 不得已要研究一下 C 的奇奇怪怪的东西

\n","body":"\n

UPDATE: 看起来今年的计概考试并没有类似往年题一样的未定义行为出现,可以说今年收敛了一些。故这里说,zy 和 chj 比起来,还是排雷 chj 😁

\n

除非有说明,均在 gcc 下测试

\n

Nice websites

\n\n

往年题?

\n
\n
\n \n c\n
#include <stdio.h>\n#define f(x) -x*x+\nint main()\n{\n    int i,j,x,n,k;\n    x=1;\n    n=2;\n    k=3;\n    i=-f(-x+n)-k;\n    j=--x+n*-x+n+-k;\n    \n    printf(\"%d\\n%d\\n\",i,j);\n}
\n

其中,gcc -E认为的是

\n
\n
\n \n c\n
i=- - -x+n*-x+n+-k;
\n

注意到 C 的运算符处理和空格有关,故结果为

\n
-4\n-1\n
\n

而 VC 认为的是

\n
\n
\n \n c\n
i=- --x+n*-x+n+-k;
\n

注意到第二句时 x 已自减,故结果为

\n
-1\n0\n
\n

h(x) != g(x) ?

\n
\n
\n \n c\n
#include <stdio.h>\n#define f(a,b) a##b\n#define g(a)   #a\n#define h(a) g(a)\n\nint main()\n{\n  printf(\"%s\\n\",h(f(1,2)));\n  printf(\"%s\\n\",g(f(1,2)));\n  return 0;\n}
\n
\n

https://stackoverflow.com/a/4368983

\n

A single '#' will create a string from the given argument, regardless of what that argument contains, while the double '##' will create a new token by concatenating the arguments.

\n
\n

An occurrence of a parameter in a function-like macro, unless it is the operand of # or ##, is expanded before substituting it and rescanning the whole for further expansion. Because g's parameter is the operand of #, the argument is not expanded but instead immediately stringified (\"f(1,2)\"). Because h's parameter is not the operand of # nor ##, the argument is first expanded (12), then substituted (g(12)), then rescanning and further expansion occurs (\"12\").

\n
\n

也就是说,碰到###后,宏就不会递归展开

\n

No print?

\n
\n
\n \n c\n
#include<stdio.h>\n\n#define TOTAL_ELEMENTS (sizeof(array) / sizeof(array[0]))\nint array[] = {23,34,12,17,204,99,16};\n\nint main()\n{\n  int d;\n\n  for(d=-1;d <= (TOTAL_ELEMENTS-2);d++)\n      printf(\"%d\\n\",array[d+1]);\n\n  return 0;\n}
\n

这是非常神奇的, 但是和宏没什么关系。

\n

首先,sizeof返回的是 unsigned long int, dint 类型,这两个比较的时候会比较迷,现行的是 unsigned preserving 策略,即把 int 变成 unsigned int,再行比较。

\n

于是TOTAL_ELEMENTS-2还是unsigned long int,而 -1 就变成巨大无比,条件自然就不可能成立了。

\n

所以呢,把TOTAL_ELEMENTS换成由unsigned int a=7;定义的a,还是一样的。

\n

还有一点有意思的是:

\n
\n

https://stackoverflow.com/questions/2084949

\n

On a platform with 32bit int with e.g.

\n
\n
\n \n c\n
int x = -1;\nunsigned y = 0xffffffff;
\n

the expression x == y would yield 1 because through the \"usual arithmetic conversions\" the value of x is converted to unsigned and thus to 0xffffffff.

\n

The expression (unsigned int)x == y is 1 as well. The only difference is that you do the conversion explicitly with a cast.

\n

The expression x == (int)y will most likely be 1 as well because converting 0xffffffff to int yields -1 on most platforms (two's complement negatives). Strictly speaking this is implementation-defined behavior and thus might vary on different platforms.

\n
\n

Safest is to check that the number is in range before casting:

\n
\n
\n \n c\n
if (x >= 0 && ((unsigned int)x) == y)
\n
\n

continue in do...while

\n
\n
\n \n c\n
#include<stdio.h>\n\nenum {false,true};\n\nint main()\n{\n    int i=1;\n    do {\n        printf(\"%d\\n\",i);\n        i++;\n        if(i < 15)\n            continue;\n    }while(false);\n    return 0;\n}
\n

跟在后面的while也是循环控制的一种,所以continue不会跳过while

\n

拓展知识:how for equals while?

\n
\n

Why does it tend to get into an infinite loop if I use continue in a while loop, but works fine in a for loop?
\nThe loop-counter increment i++ gets ignored in while loop if I use it after continue, but it works if it is in for loop.

\n

If continue ignores subsequent statements, then why doesn't it ignore the third statement of the for loop then, which contains the counter increment i++? Isn't the third statement of for loop subsequent to continue as well and should be ignored, given the third statement of for loop is executed after the loop body?

\n
\n
\n \n c\n
while(i<10)   //causes infinite loop\n{\n    ...\n    continue\n    i++\n    ...\n}\n\nfor(i=0;i<10;i++)  //works fine and exits after 10 iterations\n{\n    ...\n    continue\n    ...\n}
\n
\n

The reason is because the continue statement will short-circuit the statements that follow it in the loop body. Since the way you wrote the while loop has the increment statement following the continue statement, it gets short-circuited. You can solve this by changing your while loop.

\n

A lot of text books claim that:

\n
\n
\n \n c\n
for (i = 0; i < N; ++i) {\n    /*...*/\n}
\n

is equivalent to:

\n
\n
\n \n c\n
i = 0;\nwhile (i < N) {\n    /*...*/\n    ++i;\n}
\n

But, in reality, it is really like:

\n
\n
\n \n c\n
j = 0;\nwhile ((i = j++) < N) {\n    /*...*/\n}
\n

Or, to be a little more pedantic:

\n
\n
\n \n c\n
i = 0;\nif (i < 10) do {\n    /*...*/\n} while (++i, (i < 10));
\n

These are more equivalent, since now if the body of the while has a continue, the increment still occurs, just like in a for. The latter alternative only executes the increment after the iteration has completed, just like for (the former executes the increment before the iteration, deferring to save it in i until after the iteration).

\n
\n

著名的变态题

\n
\n
\n \n c\n
#include<stdio.h>\n\nint main() {\n    int i = 1;\n    printf(\"%d\", ++i+ ++i+ ++i);\n}
\n

著名的 ALE(bushi) 如是说:

\n
\n

operation on 'i' may be undefined

\n
\n

Stackoverflow 如是说

\n
\n

https://stackoverflow.com/a/2989771/8810271

\n

Modifying the value of i more than once without a sequence point in between the modifications results in undefined behavior. So, the results of your code are undefined.

\n
\n

简单翻译成人话就是,想一口气多次改变变量值的行为是未定义的。所以考试考一个未定义的东西有什么用呢?

\n

所以“往年题”部分提到的例子也是未定义的。

\n

讲道理说,不同主流编译器在同样的计算机上,使用相同的合法的程序,产生不同的结果,要么是编译器的附加功能,要么就是未定义的运算了吧!

\n

这只是普通的标签

\n
\n
\n \n c\n
#include<stdio.h>\nint main()\n{\n    int a=10;\n    switch(a)\n    {\n        case '1':\n            printf(\"ONE\\n\");\n            break;\n        case '2':\n            printf(\"TWO\\n\");\n            break;\n        defau1t:\n            printf(\"NONE\\n\");\n    }\n    return 0;\n}
\n

坑就在deefaultl打成了1😂

\n

居然编译器给过了!?

\n

原来假的defau1t被处理成了一个标签供goto使用,casedefault也类似于 C 里的标签,共用类似的语法,毕竟case本身就可以直接跳到任何地方,包括if里面。

\n

case真的可以随便跳吗?

\n
\n
\n \n c\n
#include<stdio.h>\nint main()\n{\n    int a=1;\n    switch(a)\n    {   int b=20;\n        case 1: printf(\"b is %d\\n\",b);\n                break;\n        default:printf(\"b is %d\\n\",b);\n                break;\n    }\n    return 0;\n}
\n

跳过了变量初始化就会报错了,编译都过不了

","createdAt":"2020-01-01T08:59:08.000Z","lastEditedAt":"2020-06-30T07:43:45.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: c","type":"tag","name":"c","color":"aaaaaa","path":"/tag/c/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file diff --git a/assets/data/post/wtf-hugo/index.json b/assets/data/post/wtf-hugo/index.json index 11b35e45c..952e22d9e 100644 --- a/assets/data/post/wtf-hugo/index.json +++ b/assets/data/post/wtf-hugo/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"post":{"id":"28","title":"谜之 Hugo | AC's Blog","summary":"","body":"\n

UPDATE: Hugo 是什么垃圾?现已弃坑。

\n

对新手不友好的 Hugo

\n

在之前 PKUCard 操作之后加了几个 Github 好友,发现很多(显然是主观感受)都在用 Hugo,也感觉之前用的 Mkdocs 不是像样的博客,早有换掉的意思。于是近期就开始上手试了一下。

\n

说 Hugo 之前先说一下 Python 的 Pelican。Pelican 也是一个博客写作应用,但是因为用户量不够,主题样式太少,并且主题的功能也不健全,(说白了就是不合胃口,)于是就很难受。

\n

不用 Hugo 不知道,用了才发现原来 Markdown 渲染可以有这么快。但是当我按照教程,选了一个主题,按照提示一步一步来的时候。。?!为什么是白页面?怎么还是白的?把配置文件全部照抄还不行?错误提示都是虚假的!令人抓狂

\n

无奈只好变成全部复制下来,再进行改动。。

\n

从 v0.55 到 v0.59 一直有 Bug 的 Hugo

\n
\n

Source block following a plain list ends up in the last plain list item #556

\n

kaushalmodi commented [on 2 Aug]
\nHello,

\n

Here's a minimal example to reproduce the issue:

\n\n
\n
This block should be out of <ul>...</ul>\nAnd also out of blockquote\n
\n
\n

Ref: https://discourse.gohugo.io/t/possible-regression-in-v0-55-5-regarding-lists-containing-code-blocks/18502/4?u=kaushalmodi

\n

/cc @aignas as you seem to be the last person to work on this Blackfriday plain-list/code block issue :)

\n
\n

来,你瞅瞅,现在还是这个样子的😭

\n

v0.60.0 UPDATE: 见“新的引擎”一节

\n

这么不稳定的 Markdown 引擎,没法和 Python 比 😏

\n

仿佛急冰的 toc 功能

\n

Hugo 自带的目录功能简直了,因为只能从 h1 开始,否则:

\n\n

自带的功能,老兄!这种事情都做得出来!NOT PYTHONIC AT ALL!

\n

最后我找到了https://gist.github.com/skyzyx/a796d66f6a124f057f3374eff0b3f99a @looeee 的代码,基本可以满足需求。

\n

修改找到的 TOC 代码

\n

如果你要更改标题级别的范围,可以把[2-4]替换成你想要的。

\n

而且这位老兄的代码就是会把标题里的一些诸如don't的标点干掉,也不会保留加粗的格式。

\n

解决办法就是去掉planify | htmlEscape, 并在

\n
\n
\n \n html\n
{{ $cleanedID := replace (replace $id \"id=\\\"\" \"\") \"\\\"\" \"\" }}
\n

后加入

\n
\n
\n \n html\n
{{- $header := replaceRE \"<h[2-4].*?>((.|\\n])+?)</h[2-4]>\" \"$1\" $header -}}
\n

谜之 Template 语法

\n

看了 Hugo 的之后这才发现 Jinja 原来是真的漂亮简洁

\n

就问你and (ConditionA) (ConditionB) 是人话吗?没有not是什么?没有括号也叫 Function?怕是 Shell 用多了?

\n

not的正确打开方式:

\n
\n
\n \n html\n
{{ if eq (reflect.IsMap site.Params.address) false }}
\n

的确。。反人类

\n

奇奇怪怪的 Toml

\n

放着好好的 YAML 和 JSON 不用,突然冒出来一个 TOML。好像 Markdown 前面加 YAML 已经很常用了,然后跟我说用 TOML,不觉得等于号 有点 丑?不觉得 引号 有点 丑?虽然在做配置文件方面可能有独到之处,对不起,Vim 原生不支持,一键秒杀颜值。

\n

新的引擎

\n

突然,Hugo 给我换了一个引擎 https://github.com/gohugoio/hugo/releases/tag/v0.60.0

\n

使用了新的引擎之后之前的问题消失了,但是变成了默认忽略 HTML,需要在config.toml里做修改

\n
\n
\n \n toml\n
[markup.goldmark.renderer]\n    unsafe = true
\n

或者config.yml

\n
\n
\n \n yaml\n
markup:\n  goldmark:\n    renderer:\n      unsafe: true
\n

新引擎,新bug

\n

headerID

\n

Goldmark 在设置 headerID 的时候,默认是仅保留字母和数字,但是这对 CJK 及其不友好。“更有甚者”,该项目的作者是日本人,竟然不考虑对本国文字的支持?!还说,仅保留字母和数字是合适的默认行为。Excuse me? 不应该原封不动保留 Unicode 才是好的默认行为吗?

\n

据说会在 Hugo v0.63.0 解决,那就拭目以待吧。。

\n

render_link

\n

本来说好可以支持 link 的 Markdown hook 的,但是呢,只支持[text](url)型,不支持<url>型。。[摊手.jpg]

\n

而且,说好引用其他 Markdown 文档的 render_link 实现居然还有点复杂。。还好找到了官方实现,不然又是大坑。。

\n

Hugo theme,注定又是一个面向 GitHub 编程。

\n

TOC 新回复

\n

我又针对 toc 写了一篇博客 hugo-toc,然后有去原来的 gist 上评论了,受到了@looeee 的回复。他说。。。他已经弃坑 Hugo 了,使用 js 解决问题。

\n

我。。。

","createdAt":"2020-01-03T13:19:16.000Z","lastEditedAt":"2020-11-02T04:31:46.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: hugo","type":"tag","name":"hugo","color":"ff3db4","path":"/tag/hugo/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"post":{"id":"28","title":"谜之 Hugo | AC's Blog","summary":"","body":"\n

UPDATE: Hugo 是什么垃圾?现已弃坑。

\n

对新手不友好的 Hugo

\n

在之前 PKUCard 操作之后加了几个 Github 好友,发现很多(显然是主观感受)都在用 Hugo,也感觉之前用的 Mkdocs 不是像样的博客,早有换掉的意思。于是近期就开始上手试了一下。

\n

说 Hugo 之前先说一下 Python 的 Pelican。Pelican 也是一个博客写作应用,但是因为用户量不够,主题样式太少,并且主题的功能也不健全,(说白了就是不合胃口,)于是就很难受。

\n

不用 Hugo 不知道,用了才发现原来 Markdown 渲染可以有这么快。但是当我按照教程,选了一个主题,按照提示一步一步来的时候。。?!为什么是白页面?怎么还是白的?把配置文件全部照抄还不行?错误提示都是虚假的!令人抓狂

\n

无奈只好变成全部复制下来,再进行改动。。

\n

从 v0.55 到 v0.59 一直有 Bug 的 Hugo

\n
\n

Source block following a plain list ends up in the last plain list item #556

\n

kaushalmodi commented [on 2 Aug]
\nHello,

\n

Here's a minimal example to reproduce the issue:

\n\n
\n
This block should be out of <ul>...</ul>\nAnd also out of blockquote\n
\n
\n

Ref: https://discourse.gohugo.io/t/possible-regression-in-v0-55-5-regarding-lists-containing-code-blocks/18502/4?u=kaushalmodi

\n

/cc @aignas as you seem to be the last person to work on this Blackfriday plain-list/code block issue :)

\n
\n

来,你瞅瞅,现在还是这个样子的😭

\n

v0.60.0 UPDATE: 见“新的引擎”一节

\n

这么不稳定的 Markdown 引擎,没法和 Python 比 😏

\n

仿佛急冰的 toc 功能

\n

Hugo 自带的目录功能简直了,因为只能从 h1 开始,否则:

\n\n

自带的功能,老兄!这种事情都做得出来!NOT PYTHONIC AT ALL!

\n

最后我找到了https://gist.github.com/skyzyx/a796d66f6a124f057f3374eff0b3f99a @looeee 的代码,基本可以满足需求。

\n

修改找到的 TOC 代码

\n

如果你要更改标题级别的范围,可以把[2-4]替换成你想要的。

\n

而且这位老兄的代码就是会把标题里的一些诸如don't的标点干掉,也不会保留加粗的格式。

\n

解决办法就是去掉planify | htmlEscape, 并在

\n
\n
\n \n html\n
{{ $cleanedID := replace (replace $id \"id=\\\"\" \"\") \"\\\"\" \"\" }}
\n

后加入

\n
\n
\n \n html\n
{{- $header := replaceRE \"<h[2-4].*?>((.|\\n])+?)</h[2-4]>\" \"$1\" $header -}}
\n

谜之 Template 语法

\n

看了 Hugo 的之后这才发现 Jinja 原来是真的漂亮简洁

\n

就问你and (ConditionA) (ConditionB) 是人话吗?没有not是什么?没有括号也叫 Function?怕是 Shell 用多了?

\n

not的正确打开方式:

\n
\n
\n \n html\n
{{ if eq (reflect.IsMap site.Params.address) false }}
\n

的确。。反人类

\n

奇奇怪怪的 Toml

\n

放着好好的 YAML 和 JSON 不用,突然冒出来一个 TOML。好像 Markdown 前面加 YAML 已经很常用了,然后跟我说用 TOML,不觉得等于号 有点 丑?不觉得 引号 有点 丑?虽然在做配置文件方面可能有独到之处,对不起,Vim 原生不支持,一键秒杀颜值。

\n

新的引擎

\n

突然,Hugo 给我换了一个引擎 https://github.com/gohugoio/hugo/releases/tag/v0.60.0

\n

使用了新的引擎之后之前的问题消失了,但是变成了默认忽略 HTML,需要在config.toml里做修改

\n
\n
\n \n toml\n
[markup.goldmark.renderer]\n    unsafe = true
\n

或者config.yml

\n
\n
\n \n yaml\n
markup:\n  goldmark:\n    renderer:\n      unsafe: true
\n

新引擎,新bug

\n

headerID

\n

Goldmark 在设置 headerID 的时候,默认是仅保留字母和数字,但是这对 CJK 及其不友好。“更有甚者”,该项目的作者是日本人,竟然不考虑对本国文字的支持?!还说,仅保留字母和数字是合适的默认行为。Excuse me? 不应该原封不动保留 Unicode 才是好的默认行为吗?

\n

据说会在 Hugo v0.63.0 解决,那就拭目以待吧。。

\n

render_link

\n

本来说好可以支持 link 的 Markdown hook 的,但是呢,只支持[text](url)型,不支持<url>型。。[摊手.jpg]

\n

而且,说好引用其他 Markdown 文档的 render_link 实现居然还有点复杂。。还好找到了官方实现,不然又是大坑。。

\n

Hugo theme,注定又是一个面向 GitHub 编程。

\n

TOC 新回复

\n

我又针对 toc 写了一篇博客 hugo-toc,然后有去原来的 gist 上评论了,受到了@looeee 的回复。他说。。。他已经弃坑 Hugo 了,使用 js 解决问题。

\n

我。。。

","createdAt":"2020-01-03T13:19:16.000Z","lastEditedAt":"2020-11-02T04:31:46.000Z","image":null,"imageLazy":"","serializedHeadings":"[]","labels":[{"id":"blog: programming","type":"blog","name":"programming","color":"60b3bc","path":"/blog/programming/"},{"id":"tag: hugo","type":"tag","name":"hugo","color":"ff3db4","path":"/tag/hugo/"}],"reactions":[],"comments":[]}},"context":{}} \ No newline at end of file diff --git a/assets/data/tag/docker/index.json b/assets/data/tag/docker/index.json index fe8dd3683..8802f5219 100644 --- a/assets/data/tag/docker/index.json +++ b/assets/data/tag/docker/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"label":{"id":"tag: docker","name":"docker","type":"tag","belongsTo":{"edges":[{"node":{"id":"75","title":"列出 USTC Docker 镜像的标签","path":"/post/docker-tag/","summary":"\n

不再为网络条件发愁!

\n","createdAt":"2020-02-28T13:38:39.000Z","lastEditedAt":"2020-06-21T12:56:24.000Z","image":null,"imageLazy":"","logo":{"src":"/blog/img/a41c7f22.png","lazySrc":"AJAAkDASIAAhEBAxEB/8QAFgABAQE$gME/8QAHhAAAgAGAwAAAAAAAAAAAAAAAAECAxESITJhgZH/xAAUAQE$AAB/8QAFREBAQ$ACH/2gAMAwEAAhEDEQA/AM6oliLoVsvj0mtQCK//2Q=="},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: docker","type":"tag","color":"71cfff","name":"docker","path":"/tag/docker/"}]}},{"node":{"id":"174","title":"Docker Cheatsheet","path":"/post/docker-cht/","summary":"\n

Docker commands are easily fogotten

\n","createdAt":"2021-07-31T09:58:17.000Z","lastEditedAt":"2021-09-11T13:38:15.000Z","image":"/blog/img/f498d489.png","imageLazy":"ACAAkDASIAAhEBAxEB/8QAFgABAQE$AID/8QAGBABAAMB$ECESL/xAAUAQE$AAC/8QAFREBAQ$AAH/2gAMAwEAAhEDEQA/AMLx2nAKjH//2Q==","logo":{"src":"/blog/img/a41c7f22.png","lazySrc":"AJAAkDASIAAhEBAxEB/8QAFgABAQE$gME/8QAHhAAAgAGAwAAAAAAAAAAAAAAAAECAxESITJhgZH/xAAUAQE$AAB/8QAFREBAQ$ACH/2gAMAwEAAhEDEQA/AM6oliLoVsvj0mtQCK//2Q=="},"labels":[{"id":"blog: cheatsheet","type":"blog","color":"6655d6","name":"cheatsheet","path":"/blog/cheatsheet/"},{"id":"tag: docker","type":"tag","color":"71cfff","name":"docker","path":"/tag/docker/"}]}}]}}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"label":{"id":"tag: docker","name":"docker","type":"tag","belongsTo":{"edges":[{"node":{"id":"75","title":"列出 USTC Docker 镜像的标签","path":"/post/docker-tag/","summary":"\n

不再为网络条件发愁!

\n","createdAt":"2020-02-28T13:38:39.000Z","lastEditedAt":"2020-06-21T12:56:24.000Z","image":null,"imageLazy":"","logo":{"src":"/blog/img/a41c7f22.png","lazySrc":"AJAAkDASIAAhEBAxEB/8QAFgABAQE$gME/8QAHhAAAgAGAwAAAAAAAAAAAAAAAAECAxESITJhgZH/xAAUAQE$AAB/8QAFREBAQ$ACH/2gAMAwEAAhEDEQA/AM6oliLoVsvj0mtQCK//2Q=="},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: docker","type":"tag","color":"71cfff","name":"docker","path":"/tag/docker/"}]}},{"node":{"id":"174","title":"Docker Cheatsheet","path":"/post/docker-cht/","summary":"\n

Docker commands are easily forgotten

\n

Above image from scmagazine.com

\n","createdAt":"2021-07-31T09:58:17.000Z","lastEditedAt":"2022-05-01T13:49:08.000Z","image":"/blog/img/6c88ced2.jpg","imageLazy":"AFAAkDASIAAhEBAxEB/8QAFQABAQ$AAL/xAAWEAEBAQ$ASL/xAAVAQEB$ACA//EABQRAQ$AAD/2gAMAwEAAhEDEQA/ALt2oDSf/9k=","logo":{"src":"/blog/img/a41c7f22.png","lazySrc":"AJAAkDASIAAhEBAxEB/8QAFgABAQE$gME/8QAHhAAAgAGAwAAAAAAAAAAAAAAAAECAxESITJhgZH/xAAUAQE$AAB/8QAFREBAQ$ACH/2gAMAwEAAhEDEQA/AM6oliLoVsvj0mtQCK//2Q=="},"labels":[{"id":"blog: cheatsheet","type":"blog","color":"6655d6","name":"cheatsheet","path":"/blog/cheatsheet/"},{"id":"tag: docker","type":"tag","color":"71cfff","name":"docker","path":"/tag/docker/"}]}}]}}},"context":{}} \ No newline at end of file diff --git a/assets/data/tag/hugo/index.json b/assets/data/tag/hugo/index.json index 0c8a8957f..98520cc27 100644 --- a/assets/data/tag/hugo/index.json +++ b/assets/data/tag/hugo/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"label":{"id":"tag: hugo","name":"hugo","type":"tag","belongsTo":{"edges":[{"node":{"id":"25","title":"TOC in Hugo","path":"/post/hugo-toc/","summary":"\n

What a challenge to build toc in hugo!

\n","createdAt":"2019-12-31T15:17:22.000Z","lastEditedAt":"2020-06-30T07:33:17.000Z","image":null,"imageLazy":"","logo":{"src":"/blog/img/706fec00.png","lazySrc":"AJAAkDASIAAhEBAxEB/8QAFgABAQE$gEE/8QAGhAAAgMBAQAAAAAAAAAAAAAAABEBAgMzcf/EABUBAQE$AEC/8QAFREBAQ$AAH/2gAMAwEAAhEDEQA/ADSmU4uU0Z1Ukc59CK7X/9k="},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: hugo","type":"tag","color":"ff3db4","name":"hugo","path":"/tag/hugo/"}]}},{"node":{"id":"28","title":"谜之 Hugo | AC's Blog","path":"/post/wtf-hugo/","summary":"","createdAt":"2020-01-03T13:19:16.000Z","lastEditedAt":"2020-11-02T04:31:46.000Z","image":null,"imageLazy":"","logo":{"src":"/blog/img/706fec00.png","lazySrc":"AJAAkDASIAAhEBAxEB/8QAFgABAQE$gEE/8QAGhAAAgMBAQAAAAAAAAAAAAAAABEBAgMzcf/EABUBAQE$AEC/8QAFREBAQ$AAH/2gAMAwEAAhEDEQA/ADSmU4uU0Z1Ukc59CK7X/9k="},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: hugo","type":"tag","color":"ff3db4","name":"hugo","path":"/tag/hugo/"}]}},{"node":{"id":"51","title":"Deploying Hugo with CircleCI","path":"/post/hugo-circle-ci/","summary":"\n

Building blog sites manually is tedious and often leads to mistakes, especially when hosting on master/docs

\n","createdAt":"2019-10-27T12:12:30.000Z","lastEditedAt":"2020-06-30T07:28:06.000Z","image":null,"imageLazy":"","logo":{"src":"/blog/img/706fec00.png","lazySrc":"AJAAkDASIAAhEBAxEB/8QAFgABAQE$gEE/8QAGhAAAgMBAQAAAAAAAAAAAAAAABEBAgMzcf/EABUBAQE$AEC/8QAFREBAQ$AAH/2gAMAwEAAhEDEQA/ADSmU4uU0Z1Ukc59CK7X/9k="},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: hugo","type":"tag","color":"ff3db4","name":"hugo","path":"/tag/hugo/"}]}}]}}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"label":{"id":"tag: hugo","name":"hugo","type":"tag","belongsTo":{"edges":[{"node":{"id":"25","title":"TOC in Hugo","path":"/post/hugo-toc/","summary":"\n

What a challenge to build toc in hugo!

\n","createdAt":"2019-12-31T15:17:22.000Z","lastEditedAt":"2020-06-30T07:33:17.000Z","image":null,"imageLazy":"","logo":{"src":"/blog/img/706fec00.png","lazySrc":"AJAAkDASIAAhEBAxEB/8QAFgABAQE$gEE/8QAGhAAAgMBAQAAAAAAAAAAAAAAABEBAgMzcf/EABUBAQE$AEC/8QAFREBAQ$AAH/2gAMAwEAAhEDEQA/ADSmU4uU0Z1Ukc59CK7X/9k="},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: hugo","type":"tag","color":"ff3db4","name":"hugo","path":"/tag/hugo/"}]}},{"node":{"id":"28","title":"谜之 Hugo | AC's Blog","path":"/post/wtf-hugo/","summary":"","createdAt":"2020-01-03T13:19:16.000Z","lastEditedAt":"2020-11-02T04:31:46.000Z","image":null,"imageLazy":"","logo":{"src":"/blog/img/706fec00.png","lazySrc":"AJAAkDASIAAhEBAxEB/8QAFgABAQE$gEE/8QAGhAAAgMBAQAAAAAAAAAAAAAAABEBAgMzcf/EABUBAQE$AEC/8QAFREBAQ$AAH/2gAMAwEAAhEDEQA/ADSmU4uU0Z1Ukc59CK7X/9k="},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: hugo","type":"tag","color":"ff3db4","name":"hugo","path":"/tag/hugo/"}]}},{"node":{"id":"51","title":"Deploying Hugo with CircleCI","path":"/post/hugo-circle-ci/","summary":"\n

Building blog sites manually is tedious and often leads to mistakes, especially when hosting on master/docs

\n","createdAt":"2019-10-27T12:12:30.000Z","lastEditedAt":"2020-06-30T07:28:06.000Z","image":null,"imageLazy":"","logo":{"src":"/blog/img/706fec00.png","lazySrc":"AJAAkDASIAAhEBAxEB/8QAFgABAQE$gEE/8QAGhAAAgMBAQAAAAAAAAAAAAAAABEBAgMzcf/EABUBAQE$AEC/8QAFREBAQ$AAH/2gAMAwEAAhEDEQA/ADSmU4uU0Z1Ukc59CK7X/9k="},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: hugo","type":"tag","color":"ff3db4","name":"hugo","path":"/tag/hugo/"}]}}]}}},"context":{}} \ No newline at end of file diff --git a/assets/data/tag/mysql/index.json b/assets/data/tag/mysql/index.json index 340fb772e..edd789f82 100644 --- a/assets/data/tag/mysql/index.json +++ b/assets/data/tag/mysql/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"label":{"id":"tag: mysql","name":"mysql","type":"tag","belongsTo":{"edges":[{"node":{"id":"22","title":"MySQL 存储 Emoji","path":"/post/mysql-emoji/","summary":"\n

最近需要使用 SQLAlchemy 存弹幕的内容,但是遇到了存 emoji 的问题。

\n","createdAt":"2019-12-23T14:21:24.000Z","lastEditedAt":"2020-09-26T07:41:43.000Z","image":null,"imageLazy":"","logo":{"src":"/blog/img/a1ba5d33.jpg","lazySrc":"AGAAkDASIAAhEBAxEB/8QAFQABAQ$AAT/xAAXEAEBAQEAAAAAAAAAAAAAAAAAIREx/8QAFAEB$AAAP/EABQRAQ$AAD/2gAMAwEAAhEDEQA/AK7vYAD/2Q=="},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: mysql","type":"tag","color":"f09011","name":"mysql","path":"/tag/mysql/"}]}},{"node":{"id":"71","title":"Mysql in WSL","path":"/post/mysql-in-wsl/","summary":"\n

😜 TL;DR

\n

查看日志/var/log/mysql/error.log,上互联网搜

\n","createdAt":"2020-02-07T08:59:02.000Z","lastEditedAt":"2020-06-24T05:55:14.000Z","image":null,"imageLazy":"","logo":{"src":"/blog/img/a1ba5d33.jpg","lazySrc":"AGAAkDASIAAhEBAxEB/8QAFQABAQ$AAT/xAAXEAEBAQEAAAAAAAAAAAAAAAAAIREx/8QAFAEB$AAAP/EABQRAQ$AAD/2gAMAwEAAhEDEQA/AK7vYAD/2Q=="},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: mysql","type":"tag","color":"f09011","name":"mysql","path":"/tag/mysql/"}]}},{"node":{"id":"164","title":"数据库查询性能优化手记","path":"/post/sql-query-perf/","summary":"\n

简单的手记,没有什么系统性。相关的变量名是经过简单替换的,以免关注点偏离(bushi

\n","createdAt":"2021-03-09T10:08:35.000Z","lastEditedAt":"2021-08-12T11:59:56.000Z","image":null,"imageLazy":"","logo":{"src":"/blog/img/a1ba5d33.jpg","lazySrc":"AGAAkDASIAAhEBAxEB/8QAFQABAQ$AAT/xAAXEAEBAQEAAAAAAAAAAAAAAAAAIREx/8QAFAEB$AAAP/EABQRAQ$AAD/2gAMAwEAAhEDEQA/AK7vYAD/2Q=="},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: mysql","type":"tag","color":"f09011","name":"mysql","path":"/tag/mysql/"}]}}]}}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"label":{"id":"tag: mysql","name":"mysql","type":"tag","belongsTo":{"edges":[{"node":{"id":"22","title":"MySQL 存储 Emoji","path":"/post/mysql-emoji/","summary":"\n

最近需要使用 SQLAlchemy 存弹幕的内容,但是遇到了存 emoji 的问题。

\n","createdAt":"2019-12-23T14:21:24.000Z","lastEditedAt":"2020-09-26T07:41:43.000Z","image":null,"imageLazy":"","logo":{"src":"/blog/img/a1ba5d33.jpg","lazySrc":"AGAAkDASIAAhEBAxEB/8QAFQABAQ$AAT/xAAXEAEBAQEAAAAAAAAAAAAAAAAAIREx/8QAFAEB$AAAP/EABQRAQ$AAD/2gAMAwEAAhEDEQA/AK7vYAD/2Q=="},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: mysql","type":"tag","color":"f09011","name":"mysql","path":"/tag/mysql/"}]}},{"node":{"id":"71","title":"Mysql in WSL","path":"/post/mysql-in-wsl/","summary":"\n

😜 TL;DR

\n

查看日志/var/log/mysql/error.log,上互联网搜

\n","createdAt":"2020-02-07T08:59:02.000Z","lastEditedAt":"2020-06-24T05:55:14.000Z","image":null,"imageLazy":"","logo":{"src":"/blog/img/a1ba5d33.jpg","lazySrc":"AGAAkDASIAAhEBAxEB/8QAFQABAQ$AAT/xAAXEAEBAQEAAAAAAAAAAAAAAAAAIREx/8QAFAEB$AAAP/EABQRAQ$AAD/2gAMAwEAAhEDEQA/AK7vYAD/2Q=="},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: mysql","type":"tag","color":"f09011","name":"mysql","path":"/tag/mysql/"}]}},{"node":{"id":"164","title":"数据库查询性能优化手记","path":"/post/sql-query-perf/","summary":"\n

简单的手记,没有什么系统性。相关的变量名是经过简单替换的,以免关注点偏离(bushi

\n","createdAt":"2021-03-09T10:08:35.000Z","lastEditedAt":"2021-08-12T11:59:56.000Z","image":null,"imageLazy":"","logo":{"src":"/blog/img/a1ba5d33.jpg","lazySrc":"AGAAkDASIAAhEBAxEB/8QAFQABAQ$AAT/xAAXEAEBAQEAAAAAAAAAAAAAAAAAIREx/8QAFAEB$AAAP/EABQRAQ$AAD/2gAMAwEAAhEDEQA/AK7vYAD/2Q=="},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: mysql","type":"tag","color":"f09011","name":"mysql","path":"/tag/mysql/"}]}}]}}},"context":{}} \ No newline at end of file diff --git a/assets/data/tag/pwa/index.json b/assets/data/tag/pwa/index.json index 0718005e0..6119ef22a 100644 --- a/assets/data/tag/pwa/index.json +++ b/assets/data/tag/pwa/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"label":{"id":"tag: pwa","name":"pwa","type":"tag","belongsTo":{"edges":[{"node":{"id":"68","title":"用 Vue 做 PWA(一):开始","path":"/post/vue-pwa-1/","summary":"\n

Vue 党快速上手 PWA

\n","createdAt":"2020-01-30T07:59:20.000Z","lastEditedAt":"2020-09-04T23:59:48.000Z","image":"/blog/img/bda492f1.png","imageLazy":"ADAAkDASIAAhEBAxEB/8QAFgABAQE$AEE/8QAGRAAAgMB$ECEVGR/8QAFAEB$AAAv/EABYRAQEB$ABAv/aAAwDAQACEQMRAD8A0SVQdaToAaWX/9k=","logo":{"src":"/blog/img/32740c5b.png","lazySrc":"ADAAkDASIAAhEBAxEB/8QAFgABAQE$AEE/8QAFxABAQEBAAAAAAAAAAAAAAAAAFEBEf/EABUBAQE$AAB/8QAFREBAQ$AAH/2gAMAwEAAhEDEQA/AM1XmQFo/9k="},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: vue","type":"tag","color":"41b883","name":"vue","path":"/tag/vue/"},{"id":"tag: pwa","type":"tag","color":"5a0fc8","name":"pwa","path":"/tag/pwa/"},{"id":"series: vue-pwa","type":"series","color":"41b883","name":"vue-pwa","path":"/series/vue-pwa/"}]}},{"node":{"id":"87","title":"用 Vue 做 PWA(二):Runtime Caching","path":"/post/vue-pwa-2/","summary":"\n

本系列已经很久没更新了,在此讨论实践过程中遇到的 Runtime Caching 的一些问题。主要注意点是在处理跨域请求上。

\n","createdAt":"2020-04-18T04:49:20.000Z","lastEditedAt":"2021-03-13T00:27:46.000Z","image":"/blog/img/bda492f1.png","imageLazy":"ADAAkDASIAAhEBAxEB/8QAFgABAQE$AEE/8QAGRAAAgMB$ECEVGR/8QAFAEB$AAAv/EABYRAQEB$ABAv/aAAwDAQACEQMRAD8A0SVQdaToAaWX/9k=","logo":{"src":"/blog/img/32740c5b.png","lazySrc":"ADAAkDASIAAhEBAxEB/8QAFgABAQE$AEE/8QAFxABAQEBAAAAAAAAAAAAAAAAAFEBEf/EABUBAQE$AAB/8QAFREBAQ$AAH/2gAMAwEAAhEDEQA/AM1XmQFo/9k="},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: vue","type":"tag","color":"41b883","name":"vue","path":"/tag/vue/"},{"id":"tag: pwa","type":"tag","color":"5a0fc8","name":"pwa","path":"/tag/pwa/"},{"id":"series: vue-pwa","type":"series","color":"41b883","name":"vue-pwa","path":"/series/vue-pwa/"}]}},{"node":{"id":"147","title":"用 Vue 做 PWA (三):理解生命周期","path":"/post/vue-pwa-3/","summary":"\n

虽然前两节的内容足以写出能用的 service worker,但是如果深入细节,仍然会有一些“匪夷所思”的现象发生。

\n","createdAt":"2020-09-03T02:40:57.000Z","lastEditedAt":"2020-09-08T03:09:07.000Z","image":"/blog/img/bda492f1.png","imageLazy":"ADAAkDASIAAhEBAxEB/8QAFgABAQE$AEE/8QAGRAAAgMB$ECEVGR/8QAFAEB$AAAv/EABYRAQEB$ABAv/aAAwDAQACEQMRAD8A0SVQdaToAaWX/9k=","logo":{"src":"/blog/img/32740c5b.png","lazySrc":"ADAAkDASIAAhEBAxEB/8QAFgABAQE$AEE/8QAFxABAQEBAAAAAAAAAAAAAAAAAFEBEf/EABUBAQE$AAB/8QAFREBAQ$AAH/2gAMAwEAAhEDEQA/AM1XmQFo/9k="},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: vue","type":"tag","color":"41b883","name":"vue","path":"/tag/vue/"},{"id":"tag: pwa","type":"tag","color":"5a0fc8","name":"pwa","path":"/tag/pwa/"},{"id":"series: vue-pwa","type":"series","color":"41b883","name":"vue-pwa","path":"/series/vue-pwa/"}]}},{"node":{"id":"149","title":"Gridsome is not that Ready for PWA","path":"/post/gridsome-pwa/","summary":"\n

How I am tring to build a nice gridsome-generated site with PWA support

\n

虽然可以勉强并入“用 Vue 做 PWA”系列,但想着能让更多人看到,就还是用国际语言吧

\n","createdAt":"2020-09-16T03:22:59.000Z","lastEditedAt":"2021-07-03T13:06:53.000Z","image":null,"imageLazy":"","logo":{"src":"/blog/img/aadfb904.png","lazySrc":"AJAAkDASIAAhEBAxEB/8QAFgABAQE$AME/8QAHBAAAgICAwAAAAAAAAAAAAAAAAECAxESITNR/8QAFQEBAQ$AAL/xAAVEQEB$AAAf/aAAwDAQACEQMRAD8ARjB1pLGuOWZ9a/RHpZIIkf/Z"},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: vue","type":"tag","color":"41b883","name":"vue","path":"/tag/vue/"},{"id":"tag: pwa","type":"tag","color":"5a0fc8","name":"pwa","path":"/tag/pwa/"},{"id":"tag: gridsome","type":"tag","color":"00a672","name":"gridsome","path":"/tag/gridsome/"}]}},{"node":{"id":"173","title":"`skipWaiting()` with `StaleWhileRevalidate` the right way","path":"/post/pwa-skipwaiting/","summary":"\n

It is common to use workbox StaleWhileRevalidate strategy to cache resources which may take some time to fetch. Usually the resource needs to be updated but not immediately. However if the resource request takes too much time to complete, service worker's life cycle and some functionality may be impacted, especially self.skipWaiting().

\n","createdAt":"2021-06-28T04:31:10.000Z","lastEditedAt":"2021-06-28T10:11:41.000Z","image":null,"imageLazy":"","logo":{"src":"/blog/img/32740c5b.png","lazySrc":"ADAAkDASIAAhEBAxEB/8QAFgABAQE$AEE/8QAFxABAQEBAAAAAAAAAAAAAAAAAFEBEf/EABUBAQE$AAB/8QAFREBAQ$AAH/2gAMAwEAAhEDEQA/AM1XmQFo/9k="},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: pwa","type":"tag","color":"5a0fc8","name":"pwa","path":"/tag/pwa/"}]}}]}}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"label":{"id":"tag: pwa","name":"pwa","type":"tag","belongsTo":{"edges":[{"node":{"id":"68","title":"用 Vue 做 PWA(一):开始","path":"/post/vue-pwa-1/","summary":"\n

Vue 党快速上手 PWA

\n","createdAt":"2020-01-30T07:59:20.000Z","lastEditedAt":"2020-09-04T23:59:48.000Z","image":"/blog/img/bda492f1.png","imageLazy":"ADAAkDASIAAhEBAxEB/8QAFgABAQE$AEE/8QAGRAAAgMB$ECEVGR/8QAFAEB$AAAv/EABYRAQEB$ABAv/aAAwDAQACEQMRAD8A0SVQdaToAaWX/9k=","logo":{"src":"/blog/img/32740c5b.png","lazySrc":"ADAAkDASIAAhEBAxEB/8QAFgABAQE$AEE/8QAFxABAQEBAAAAAAAAAAAAAAAAAFEBEf/EABUBAQE$AAB/8QAFREBAQ$AAH/2gAMAwEAAhEDEQA/AM1XmQFo/9k="},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: vue","type":"tag","color":"41b883","name":"vue","path":"/tag/vue/"},{"id":"tag: pwa","type":"tag","color":"5a0fc8","name":"pwa","path":"/tag/pwa/"},{"id":"series: vue-pwa","type":"series","color":"41b883","name":"vue-pwa","path":"/series/vue-pwa/"}]}},{"node":{"id":"87","title":"用 Vue 做 PWA(二):Runtime Caching","path":"/post/vue-pwa-2/","summary":"\n

本系列已经很久没更新了,在此讨论实践过程中遇到的 Runtime Caching 的一些问题。主要注意点是在处理跨域请求上。

\n","createdAt":"2020-04-18T04:49:20.000Z","lastEditedAt":"2021-03-13T00:27:46.000Z","image":"/blog/img/bda492f1.png","imageLazy":"ADAAkDASIAAhEBAxEB/8QAFgABAQE$AEE/8QAGRAAAgMB$ECEVGR/8QAFAEB$AAAv/EABYRAQEB$ABAv/aAAwDAQACEQMRAD8A0SVQdaToAaWX/9k=","logo":{"src":"/blog/img/32740c5b.png","lazySrc":"ADAAkDASIAAhEBAxEB/8QAFgABAQE$AEE/8QAFxABAQEBAAAAAAAAAAAAAAAAAFEBEf/EABUBAQE$AAB/8QAFREBAQ$AAH/2gAMAwEAAhEDEQA/AM1XmQFo/9k="},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: vue","type":"tag","color":"41b883","name":"vue","path":"/tag/vue/"},{"id":"tag: pwa","type":"tag","color":"5a0fc8","name":"pwa","path":"/tag/pwa/"},{"id":"series: vue-pwa","type":"series","color":"41b883","name":"vue-pwa","path":"/series/vue-pwa/"}]}},{"node":{"id":"147","title":"用 Vue 做 PWA (三):理解生命周期","path":"/post/vue-pwa-3/","summary":"\n

虽然前两节的内容足以写出能用的 service worker,但是如果深入细节,仍然会有一些“匪夷所思”的现象发生。

\n","createdAt":"2020-09-03T02:40:57.000Z","lastEditedAt":"2020-09-08T03:09:07.000Z","image":"/blog/img/bda492f1.png","imageLazy":"ADAAkDASIAAhEBAxEB/8QAFgABAQE$AEE/8QAGRAAAgMB$ECEVGR/8QAFAEB$AAAv/EABYRAQEB$ABAv/aAAwDAQACEQMRAD8A0SVQdaToAaWX/9k=","logo":{"src":"/blog/img/32740c5b.png","lazySrc":"ADAAkDASIAAhEBAxEB/8QAFgABAQE$AEE/8QAFxABAQEBAAAAAAAAAAAAAAAAAFEBEf/EABUBAQE$AAB/8QAFREBAQ$AAH/2gAMAwEAAhEDEQA/AM1XmQFo/9k="},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: vue","type":"tag","color":"41b883","name":"vue","path":"/tag/vue/"},{"id":"tag: pwa","type":"tag","color":"5a0fc8","name":"pwa","path":"/tag/pwa/"},{"id":"series: vue-pwa","type":"series","color":"41b883","name":"vue-pwa","path":"/series/vue-pwa/"}]}},{"node":{"id":"149","title":"Gridsome is not that Ready for PWA","path":"/post/gridsome-pwa/","summary":"\n

How I am tring to build a nice gridsome-generated site with PWA support

\n

虽然可以勉强并入“用 Vue 做 PWA”系列,但想着能让更多人看到,就还是用国际语言吧

\n","createdAt":"2020-09-16T03:22:59.000Z","lastEditedAt":"2021-07-03T13:06:53.000Z","image":null,"imageLazy":"","logo":{"src":"/blog/img/aadfb904.png","lazySrc":"AJAAkDASIAAhEBAxEB/8QAFgABAQE$AME/8QAHBAAAgICAwAAAAAAAAAAAAAAAAECAxESITNR/8QAFQEBAQ$AAL/xAAVEQEB$AAAf/aAAwDAQACEQMRAD8ARjB1pLGuOWZ9a/RHpZIIkf/Z"},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: vue","type":"tag","color":"41b883","name":"vue","path":"/tag/vue/"},{"id":"tag: pwa","type":"tag","color":"5a0fc8","name":"pwa","path":"/tag/pwa/"},{"id":"tag: gridsome","type":"tag","color":"00a672","name":"gridsome","path":"/tag/gridsome/"}]}},{"node":{"id":"173","title":"`skipWaiting()` with `StaleWhileRevalidate` the right way","path":"/post/pwa-skipwaiting/","summary":"\n

It is common to use workbox StaleWhileRevalidate strategy to cache resources which may take some time to fetch. Usually the resource needs to be updated but not immediately. However if the resource request takes too much time to complete, service worker's life cycle and some functionality may be impacted, especially self.skipWaiting().

\n","createdAt":"2021-06-28T04:31:10.000Z","lastEditedAt":"2021-06-28T10:11:41.000Z","image":null,"imageLazy":"","logo":{"src":"/blog/img/32740c5b.png","lazySrc":"ADAAkDASIAAhEBAxEB/8QAFgABAQE$AEE/8QAFxABAQEBAAAAAAAAAAAAAAAAAFEBEf/EABUBAQE$AAB/8QAFREBAQ$AAH/2gAMAwEAAhEDEQA/AM1XmQFo/9k="},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: pwa","type":"tag","color":"5a0fc8","name":"pwa","path":"/tag/pwa/"}]}}]}}},"context":{}} \ No newline at end of file diff --git a/assets/data/tag/vue/index.json b/assets/data/tag/vue/index.json index f717b662b..43b0f6e9e 100644 --- a/assets/data/tag/vue/index.json +++ b/assets/data/tag/vue/index.json @@ -1 +1 @@ -{"hash":"gridsome","data":{"label":{"id":"tag: vue","name":"vue","type":"tag","belongsTo":{"edges":[{"node":{"id":"68","title":"用 Vue 做 PWA(一):开始","path":"/post/vue-pwa-1/","summary":"\n

Vue 党快速上手 PWA

\n","createdAt":"2020-01-30T07:59:20.000Z","lastEditedAt":"2020-09-04T23:59:48.000Z","image":"/blog/img/bda492f1.png","imageLazy":"ADAAkDASIAAhEBAxEB/8QAFgABAQE$AEE/8QAGRAAAgMB$ECEVGR/8QAFAEB$AAAv/EABYRAQEB$ABAv/aAAwDAQACEQMRAD8A0SVQdaToAaWX/9k=","logo":{"src":"/blog/img/32740c5b.png","lazySrc":"ADAAkDASIAAhEBAxEB/8QAFgABAQE$AEE/8QAFxABAQEBAAAAAAAAAAAAAAAAAFEBEf/EABUBAQE$AAB/8QAFREBAQ$AAH/2gAMAwEAAhEDEQA/AM1XmQFo/9k="},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: vue","type":"tag","color":"41b883","name":"vue","path":"/tag/vue/"},{"id":"tag: pwa","type":"tag","color":"5a0fc8","name":"pwa","path":"/tag/pwa/"},{"id":"series: vue-pwa","type":"series","color":"41b883","name":"vue-pwa","path":"/series/vue-pwa/"}]}},{"node":{"id":"80","title":"Vue 加 Hammer 处理手势","path":"/post/vue-hammer/","summary":"\n

接近原生 App 体验,手势是少不了的。但是无论是 Vue 还是 Vuetify 对手势的支持并不好,于是需要 Hammer.js 支持。

\n","createdAt":"2020-01-27T08:33:10.000Z","lastEditedAt":"2020-07-03T02:20:23.000Z","image":null,"imageLazy":"","logo":{"src":"/blog/img/f846bbd9.png","lazySrc":"AJAAkDASIAAhEBAxEB/8QAFgABAQE$wAC/8QAGxABAAICAwAAAAAAAAAAAAAAAQACERIhUfD/xAAVAQEB$AAAf/EABURAQE$AAR/9oADAMBAAIRAxEAPwABq03QycY7h728TMoSP//Z"},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: vue","type":"tag","color":"41b883","name":"vue","path":"/tag/vue/"}]}},{"node":{"id":"87","title":"用 Vue 做 PWA(二):Runtime Caching","path":"/post/vue-pwa-2/","summary":"\n

本系列已经很久没更新了,在此讨论实践过程中遇到的 Runtime Caching 的一些问题。主要注意点是在处理跨域请求上。

\n","createdAt":"2020-04-18T04:49:20.000Z","lastEditedAt":"2021-03-13T00:27:46.000Z","image":"/blog/img/bda492f1.png","imageLazy":"ADAAkDASIAAhEBAxEB/8QAFgABAQE$AEE/8QAGRAAAgMB$ECEVGR/8QAFAEB$AAAv/EABYRAQEB$ABAv/aAAwDAQACEQMRAD8A0SVQdaToAaWX/9k=","logo":{"src":"/blog/img/32740c5b.png","lazySrc":"ADAAkDASIAAhEBAxEB/8QAFgABAQE$AEE/8QAFxABAQEBAAAAAAAAAAAAAAAAAFEBEf/EABUBAQE$AAB/8QAFREBAQ$AAH/2gAMAwEAAhEDEQA/AM1XmQFo/9k="},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: vue","type":"tag","color":"41b883","name":"vue","path":"/tag/vue/"},{"id":"tag: pwa","type":"tag","color":"5a0fc8","name":"pwa","path":"/tag/pwa/"},{"id":"series: vue-pwa","type":"series","color":"41b883","name":"vue-pwa","path":"/series/vue-pwa/"}]}},{"node":{"id":"96","title":"Writing Blog with GitHub Issue and Vue Apollo","path":"/post/vue-iblog/","summary":"\n

Some tricks when using vue-apollo to communicate with GitHub API v4

\n","createdAt":"2020-04-24T00:00:00.000Z","lastEditedAt":"2020-09-04T07:47:20.000Z","image":"/blog/img/e4670a56.png","imageLazy":"ADAAkDASIAAhEBAxEB/8QAFgABAQE$AEE/8QAFRABAQ$ABH/xAAUAQE$AAA/8QAFBEB$AAAP/aAAwDAQACEQMRAD8A2RQB/9k=","logo":{"src":"/blog/img/f846bbd9.png","lazySrc":"AJAAkDASIAAhEBAxEB/8QAFgABAQE$wAC/8QAGxABAAICAwAAAAAAAAAAAAAAAQACERIhUfD/xAAVAQEB$AAAf/EABURAQE$AAR/9oADAMBAAIRAxEAPwABq03QycY7h728TMoSP//Z"},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: vue","type":"tag","color":"41b883","name":"vue","path":"/tag/vue/"}]}},{"node":{"id":"147","title":"用 Vue 做 PWA (三):理解生命周期","path":"/post/vue-pwa-3/","summary":"\n

虽然前两节的内容足以写出能用的 service worker,但是如果深入细节,仍然会有一些“匪夷所思”的现象发生。

\n","createdAt":"2020-09-03T02:40:57.000Z","lastEditedAt":"2020-09-08T03:09:07.000Z","image":"/blog/img/bda492f1.png","imageLazy":"ADAAkDASIAAhEBAxEB/8QAFgABAQE$AEE/8QAGRAAAgMB$ECEVGR/8QAFAEB$AAAv/EABYRAQEB$ABAv/aAAwDAQACEQMRAD8A0SVQdaToAaWX/9k=","logo":{"src":"/blog/img/32740c5b.png","lazySrc":"ADAAkDASIAAhEBAxEB/8QAFgABAQE$AEE/8QAFxABAQEBAAAAAAAAAAAAAAAAAFEBEf/EABUBAQE$AAB/8QAFREBAQ$AAH/2gAMAwEAAhEDEQA/AM1XmQFo/9k="},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: vue","type":"tag","color":"41b883","name":"vue","path":"/tag/vue/"},{"id":"tag: pwa","type":"tag","color":"5a0fc8","name":"pwa","path":"/tag/pwa/"},{"id":"series: vue-pwa","type":"series","color":"41b883","name":"vue-pwa","path":"/series/vue-pwa/"}]}},{"node":{"id":"149","title":"Gridsome is not that Ready for PWA","path":"/post/gridsome-pwa/","summary":"\n

How I am tring to build a nice gridsome-generated site with PWA support

\n

虽然可以勉强并入“用 Vue 做 PWA”系列,但想着能让更多人看到,就还是用国际语言吧

\n","createdAt":"2020-09-16T03:22:59.000Z","lastEditedAt":"2021-07-03T13:06:53.000Z","image":null,"imageLazy":"","logo":{"src":"/blog/img/aadfb904.png","lazySrc":"AJAAkDASIAAhEBAxEB/8QAFgABAQE$AME/8QAHBAAAgICAwAAAAAAAAAAAAAAAAECAxESITNR/8QAFQEBAQ$AAL/xAAVEQEB$AAAf/aAAwDAQACEQMRAD8ARjB1pLGuOWZ9a/RHpZIIkf/Z"},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: vue","type":"tag","color":"41b883","name":"vue","path":"/tag/vue/"},{"id":"tag: pwa","type":"tag","color":"5a0fc8","name":"pwa","path":"/tag/pwa/"},{"id":"tag: gridsome","type":"tag","color":"00a672","name":"gridsome","path":"/tag/gridsome/"}]}}]}}},"context":{}} \ No newline at end of file +{"hash":"gridsome","data":{"label":{"id":"tag: vue","name":"vue","type":"tag","belongsTo":{"edges":[{"node":{"id":"68","title":"用 Vue 做 PWA(一):开始","path":"/post/vue-pwa-1/","summary":"\n

Vue 党快速上手 PWA

\n","createdAt":"2020-01-30T07:59:20.000Z","lastEditedAt":"2020-09-04T23:59:48.000Z","image":"/blog/img/bda492f1.png","imageLazy":"ADAAkDASIAAhEBAxEB/8QAFgABAQE$AEE/8QAGRAAAgMB$ECEVGR/8QAFAEB$AAAv/EABYRAQEB$ABAv/aAAwDAQACEQMRAD8A0SVQdaToAaWX/9k=","logo":{"src":"/blog/img/32740c5b.png","lazySrc":"ADAAkDASIAAhEBAxEB/8QAFgABAQE$AEE/8QAFxABAQEBAAAAAAAAAAAAAAAAAFEBEf/EABUBAQE$AAB/8QAFREBAQ$AAH/2gAMAwEAAhEDEQA/AM1XmQFo/9k="},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: vue","type":"tag","color":"41b883","name":"vue","path":"/tag/vue/"},{"id":"tag: pwa","type":"tag","color":"5a0fc8","name":"pwa","path":"/tag/pwa/"},{"id":"series: vue-pwa","type":"series","color":"41b883","name":"vue-pwa","path":"/series/vue-pwa/"}]}},{"node":{"id":"80","title":"Vue 加 Hammer 处理手势","path":"/post/vue-hammer/","summary":"\n

接近原生 App 体验,手势是少不了的。但是无论是 Vue 还是 Vuetify 对手势的支持并不好,于是需要 Hammer.js 支持。

\n","createdAt":"2020-01-27T08:33:10.000Z","lastEditedAt":"2020-07-03T02:20:23.000Z","image":null,"imageLazy":"","logo":{"src":"/blog/img/f846bbd9.png","lazySrc":"AJAAkDASIAAhEBAxEB/8QAFgABAQE$wAC/8QAGxABAAICAwAAAAAAAAAAAAAAAQACERIhUfD/xAAVAQEB$AAAf/EABURAQE$AAR/9oADAMBAAIRAxEAPwABq03QycY7h728TMoSP//Z"},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: vue","type":"tag","color":"41b883","name":"vue","path":"/tag/vue/"}]}},{"node":{"id":"87","title":"用 Vue 做 PWA(二):Runtime Caching","path":"/post/vue-pwa-2/","summary":"\n

本系列已经很久没更新了,在此讨论实践过程中遇到的 Runtime Caching 的一些问题。主要注意点是在处理跨域请求上。

\n","createdAt":"2020-04-18T04:49:20.000Z","lastEditedAt":"2021-03-13T00:27:46.000Z","image":"/blog/img/bda492f1.png","imageLazy":"ADAAkDASIAAhEBAxEB/8QAFgABAQE$AEE/8QAGRAAAgMB$ECEVGR/8QAFAEB$AAAv/EABYRAQEB$ABAv/aAAwDAQACEQMRAD8A0SVQdaToAaWX/9k=","logo":{"src":"/blog/img/32740c5b.png","lazySrc":"ADAAkDASIAAhEBAxEB/8QAFgABAQE$AEE/8QAFxABAQEBAAAAAAAAAAAAAAAAAFEBEf/EABUBAQE$AAB/8QAFREBAQ$AAH/2gAMAwEAAhEDEQA/AM1XmQFo/9k="},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: vue","type":"tag","color":"41b883","name":"vue","path":"/tag/vue/"},{"id":"tag: pwa","type":"tag","color":"5a0fc8","name":"pwa","path":"/tag/pwa/"},{"id":"series: vue-pwa","type":"series","color":"41b883","name":"vue-pwa","path":"/series/vue-pwa/"}]}},{"node":{"id":"96","title":"Writing Blog with GitHub Issue and Vue Apollo","path":"/post/vue-iblog/","summary":"\n

Some tricks when using vue-apollo to communicate with GitHub API v4

\n","createdAt":"2020-04-24T00:00:00.000Z","lastEditedAt":"2020-09-04T07:47:20.000Z","image":"/blog/img/e4670a56.png","imageLazy":"ADAAkDASIAAhEBAxEB/8QAFgABAQE$AEE/8QAFRABAQ$ABH/xAAUAQE$AAA/8QAFBEB$AAAP/aAAwDAQACEQMRAD8A2RQB/9k=","logo":{"src":"/blog/img/f846bbd9.png","lazySrc":"AJAAkDASIAAhEBAxEB/8QAFgABAQE$wAC/8QAGxABAAICAwAAAAAAAAAAAAAAAQACERIhUfD/xAAVAQEB$AAAf/EABURAQE$AAR/9oADAMBAAIRAxEAPwABq03QycY7h728TMoSP//Z"},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: vue","type":"tag","color":"41b883","name":"vue","path":"/tag/vue/"}]}},{"node":{"id":"147","title":"用 Vue 做 PWA (三):理解生命周期","path":"/post/vue-pwa-3/","summary":"\n

虽然前两节的内容足以写出能用的 service worker,但是如果深入细节,仍然会有一些“匪夷所思”的现象发生。

\n","createdAt":"2020-09-03T02:40:57.000Z","lastEditedAt":"2020-09-08T03:09:07.000Z","image":"/blog/img/bda492f1.png","imageLazy":"ADAAkDASIAAhEBAxEB/8QAFgABAQE$AEE/8QAGRAAAgMB$ECEVGR/8QAFAEB$AAAv/EABYRAQEB$ABAv/aAAwDAQACEQMRAD8A0SVQdaToAaWX/9k=","logo":{"src":"/blog/img/32740c5b.png","lazySrc":"ADAAkDASIAAhEBAxEB/8QAFgABAQE$AEE/8QAFxABAQEBAAAAAAAAAAAAAAAAAFEBEf/EABUBAQE$AAB/8QAFREBAQ$AAH/2gAMAwEAAhEDEQA/AM1XmQFo/9k="},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: vue","type":"tag","color":"41b883","name":"vue","path":"/tag/vue/"},{"id":"tag: pwa","type":"tag","color":"5a0fc8","name":"pwa","path":"/tag/pwa/"},{"id":"series: vue-pwa","type":"series","color":"41b883","name":"vue-pwa","path":"/series/vue-pwa/"}]}},{"node":{"id":"149","title":"Gridsome is not that Ready for PWA","path":"/post/gridsome-pwa/","summary":"\n

How I am tring to build a nice gridsome-generated site with PWA support

\n

虽然可以勉强并入“用 Vue 做 PWA”系列,但想着能让更多人看到,就还是用国际语言吧

\n","createdAt":"2020-09-16T03:22:59.000Z","lastEditedAt":"2021-07-03T13:06:53.000Z","image":null,"imageLazy":"","logo":{"src":"/blog/img/aadfb904.png","lazySrc":"AJAAkDASIAAhEBAxEB/8QAFgABAQE$AME/8QAHBAAAgICAwAAAAAAAAAAAAAAAAECAxESITNR/8QAFQEBAQ$AAL/xAAVEQEB$AAAf/aAAwDAQACEQMRAD8ARjB1pLGuOWZ9a/RHpZIIkf/Z"},"labels":[{"id":"blog: programming","type":"blog","color":"60b3bc","name":"programming","path":"/blog/programming/"},{"id":"tag: vue","type":"tag","color":"41b883","name":"vue","path":"/tag/vue/"},{"id":"tag: pwa","type":"tag","color":"5a0fc8","name":"pwa","path":"/tag/pwa/"},{"id":"tag: gridsome","type":"tag","color":"00a672","name":"gridsome","path":"/tag/gridsome/"}]}}]}}},"context":{}} \ No newline at end of file diff --git a/blog/cheatsheet/index.html b/blog/cheatsheet/index.html index 4af6e4981..e11751916 100644 --- a/blog/cheatsheet/index.html +++ b/blog/cheatsheet/index.html @@ -16,10 +16,11 @@ } -
AC Dustbin
Hit Enter to do fulltext search on GitHub
WSL Cheatsheet
2021-10-05 10:49 2021-10-14 02:17
+
AC Dustbin
Hit Enter to do fulltext search on GitHub
WSL Cheatsheet
2021-10-05 10:49 2021-10-14 02:17

WSL issues or tricks I had to search for again and again

-
Docker Cheatsheet
2021-07-31 09:58 2021-09-11 13:38
-

Docker commands are easily fogotten

+
Docker Cheatsheet
2021-07-31 09:58 2022-05-01 13:49
+

Docker commands are easily forgotten

+

Above image from scmagazine.com

高频公式表
2021-04-05 00:51 2021-04-05 01:01

名书镇贴

东西慢慢加进来好了,顺便看看公式渲染的情况。

@@ -32,6 +33,6 @@
FFmpeg Cheatsheet
2020-03-21 23:56 2020-06-30 09:14
换源集合
2020-02-28 14:29 2021-12-09 02:49

各种换源,我要的都在这里了。针对天朝就不用国际语言了 😄

- + diff --git a/blog/moments/index.html b/blog/moments/index.html index 1933b1dfd..34e208170 100644 --- a/blog/moments/index.html +++ b/blog/moments/index.html @@ -16,7 +16,9 @@ } -
AC Dustbin
Hit Enter to do fulltext search on GitHub
腾讯会议 Linux 客户端无法正常结束共享的 Workaround
2022-02-27 09:02 2022-02-27 09:02
+
AC Dustbin
Hit Enter to do fulltext search on GitHub
免费下网易云会员歌曲 MP3 竟如此简单?
2022-05-01 08:50 2022-05-01 08:50
+

我坦白,我是标题党

+
腾讯会议 Linux 客户端无法正常结束共享的 Workaround
2022-02-27 09:02 2022-02-27 09:02

就改下窗口状态的事儿

听 PingCAP 黄东旭谈开源社区与文化
2021-11-20 11:15 2021-11-20 11:23

旁听《开源软件与技术》课程特邀讲座,甚至白拿了一个 TiDB 的周边小礼品(会的 USB 线)

@@ -44,6 +46,6 @@
Lucky GitHub Number
2020-01-13 08:07 2020-06-30 07:08

Record the lucky GitHub contribution number.

Why is EEPROM called "ROM"?
2020-01-07 12:16 2020-07-10 12:43
- + diff --git a/blog/programming/index.html b/blog/programming/index.html index 2d39dd4e8..ed10a4d92 100644 --- a/blog/programming/index.html +++ b/blog/programming/index.html @@ -24,8 +24,10 @@ } -
AC Dustbin
Hit Enter to do fulltext search on GitHub
`skipWaiting()` with `StaleWhileRevalidate` the right way
2021-06-28 04:31 2021-06-28 10:11
-

It is common to use workbox StaleWhileRevalidate strategy to cache resources which may take some time to fetch. Usually the resource needs to be updated but not immediately. However if the resource request takes too much time to complete, service worker's life cycle and some functionality may be impacted, especially self.skipWaiting().

+
AC Dustbin
Hit Enter to do fulltext search on GitHub
Another Move of My Blog?
2022-04-06 04:00 2022-05-01 08:55
+

It really needs a serious discussion.

+
`skipWaiting()` with `StaleWhileRevalidate` the right way
2021-06-28 04:31 2021-06-28 10:11
+

It is common to use workbox StaleWhileRevalidate strategy to cache resources which may take some time to fetch. Usually the resource needs to be updated but not immediately. However if the resource request takes too much time to complete, service worker's life cycle and some functionality may be impacted, especially self.skipWaiting().

Value of Open Source Software: A Personal View
2021-06-25 10:35 2021-06-25 10:35

This article was submitted as an English class essay. Originally, I wanted to talk about open source landscape in China, but I found that just explaining what open source is takes hundreds of words. (这就是你把本来想写的部分鸽掉的理由?

数据库查询性能优化手记
2021-03-09 10:08 2021-08-12 11:59
@@ -71,7 +73,7 @@

Intl 是个好东西,但是还不是很强大,浏览器支持上也有 Node 和 Android 的小缺憾。

Mysql in WSL
2020-02-07 08:59 2020-06-24 05:55

😜 TL;DR

-

查看日志/var/log/mysql/error.log,上互联网搜

+

查看日志/var/log/mysql/error.log,上互联网搜

From Python to JavaScript
2020-02-07 04:23 2021-02-22 08:52

Although the title Top Ten Mistakes Python Programmers Make When Learning JavaScript may sound better, I have only noticed a few mistakes now. And I will update this post when I make more mistakes 😄

异步 & 异步获取命令输出
2020-02-05 10:53 2020-06-30 08:58
@@ -79,7 +81,7 @@
用 Vue 做 PWA(一):开始
2020-01-30 07:59 2020-09-04 23:59

Vue 党快速上手 PWA

Vue 加 Hammer 处理手势
2020-01-27 08:33 2020-07-03 02:20
-

接近原生 App 体验,手势是少不了的。但是无论是 Vue 还是 Vuetify 对手势的支持并不好,于是需要 Hammer.js 支持。

+

接近原生 App 体验,手势是少不了的。但是无论是 Vue 还是 Vuetify 对手势的支持并不好,于是需要 Hammer.js 支持。

Introduce and Setup Tmux
2020-01-21 13:55 2020-07-10 12:46

Setup and introduce Tmux so that I can use it

Working with tmux and SSH
2020-01-21 13:55 2020-07-03 01:54
@@ -104,7 +106,7 @@
MySQL 存储 Emoji
2019-12-23 14:21 2020-09-26 07:41

最近需要使用 SQLAlchemy 存弹幕的内容,但是遇到了存 emoji 的问题。

Deploying Hugo with CircleCI
2019-10-27 12:12 2020-06-30 07:28
-

Building blog sites manually is tedious and often leads to mistakes, especially when hosting on master/docs

+

Building blog sites manually is tedious and often leads to mistakes, especially when hosting on master/docs

Setup Vim Airline (in Termux)
2019-02-09 00:00 2021-02-22 01:59
diff --git a/img/6c88ced2.jpg b/img/6c88ced2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..58dbe3be2886606a8a0c16a468986fc8acb3553d GIT binary patch literal 100032 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 literal 0 HcmV?d00001 diff --git a/img/f498d489.png b/img/f498d489.png deleted file mode 100644 index 6eecb6bde89696b5c08b9ce30d56deeb6ff9300f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24928 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 UP?Gn&!S5zDWvz!r|60BHAHQ9q%m4rY diff --git a/index.html b/index.html index e7a7daea1..a6aad76d9 100644 --- a/index.html +++ b/index.html @@ -20,13 +20,14 @@ } -
AC Dustbin

Hi! Thanks for your interest in my blog!

I'm Allan Chain, a programming hobbyist and open source lover. My primary programming languages are Python and JavaScript. I'm also having (or had) "fun" with

This blog mainly hosts my notes when programming, as well as some other interesting bits.

Blog Entries

Cheatsheet
One sheet to get them all!
Programming
Exciting or strange things for programming which worth noting

Recent Updates

腾讯会议 Linux 客户端无法正常结束共享的 Workaround
2022-02-27 09:02 2022-02-27 09:02
-

就改下窗口状态的事儿

-
A Simple Way to Use More Addons (e.g. Tampermonkey) on Newest Fenix (FireFox Android)
2021-03-07 09:34 2022-01-27 12:29
-

Yes, very simple and... violent. It's about compiling your own copy of Fenix.

-
换源集合
2020-02-28 14:29 2021-12-09 02:49
-

各种换源,我要的都在这里了。针对天朝就不用国际语言了 😄

-

Friends

ᴍɪᴍɪ
做了一点微小的工作
txtyb
The quieter you be, the more you can hear.
Yixuan-Wang
This is a blоg, not a blοg.
- +
AC Dustbin

Hi! Thanks for your interest in my blog!

I'm Allan Chain, a programming hobbyist and open source lover. My primary programming languages are Python and JavaScript. I'm also having (or had) "fun" with

This blog mainly hosts my notes when programming, as well as some other interesting bits.

Blog Entries

Moments
听说我不发朋友圈?
Cheatsheet
One sheet to get them all!
Programming
Exciting or strange things for programming which worth noting

Recent Updates

Docker Cheatsheet
2021-07-31 09:58 2022-05-01 13:49
+

Docker commands are easily forgotten

+

Above image from scmagazine.com

+
Another Move of My Blog?
2022-04-06 04:00 2022-05-01 08:55
+

It really needs a serious discussion.

+
免费下网易云会员歌曲 MP3 竟如此简单?
2022-05-01 08:50 2022-05-01 08:50
+

我坦白,我是标题党

+

Friends

ᴍɪᴍɪ
做了一点微小的工作
txtyb
The quieter you be, the more you can hear.
Yixuan-Wang
This is a blоg, not a blοg.
+ diff --git a/labels/index.html b/labels/index.html index d54e08f5e..d3a6fceff 100644 --- a/labels/index.html +++ b/labels/index.html @@ -12,7 +12,7 @@ } - - + + diff --git a/offline/index.html b/offline/index.html index c740f4240..e153d36b6 100644 --- a/offline/index.html +++ b/offline/index.html @@ -12,7 +12,7 @@ } -
AC Dustbin
+
AC Dustbin
diff --git a/post/about/index.html b/post/about/index.html index 3fbd603e9..b3a33f1f1 100644 --- a/post/about/index.html +++ b/post/about/index.html @@ -35,7 +35,7 @@
AC Dustbin

About This Blog

2020-07-18 14:09 2021-01-23 00:49

Why "AC Dustbin"?

"AC" is short for "Allan Chain", my GitHub ID. It is not unique, but it's short 😄

-

"dustbin" == "dust" + "bin" Every blog post, or thought, is just like a piece of dust, which is not a big deal to the whole world. But I try to keep all the dust organized in a bin, and maybe It will be helpful to someone.

+

"dustbin" == "dust" + "bin" Every blog post, or thought, is just like a piece of dust, which is not a big deal to the whole world. But I try to keep all the dust organized in a bin, and maybe It will be helpful to someone.

What? Why "Allan Chain"? That's a long story but I am keeping it short here.

Well, "Allan" is the English name I chose 7 years ago. This is a completely valid name while different from common "Alan". However, "Chain" is a invented last name. It's pronounced like my family name. Besides, you will get the name of my homeland by reordering these five letters.

Why Issues?

@@ -50,6 +50,6 @@

Friends Section

Contribute Your Idea

Your issue will never be rendered as a blog post. If you found a bug or have an idea about wonderful features, just send a issue and I will take a good look at it ❤️

Edit: Discussions is alive!

- + diff --git a/post/ahk-numpad/index.html b/post/ahk-numpad/index.html index 434419521..d58e639c7 100644 --- a/post/ahk-numpad/index.html +++ b/post/ahk-numpad/index.html @@ -58,10 +58,10 @@ #If

In case you are not familiar with ahk syntax (or I forget in the future):

    -
  • :: separates the hotkey and the action
  • -
  • ^! means Ctrl + Alt, full list of hotkey modifier symbols
  • -
  • #If creates context-sensitive hotkeys and hotstrings. Reference
  • -
  • mode := 0 sets the initial mode. Change to 1 to enable at start
  • +
  • :: separates the hotkey and the action
  • +
  • ^! means Ctrl + Alt, full list of hotkey modifier symbols
  • +
  • #If creates context-sensitive hotkeys and hotstrings. Reference
  • +
  • mode := 0 sets the initial mode. Change to 1 to enable at start

Notice that the script above does not fully simulate numpad, it is sending normal number keys (keys on top) instead of numpad keys. To send numpad keys:

@@ -87,6 +87,6 @@ 0::Numpad0 #If

But the use case is rare. The first script is enough for me.

- + diff --git a/post/bash-cht/index.html b/post/bash-cht/index.html index fe050ce17..3b1a7e1c0 100644 --- a/post/bash-cht/index.html +++ b/post/bash-cht/index.html @@ -33,13 +33,13 @@
AC Dustbin

Bash Cheatsheet

2020-04-18 04:48 2020-06-30 06:59
-

**/* not recursive?

+

**/* not recursive?

shell
shopt -s globstar
-

Recursive chmod

+

Recursive chmod

From https://stackoverflow.com/a/11512211/8810271

Want set all files with mode 644 and all sub directories 755?

@@ -49,7 +49,7 @@

Recursive chmod

find /opt/lampp/htdocs -type d -exec chmod 755 {} \;
 find /opt/lampp/htdocs -type f -exec chmod 644 {} \;
-

chmod 644 {} \; specifies the command that will be executed by find for each file. {} is replaced by the file path, and the semicolon denotes the end of the command (escaped, otherwise it would be interpreted by the shell instead of find).

+

chmod 644 {} \; specifies the command that will be executed by find for each file. {} is replaced by the file path, and the semicolon denotes the end of the command (escaped, otherwise it would be interpreted by the shell instead of find).

If Statement

@@ -63,7 +63,7 @@

If Statement

Single Square Brackets

-These are frequently used, and search CONDITIONAL in bash man page for more. +These are frequently used, and search CONDITIONAL in bash man page for more. @@ -134,21 +134,21 @@

Single Square Brackets

From https://stackoverflow.com/a/31366734/8810271

-

[ is just a regular command with a weird name.

-

] is just an argument of [ that prevents further arguments from being used.

-

Nothing is altered in the way that Bash parses the command. In particular, < is redirection, && and || concatenate multiple commands, ( ) generates subshells unless escaped by \, and word expansion happens as usual.

-

[[ X ]] is a single construct that makes X be parsed magically. <, &&, || and () are treated specially, and word splitting rules are different. There are also further differences like = and =~.

-

[ is a built-in command (compgen -b), and [[ is a keyword (compgen -k)

+

[ is just a regular command with a weird name.

+

] is just an argument of [ that prevents further arguments from being used.

+

Nothing is altered in the way that Bash parses the command. In particular, < is redirection, && and || concatenate multiple commands, ( ) generates subshells unless escaped by \, and word expansion happens as usual.

+

[[ X ]] is a single construct that makes X be parsed magically. <, &&, || and () are treated specially, and word splitting rules are different. There are also further differences like = and =~.

+

[ is a built-in command (compgen -b), and [[ is a keyword (compgen -k)

-

That's why you need to type spaces after [ and before ]. Furthermore, there is no need to type an entire if statement to debug a condition. Just type [[ 0 > 1 ]] and see your shell prompt (if configured) !

+

That's why you need to type spaces after [ and before ]. Furthermore, there is no need to type an entire if statement to debug a condition. Just type [[ 0 > 1 ]] and see your shell prompt (if configured) !

Listing awfully named files in order

-

Sometimes, you get files like photo_1.jpg, photo_2.jpg, ..., photo_10.jpg, ...

-

If using plain *.jpg, you will get photo_1.jpg, photo_10.jpg, ..., photo_2.jpg, ...

-

Quite annoying, isn't it? Solve it by ls -v, namely sort by version.

+

Sometimes, you get files like photo_1.jpg, photo_2.jpg, ..., photo_10.jpg, ...

+

If using plain *.jpg, you will get photo_1.jpg, photo_10.jpg, ..., photo_2.jpg, ...

+

Quite annoying, isn't it? Solve it by ls -v, namely sort by version.

From man page:

Sort by WORD instead of name: none (-U), size (-S), time (-t), version (-v), extension (-X)

- + diff --git a/post/c-review/index.html b/post/c-review/index.html index 6c81458cb..23b0ae202 100644 --- a/post/c-review/index.html +++ b/post/c-review/index.html @@ -101,13 +101,13 @@

理解指针-数组传递

average(*score, 12); search(score, 2); } -

score 看成一维数组,
-则*score就是获得该数组的第一个元素。
+

score 看成一维数组,
+则*score就是获得该数组的第一个元素。
由于二维数组,第一个元素还是数组。
-average(*score,12)就相当于传一个数组进去。
-与float *p相符。

-

float (*p)[4]是 a pointer to array of 4 float,
-score本身一维数组就等同于指针,指向第一个数组元素。
+average(*score,12)就相当于传一个数组进去。
+与float *p相符。

+

float (*p)[4]是 a pointer to array of 4 float,
+score本身一维数组就等同于指针,指向第一个数组元素。
故这两个是相符的。

指针与字符串

常量区?

@@ -170,6 +170,6 @@

交换两个值,不用临时变量

b = b ^ (a ^ b) = a ^ b ^ b = a ^ 0 = aa = (a ^ b) ^ a = a ^ a ^ b = 0 ^ b = b*/
- + diff --git a/post/compile-fenix/index.html b/post/compile-fenix/index.html index 3a455d8d5..a6b67776b 100644 --- a/post/compile-fenix/index.html +++ b/post/compile-fenix/index.html @@ -42,8 +42,8 @@

Updated Method

Create Your Own Addon Collection

Go to https://addons.mozilla.org/ and create a collection. Notice your user name and the collection name.

Create Your Own Addon Collection

-

Modify app/build.gradle

-

Modify applicationId and sharedUserId to avoid conflict.

+

Modify app/build.gradle

+

Modify applicationId and sharedUserId to avoid conflict.

@@ -96,7 +96,7 @@

Modify app/build.gradle

shell
sed -i -e 's/org\.mozilla"/org.mozilla.allanchain"/' -e 's/org.mozilla.firefox/org.mozilla.firefox.allanchain/g' -e 's/7dfae8669acc4312a65e8ba5553036/more-addons/' -e 's/\\"mozilla\\"/\\"Allan Chain\\"/' app/build.gradle
-

You may also want to set local.properties and / or other properties according to fenix README. For example:

+

You may also want to set local.properties and / or other properties according to fenix README. For example:

@@ -108,7 +108,7 @@

Compile

shell
./gradlew app:assembleRelease
-

You are done! 🎉 The apk is available at app\build\outputs\apk\release

- +

You are done! 🎉 The apk is available at app\build\outputs\apk\release

+ diff --git a/post/compile-vim-python3/index.html b/post/compile-vim-python3/index.html index 1070ba64f..fd3373b3e 100644 --- a/post/compile-vim-python3/index.html +++ b/post/compile-vim-python3/index.html @@ -37,7 +37,7 @@

Disclaimer: There may be many errors and unneccesary parts in this post. But the command works, at least on my machine.

Background

-

Somewhat old system. No sudo. Want awesome Vim8 +Python3.8

+

Somewhat old system. No sudo. Want awesome Vim8 +Python3.8

Quick Answer

@@ -66,19 +66,19 @@

Clean before try again

shell
make clean distclean

Options v.s. Env vars

-

Both are important. For example, do not pass vi_cv_path_python as an option, although it's in lower case 😄

+

Both are important. For example, do not pass vi_cv_path_python as an option, although it's in lower case 😄

User install

shell
--prefix=$HOME
-

What is python3-config-dir

+

What is python3-config-dir

https://vi.stackexchange.com/a/18509

-

A directory containing config.c

-

-rdynamic?

-

It just solves undefined symbol: PyTuple_Type: vim/vim#5509 (comment)

-

Also, just go dynamic (with +python3/dyn) is fine:

+

A directory containing config.c

+

-rdynamic?

+

It just solves undefined symbol: PyTuple_Type: vim/vim#5509 (comment)

+

Also, just go dynamic (with +python3/dyn) is fine:

@@ -88,6 +88,6 @@

-rdynamic?

--with-python3-command=python3.8 \ --with-python3-config-dir=$HOME/.pyenv/versions/3.8.5/lib/python3.8/config-3.8-x86_64-linux-gnu \ --prefix=$HOME
- + diff --git a/post/damn-gpg/index.html b/post/damn-gpg/index.html index 659d8edf9..8caa7f287 100644 --- a/post/damn-gpg/index.html +++ b/post/damn-gpg/index.html @@ -68,9 +68,9 @@

And today, when I have succeeded in signing many commits in different repos, I failed to sign this repo...

Type:

-
git config -l
+
git config -l
 
-

And I saw two user.signingkey there... Interesting ...

+

And I saw two user.signingkey there... Interesting ...

One is global and one is local, the local one is introduced in the early age when I configure the GPG key generated by windows locally and forgot to remove it...


Alright, damn GPG again.

@@ -86,7 +86,7 @@ gpg: signing failed: Inappropriate ioctl for device gpg: [stdin]: clear-sign failed: Inappropriate ioctl for device

GPG NEEDS A FOLLISH TTY?!

-
export GPG_TTY=$(tty)
+
export GPG_TTY=$(tty)
 

That solved the problem


@@ -102,7 +102,7 @@ gpg: can't connect to the agent: IPC connect call failed gpg: keydb_search failed: No agent running gpg: no default secret key: No agent running -gpg: [stdin]: clear-sign failed: No agent running">
gpg: WARNING: unsafe ownership on homedir '/home/ac/.gnupg'
+gpg: [stdin]: clear-sign failed: No agent running">
gpg: WARNING: unsafe ownership on homedir '/home/ac/.gnupg'
 gpg: can't connect to the agent: IPC connect call failed
 gpg: can't connect to the agent: IPC connect call failed
 gpg: keydb_search failed: No agent running
@@ -128,6 +128,6 @@
 
  • https://stackoverflow.com/questions/41525221
  • https://askubuntu.com/questions/1167333
  • - + diff --git a/post/docker-cht/index.html b/post/docker-cht/index.html index a058fc1f4..5d48987ae 100644 --- a/post/docker-cht/index.html +++ b/post/docker-cht/index.html @@ -32,8 +32,9 @@ } -
    AC Dustbin

    Docker Cheatsheet

    2021-07-31 09:58 2021-09-11 13:38
    - + diff --git a/post/dsa-init/index.html b/post/dsa-init/index.html index ee77f6696..285fe94f8 100644 --- a/post/dsa-init/index.html +++ b/post/dsa-init/index.html @@ -36,31 +36,31 @@

    g++ & VS Code 党对《数据结构与算法》的环境准备

    有必要解释一下名称的由来:Data Structure and Algorithm

    -

    万恶exe

    +

    万恶exe

    删掉删掉,养虎遗患🐕

    文件编码

    作为可以追溯到 2008 年的程序们,使用 GBK 之类的编码和 CRLF 的确是正常的,但是看着觉得别扭。

    -

    所以使用iconv转换编码,dos2unix转换换行符:

    +

    所以使用iconv转换编码,dos2unix转换换行符:

    shell
    for file in **/*.cpp; do iconv -f GBK -t utf-8 $file -o $file; dos2unix $file; done
    -

    转完之后才发现,还有头文件。。改成.h再来一次完事。

    +

    转完之后才发现,还有头文件。。改成.h再来一次完事。

    WSL + VS Code

    使用微软“黑科技”,VS Code 使用 WSL 的环境,就不用担心 MSVC 这玩意了。

    安装不多说,参考https://code.visualstudio.com/docs/cpp/config-wsl

    -

    配置编译启动的时候有需要注意的地方。VS Code 编译调试 c++ 的时候分两步,配置信息放在两个配置文件中,分别为.vscode/tasks.json, .vscode/launch.json

    +

    配置编译启动的时候有需要注意的地方。VS Code 编译调试 c++ 的时候分两步,配置信息放在两个配置文件中,分别为.vscode/tasks.json, .vscode/launch.json

      -
    • 点击调试,按照提示新建一个launch.json
    • -
    • 我选择了g++ build and debug active file
    • -
    • launch.json无需改动
    • -
    • 再次调试,按照提示新建一个tasks.json
    • -
    • 选择g++ build active file
    • -
    • tasks.json修改稍等叙述
    • +
    • 点击调试,按照提示新建一个launch.json
    • +
    • 我选择了g++ build and debug active file
    • +
    • launch.json无需改动
    • +
    • 再次调试,按照提示新建一个tasks.json
    • +
    • 选择g++ build active file
    • +
    • tasks.json修改稍等叙述
    -

    但是新建tasks.json好像并不可靠,有时并没有对应选项。。

    -

    由于多为多文件项目,直接默认的编译只会编译当前文件,故对tasks.json改动如下,编译文件所在目录下所有 cpp 文件:

    +

    但是新建tasks.json好像并不可靠,有时并没有对应选项。。

    +

    由于多为多文件项目,直接默认的编译只会编译当前文件,故对tasks.json改动如下,编译文件所在目录下所有 cpp 文件:

    @@ -90,7 +90,7 @@

    WSL + VS Code

    } ] }
    -

    万恶的gets

    +

    万恶的gets

    虽然该函数已经(2011?)正式退出,但是作为之前的代码,还是有它的身影。

    参考 https://stackoverflow.com/q/1694036/8810271

    只需将

    @@ -110,9 +110,9 @@

    万恶的gets

    printf("生成多少随机数:\n"); fgets(str, 32, stdin);

    就没什么大问题了。

    -

    StdAfx?

    +

    StdAfx?

    https://stackoverflow.com/questions/4726155

    用于预编译一些头文件以达到加速效果。但是对于这种小项目,预编译加速效果有限,于是我并没有进行相关配置。

    - + diff --git a/post/fabricmc/index.html b/post/fabricmc/index.html index ae135d8b2..a8bfba1fa 100644 --- a/post/fabricmc/index.html +++ b/post/fabricmc/index.html @@ -41,8 +41,8 @@

    JDK, JRE

    JRE (Java Runtime Environment): essential for Java application to run

    JDK (Java Development Kit): Includes JRE, with tools to compile Java source Code

    Not Enough?

    -

    As a (fresh) Minecraft player, I have already installed jre8. And I also have openjdk8 installed with Android Studio. However thats not enough.

    -

    openjdk does not include JavaFX, which is required for HMCL. And jre8 does not include tools to compile. So let's get official Java SE Development Kit 8 .(Most mods use this version.)

    +

    As a (fresh) Minecraft player, I have already installed jre8. And I also have openjdk8 installed with Android Studio. However thats not enough.

    +

    openjdk does not include JavaFX, which is required for HMCL. And jre8 does not include tools to compile. So let's get official Java SE Development Kit 8 .(Most mods use this version.)

    Setup Env and Config

    For example:

    @@ -64,10 +64,10 @@

    Build Fresh Mod!

    If network connection is poor, you can:

    • -

      manually extract downloaded gradle release (e.g. gradle-6.5-bin.zip) to $HOME\.gradle\wrapper\dists\gradle-6.5-bin\<hash>\ and touch gradle-6.5-bin.zip.ok

      +

      manually extract downloaded gradle release (e.g. gradle-6.5-bin.zip) to $HOME\.gradle\wrapper\dists\gradle-6.5-bin\<hash>\ and touch gradle-6.5-bin.zip.ok

    • -

      manually put minecraft-1.16.1-<server/client>.jar to $HOME\.gradle\caches\fabric-loom\

      +

      manually put minecraft-1.16.1-<server/client>.jar to $HOME\.gradle\caches\fabric-loom\

    • then restart building process

      @@ -75,8 +75,8 @@

      Build Fresh Mod!

    Get docs

    You will get confused with the code quickly if you just read fabric wiki. Where are docs for all these APIs?

    -

    Just go to fabric's own maven repo, navigate to net/fabricmc/yarn/<build-folder>, download and extract javadoc.jar, and you will get docs for net.minecraft.blahblah

    -

    And net/fabricmc/sponge-mixin for mixins.

    +

    Just go to fabric's own maven repo, navigate to net/fabricmc/yarn/<build-folder>, download and extract javadoc.jar, and you will get docs for net.minecraft.blahblah

    +

    And net/fabricmc/sponge-mixin for mixins.

    But docs are no very useful, you often need the source code.

    Quick Tip: What Commands are Supported?

    @@ -95,13 +95,13 @@

    Generate Minecraft Source

    gradlew genSources

    This will decompile Minecraft and download game assets. Open the project in VSCode, maybe wait a while for project to be imported. You can now right click class or method name and select "go to implementations" to see the source.

    Debug the Mod

    -

    Nobody would like to build the mod, drag the jar to mods directory, restart minecraft game, to see the effect. To debug right inside VSCode, generate launch.json:

    +

    Nobody would like to build the mod, drag the jar to mods directory, restart minecraft game, to see the effect. To debug right inside VSCode, generate launch.json:

    batchfile
    gradlew vscode
    -

    Now Minecraft Client and Minecraft Server option is available in the debug panel!

    - +

    Now Minecraft Client and Minecraft Server option is available in the debug panel!

    + diff --git a/post/ffmpeg-cht/index.html b/post/ffmpeg-cht/index.html index ae8a2513d..145590949 100644 --- a/post/ffmpeg-cht/index.html +++ b/post/ffmpeg-cht/index.html @@ -65,7 +65,7 @@

    Get help from command line

    -an disable audio -acodec codec force audio codec ('copy' to copy stream) -vol volume change audio volume (256=normal) --af filter_graph set audio filters">
    Global options (affect whole program instead of just one file:
    +-af filter_graph    set audio filters">
    Global options (affect whole program instead of just one file:
     -loglevel loglevel  set logging level
     -v loglevel         set logging level
     -y                  overwrite output files
    @@ -106,7 +106,7 @@ 

    裁剪

    shell
    ffmpeg -i input.m4a -ss 0 -t 18 cut.m4a
      -
    • -ss 表开始,HH:MM:SS.xxx
    • +
    • -ss 表开始,HH:MM:SS.xxx
    • -t 表时长
    • -to 表结束
    @@ -118,7 +118,7 @@

    拼接

    ffmpeg -safe 0 -f concat -i list.txt -c copy output.mp4

    list.txt 内容为:

    file 1.mp4
    +file 2.mp4">
    file 1.mp4
     file 2.mp4
     

    生成静音的音频

    @@ -148,7 +148,7 @@

    合理转成 GIF

    shell
    ffmpeg -y -i file.mp4 -i palette.png -filter_complex paletteuse -r 10 -s 320x480 file.gif

    More options documented here.

    -

    所以使用我的 ffmpegroup脚本,第二步就是

    +

    所以使用我的 ffmpegroup脚本,第二步就是

    @@ -166,6 +166,6 @@

    直接倍速

    shell
    ffmpeg -itsscale 0.01666 -i input.mkv -c copy output.mkv
    - + diff --git a/post/from-python-to-js/index.html b/post/from-python-to-js/index.html index c9281a8ca..43d3742c5 100644 --- a/post/from-python-to-js/index.html +++ b/post/from-python-to-js/index.html @@ -47,10 +47,10 @@
    AC Dustbin

    From Python to JavaScript

    2020-02-07 04:23 2021-02-22 08:52
    -

    The new Operator

    -

    You can simply create an instance of a class by ClassName(...) in Python. But in JavaScript you need the new operator. It can be easily forgotten!

    +

    The new Operator

    +

    You can simply create an instance of a class by ClassName(...) in Python. But in JavaScript you need the new operator. It can be easily forgotten!

    See also https://stackoverflow.com/q/1646698/8810271, https://stackoverflow.com/q/383402/8810271

    -

    But why sometimes it still works when I forget new? For example:

    +

    But why sometimes it still works when I forget new? For example:

    @@ -71,7 +71,7 @@

    The new Operator

    return new foo(); // constructor logic follows... }
    -

    However, you must use new if you are using class:

    +

    However, you must use new if you are using class:

    @@ -157,7 +157,7 @@

    Weird map / Are they Different?

    2 1 [ 1, 2, 3 ] 3 2 [ 1, 2, 3 ] undefined
    -

    mapwill pass 3 parameters: value, index, and the Array itself, as shown in [1, 2, 3].map(console.log)

    +

    mapwill pass 3 parameters: value, index, and the Array itself, as shown in [1, 2, 3].map(console.log)

    And, passing too many arguments will not cause error:

    @@ -168,26 +168,26 @@

    Weird map / Are they Different?

    > g(1, 2, 3) 1 undefined
    -

    And as for parseInt, there is a second parameter (see below). That's why parseInt and s => parseInt(s) are different.

    -

    parseInt v.s. int

    +

    And as for parseInt, there is a second parameter (see below). That's why parseInt and s => parseInt(s) are different.

    +

    parseInt v.s. int

    -

    parseInt(string [, radix])

    +

    parseInt(string [, radix])

    Parameters

    string

    -

    The value to parse. If this argument is not a string, then it is converted to one using the ToString abstract operation. Leading whitespace in this argument is ignored.

    +

    The value to parse. If this argument is not a string, then it is converted to one using the ToString abstract operation. Leading whitespace in this argument is ignored.

    radix Optional

    An integer between 2 and 36 that represents the radix (the base in mathematical numeral systems) of the string. Be careful—this does not default to 10!

    -

    If radix is undefined, 0, or unspecified, JavaScript assumes the following:

    +

    If radix is undefined, 0, or unspecified, JavaScript assumes the following:

      -
    1. If the input string begins with "0x" or "0X" (a zero, followed by lowercase or uppercase X), radix is assumed to be 16 and the rest of the string is parsed as a hexidecimal number.
    2. -
    3. If the input string begins with "0" (a zero), radix is assumed to be 8 (octal) or 10 (decimal). Exactly which radix is chosen is implementation-dependent. ECMAScript 5 clarifies that 10 (decimal) should be used, but not all browsers support this yet. For this reason, always specify a radix when using parseInt.
    4. -
    5. If the input string begins with any other value, the radix is 10 (decimal).
    6. +
    7. If the input string begins with "0x" or "0X" (a zero, followed by lowercase or uppercase X), radix is assumed to be 16 and the rest of the string is parsed as a hexidecimal number.
    8. +
    9. If the input string begins with "0" (a zero), radix is assumed to be 8 (octal) or 10 (decimal). Exactly which radix is chosen is implementation-dependent. ECMAScript 5 clarifies that 10 (decimal) should be used, but not all browsers support this yet. For this reason, always specify a radix when using parseInt.
    10. +
    11. If the input string begins with any other value, the radix is 10 (decimal).
    -

    If the first character cannot be converted to a number, parseInt returns NaN unless the radix is bigger than 10.

    +

    If the first character cannot be converted to a number, parseInt returns NaN unless the radix is bigger than 10.

    -

    Well, that makes sense. And I'm going to specify a radix when using parseInt.

    +

    Well, that makes sense. And I'm going to specify a radix when using parseInt.

    And note that Python is more friendly:

    parseInt v.s. int | by whitespace. The base defaults to 10. Valid bases are 0 and 2-36. | Base 0 means to interpret the base from the string as an integer literal. | >>> int('0b100', base=0) - | 4">
    class int(object)
    + |  4">
    class int(object)
      |  int([x]) -> integer
      |  int(x, base=10) -> integer
      |
    @@ -230,12 +230,12 @@ 

    Dead Variable

    ReferenceError: nothing is not defined > let a = 1 Thrown: -SyntaxError: Identifier 'a' has already been declared +SyntaxError: Identifier 'a' has already been declared > a = 1 Thrown: ReferenceError: a is not defined
    -

    Once you have a typo when using let in console (such as forgetting new), the variable name will never come back. That's because variable initialization did not complete successfully, and you can't re-declare a variable that's already been declared.

    -

    Worse still, you cannot delete the variable declared using let, const or var. Only things like "global variable" can be deleted. See also https://stackoverflow.com/q/1596782/8810271

    +

    Once you have a typo when using let in console (such as forgetting new), the variable name will never come back. That's because variable initialization did not complete successfully, and you can't re-declare a variable that's already been declared.

    +

    Worse still, you cannot delete the variable declared using let, const or var. Only things like "global variable" can be deleted. See also https://stackoverflow.com/q/1596782/8810271

    You have to reinvent a good variable name, or reopen the console.

    Sure Sorted?

    @@ -246,9 +246,9 @@

    Sure Sorted?

    [ 1, 2 ] > [8, 10].sort() [ 10, 8 ]
    -

    No, JS sort is just String sorting:

    +

    No, JS sort is just String sorting:

    -

    The sort() method sorts the elements of an array in place and returns the sorted array. The default sort order is ascending, built upon converting the elements into strings, then comparing their sequences of UTF-16 code units values.

    +

    The sort() method sorts the elements of an array in place and returns the sorted array. The default sort order is ascending, built upon converting the elements into strings, then comparing their sequences of UTF-16 code units values.

    And if you want number sorting, write:

    diff --git a/post/get-wechat-emoji/index.html b/post/get-wechat-emoji/index.html index beea7e6b2..e32069df4 100644 --- a/post/get-wechat-emoji/index.html +++ b/post/get-wechat-emoji/index.html @@ -37,13 +37,13 @@

    在 GitHub 上直接搜微信表情,搜出来的多是 https://res.wx.qq.com/mpres/htmledition/images/icon/emotion/0.gif 系列,但这些表情不仅已经过时,而且不清晰(如 emoji demo),甚至有白底。很不方便。

    微信 APK

    -

    很自然会想到直接从官方网站下载的 APK 提取。但是会发现 APK 里只有在 assets/newemoji 里有一些新的 emoji。分辨率是 64x64 的,很令人满意。

    +

    很自然会想到直接从官方网站下载的 APK 提取。但是会发现 APK 里只有在 assets/newemoji 里有一些新的 emoji。分辨率是 64x64 的,很令人满意。

    继续搜索

    最后在微信开放社区里发现这个帖子,提到了 miniprogram-component-plus 项目中有微信的 emoji。在该项目中搜索 emoji 就有表情雪碧图的链接,如下图所示:

    Emoji Sprite

    -

    光有雪碧图还不够,难道要手动建立对应关系?继续在该项目下搜索就发现 src/emoji/emoji_positon.less 文件里有相应样式。

    +

    光有雪碧图还不够,难道要手动建立对应关系?继续在该项目下搜索就发现 src/emoji/emoji_positon.less 文件里有相应样式。

    注意事项

    使用表情应当遵守微信相应许可。

    - + diff --git a/post/gh-action-cache/index.html b/post/gh-action-cache/index.html index c9bbe0b37..b9d9baf19 100644 --- a/post/gh-action-cache/index.html +++ b/post/gh-action-cache/index.html @@ -38,23 +38,23 @@

    To be clear, I am talking about https://github.com/actions/cache. Better to have a look at the doc first.

    What should be hashed?

    Make sure the file to hash does not always change on every build. After all, you want the cache hit in some cases.

    -

    For example, the project is written in JavaScript and uses npm as package manager. You decided to run GitHub Actions to release the package every time you push a tag. And you choose to hash package-lock.json... Sorry, never hits. Cache is completely useless. That's because the version always changes, even thought the dependencies doesn't, which is what you mean.

    -

    Instead, hash rest of the file, use tail -n +4 for example.

    +

    For example, the project is written in JavaScript and uses npm as package manager. You decided to run GitHub Actions to release the package every time you push a tag. And you choose to hash package-lock.json... Sorry, never hits. Cache is completely useless. That's because the version always changes, even thought the dependencies doesn't, which is what you mean.

    +

    Instead, hash rest of the file, use tail -n +4 for example.

    Know what you are hashing

    Why? Don't I know what I am hashing?

    Maybe, when using glob.

    Cache action evaluates hash twice, which can cause problems with glob. For example, actions/cache#344

    -

    hashFiles('**/yarn.lock') is wrong cause it picks yarn.lock files from node_module (as part of the key) at the end of the build, but tries to restore with empty node_modules which produced different hash.

    +

    hashFiles('**/yarn.lock') is wrong cause it picks yarn.lock files from node_module (as part of the key) at the end of the build, but tries to restore with empty node_modules which produced different hash.

    Be careful with cache scope

    GitHub doc

    A workflow can access and restore a cache created in the current branch, the base branch (including base branches of forked repositories), or the default branch (usually master)

    -

    Caches between two parallel branches are not shared. And tags only have access to caches created in default branch. If you want to share cache between build on tags, you would like to keep cache in master scope. For example, use a update-cache.yml action to keep track of cache.

    +

    Caches between two parallel branches are not shared. And tags only have access to caches created in default branch. If you want to share cache between build on tags, you would like to keep cache in master scope. For example, use a update-cache.yml action to keep track of cache.

    My Solution

    Checkout https://github.com/AllanChain/webnav/tree/3ddd12f707fc374f907f5a2ac26aaf98a7c9a3d2/.github/workflows

    - + diff --git a/post/git-cht/index.html b/post/git-cht/index.html index b50557cef..f76852e69 100644 --- a/post/git-cht/index.html +++ b/post/git-cht/index.html @@ -62,18 +62,18 @@

    Delete tag

    Remove submodule

    From https://stackoverflow.com/a/1260982/8810271

      -
    1. Delete the relevant section from the .gitmodules file.
    2. -
    3. Stage the .gitmodules changes:
      -git add .gitmodules
    4. -
    5. Delete the relevant section from .git/config.
    6. +
    7. Delete the relevant section from the .gitmodules file.
    8. +
    9. Stage the .gitmodules changes:
      +git add .gitmodules
    10. +
    11. Delete the relevant section from .git/config.
    12. Remove the submodule files from the working tree and index:
      -git rm --cached path_to_submodule (no trailing slash).
    13. -
    14. Remove the submodule's .git directory:
      -rm -rf .git/modules/path_to_submodule
    15. +git rm --cached path_to_submodule (no trailing slash). +
    16. Remove the submodule's .git directory:
      +rm -rf .git/modules/path_to_submodule
    17. Commit the changes:
      -git commit -m "Removed submodule <name>"
    18. +git commit -m "Removed submodule <name>"
    19. Delete the now untracked submodule files:
      -rm -rf path_to_submodule
    20. +rm -rf path_to_submodule

    Split repo

    Single File

    @@ -110,6 +110,6 @@

    How to make file +x in Git on Windows?

    shell
    git update-index --chmod=+x foo.sh
    - + diff --git a/post/gridsome-pwa/index.html b/post/gridsome-pwa/index.html index 0880170b8..aeb9e6da8 100644 --- a/post/gridsome-pwa/index.html +++ b/post/gridsome-pwa/index.html @@ -37,11 +37,11 @@

    虽然可以勉强并入“用 Vue 做 PWA”系列,但想着能让更多人看到,就还是用国际语言吧

    Preface: Why I Need PWA

    -

    More than 300 KB of vendor chunks (vue, vuex, vue-router, vue-meta, vuetify, etc.) with a not-that-fast network is very slow to load. I really don't want it to happen again. Also if the page service is down or unreachable (does happen from time to time, at least for my region), I want the site still to work properly.

    +

    More than 300 KB of vendor chunks (vue, vuex, vue-router, vue-meta, vuetify, etc.) with a not-that-fast network is very slow to load. I really don't want it to happen again. Also if the page service is down or unreachable (does happen from time to time, at least for my region), I want the site still to work properly.

    First of All: Optimise Assets

    If your sites updates frequently, you need to change the default behavior.

    Cache Busting

    -

    By default, gridsome write hashes to page html and json file. While it helps controlling versions, the hash itself is unreliable. That's becuase the hash is generated by webpack build, which is somewhat random. I am even getting different hashes when building gridsome.org, without changing a single character!

    +

    By default, gridsome write hashes to page html and json file. While it helps controlling versions, the hash itself is unreliable. That's becuase the hash is generated by webpack build, which is somewhat random. I am even getting different hashes when building gridsome.org, without changing a single character!

    Also, if the hashes do work properly, I don't want to invalidate all cached data just because I change one byte of javascript code.

    So I jut turned cache busting off:

    @@ -66,7 +66,7 @@

    Cache Busting

    } }

    Splitting Chunks

    -

    By default, gridsome packs main.js, App.vue, etc., and used node modules into one app.hash.js, which is more than 300 KB in my case, basically vuetify and gridsome with modules it depends on. Changing one byte of App.vue means downloading all 300 KB again, which is a pretty awful UX.

    +

    By default, gridsome packs main.js, App.vue, etc., and used node modules into one app.hash.js, which is more than 300 KB in my case, basically vuetify and gridsome with modules it depends on. Changing one byte of App.vue means downloading all 300 KB again, which is a pretty awful UX.

    Let's split the chunks:

    @@ -98,8 +98,8 @@

    Splitting Chunks

    }) } }
    -

    I can only split out 1 chunk if maxInitialRequests is not set.

    -

    Note: The above code overwrites gridsome's css: { split: false } config and always splits CSS. To disable CSS splitting, add these lines:

    +

    I can only split out 1 chunk if maxInitialRequests is not set.

    +

    Note: The above code overwrites gridsome's css: { split: false } config and always splits CSS. To disable CSS splitting, add these lines:

    @@ -115,9 +115,9 @@

    Precache v.s. Runtime Cache

    In short, precache caches all files specified in the manifest, which is usually a list of js and css assets, at install time.

    Runtime cache is more flexiable and have a lot of strategies to choose.

    My Best Practice Now

    -

    Precache the assets, use NetworkFirst strategy for HTML pages and post data, and CacheFirst for images. Don't worry if the network is slow and still taking a long time to load, just set networkTimeoutSeconds.

    +

    Precache the assets, use NetworkFirst strategy for HTML pages and post data, and CacheFirst for images. Don't worry if the network is slow and still taking a long time to load, just set networkTimeoutSeconds.

    Why not...

    -
    StaleWhileRevalidate for JSON Data Files
    +
    StaleWhileRevalidate for JSON Data Files

    The user will not receive changes immediately:

    1. user browse version A of the page
    2. @@ -127,7 +127,7 @@
      StaleWhileRevalidate for JSON Data Files

    Also, caching JSON data means gridsome/gridsome#1032 (comment)

    Add Revision to JSON Data Files
    -

    Yes, this is posible by using injectManifest and precache some of them, while runtime caching with a handler to add revision to the url. (Detailed implemantation)

    +

    Yes, this is posible by using injectManifest and precache some of them, while runtime caching with a handler to add revision to the url. (Detailed implemantation)

    However, compiling service worker with webpack is currently limited that I cannot split chunks with it. That means if the post data changed one byte, the user have to redownload the service worker file again, which is about 50 KB. Even if I managed to split the service worker, that's still 13 KB of manifest and will grow overtime. That's quite annoying that the service worker is always updating, slowly.

    Of course if you are sure your users' network is fast, you can ignore above drawback.

    Just Precache all JSON Data Files
    @@ -135,7 +135,7 @@
    Just Precache all JSON Data Files

    For example, first visit to https://v3.vuejs.org/ takes a long time to complete. ~400 files to precache.

    Of course if the users should be able to browse the full site while offline, and you do not care first visit loading and service worker installing time, go ahead with precaching.

    Still Problem

    -

    Though using NetworkFirst, sw will still use the cached JSON data version if offline. And the cached file may have different versions. For example:

    +

    Though using NetworkFirst, sw will still use the cached JSON data version if offline. And the cached file may have different versions. For example:

    1. user browse version A of page I (site version: A, page I version: A)
    2. version B published
    3. @@ -144,6 +144,6 @@

      Still Problem

    4. user browse page I, broken

    Since the user is offline, it is expected that some pages are not available. Better to show freindly offline message instead of getting a wrong version of data and having the broken page. Maybe using the GraphQL query hash as part of JSON filename?

    - + diff --git a/post/hugo-circle-ci/index.html b/post/hugo-circle-ci/index.html index 75da3de12..c16e76663 100644 --- a/post/hugo-circle-ci/index.html +++ b/post/hugo-circle-ci/index.html @@ -33,13 +33,13 @@
    AC Dustbin

    Deploying Hugo with CircleCI

    2019-10-27 12:12 2020-06-30 07:28
    -

    Think about when you happily update the blog post and every thing looks fine by hugo server, you just typed git add, git commit, git push without a second thought, only to find that you forgot to build the site to docs directory. And you git add, git commit, git push again, finding it a pain to reinvent a fancy commit message. Let's get rid of this!

    +

    Think about when you happily update the blog post and every thing looks fine by hugo server, you just typed git add, git commit, git push without a second thought, only to find that you forgot to build the site to docs directory. And you git add, git commit, git push again, finding it a pain to reinvent a fancy commit message. Let's get rid of this!

    Deploying Hugo with CircleCI: Glance

    -

    There is a good article, but is for version 2 and heavily used && which is not beautiful in YAML.

    +

    There is a good article, but is for version 2 and heavily used && which is not beautiful in YAML.

    Version 2.1

    -

    Here is the snippet, where workflows is at root level:

    +

    Here is the snippet, where workflows is at root level:

    @@ -71,8 +71,8 @@

    Beautiful YAML

    Don't forget to set SSH keys with push access

    Just follow the official documentation

    Other things to know

    -

    Tell CircleCI to do nothing on gh-pages

    -

    You probably needs to first manually checkout an orphan branch gh-pages and add the .circleci/config.yml in to let CircleCI know that this branch shall not be deployed.

    +

    Tell CircleCI to do nothing on gh-pages

    +

    You probably needs to first manually checkout an orphan branch gh-pages and add the .circleci/config.yml in to let CircleCI know that this branch shall not be deployed.

    just:

    @@ -83,7 +83,7 @@

    Tell CircleCI to do nothing on gh-pages

    git add .circleci/config.yml git push -u origin gh-pages

    Set up timezone

    -

    If you want to include time information in the commit, you well probably find that TZ=xx/xx does not work. That's because the image does not contain the necessary package. Just install it:

    +

    If you want to include time information in the commit, you well probably find that TZ=xx/xx does not work. That's because the image does not contain the necessary package. Just install it:

    @@ -143,6 +143,6 @@

    Finally my config file

    filters: branches: only: master
    - + diff --git a/post/hugo-toc/index.html b/post/hugo-toc/index.html index f4829334b..13ca802e8 100644 --- a/post/hugo-toc/index.html +++ b/post/hugo-toc/index.html @@ -38,7 +38,7 @@

    As mentioned in previous blog about Hugo #28, The built-in Toc feature is very inconvenient. And recently, I find that the code I found on the Internet is not perfect. It just ensures enough end tags are rendered, And do not support where the first header is not the biggest. And I tried to fix them all.

    First Attempt

    It is easy to render all the start tags, and the code I copied is correctly dealing with start tags, so it wouldn't be covered here.

    -

    I tried to remove some of the unnecessary end tags. The errors by htmlhint was less, but still some: some markdown files has some empty level of headers, which is not handled correctly, such as:

    +

    I tried to remove some of the unnecessary end tags. The errors by htmlhint was less, but still some: some markdown files has some empty level of headers, which is not handled correctly, such as:

    • Header 1 @@ -87,9 +87,9 @@

      First Attempt

      </ul></li> </ul></li> </ul> -

      By looking into it carefully, you would find that when dealing with the end tags, first render </li>, and render </ul></li> per loop fits the usual case, but if some level is skipped, you should just render </ul>

      +

      By looking into it carefully, you would find that when dealing with the end tags, first render </li>, and render </ul></li> per loop fits the usual case, but if some level is skipped, you should just render </ul>

      Second Attempt

      -

      I use a variable to record how many blank level previous indent made, and render corresponding number of </ul> when dedenting, and rest will be </ul></li>

      +

      I use a variable to record how many blank level previous indent made, and render corresponding number of </ul> when dedenting, and rest will be </ul></li>

      But this is still not enough, what if your dedent is less than the blank level, or in other words, you don't actually need to close all these blank levels, such as:

        @@ -159,33 +159,33 @@

        Second Attempt

        </li> </ul>

        Third Attempt

        -

        I need a stack to record what levels are left blank, and just render </ul> if closing that level, and pop that level out of the stack. Else, I will render </ul></li>

        +

        I need a stack to record what levels are left blank, and just render </ul> if closing that level, and pop that level out of the stack. Else, I will render </ul></li>

        But unfortunately, Hugo does not have a stack implementation, so I have to build a wheel.

        -

        Luckily, Hugo has Scratch, which supports:

        +

        Luckily, Hugo has Scratch, which supports:

        • Create an entry
            -
          • $.Scratch.Set "key" slice
          • +
          • $.Scratch.Set "key" slice
        • Add to an entry
            -
          • $.Scratch.Add "key" 2
          • +
          • $.Scratch.Add "key" 2
        • Get all of an entry
            -
          • $.Scratch.Get "key"
          • +
          • $.Scratch.Get "key"
        • Delete the whole key
            -
          • $.Scratch.Delete "key"
          • +
          • $.Scratch.Delete "key"
        • In the value
            -
          • if in ($.Scratch.Get "key") .
          • +
          • if in ($.Scratch.Get "key") .
        @@ -200,8 +200,8 @@

        Third Attempt

        {{- range seq (sub (len $tmp) 1) -}} {{- $.Scratch.Add "bareul" (index $tmp (sub . 1)) -}} {{- end -}} -

        Note that in hugo, seq is 1-based, but index is 0-based 😂

        -

        Besides, {{ seq $a [1] $b}} only supports auto detect increase or decrease, which means at least one element will be generated, and you cannot force a seq not to be executed by using {{ seq $a 1 $b}} if by chance $b is smaller than $a😭. But bare {{ seq $a }} will do nothing if $a is 0.

        +

        Note that in hugo, seq is 1-based, but index is 0-based 😂

        +

        Besides, {{ seq $a [1] $b}} only supports auto detect increase or decrease, which means at least one element will be generated, and you cannot force a seq not to be executed by using {{ seq $a 1 $b}} if by chance $b is smaller than $a😭. But bare {{ seq $a }} will do nothing if $a is 0.

        As a result, manually add and sub will be inevitable...

        Ultimate Solution

        The discussion above did not cover the tricks to handle start of the toc and end of it. But it is a simple trick if you understand what I mentioned in the Third Attempt Section.

        @@ -218,7 +218,7 @@

        Ultimate Solution

      • Header 1
      -

      Just make a loop to find the biggest header, and render correct number of <ul>s before and record blank

      +

      Just make a loop to find the biggest header, and render correct number of <ul>s before and record blank

      diff --git a/post/jupyter-cht/index.html b/post/jupyter-cht/index.html index 93713fce3..b14446330 100644 --- a/post/jupyter-cht/index.html +++ b/post/jupyter-cht/index.html @@ -37,7 +37,7 @@

      JupyterLab

      Extensions out of sync?

      -

      Go to <python>\share\jupyter\lab\settings, edit (or delete) those JSON files.

      +

      Go to <python>\share\jupyter\lab\settings, edit (or delete) those JSON files.

      Matplotlib

      官方 cheat sheet

      @@ -75,7 +75,7 @@

      双 Y 轴

      plt.show()

      size

      -

      plot 中使用 markersize 或者 ms 来修改点的大小, 而在 scatter 中直接使用 sizes 即可.

      +

      plot 中使用 markersize 或者 ms 来修改点的大小, 而在 scatter 中直接使用 sizes 即可.

      Scipy

      B-Spline

      scipy doc

      @@ -89,7 +89,7 @@

      B-Spline

      plt.plot(xx, make_interp_spline(x, y, k=2)(xx))

      numpy

      同阶矩阵逐项相乘

      -

      称为 Hadamard Product(哈达玛积),np.multiply() 得到

      +

      称为 Hadamard Product(哈达玛积),np.multiply() 得到

      pandas

      Rolling Mean

      From https://stackoverflow.com/a/49016377/8810271

      @@ -99,6 +99,6 @@

      Rolling Mean

      python
      df.rolling(100).mean() 
      - + diff --git a/post/jupyter-raspi/index.html b/post/jupyter-raspi/index.html index 433e80eba..f79e163b0 100644 --- a/post/jupyter-raspi/index.html +++ b/post/jupyter-raspi/index.html @@ -47,7 +47,7 @@
      AC Dustbin

      Jupyter on Raspberry Pi

      2020-02-26 12:20 2020-07-01 12:33

      Start Jupyter as service

      From https://gist.github.com/whophil/5a2eab328d2f8c16bb31c9ceaf23164f

      -

      /etc/systemd/system/jupyter.service:

      +

      /etc/systemd/system/jupyter.service:

      @@ -71,7 +71,7 @@

      Start Jupyter as service

      [Install] WantedBy=multi-user.target
      -

      To use, just do it in systemctl way:

      +

      To use, just do it in systemctl way:

      @@ -82,7 +82,7 @@

      Start Jupyter as service

      sudo systemctl enable jupyter

      NOTICE

      -

      run systemctl daemon-reload often when changing unit file. Or it will silently use the old bad file.

      +

      run systemctl daemon-reload often when changing unit file. Or it will silently use the old bad file.

      不要问我是怎么知道的

      libf77blas.so.3: cannot open shared object file

      @@ -152,7 +152,7 @@

      Configuration

      -dfs DFFONTSIZE pandas dataframe fontsize -ofs OUTFONTSIZE output area fontsize -mathfs MATHFONTSIZE mathjax fontsize (in %) --cellw CELLWIDTH set cell width (px or %)">
      -T, --toolbar         make toolbar visible
      +-cellw CELLWIDTH      set cell width (px or %)">
      -T, --toolbar         make toolbar visible
       -N, --nbname          nb name/logo visible
       -t THEME              theme name to install
       -fs MONOSIZE          code font-size
      @@ -164,12 +164,12 @@ 

      Configuration

      -cellw CELLWIDTH set cell width (px or %)
      -

      Font sizes are in pt

      +

      Font sizes are in pt

      Behind Nginx

      Reference: https://gist.github.com/christopherbaek/39e6c432e212ca7a67ffe015fe869664

        -
      • Disable SSL and bind IP back to localhost, add base_url
      • +
      • Disable SSL and bind IP back to localhost, add base_url
      @@ -200,6 +200,6 @@

      Behind Nginx

      proxy_set_header Connection "upgrade"; proxy_set_header Origin ""; }
      - + diff --git a/post/jupyter-wolfram/index.html b/post/jupyter-wolfram/index.html index a903cd12e..f24483206 100644 --- a/post/jupyter-wolfram/index.html +++ b/post/jupyter-wolfram/index.html @@ -46,7 +46,7 @@
      AC Dustbin

      Install Wolfram With Jupyter

      2020-02-26 09:50 2020-07-01 12:28

      From https://github.com/wjxway/PKU-shuakeji, I find that I can install free Wolfram Mathematica on Raspberry Pi.

      -

      Go ahead and apt install wolfram-engine wolframscript

      +

      Go ahead and apt install wolfram-engine wolframscript

      Get Wolfram Language For Jupyter Guide here: https://github.com/WolframResearch/WolframLanguageForJupyter/

      WTF PATH

      If try the first approach:

      @@ -56,9 +56,9 @@

      WTF PATH

      shell
      ./configure-jupyter.wls add

      Error occurred:

      -
      configure-jupyter.wls: Jupyter installation on Environment["PATH"] not found.
      +
      configure-jupyter.wls: Jupyter installation on Environment["PATH"] not found.
       
      -

      But jupyter is in PATH indeed:

      +

      But jupyter is in PATH indeed:

      @@ -67,7 +67,7 @@

      WTF PATH

      /usr/local/bin/jupyter $ echo $PATH /home/pi/.local/bin:/home/pi/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/games:/usr/games
      -

      However in wolframscript

      +

      However in wolframscript

      @@ -79,10 +79,10 @@

      WTF PATH

      In[1]:= /opt/Wolfram/WolframEngine/12.0/Executables:/opt/Wolfram/WolframEngine/12.0/S\ > ystemFiles/Graphics/Binaries/Linux-ARM:/home/pi/.local/bin:/usr/bin:/bin
      -

      Well, PATH is different...

      -

      As said in ./configure-jupyter.wls help, you can specify jupyter path by

      +

      Well, PATH is different...

      +

      As said in ./configure-jupyter.wls help, you can specify jupyter path by

      configure-jupyter.wls add "/absolute/path/to/Wolfram-Engine-binary--not-wolframscript" "path/to/Jupyter-binary"
      +        adds the provided absolute Wolfram Engine binary path to the provided Jupyter binary path">
      configure-jupyter.wls add "/absolute/path/to/Wolfram-Engine-binary--not-wolframscript" "path/to/Jupyter-binary"
               adds the provided absolute Wolfram Engine binary path to the provided Jupyter binary path
       

      If you try the second, you can avoid providing path to Wolfram Engine binary:

      @@ -92,8 +92,8 @@

      WTF PATH

      mathematica
      ConfigureJupyter["Add", "JupyterInstallation" -> "..."]

      WTF GFW

      -

      However, it seems to update the paclet from server, which is very slow here...

      +

      However, it seems to update the paclet from server, which is very slow here...

      Luckily, success.

      - + diff --git a/post/linux-cht/index.html b/post/linux-cht/index.html index 1181799f8..cc07ed357 100644 --- a/post/linux-cht/index.html +++ b/post/linux-cht/index.html @@ -35,9 +35,9 @@
      AC Dustbin

      Linux (Ubuntu) Cheatsheet

      2020-04-18 04:48 2020-06-30 06:59
      -

      systemctl

      +

      systemctl

      Where are service files?

      -
      /etc/systemd/system/service-name.service
      +
      /etc/systemd/system/service-name.service
       

      Start at Boot

      @@ -45,15 +45,15 @@

      Start at Boot

      shell
      sudo systemctl enable sshd.service
      -

      disable is the opposite.

      +

      disable is the opposite.

      Use Environment in Service Unit File

      [Service]
      +Environment=PATH=/home/pi/.local/bin:/home/pi/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin">
      [Service]
       Environment=PATH=/home/pi/.local/bin:/home/pi/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
       

      📔 Note

      -

      $PATH do not have its special meaning here.

      +

      $PATH do not have its special meaning here.

      https://askubuntu.com/q/1014480 for advanced.

      Unit File Template

      @@ -75,7 +75,7 @@

      Unit File Template

      #KillMode=mixed [Install] -WantedBy=multi-user.target">
      [Unit]
      +WantedBy=multi-user.target">
      [Unit]
       Description=Jupyter Notebook
       
       [Service]
      @@ -96,18 +96,18 @@ 

      Unit File Template

      Exited Without Error Log?

      From https://unix.stackexchange.com/a/225407

      -

      Use journalctl command. e.g.

      +

      Use journalctl command. e.g.

      shell
      journalctl -u service-name.service
      -

      -u is short for --unit. More useful options:

      +

      -u is short for --unit. More useful options:

        -
      • -f, --follow: Show only the most recent journal entries, and continuously print new entries as they are appended to the journal.
      • -
      • -r, --reverse: Reverse output so that the newest entries are displayed first.
      • +
      • -f, --follow: Show only the most recent journal entries, and continuously print new entries as they are appended to the journal.
      • +
      • -r, --reverse: Reverse output so that the newest entries are displayed first.
      -

      apt

      +

      apt

      List All Versions

      https://askubuntu.com/q/473886

      @@ -139,10 +139,10 @@

      List All Users

      shell
      less /etc/passwd
      -

      lsof

      +

      lsof

      LiSt Open Files. A very powerful tool.

      For network checking:

      -
      ls -i [46][protocol][@hostname|hostaddr][:service|port]
      +
      ls -i [46][protocol][@hostname|hostaddr][:service|port]
       

      e.g.

      @@ -150,7 +150,7 @@

      lsof

      shell
      sudo lsof -i :80
      -

      find

      +

      find

      @@ -164,6 +164,6 @@

      No Wireless Connection After Sleep

      shell
      sudo service network-manager restart
      - + diff --git a/post/manjaro-first-glance/index.html b/post/manjaro-first-glance/index.html index f77c5d560..e79f7b109 100644 --- a/post/manjaro-first-glance/index.html +++ b/post/manjaro-first-glance/index.html @@ -51,15 +51,15 @@

      Ventoy 装双系统?

      接下来就是把镜像下到 U 盘里,就能安装了。唯一要留心的就是分区,多看几篇文章就差不多了。

      配置、使用系统的体验?

      其实更多的还是 Linux 的共同的体验 😅

      -

      无非就是装一些软件包嘛,顺便说 AUR 是真的香,就可惜在垃圾校园网下 git clone 有点……

      +

      无非就是装一些软件包嘛,顺便说 AUR 是真的香,就可惜在垃圾校园网下 git clone 有点……

      主要还是中文的字体、输入法有一点点麻烦,看上去有点怪怪的,可能是微软雅黑看多了吧……

      触摸板的支持确实不如 Windows 下丝滑,就很奇怪 Windows 下的浏览器触摸板可以实现与 Ctrl + + 不同的缩放,双指滑动前进后退的功能也找不到……

      还有指纹读取器的问题,也是醉了。这个 Goodix 厂商就没打算让人来写这个驱动,好家伙要靠逆向工程才能让 libfprint 支持,那估计(至少该电脑的)有生之年是不太可能了……

      以及刚刚知道“模板”文件夹的神奇功能:What is the "Templates" folder in the home directory for? 直接完爆注册表!

      还有 XFCE 的自定义确实够强,尤其是面板(任务栏),控件确实不错。

      -

      哦对了,说了双系统怎么能不提时间问题呢?Windows 在主板里存的是本地时间,而 Linux 存的是 UTC 导致切换系统后时间不对。解决方法也很简单,百度一下就有了:sudo timedatectl set-local-rtc true

      +

      哦对了,说了双系统怎么能不提时间问题呢?Windows 在主板里存的是本地时间,而 Linux 存的是 UTC 导致切换系统后时间不对。解决方法也很简单,百度一下就有了:sudo timedatectl set-local-rtc true

      Linux 的桌面环境资源占用率确实低,跑起 MC 不在话下

      其他?再说吧。东西多且杂就不拟小标题了。

      - + diff --git a/post/markdown-cht/index.html b/post/markdown-cht/index.html index d83d801f3..b17070d07 100644 --- a/post/markdown-cht/index.html +++ b/post/markdown-cht/index.html @@ -35,15 +35,15 @@
      AC Dustbin

      Markdown Cheatsheet

      2020-04-18 04:48 2020-07-17 15:18
      -

      Escaping `

      -

      The general principle is, escape ` with more `s.

      -

      For example, if you want to escape a single `, just wrap it with two on each side, and add spaces, that is, `` ` ``.

      +

      Escaping `

      +

      The general principle is, escape ` with more `s.

      +

      For example, if you want to escape a single `, just wrap it with two on each side, and add spaces, that is, `` ` ``.

      If you want to escape three back quotes in a code block, use 4 for the code fence:

      ````
      +````">
      ````
       ```python
       print('hello')
       ```
      @@ -52,7 +52,7 @@ 

      Escaping `

      Produces:

      ```python
      +```">
      ```python
       print('hello')
       ```
       
      @@ -88,7 +88,7 @@

      Awesome Dropdown!

      Reference: https://github.github.com/gfm/#html-block

      The trick is, the blank line terminates the html block

      Center a Image in GFM

      -

      Use HTML tag p with align to wrap img

      +

      Use HTML tag p with align to wrap img

      @@ -97,6 +97,6 @@

      Center a Image in GFM

      <img src="https://github.com/favicon.ico"> </p>

      - + diff --git a/post/md-cn-bold/index.html b/post/md-cn-bold/index.html index dca6aa3a0..1ccef2f03 100644 --- a/post/md-cn-bold/index.html +++ b/post/md-cn-bold/index.html @@ -38,15 +38,15 @@

      问题现象

      明明电脑端显示很正常的粗体,到了华为手机的浏览器里就只对英文加粗,中文的被忽略,仍然显示正常字体。

      本质原因

      -

      安卓机的字体并不如苹果机的细腻,即 font-weight 的变化很有限。

      +

      安卓机的字体并不如苹果机的细腻,即 font-weight 的变化很有限。

      可查看 https://allanchain.gitee.io/html/font-weight.html 了解你的手机对字体粗细的尊重情况。比如我的机子:

      Screenshot_2020-08-22-08-53-32

      -

      可以看到就在 600 的位置,也就是 GitHub <strong> 指定的 font-weight,英文/数字被加粗了但是中文没有。。

      +

      可以看到就在 600 的位置,也就是 GitHub <strong> 指定的 font-weight,英文/数字被加粗了但是中文没有。。

      扩展资料

      禾几的微信小程序踩坑*2 中也有类似叙述,并附有多种机型的截图,可惜未标注具体机型。

      解决方法?

      要么换字体,要么换浏览器用插件、脚本 😂

      -

      如果是网站开发者,一定要做好主流机子的字体兼容性测试。看起来 700 到 900 或者 bold 是比较合适的。

      - +

      如果是网站开发者,一定要做好主流机子的字体兼容性测试。看起来 700 到 900 或者 bold 是比较合适的。

      + diff --git a/post/moving-blog-to-astro/index.html b/post/moving-blog-to-astro/index.html new file mode 100644 index 000000000..dea8a4d77 --- /dev/null +++ b/post/moving-blog-to-astro/index.html @@ -0,0 +1,61 @@ + + + + Another Move of My Blog? - AC Dustbin + + +
      AC Dustbin

      Another Move of My Blog?

      2022-04-06 04:00 2022-05-01 08:55
      +

      Well, yes. I'm planning another move.

      +

      It's not only because that the front-end ecosystem is moving fast, but also about all the unsolved problems.

      +

      Brief History

      +

      Before I presenting the motivation and possibilities of moving to astro, let me briefly introduce the history.

      +

      The oldest version of blog is ProgrammingNotes, which has been archived. It was built with MkDocs. It is a great tool for documentation, but not for blog. It's more like a knowledge base than a actual blog.

      +

      Then I switched to Hugo. I chose Hugo mainly because its wide adoption in simple personal websites. There are hundreds of awesome templates for displaying your experience and publications. And I made my own theme from a great theme called XMag. During the process of making and maintaining the theme, I found that Hugo's template language is really counter-intuitive and hard to do it right. Also the comment on GitHub system is hard. The OAuth and "behave on your behalf" approach is not elegant. So I chose to blog directly with GitHub issues. And I was learning Vue, so Gridsome is a natural choice.

      +

      The Motivation

      +

      But Gridsome has it's own problem. It's updating slowly. Very slow. The Vue ecosystem has changed a lot in recent years, and Gridsome fails to keep up.

      +

      Alright, if that's the only reason, I'll just stick to Gridsome. But there are more.

      +

      It was a wrong choice to use Vuetify for a blog. Vuetify provides a lot of useful components, and is a fantastic framework for apps. It was the only great UI framework for Vue I knew at that time. However, it just make a log site bloated. There is too much JavaScript and CSS for a site deployed on GitHub Pages and visited mainly in my region. Although I'm using PWA (Progressive Web App) tech to ease the pain, the first page load is still slow, and PWA itself is introducing a lot of inconsistent and buggy behaviors. Besides, Vuetify's design is not suitable for blog, or maybe it's my fault not doing this in a right way. People are just tired of material designs, probably because it looks like a cheap app.

      +

      Things will be different if I migrate the blog's UI system to Tailwind / WindiCSS / UnoCSS. The bundle size will be small, and it will be easy if there is another move in the future. I have already used both WindiCSS and UnoCSS in a couple of projects, and they are awesome, especially the attributify mode.

      +

      Also, with the new micro-blog, the old 3-part division (moments, cheat sheets, programming) is no longer needed. The category can just act like a normal tag. Also I'm planning to adopt a common blog index page, namely focusing on the recent posts, and hopefully making it more attractive.

      +

      Possibilities of Using Astro

      +

      Why not Vite SSG?

      +

      One already have great SSG (Static Site Generation) in Vite, why bother using another framework? The answer is that the SSG Vite community provides is not suitable for a blog. I really don't like the idea that every page is a component. In the context of blogging, the blog content is more data than components.

      +

      Advantages of Using Astro

      +

      Astro fetches data ones at build time just like Gridsome, while Nuxt still fetches data when switching pages. So the logic of fetching data from GitHub Issues can just be reused.

      +

      Besides, when it comes to making blog content interactive (i.e. copying code block, making interactive table of contents), the amount of required vanilla JavaScript is no less than actual Vue code. So it might be better just embracing vanilla JavaScript and create Vue components when necessary.

      +

      Moving Blog too Often?

      +

      Yes, it's a bad practice to constantly move blogs without writing many posts in between. However, the blog is not just a website to log something, it's also a place we explore and play around with new web technologies!

      +

      Besides, during this move, adopting Astro is just a small part of it. It's just a little more than a "regular" update.

      + + + diff --git a/post/mysql-emoji/index.html b/post/mysql-emoji/index.html index 6561c78cc..2e92466ff 100644 --- a/post/mysql-emoji/index.html +++ b/post/mysql-emoji/index.html @@ -36,13 +36,13 @@

      最近需要使用 SQLAlchemy 存弹幕的内容,但是遇到了存 emoji 的问题。

      UPDATE: See https://docs.sqlalchemy.org/en/13/dialects/mysql.html#unicode for more info

      -

      utf8_bin?

      -

      一开始就套用存储中文姓名的那一套,使用utf8_bin的 collation,觉得 utf8 这种万能的东西直接用就行了。可谁知给我报错:

      -
      mysql.connector.errors.DatabaseError: 1366 (HY000): Incorrect string value: '\xE8\x86\x9C' for column 'text' at row 1
      +

      utf8_bin?

      +

      一开始就套用存储中文姓名的那一套,使用utf8_bin的 collation,觉得 utf8 这种万能的东西直接用就行了。可谁知给我报错:

      +
      mysql.connector.errors.DatabaseError: 1366 (HY000): Incorrect string value: '\xE8\x86\x9C' for column 'text' at row 1
       

      蛤?竟有如此操作?

      -

      utf8mb4_unicode_ci?

      -

      一波搜索之后就看到了使用utf8mb4_unicode_ci的 collation。于是就写:

      +

      utf8mb4_unicode_ci?

      +

      一波搜索之后就看到了使用utf8mb4_unicode_ci的 collation。于是就写:

      @@ -69,7 +69,7 @@

      直接escape

      >>> text.encode('unicode_escape').decode()
       >>> text.decode('unicode_escape').encode()

      总算成功存储和读取了!

      -

      人人都说utf8mb4_unicode_ci

      +

      人人都说utf8mb4_unicode_ci

      为什么就是不行呢?

      哦,还要改数据库 charset, collation

      @@ -78,9 +78,9 @@

      人人都说utf8mb4_unicode_ci

      sql
      ALTER DATABASE databasename CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
       ALTER TABLE tablename CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
      -

      还要给 SQLAlchemy 加 ?charset=utf8mb4

      +

      还要给 SQLAlchemy 加 ?charset=utf8mb4

      -

      Edit: 使用 SQLALCHEMY_ENGINE_OPTIONS.connect_args 不应该加 ?charset=utf8mb4,加完之后会优先认 ?charset=utf8mb4,导致 collation 默认 utf8mb4_0900_ai_ci (which is MySQL 8.0's default but not implemented in MySQL 5.7)

      +

      Edit: 使用 SQLALCHEMY_ENGINE_OPTIONS.connect_args 不应该加 ?charset=utf8mb4,加完之后会优先认 ?charset=utf8mb4,导致 collation 默认 utf8mb4_0900_ai_ci (which is MySQL 8.0's default but not implemented in MySQL 5.7)

      可是为什么还是不行?

      @@ -90,7 +90,7 @@

      人人都说utf8mb4_unicode_ci

      mysql.connector.errors.DatabaseError: 1273 (HY000): Unknown collation: 'utf8mb4_0900_ai_ci'

      我几几年用过这编码?

      版本大坑

      -

      我找到了mysql-connector-python的官方文档:https://dev.mysql.com/doc/connector-python/en/connector-python-connectargs.html

      +

      我找到了mysql-connector-python的官方文档:https://dev.mysql.com/doc/connector-python/en/connector-python-connectargs.html

      collation 一栏中:

    @@ -103,15 +103,15 @@

    版本大坑

    - - + +
    collationutf8mb4_general_ai_ci (is utf8_general_ci in 2.xcollationutf8mb4_general_ai_ci (is utf8_general_ci in 2.x Which MySQL collation to use. The 8.x default values are generated from the latest MySQL Server 8.0 defaults.

    什么?8.0 的默认值?我们只有 5.7 呢!

    -

    虽然并不是utf8mb4_0900_ai_ci这种东西,我还是试着把链接数据库参数修改成utf8mb4_unicode_ci

    +

    虽然并不是utf8mb4_0900_ai_ci这种东西,我还是试着把链接数据库参数修改成utf8mb4_unicode_ci

    @@ -120,12 +120,12 @@

    版本大坑

    connect_args: collation: utf8mb4_unicode_ci

    噫!成功了!

    -

    事实证明utf8mb4_0900_ai_ciutf8mb4_general_ai_ci应该是同义词一样的存在。

    +

    事实证明utf8mb4_0900_ai_ciutf8mb4_general_ai_ci应该是同义词一样的存在。

    可是:

    MySQL Connector/Python 8.0 is highly recommended for use with MySQL Server 8.0, 5.7, 5.6, and 5.5. Please upgrade to MySQL Connector/Python 8.0.

    -

    好一个recommended,就给我挖这种坑?版本问题害死人!

    - +

    好一个recommended,就给我挖这种坑?版本问题害死人!

    + diff --git a/post/mysql-in-wsl/index.html b/post/mysql-in-wsl/index.html index a8350ea68..c39f26398 100644 --- a/post/mysql-in-wsl/index.html +++ b/post/mysql-in-wsl/index.html @@ -46,17 +46,17 @@
    AC Dustbin

    Mysql in WSL

    2020-02-07 08:59 2020-06-24 05:55

    在使用 Windows Subsystem for Linux 时,可能因为缺少对 MySQL 是预置安装,会遇到一些问题

    如果说

    -
    ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2)
    +
    ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2)
     
    -

    可以试试建立 /var/run/mysqld 目录,并给 mysql 权限

    +

    可以试试建立 /var/run/mysqld 目录,并给 mysql 权限

    如果说

    -
    No directory, logging in with HOME=/
    +
    No directory, logging in with HOME=/
     
    -

    可以试试 sudo usermod -d /var/lib/mysql/ mysql

    +

    可以试试 sudo usermod -d /var/lib/mysql/ mysql


    更新了一次系统后,MySQL 又无法启动了。刚开始还以为是更新的时候之前的配置被抹掉了,可并不是。。

    UPDATE: 不要听下面这个部分

    @@ -81,6 +81,6 @@ sudo rm -rf /etc/mysql /var/lib/mysql sudo apt autoremove sudo apt autoclean
    - + diff --git a/post/netease-music-bp/index.html b/post/netease-music-bp/index.html new file mode 100644 index 000000000..53c0664e3 --- /dev/null +++ b/post/netease-music-bp/index.html @@ -0,0 +1,45 @@ + + + + 免费下网易云会员歌曲 MP3 竟如此简单? - AC Dustbin + + +
    AC Dustbin

    免费下网易云会员歌曲 MP3 竟如此简单?

    2022-05-01 08:50 2022-05-01 08:50
    +

    背景:出于一些正经的教学目的,我要从网易云上下载一个会员专享的专辑

    +

    条件:近日某校内社交平台友很多送会员的帖子

    +

    方案:说一句“谢谢洞主”即可白拿一个 7 日会员体验。然后现下一个网易云音乐客户端,下好曲子拷贝到电脑之后就可以卸载了

    +

    手段:万能的 GitHub 一搜就有 ncm 格式转 mp3 的脚本,也不长,直接复制下来运行

    +

    小贴士:因为脚本需要用到 AES 加解密,原本的 pycrypto 库已经停止维护,在 Python 3.10 上无法正常运行,所以需要安装 pycryptodome 库,它是前者的 drop-in replacement.

    + + + diff --git a/post/npm-reg/index.html b/post/npm-reg/index.html index a7288cdba..f5adddc49 100644 --- a/post/npm-reg/index.html +++ b/post/npm-reg/index.html @@ -36,16 +36,16 @@

    The story of mirrors, ipv6, and yarn 2

    Above image background from https://travelandleisureindia.in/great-wall-of-china-visitors-cap/

    -

    Well, you might say, npm config set registry xxx does the trick. I know how to set npm registry mirror of course. I just have another story to tell.

    +

    Well, you might say, npm config set registry xxx does the trick. I know how to set npm registry mirror of course. I just have another story to tell.

    Registries are Shown in Lock File

    Npm or yarn classic, no matter which you are using, recommends you to commit the lock file (at least in several situations) and writes registry into lock file. What if you want to speed up development using one registry while speeding up CI with another registry, due to differrent network environments? What if the registry committed in version control is simply not reachable by others, which cannot be easily solved without deleting lock file? For more situations, see yarnpkg/yarn/issues/3330.

    Say Hello to Yarn 2

    -

    Although PnP mode introducedn in yarn 2 has many capability issues, you can simply turn it of by setting nodeLinker: node-modules in .yarnrc.yml. Enjoy registry agnostic lock file... to some degree. https://registry.npm.taobao.org is not the case, because it doesn't follow the standard url patterns from the npm registry. See yarnpkg/berry#2192 (comment) for the explanation. But good news, https://repo.huaweicloud.com/repository/npm/ works great.

    +

    Although PnP mode introducedn in yarn 2 has many capability issues, you can simply turn it of by setting nodeLinker: node-modules in .yarnrc.yml. Enjoy registry agnostic lock file... to some degree. https://registry.npm.taobao.org is not the case, because it doesn't follow the standard url patterns from the npm registry. See yarnpkg/berry#2192 (comment) for the explanation. But good news, https://repo.huaweicloud.com/repository/npm/ works great.

    Why Registry Works Fine in Browser but Fails in Terminal?

    -

    This is a little bit confusing. I pinged registry.npmjs.org and found it was an ipv6 address. Then I tried to ping resolved ipv4 address (dig registry.npmjs.org A) but all of them are timed out. Maybe both npm and yarn do not support ipv6, in year 2021?

    +

    This is a little bit confusing. I pinged registry.npmjs.org and found it was an ipv6 address. Then I tried to ping resolved ipv4 address (dig registry.npmjs.org A) but all of them are timed out. Maybe both npm and yarn do not support ipv6, in year 2021?

    Nico Schottelius' blog post The Nodejs in IPv6 only networks problem is to the point. It's basically a NodeJS bug (nodejs/node/pull/31567). NodeJS reorders the DNS result so that IPv4 addresses come before IPv6 addresses by default, making it unfriendly to ipv6 network environments.

    Workaround

    Set hosts to an ipv6 address to overwrite DNS result.

    - + diff --git a/post/pipenv-and-poetry/index.html b/post/pipenv-and-poetry/index.html index 12e23d7fd..dd5ed6160 100644 --- a/post/pipenv-and-poetry/index.html +++ b/post/pipenv-and-poetry/index.html @@ -40,7 +40,7 @@

    Preface

    I am not an expert. Wrote this section just for completion.

    Why not simply pip

    -

    pip install is simple and naive. You will get crazy if you come from JavaScript world where both npm and yarn are excellent package managers. It even takes effort to manage production and development dependecies in two separate list. A tool for handling all the dependencies and virtual env works is handy, and can simplify workflows.

    +

    pip install is simple and naive. You will get crazy if you come from JavaScript world where both npm and yarn are excellent package managers. It even takes effort to manage production and development dependecies in two separate list. A tool for handling all the dependencies and virtual env works is handy, and can simplify workflows.

    What do they actuallly do

    Pretty much like mentioned above. Quoting from pipenv's doc for some detailed feature:

    @@ -61,7 +61,7 @@

    Why this happens

    In node package ecosystem a project has hundreds of dependencies to resolve, install and lock, but still faster than pipenv or poetry. That's because in python package ecosystem, many packages are not properly formed and it's impossible to get it's dependencies without downloading it.

    See also https://python-poetry.org/docs/faq/#why-is-the-dependency-resolution-process-slow

    How to solve in pipenv

    -

    pipenv is slow at locking, so you may want use --skip-lock flag or set PIPENV_SKIP_LOCK env when the network is poor, then pipenv lock when network connection is good.

    +

    pipenv is slow at locking, so you may want use --skip-lock flag or set PIPENV_SKIP_LOCK env when the network is poor, then pipenv lock when network connection is good.

    How to solve in poetry

    Poetry is slow at dependency resolution. Quoting from poetry doc:

    @@ -70,33 +70,33 @@

    How to solve in poetry

    Other pipenv problems

    Lock file not cross platform

    Ironically, deterministic builds actually leads to wrong packages installed on different platforms.

    -

    Lock files may be version-dependent. Take pytest for example, if you install and lock on python 3.8, the importlib_metadata is not installed because py3.8 already have importlib.metadata included. But when you install based on the lock file on py3.6, importlib_metadata is not found. This is not happening when directly install pytest on py3.6

    +

    Lock files may be version-dependent. Take pytest for example, if you install and lock on python 3.8, the importlib_metadata is not installed because py3.8 already have importlib.metadata included. But when you install based on the lock file on py3.6, importlib_metadata is not found. This is not happening when directly install pytest on py3.6

    How to solve

      -
    1. Use markers for subdependencies: importlib_metadata = {version="~=1.7.0", python_version="<'3.8'"}
    2. -
    3. Or use pyenv / pyenv-win
    4. +
    5. Use markers for subdependencies: importlib_metadata = {version="~=1.7.0", python_version="<'3.8'"}
    6. +
    7. Or use pyenv / pyenv-win

    apt install may be outdated

    Better install via pip(x)

    Script shortcut cannot combine multi commands

    pypa/pipenv#2283

    -

    You can write command A && command B in package.json, but not Pipfile. All you can do is write in a shell script.

    +

    You can write command A && command B in package.json, but not Pipfile. All you can do is write in a shell script.

    No python version range

    pypa/pipenv#1050

    -

    If you are sure that your program and Pipfile.lock works for python > 3.5.2, remove python requirement 😏

    +

    If you are sure that your program and Pipfile.lock works for python > 3.5.2, remove python requirement 😏

    No extra denpendencies

    This can be frustrating if you want uWSGI to be installed only on production machine, but not on your PC.

    Other poetry problems

    -

    Prefer python

    -

    Use poetry with pyenv if python -V outputs 2.7.x. Or it will be an awful develop experience.

    +

    Prefer python

    +

    Use poetry with pyenv if python -V outputs 2.7.x. Or it will be an awful develop experience.

    Or if you are sure you just need to create venv with system's python3, you can choose pip(x) install it.

    No built-in dot-env and handy scripts

    -

    But you can make use of the power of pyproject.toml and use tools like taskipy and poethepoet

    +

    But you can make use of the power of pyproject.toml and use tools like taskipy and poethepoet

    Not supported by github dependency graph

    https://docs.github.com/en/github/visualizing-repository-data-with-graphs/about-the-dependency-graph#supported-package-ecosystems

    But dependabot does support it.


    More info and solutions welcome!

    - + diff --git a/post/pkg-source/index.html b/post/pkg-source/index.html index 88b7c9aea..0699e5dec 100644 --- a/post/pkg-source/index.html +++ b/post/pkg-source/index.html @@ -58,10 +58,10 @@

    npm

    shell
    yarn config set sharp_binary_host "https://npm.taobao.org/mirrors/sharp"

    electron 源

    -
    npm config set electron_mirror https://npm.taobao.org/mirrors/electron/
    +
    npm config set electron_mirror https://npm.taobao.org/mirrors/electron/
     

    docker

    -

    /etc/docker/daemon.json 或者直接 Docker Desktop 的 GUI 界面里修改:

    +

    /etc/docker/daemon.json 或者直接 Docker Desktop 的 GUI 界面里修改:

    @@ -70,7 +70,7 @@

    docker

    "https://hub-mirror.c.163.com", ]

    maven

    -

    $HOME/.gradle/init.gradle

    +

    $HOME/.gradle/init.gradle

    @@ -84,6 +84,6 @@

    maven

    mavenCentral() } }
    - + diff --git a/post/pwa-skipwaiting/index.html b/post/pwa-skipwaiting/index.html index 8c621a835..091c58582 100644 --- a/post/pwa-skipwaiting/index.html +++ b/post/pwa-skipwaiting/index.html @@ -45,12 +45,12 @@
    AC Dustbin

    `skipWaiting()` with `StaleWhileRevalidate` the right way

    2021-06-28 04:31 2021-06-28 10:11
    -

    Why skipWaiting is important?

    +

    Why skipWaiting is important?

    By default, a service worker takes over the page from start to end, even if new service worker is discovered and installed. This behavior ensures consistency. However if the update is important and does not have conflict with the old one, you may want the new one to activate as soon as installed.

    -

    Another use case is click to refresh feature. Remember the old service worker is still in charge even though refreshing the page. To update the PWA app without leaving it, skip the waiting phase of new service worker is needed. Thus after location.reload(), new service worker with new precached assets are there. We will focus on this use case in this post.

    -

    How to skipWaiting without StaleWhileRevalidate strategy

    +

    Another use case is click to refresh feature. Remember the old service worker is still in charge even though refreshing the page. To update the PWA app without leaving it, skip the waiting phase of new service worker is needed. Thus after location.reload(), new service worker with new precached assets are there. We will focus on this use case in this post.

    +

    How to skipWaiting without StaleWhileRevalidate strategy

    As you may seen in many documentations and tutorials, it is quite straightforward:

    @@ -154,41 +154,41 @@

    Complete code for demonstration

    console.log('Listening on http://localhost:8345')

    The server is pretty straightforward, using node-static to serve files.

    -

    A favicon.ico is also needed to avoid favicon.ico not found error.

    -

    Now run index.js and:

    +

    A favicon.ico is also needed to avoid favicon.ico not found error.

    +

    Now run index.js and:

    1. open http://localhost:8345/normal/
    2. wait service worker installed and close page
    3. -
    4. change service-worker.js
    5. +
    6. change service-worker.js
    7. open the page again
    8. open the console, check "Preserve log"
    9. click the "Click to skip waiting" button
    10. -
    11. change service-worker.js, manually reload page and test again
    12. +
    13. change service-worker.js, manually reload page and test again

    You may found something like this in console:

    (index):16 installed 1624852106984
    +activated 1624852110393">
    (index):16 installed 1624852106984
     service-worker.js:7 trigger skipWaiting at 1624852110391
     (index):16 activating 1624852110391
     activated 1624852110393
     

    Then page reloads, the button stay disabled because the new service worker is active and no service worker waiting.

    -

    Open Network tab, you can see slow.json is canceled because of page refresh. Or in Firefox it's directly logged into console:

    +

    Open Network tab, you can see slow.json is canceled because of page refresh. Or in Firefox it's directly logged into console:

    installed 1624852139119 normal:16:17
    +Uncaught (in promise) TypeError: NetworkError when attempting to fetch resource.">
    installed 1624852139119 normal:16:17
     trigger skipWaiting at 1624852141791 service-worker.js:7:13
     activating 1624852141795 normal:16:17
     activated 1624852141797 normal:16:17
     Uncaught (in promise) TypeError: NetworkError when attempting to fetch resource.
     
    -

    This is intended because you almost always want to cancel fetching when refreshing the page. But that's where the problem lies when using StaleWhileRevalidate strategy

    -

    Still waiting after skipWaiting, when StaleWhileRevalidate

    -

    Now let's add StaleWhileRevalidate strategy:

    +

    This is intended because you almost always want to cancel fetching when refreshing the page. But that's where the problem lies when using StaleWhileRevalidate strategy

    +

    Still waiting after skipWaiting, when StaleWhileRevalidate

    +

    Now let's add StaleWhileRevalidate strategy:

    @@ -233,7 +233,7 @@

    Still waiting after skipWaiting, when StaleWhi workbox-core.dev.js:45 View the final response here. (index):16 activating 1624853116470 (index):16 activated 1624853117482 -Navigated to http://localhost:8345/stuck/">
    (index):16 installed 1624853106784
    +Navigated to http://localhost:8345/stuck/">
    (index):16 installed 1624853106784
     workbox-core.dev.js:45 workbox Router is responding to: /stuck/slow.json
     service-worker.js:17 trigger skipWaiting at 1624853112301
     workbox-core.dev.js:45 workbox Using StaleWhileRevalidate to respond to '/stuck/slow.json'
    @@ -249,16 +249,16 @@ 

    Still waiting after skipWaiting, when StaleWhi workbox-core.dev.js:45 workbox Using StaleWhileRevalidate to respond to '/stuck/slow.json' service-worker.js:17 trigger skipWaiting at 1624853348949 (index):16 activating 1624853352867 -(index):16 activated 1624853353871">
    (index):16 installed 1624853346141
    +(index):16 activated 1624853353871">
    (index):16 installed 1624853346141
     workbox-core.dev.js:45 workbox Router is responding to: /stuck/slow.json
     workbox-core.dev.js:45 workbox Using StaleWhileRevalidate to respond to '/stuck/slow.json'
     service-worker.js:17 trigger skipWaiting at 1624853348949
     (index):16 activating 1624853352867
     (index):16 activated 1624853353871
     

    -

    Though cached, StaleWhileRevalidate strategy still revalidates the resource in the background. Thus the old service worker is unable to stop until the request finishes.

    +

    Though cached, StaleWhileRevalidate strategy still revalidates the resource in the background. Thus the old service worker is unable to stop until the request finishes.

    The solution

    -

    It's natural to think of aborting the request in the old service worker. Let's add AbortController. Notice that you need to let new service worker to skip waiting, and let old service worker to abort fetches. Don't get confused.

    +

    It's natural to think of aborting the request in the old service worker. Let's add AbortController. Notice that you need to let new service worker to skip waiting, and let old service worker to abort fetches. Don't get confused.

    @@ -341,7 +341,7 @@

    The solution

    at StaleWhileRevalidate.makeRequest (https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-strategies.dev.js:1005:15) (index):16 activating 1624854001616 (index):16 activated 1624854002627 -Navigated to http://localhost:8345/solution/">
    (index):16 installed 1624853994162
    +Navigated to http://localhost:8345/solution/">
    (index):16 installed 1624853994162
     workbox-core.dev.js:45 workbox Router is responding to: /solution/slow.json
     service-worker.js:21 trigger skipWaiting at 1624854001606
     workbox-strategies.dev.js:1005 Uncaught (in promise) no-response: The strategy could not generate a response for 'http://localhost:8345/solution/slow.json'. The underlying error is AbortError: The user aborted a request..
    diff --git a/post/python-async-subprocess/index.html b/post/python-async-subprocess/index.html
    index 07193eb08..81f8c70ee 100644
    --- a/post/python-async-subprocess/index.html
    +++ b/post/python-async-subprocess/index.html
    @@ -66,7 +66,7 @@ 

    简单例子

    http_client = AsyncHTTPClient() response = await http_client.fetch(url) return response.body
    -

    虽然看起来除了async await之外和同步的没有什么区别,但是阻塞的函数并不能直接改成异步的函数,因为异步函数会立即返回,而且写程序的时候要明确告知什么时候停下。接下来详细讨论一下。

    +

    虽然看起来除了async await之外和同步的没有什么区别,但是阻塞的函数并不能直接改成异步的函数,因为异步函数会立即返回,而且写程序的时候要明确告知什么时候停下。接下来详细讨论一下。

    Get Your Hands Dirty!

    Based on https://realpython.com/async-io-python/

    @@ -92,9 +92,9 @@

    Get Your Hands Dirty!

    asyncio.run(main()) elapsed = time.perf_counter() - s print(f"{__file__} executed in {elapsed:0.2f} seconds.")
    -
    One One One Two Two Two aioplay.py executed in 1.00 seconds.
    +
    One One One Two Two Two aioplay.py executed in 1.00 seconds.
     
    -

    只是直接调用count(),会返回<coroutine object count at ...>,而并不会立即执行,这和 JS 不一样。插播 JS:

    +

    只是直接调用count(),会返回<coroutine object count at ...>,而并不会立即执行,这和 JS 不一样。插播 JS:

    @@ -105,13 +105,13 @@

    Get Your Hands Dirty!

    Promise { undefined, domain: - ... }">
    1
    +   ... }">
    1
     Promise {
       undefined,
       domain:
        ... }
     
    -

    但是在一个异步函数里调用而不用await变现,会直接报错:

    +

    但是在一个异步函数里调用而不用await变现,会直接报错:

    @@ -131,12 +131,12 @@

    Get Your Hands Dirty!

    RuntimeWarning: Enable tracemalloc to get the object allocation traceback

    这又和 JS 不一样。


    -

    如果不使用await asyncio.sleep,而是使用time.sleep来模拟一个阻塞的操作:

    -
    One Two One Two One Two aioplay.py executed in 3.00 seconds.
    +

    如果不使用await asyncio.sleep,而是使用time.sleep来模拟一个阻塞的操作:

    +
    One Two One Two One Two aioplay.py executed in 3.00 seconds.
     

    没有切入点,只好顺序执行


    -

    如果修改main,成为一个循环:

    +

    如果修改main,成为一个循环:

    @@ -144,9 +144,9 @@

    Get Your Hands Dirty!

    async def main():
         for _ in range(3):
             await count()
    -
    One Two One Two One Two aioplay.py executed in 3.00 seconds.
    +
    One Two One Two One Two aioplay.py executed in 3.00 seconds.
     
    -

    因为await会等到函数结束后才进行下一步操作。asyncio.gather(count(), count(), count())正是为了让它们一起运行。

    +

    因为await会等到函数结束后才进行下一步操作。asyncio.gather(count(), count(), count())正是为了让它们一起运行。

    获取命令输出

    注意 Windows 下要指定路径

    @@ -159,8 +159,8 @@

    获取命令输出

    shell=False, stdout=subprocess.PIPE) for line in iter(p.stdout.readline, b''): print(line.rstrip().decode())
    -

    这里的 iter 为不常见用法,指一直调用此函数,直至返回b''

    -

    或者使用with

    +

    这里的 iter 为不常见用法,指一直调用此函数,直至返回b''

    +

    或者使用with

    @@ -179,7 +179,7 @@

    获取命令输出

    for line in p.stdout: print(line.rstrip().decode())

    异步获取命令输出

    -

    可惜上述是阻塞的,不能用于异步。这就需要asyncio.subprocess出场了。

    +

    可惜上述是阻塞的,不能用于异步。这就需要asyncio.subprocess出场了。

    @@ -228,7 +228,7 @@

    异步获取命令输出

    2> 2> --- www.a.shifen.com ping statistics --- 2> 4 packets transmitted, 4 received, 0% packet loss, time 3004ms -2> rtt min/avg/max/mdev = 31.721/35.892/46.580/6.182 ms">
    1> PING www.a.shifen.com (183.232.231.174) 56(84) bytes of data.
    +2> rtt min/avg/max/mdev = 31.721/35.892/46.580/6.182 ms">
    1> PING www.a.shifen.com (183.232.231.174) 56(84) bytes of data.
     1> 64 bytes from 183.232.231.174 (183.232.231.174): icmp_seq=1 ttl=55 time=34.2 ms
     2> PING www.a.shifen.com (183.232.231.174) 56(84) bytes of data.
     2> 64 bytes from 183.232.231.174 (183.232.231.174): icmp_seq=1 ttl=55 time=46.6 ms
    @@ -247,7 +247,7 @@ 

    异步获取命令输出

    2> 4 packets transmitted, 4 received, 0% packet loss, time 3004ms 2> rtt min/avg/max/mdev = 31.721/35.892/46.580/6.182 ms
    -

    async for?

    +

    async for?

    @@ -306,11 +306,11 @@

    async for?

    One Two Yeah!
    +aioplay.py executed in 3.00 seconds.">
    One Two Yeah!
     One Two Yeah!
     One Two Yeah!
     aioplay.py executed in 3.00 seconds.
     
    - + diff --git a/post/raspi-tricks/index.html b/post/raspi-tricks/index.html index 92bec3be9..177900e33 100644 --- a/post/raspi-tricks/index.html +++ b/post/raspi-tricks/index.html @@ -42,7 +42,7 @@

    Get the Temperature

    shell
    cat /sys/class/thermal/thermal_zone0/temp

    Outputs:

    -
    29482
    +
    29482
     

    And here is bash script to create temperature PS1

    @@ -57,8 +57,8 @@

    Get the Temperature

    fi prompt_section $color "${temp::${#temp}-3}.${temp:${#temp}-3}" fi
    -

    Adding a dot is just mixing bash substrings (${string:offset[:length]}) with bash string lengths (${#string})

    -

    vcgencmd

    +

    Adding a dot is just mixing bash substrings (${string:offset[:length]}) with bash string lengths (${#string})

    +

    vcgencmd

    From https://www.raspberrypi.org/documentation/raspbian/applications/vcgencmd.md

    To get all commands, use:

    @@ -72,10 +72,10 @@

    Voltage

    shell
    vcgencmd get_throttled
    -

    If voltage is okay, you will get 0x0

    -

    Else, say 0x50000, convert it to binary, and see each bit:

    +

    If voltage is okay, you will get 0x0

    +

    Else, say 0x50000, convert it to binary, and see each bit:

    19 18 17 16 15 14 13 12 11 10  9  8  7  6  5  4  3  2  1  0
    + 0  1  0  1  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0">
    19 18 17 16 15 14 13 12 11 10  9  8  7  6  5  4  3  2  1  0
      0  1  0  1  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
     
    @@ -120,7 +120,7 @@

    Voltage

    -

    Generally, the 3 0s in the middle do not have specific meaning. Just need to care about the first 5 and the last 0.

    +

    Generally, the 3 0s in the middle do not have specific meaning. Just need to care about the first 5 and the last 0.

    Screen on / off

    When you start the Raspberry Pi with display / monitor / screen on, it will automatically turn on display power. If not, display power will never turn on even if you later connect it to a screen. So it is necessary to control the display power.

    @@ -128,8 +128,8 @@

    Screen on / off

    shell
    vcgencmd display_power 1
    -

    1 for on and 0 for off. bare vcgencmd display_power shows current state.

    +

    1 for on and 0 for off. bare vcgencmd display_power shows current state.

    It is possible to specify a certain display ID. For more info, visit the doc above.

    - + diff --git a/post/react-virtual-scroll/index.html b/post/react-virtual-scroll/index.html index a9d57f0b0..810db7591 100644 --- a/post/react-virtual-scroll/index.html +++ b/post/react-virtual-scroll/index.html @@ -38,11 +38,11 @@

    Virtual scroll 解决了什么问题

    Virtual scroll 是绘制超大型列表的性能解决方案。众所周知当 DOM 元素变得很多的时候,渲染元素将消耗可观的时间。即使元素是分配获取、绘制的,浏览器的内存消耗、计算元素位置的 CPU 消耗等也会上升,列表滚动及其他操作的延迟就会增加。

    Virtual scroll 是怎么做的

    -

    正如某相关模块 react-window 的名字,你是通过一扇窗户来看这个大列表,也只有窗户里的东西才会绘制(当然可以设置余量 overscan)。

    +

    正如某相关模块 react-window 的名字,你是通过一扇窗户来看这个大列表,也只有窗户里的东西才会绘制(当然可以设置余量 overscan)。

    其他解决方案的问题

    (没错,我只知道 LazyLoad)

    Lazyload

    -

    对于 React 而言,react-lazyload 中需要 lazyload 的元素呆在同一个列表中,这意味着如果一个页面有两个列表,一个的滚动将引起另一个列表内元素的重新计算。而且如果你有一个 overflow: scroll 的列表和一个随页面滚动的列表,将引起事件处理的混乱。

    +

    对于 React 而言,react-lazyload 中需要 lazyload 的元素呆在同一个列表中,这意味着如果一个页面有两个列表,一个的滚动将引起另一个列表内元素的重新计算。而且如果你有一个 overflow: scroll 的列表和一个随页面滚动的列表,将引起事件处理的混乱。

    一般来说,LazyLoad 并没有解决 DOM 元素数量级的问题,只是把复杂的元素变得简单,到时候再绘制。如果有大规模筛选列表的操作,Unmount LazyLoad component 也是一笔不小的开支。

    如果滚动到很下方,对上面的元素如何处置又是一个问题。Unmount 吧,滚动位置会突变;hide 吧,实质作用并不大,仍然参与计算,内存也占用着。

    Virtual scroll 的难点及 React 现有模块简评

    @@ -61,6 +61,6 @@

    性能和懒难以兼得

    bug 和问题频出

    (看了 Issue 区就有点劝退了)

    简单讲一个 react-virtual 碰到的问题。因为元素高度会变化,在图片加载后让 react-virtual 重新计算。但是重新计算意味着把所有元素的高度都看做是估计值。将会出现多次尝试绘制,再次触发的重新计算甚至引起绘制的死循环。同时滚动会出现跳跃。。😭

    - + diff --git a/post/showcase/index.html b/post/showcase/index.html index be2294cb4..7d27508ea 100644 --- a/post/showcase/index.html +++ b/post/showcase/index.html @@ -57,13 +57,13 @@

    Hello!

    even('write').code ? ah : ha
    -

    code in head of course!

    -
    code with unspecified language
    +

    code in head of course!

    +
    code with unspecified language
     
    -
    or code with indent
    +
    or code with indent
     

    More complex heading level!

    -
    A very very very very very very very very very very very very very very very very very very very very very very very very very very very very very long code
    +
    A very very very very very very very very very very very very very very very very very very very very very very very very very very very very very long code
     

    And more!

    Paragraph

    @@ -75,6 +75,6 @@
    Should Be Visible

    $$
    \int_{- \infty}^{\infty} \frac{1}{x} \mathrm d x
    $$

    - + diff --git a/post/sql-query-perf/index.html b/post/sql-query-perf/index.html index 01d4e0699..3782e1ffb 100644 --- a/post/sql-query-perf/index.html +++ b/post/sql-query-perf/index.html @@ -50,12 +50,12 @@

    尝试 1:使用 SQLAlchemy 的 class DayInfo(Base): __tablename__ = "day_info" code = Column(String(8), ForeignKey("basic_info.code"), primary_key=True) -

    并在读取中使用 limit 控制读取的个数。处理 3 天的数据查找指定条件,用时约 1 分钟。

    +

    并在读取中使用 limit 控制读取的个数。处理 3 天的数据查找指定条件,用时约 1 分钟。

    尝试 2:一次性读取所有的记录

    按照尝试 1 的做法数据库请求的个数会很多,数千次请求不会很快。如果改用所有数据合并到一次请求当中,则大大加快。用时达到 8 秒。

    问题 1:启动时间

    由于需要导入大量的库,启动时间约有 4 秒钟,不容乐观。

    -

    使用 PYTHONPROFILEIMPORTTIME=1 运行,并将 stderr 导出至文件,使用 tuna 查看,发现大头是 pandas 和 SQLAlchemy。由于 pandas 并不是每次运行都需要使用,故将其从 top level import 挪到的相应的函数中。时间减少了 2 秒。

    +

    使用 PYTHONPROFILEIMPORTTIME=1 运行,并将 stderr 导出至文件,使用 tuna 查看,发现大头是 pandas 和 SQLAlchemy。由于 pandas 并不是每次运行都需要使用,故将其从 top level import 挪到的相应的函数中。时间减少了 2 秒。

    此时用时达到 6 秒。

    尝试 3:使用 List Comprehension 运行匹配算法

    时间几乎没有变化。后来知道是匹配算法本来运行时间就短的原因。

    @@ -72,7 +72,7 @@

    尝试 4:使用 SQLAlchemy partitions():

    时间并没有显著变化,但是内存的占用从大约 9% 降至 2%。可以肯定是 Python 的对象数量减少带来的。

    尝试 5:更换 Python 的 MySQL 连接库

    -

    使用 python -m cProfile -o program.prof -m myprog ,再使用 snakeviz 可视化,发现很多时间被用在了 parse string, int 等上。而且使用 top 命令也可以看到 CPU 占满了一个核。将 PyMySQL 更换为 mysqlclient 后,处理 40 天数据从 22 秒降至 12 到 13 秒。

    +

    使用 python -m cProfile -o program.prof -m myprog ,再使用 snakeviz 可视化,发现很多时间被用在了 parse string, int 等上。而且使用 top 命令也可以看到 CPU 占满了一个核。将 PyMySQL 更换为 mysqlclient 后,处理 40 天数据从 22 秒降至 12 到 13 秒。

    问题 2:分页

    由于股票数据很多,尽管尝试 4 中使用了分页,但是在 MySQL 中,数据仍是一次读取。

    参考了网上的资料:

    @@ -80,7 +80,7 @@

    问题 2:分页

  • https://www.xarg.org/2011/10/optimized-pagination-using-mysql/
  • https://qsli.github.io/2016/09/30/pagination/PPC2009_mysql_pagination.pdf
  • -

    看到应该结合所选的条件进行分页而不是暴力地 LIMIT OFFSET. 如:

    +

    看到应该结合所选的条件进行分页而不是暴力地 LIMIT OFFSET. 如:

    @@ -94,7 +94,7 @@

    问题 2:分页

    ) LIMIT 30

    尝试 6:不使用 SQLAlchemy

    -

    进一步 profile 看到有很多运行时间是花在 SQLAlchemy 上的。直接跳过 ORM 层操作,时间减少了约 1 秒(可能就是不 import SQLAlchemy 带来的),但是在运行时间中,CPU 占用比率((user + sys)/real) 从 96% 降至 50% - 60%。这意味着性能瓶颈由原来的 CPU 转化为 CPU 和 IO

    +

    进一步 profile 看到有很多运行时间是花在 SQLAlchemy 上的。直接跳过 ORM 层操作,时间减少了约 1 秒(可能就是不 import SQLAlchemy 带来的),但是在运行时间中,CPU 占用比率((user + sys)/real) 从 96% 降至 50% - 60%。这意味着性能瓶颈由原来的 CPU 转化为 CPU 和 IO

    问题 3:Query Time

    过程中发现 MariaDB 突然表现出了缓存功能,再次运行后 IO 时间显著减小至可以忽略。所以 MySQL 的 query 速度还有很大的优化空间。

    使用

    @@ -112,9 +112,9 @@

    问题 3:Query Time

    SET GLOBAL slow_query_log_file='/var/log/mysql/sql-slow.log'; SET GLOBAL long_query_time = 1;

    查阅日志可以看到,一条一万数据的 select 花了 7 秒钟才完成。

    -
    Query_time: 6.917748  Lock_time: 0.000308  Rows_sent: 10000  Rows_examined: 799
    +
    Query_time: 6.917748  Lock_time: 0.000308  Rows_sent: 10000  Rows_examined: 799
     
    -

    为方便调试性能,需要关闭缓存功能,可使用 SELECT SQL_NO_CACHE ... 实现。

    +

    为方便调试性能,需要关闭缓存功能,可使用 SELECT SQL_NO_CACHE ... 实现。

    如问题 2 中的 query:

    @@ -148,7 +148,7 @@

    问题 3:Query Time

    JOIN day_info ON basic_info.code=day_info.code WHERE basic_info.code='specificode' AND day_info.date > '2021-02-09';

    则半秒钟内即可完成。

    -

    有没有将两个合成一个但又不影响性能的方法呢?是有的。使用 UNION 合并两个 query,并注意要用 UNION ALL 跳过去重步骤,并用括号第把二个语句括起来。如果不使用括号,则 LIMIT 将被识别为整个大语句的,严重影响速度。

    +

    有没有将两个合成一个但又不影响性能的方法呢?是有的。使用 UNION 合并两个 query,并注意要用 UNION ALL 跳过去重步骤,并用括号第把二个语句括起来。如果不使用括号,则 LIMIT 将被识别为整个大语句的,严重影响速度。

    @@ -168,12 +168,12 @@

    问题 3:Query Time

    LIMIT 5000 );

    处理 40 天数据降至 7 秒。

    -

    尝试 7:use_result vs store_result

    -

    在 mysqlclient 中,默认的 cursor 类在底层使用 store_result,即将每一次查询的结果一股脑存下来备用。相比之下 use_result 就人性化很多,先放在 server 里有需要再取,也符合 fetchmany 的初衷。

    -

    使用默认 Cursor 类的 profile 结果:

    +

    尝试 7:use_result vs store_result

    +

    在 mysqlclient 中,默认的 cursor 类在底层使用 store_result,即将每一次查询的结果一股脑存下来备用。相比之下 use_result 就人性化很多,先放在 server 里有需要再取,也符合 fetchmany 的初衷。

    +

    使用默认 Cursor 类的 profile 结果:

    before

    -

    使用 SSCursor 类的 profile 结果(橘色那块是 execute,剩下那块是另一个与 SQL 无关的函数,也就是上图较深灰的部分):

    +

    使用 SSCursor 类的 profile 结果(橘色那块是 execute,剩下那块是另一个与 SQL 无关的函数,也就是上图较深灰的部分):

    after

    - + diff --git a/post/tmux-setup/index.html b/post/tmux-setup/index.html index 182772ac8..4f5b19e5b 100644 --- a/post/tmux-setup/index.html +++ b/post/tmux-setup/index.html @@ -46,7 +46,7 @@

    Enable 256 color vim

    shell
    export TERM=xterm-256color
    -

    in .tmux.conf:

    +

    in .tmux.conf:

    @@ -59,7 +59,7 @@

    Enable 256 color vim

    shell
    tmux source-file ~/.tmux.conf

    All above make sure tmux have full 256 colors support.

    -

    And in your vimrc, add:

    +

    And in your vimrc, add:

    @@ -93,7 +93,7 @@

    Resize Panes on Keyboard

    :resize-pane -D 10 (Resizes the current pane down by 10 cells) :resize-pane -U 10 (Resizes the current pane upward by 10 cells) :resize-pane -L 10 (Resizes the current pane left by 10 cells) -:resize-pane -R 10 (Resizes the current pane right by 10 cells)">
    :resize-pane -D (Resizes the current pane down)
    +:resize-pane -R 10 (Resizes the current pane right by 10 cells)">
    :resize-pane -D (Resizes the current pane down)
     :resize-pane -U (Resizes the current pane upward)
     :resize-pane -L (Resizes the current pane left)
     :resize-pane -R (Resizes the current pane right)
    @@ -108,6 +108,6 @@ 

    Border Chaos

    It turns out to be the old problem with MinTTY. As you can see on the top-middle of the screenshot, the border is displayed in full width (2 chars), with introduced the chaos.

    To solve it, inspired by mintty/mintty#615, all you need to do is Options → Text → Locale: C (Or any language with sane character width). And the broken vim airline is solved too!

    fixed

    - + diff --git a/post/tmux-ssh/index.html b/post/tmux-ssh/index.html index a31cf823b..8149902cd 100644 --- a/post/tmux-ssh/index.html +++ b/post/tmux-ssh/index.html @@ -37,7 +37,7 @@

    How to automatically start tmux on SSH session?

    From https://stackoverflow.com/a/40192494/8810271

    -

    Just put it in server-side .bashrc

    +

    Just put it in server-side .bashrc

    @@ -55,16 +55,16 @@

    Detach from tmux session and close SSH session with 1 command

    tmux detach -P

    Copy Content Inside tmux

      -
    1. enter copy mode using c-b [
    2. -
    3. navigate to beginning of text, you want to select and hit C-Space
    4. +
    5. enter copy mode using c-b [
    6. +
    7. navigate to beginning of text, you want to select and hit C-Space
    8. move around using arrow keys to select region
    9. -
    10. when you reach end of region simply hit M-w to copy the region
    11. -
    12. now c-b ] will paste the selection
    13. +
    14. when you reach end of region simply hit M-w to copy the region
    15. +
    16. now c-b ] will paste the selection

    Copy over SSH with MinTTY

    Options → Selection → Allow setting selection

    And copy as usual in tmux.

    -

    If inside vim, enter copy mode first by hitting c-b [

    - +

    If inside vim, enter copy mode first by hitting c-b [

    + diff --git a/post/upgrade-ubuntu/index.html b/post/upgrade-ubuntu/index.html index 111b920c8..5ffbd1793 100644 --- a/post/upgrade-ubuntu/index.html +++ b/post/upgrade-ubuntu/index.html @@ -36,9 +36,9 @@

    Ever feel frustated when a new release is available but it keeps saying no release found?

    The upgrade strategy

    -

    The configuration file lies in /etc/update-manager/release-upgrades. Change to normal if not upgrating to a LTS release.

    +

    The configuration file lies in /etc/update-manager/release-upgrades. Change to normal if not upgrating to a LTS release.

    Upgrate to devel release

    -

    By default, you can only upgrade after the first point release, say 20.04.1. If you can't wait, run do-release-upgrade -d to upgrade to the devel release.

    - +

    By default, you can only upgrade after the first point release, say 20.04.1. If you can't wait, run do-release-upgrade -d to upgrade to the devel release.

    + diff --git a/post/vim-ariline/index.html b/post/vim-ariline/index.html index 084472980..470665f23 100644 --- a/post/vim-ariline/index.html +++ b/post/vim-ariline/index.html @@ -58,7 +58,7 @@
    AC Dustbin

    Setup Vim Airline (in Termux)

    2019-02-09 00:00 2021-02-22 01:59

    Vim airline is a tool to enhance the looking of vim. And it is a little tricky to setup especially in termux.

    -

    As I did not install any package manager for vim (in fact, I failed to install and setup Vundle), I need to clone the repo to ~/.vim/pack/dist/start/ to let vim automatically load the package. And I see the airline when I opened vim.

    +

    As I did not install any package manager for vim (in fact, I failed to install and setup Vundle), I need to clone the repo to ~/.vim/pack/dist/start/ to let vim automatically load the package. And I see the airline when I opened vim.

    By the way, all packages I tested (NERDTree, indentLine, gitgutter) all support this kind of operation.

    @@ -76,10 +76,10 @@

    Only mode and filename is displayed!

    \ 'x': 80, \ 'y': 80}
    -

    Note: Only when nocompatible set can you use \ to continue line.

    +

    Note: Only when nocompatible set can you use \ to continue line.

    1

    -

    No fancy > s there!

    +

    No fancy > s there!

    Enable fancy fonts!

    @@ -133,7 +133,7 @@

    Fancy style?

    let g:airline_theme = 'badwolf'

    7
    8

    -

    Do not want extra > s?

    +

    Do not want extra > s?

    Disable it!

    @@ -141,6 +141,6 @@

    Do not want extra > s?

    viml
    let g:airline_skip_empty_sections = 1

    9

    - + diff --git a/post/vim-cht/index.html b/post/vim-cht/index.html index 6178b00b9..fef519b62 100644 --- a/post/vim-cht/index.html +++ b/post/vim-cht/index.html @@ -34,7 +34,7 @@
    AC Dustbin

    Vim Cheatsheet

    2020-04-18 04:48 2020-07-01 13:01

    Getting help

    -

    ctrl+] to follow link, ctrl+t to trace back.

    +

    ctrl+] to follow link, ctrl+t to trace back.

    Quit all

    @@ -43,10 +43,10 @@

    Quit all

    :qa

    Scroll the terminal

    From https://stackoverflow.com/a/50545253/8810271

    -

    ctrl+w N (notice the capital N) to enter terminal normal mode. You can even search in the terminal output!

    -

    And hit either i or a to enter insert mode.

    +

    ctrl+w N (notice the capital N) to enter terminal normal mode. You can even search in the terminal output!

    +

    And hit either i or a to enter insert mode.

    Open terminal / help page verically

    -

    Use the :vert[ical] command modifier:

    +

    Use the :vert[ical] command modifier:

    @@ -54,18 +54,18 @@

    Open terminal / help page verically

    :vert term
     :vert help ex

    Enter normal mode for command history

    -

    CTRL-F q: q/ q?

    +

    CTRL-F q: q/ q?

    Paste yanked text into the Vim command line

    From https://stackoverflow.com/a/3997110/8810271

    -

    Hit Ctrl-R then ". If you have literal control characters in what you have yanked, use Ctrl-R, Ctrl-O, ".

    +

    Hit Ctrl-R then ". If you have literal control characters in what you have yanked, use Ctrl-R, Ctrl-O, ".

    PS: this Stack Overflow answer is excellent, maybe I will translate it into Chinese later.

    Excute command on matched line

    -

    :h global for more information

    -
    :[range]g[lobal]/{pattern}/[cmd]
    +

    :h global for more information

    +
    :[range]g[lobal]/{pattern}/[cmd]
     

    Execute the Ex command [cmd] (default ":p") on the lines within [range] where {pattern} matches.

    -

    For pattern not match, use :g! or :v instead. You can use another charater as delimiter or even nest g and v.

    -

    If you want to excute normal commands, just :g/This line/norm 3dd.

    +

    For pattern not match, use :g! or :v instead. You can use another charater as delimiter or even nest g and v.

    +

    If you want to excute normal commands, just :g/This line/norm 3dd.

    What is Ex mode?

    Switch to "Ex" mode. This is a bit like typing ":" commands one after another, except:

      @@ -81,6 +81,6 @@

      Measure startup time

      shell
    vim --startuptime vim.log
    - + diff --git a/post/vscode-setup/index.html b/post/vscode-setup/index.html index 0ef6b5483..9214a93f6 100644 --- a/post/vscode-setup/index.html +++ b/post/vscode-setup/index.html @@ -67,7 +67,7 @@

    为什么选择 VSCode

    基于插件 (Extension) 的模式

    与 JetBrains (IntelliJ IDEA, PyCharm, WebStorm, PhpStorm, CLion......) 等编辑器不同的是,VSCode 是为了支持各种语言而生的,只要安装对应的插件,但是支持程度得看相应插件的水平了。与 VSCode 相类似的还有 Sublime Text, Vim, Atom, Emacs 等。

    比如,Python 就有 Python 插件(对,就叫这名),Vue 就有 Vetur,Docker 就有 Docker...

    -

    当然,一些语言 (C / C++, JavaScript, JSON, CSS...) 的插件已经随 VSCode 默认安装,就像手机出厂的时候就不是一块板砖。具体列表可以在侧边栏扩展 (Extensions) 标签页下,输入 @builtin 查看。如果自带的插件功能上没有欠缺的话,就不必另行安装插件。

    +

    当然,一些语言 (C / C++, JavaScript, JSON, CSS...) 的插件已经随 VSCode 默认安装,就像手机出厂的时候就不是一块板砖。具体列表可以在侧边栏扩展 (Extensions) 标签页下,输入 @builtin 查看。如果自带的插件功能上没有欠缺的话,就不必另行安装插件。

    大家的选择

    @@ -146,7 +146,7 @@

    上手 VSCode

    菜单 Help -> Tips and Tricks

    高颜值 / 好看的主题 (Awesome Theme)

    主题 (Theme) 一般是一种配色方案。在 VSCode 下,主题可以是传统意义上的配色主题,也可指图标主题,即 VSCode 中涉及文件的视图,文件(夹)名前面的图标。

    -

    已经安装的主题可以通过快捷键 Ctrl + K Ctrl + T 查看和切换。同时,在扩展 (Extensions) 标签页下,输入 @category:"themes" 可以查找所有的主题。而 https://vscodethemes.com/ 也提供了不同主题的预览,更加便于发现好主题。

    +

    已经安装的主题可以通过快捷键 Ctrl + K Ctrl + T 查看和切换。同时,在扩展 (Extensions) 标签页下,输入 @category:"themes" 可以查找所有的主题。而 https://vscodethemes.com/ 也提供了不同主题的预览,更加便于发现好主题。

    此处安利 One Dark

    语法高亮 (Syntax Highlighting)

    谁也不想打开一段代码像记事本一样通篇一个颜色,不分主次、不分类别,看上去很吃力。

    @@ -156,17 +156,17 @@

    语法高亮 (Syntax Highlighting)

    智能补全 (Auto Completion, IntelliSense)

    普通的变量名补全,可以让你少打几个字,让编辑器完成剩下的,不仅可以加快速度,让你从琐碎的劳动中解脱出来,还可以让你不再因为打字麻烦而不规范地命名变量和函数名称。

    就像使用拼音输入法,用久了之后谁也不想每个字都拼出来才能把词语打出吧。手机上的输入法还会根据前面的输入建议接下来的词语,或者调整下一个拼音中不同候选词的位置,(不管人工智能还是人工智障,总之)提供了很大的便利。

    -

    同时,在变量和函数的补全中,也省去了许多(不是全部)查阅文档的工作,也避免了变量名和函数名打错的情况。比如在码了多年 Python 和多年 JavaScript 之后,分不清 .startsWith, .starts_with, .startswith, 自动补全可以省去因记不清每次都上网查文档的烦恼。又比如有的时候 team work,由于习惯一直以为队友打的是过去式 created_blahblah, 但是实际上是 create_blahblah, 这时使用智能补全可以很好的避免变量误写。在下面的代码检查中可以看到另一种避免方式,但是它对一些 dynamic 的语言, implicit 的写法,(比如 SQLAlchemy,)也几乎束手无策。

    +

    同时,在变量和函数的补全中,也省去了许多(不是全部)查阅文档的工作,也避免了变量名和函数名打错的情况。比如在码了多年 Python 和多年 JavaScript 之后,分不清 .startsWith, .starts_with, .startswith, 自动补全可以省去因记不清每次都上网查文档的烦恼。又比如有的时候 team work,由于习惯一直以为队友打的是过去式 created_blahblah, 但是实际上是 create_blahblah, 这时使用智能补全可以很好的避免变量误写。在下面的代码检查中可以看到另一种避免方式,但是它对一些 dynamic 的语言, implicit 的写法,(比如 SQLAlchemy,)也几乎束手无策。

    与语法高亮类似,补全功能也基本都集成在插件当中。一般情况下在你输入的时候就会出现提示,如果不出现,可以通过 Ctrl + Space 唤出。实现上一般是通过后台开一个语言服务器 (Language Server), 通过协议 (Language Server Protocol, LSP) 与编辑器前端通讯,由服务器负责分析代码,给出建议。比如 Python 的语言服务器有 Jedi, Pylance, 而 JavaScript / TypeScript 有 TSServer, 虽然 Vue 只是一个框架,也有 VLS (Vue Language Server).

    -

    注意对于 Python,如果要对虚拟环境里的库进行补全,则必须要配置 Python 的解释器路径。Ctrl + Shift + P 显示命令菜单,输入 Python: Select Interpreter 设置正确的解释器路径。

    +

    注意对于 Python,如果要对虚拟环境里的库进行补全,则必须要配置 Python 的解释器路径。Ctrl + Shift + P 显示命令菜单,输入 Python: Select Interpreter 设置正确的解释器路径。

    代码格式化 (Formatting)

    在解释格式化之前,应当解释什么是代码风格 (Code Style). 代码风格就是代码看起来长什么样,拆开来说,何时使用空格,何时换行,如何缩进,左大括号是否另起一行,有多种写法时选择哪一种,等等。那么代码格式化,就是将每个人写出来的代码规范为统一的格式。这在团队合作中显得更加重要,谁也不想代码乱成一锅粥,这个函数是这个风格,那个函数又是另外一个风格。

    代码格式化的任务往往不是插件本身完成的,插件只是和现有的代码格式化工具 (Formatter) 集成。每个语言都有自己的格式化工具,比如 Python 的 Black, YAPF, 和 JavaScript 的 Prettier. 使用时只需快捷键 Shift + Alt + F 或右键菜单 -> Format Document( With...)

    代码检查 (Linting)

    代码检查一大功能就是在不运行你的代码的情况下,分析代码中存在的问题,或不符合最佳实践 (Best Practice) 的地方。与代码格式化类似,插件只是和现有的代码检查器 (Linter) 集成,比如根据 linter 的输出,在编辑器中有问题的地方显示红色黄色下划线。

    但是有的代码检查器又兼具检查代码风格甚至代码格式化的功能,比如 Python 的 Flake8,JavaScript 的 ESLint 等。但是相比于专门的 formatter 来说,linter 的格式要求与格式化能力往往要弱一些。

    -

    编辑器的代码检查功能需要额外配置。比如 Python 需要 Ctrl + Shift + P 显示所有命令,输入 Python: Select Linter 选择 linter。对于 JavaScript 就要简单得多,在项目使用并且配置好 ESLint 的前提下,安装 ESLint 插件就可以进行检查。

    -

    以 ESLint 为例, getter-return (enforce return statements in getters) 就是发现错误,eqeqeq (require the use of === and !==) 就是最佳实践,no-mixed-spaces-and-tabs(disallow mixed spaces and tabs for indentation) 就是对格式的要求。

    +

    编辑器的代码检查功能需要额外配置。比如 Python 需要 Ctrl + Shift + P 显示所有命令,输入 Python: Select Linter 选择 linter。对于 JavaScript 就要简单得多,在项目使用并且配置好 ESLint 的前提下,安装 ESLint 插件就可以进行检查。

    +

    以 ESLint 为例, getter-return (enforce return statements in getters) 就是发现错误,eqeqeq (require the use of === and !==) 就是最佳实践,no-mixed-spaces-and-tabs(disallow mixed spaces and tabs for indentation) 就是对格式的要求。

    自动缩进 (Auto Indentation)

    代码没有缩进就没有层次。自动缩进顾名思义就是减少手动调整缩进的繁琐。自动缩进对于 Python 来说比较重要。当然 Python 是对缩进敏感的,基础的自动缩进已经由 Python 插件完成了。但是根据 PEP8 的风格规范,Python 断行、括号内换行有特殊的规范,而 Python 插件对比如括号内回车换行的自动缩进并没有很好的支持,这时需要插件 Python Indent 的帮忙。

    快捷键 (Keyboard Shortcut)

    @@ -183,9 +183,9 @@

    代码片段 (Code Snippets)

    }catch(error){// ...} -

    代码片段的功能就是帮你打完这些模板式的语句(块)。仍然以 try...catch 为例。在 JavaScript 代码中,输入 try,则会自动提示 trycatch,如果不出现,可以通过 Ctrl + Space 唤出。这时在建议菜单中选中 trycatch (点击、回车或 Tab),就会补全。具体可自行尝试。

    +

    代码片段的功能就是帮你打完这些模板式的语句(块)。仍然以 try...catch 为例。在 JavaScript 代码中,输入 try,则会自动提示 trycatch,如果不出现,可以通过 Ctrl + Space 唤出。这时在建议菜单中选中 trycatch (点击、回车或 Tab),就会补全。具体可自行尝试。


    其他的以后再说吧(逃

    - + diff --git a/post/vue-hammer/index.html b/post/vue-hammer/index.html index d903b647a..7673acf77 100644 --- a/post/vue-hammer/index.html +++ b/post/vue-hammer/index.html @@ -33,15 +33,15 @@
    AC Dustbin

    Vue 加 Hammer 处理手势

    2020-01-27 08:33 2020-07-03 02:20

    Hammer.js

    -

    hammerjs 在拙劣的搜索之后,才发现了这个库。虽然有 一个 Vue 的封装库 vue-touch,但是它年久失修,而且其组件名称v-touch与 Vuetify 的某组件重名。。。

    +

    hammerjs 在拙劣的搜索之后,才发现了这个库。虽然有 一个 Vue 的封装库 vue-touch,但是它年久失修,而且其组件名称v-touch与 Vuetify 的某组件重名。。。

    使用

    http://hammerjs.github.io/

    文档非常简单,提几点需要注意的地方

    Vue 引用 DOM 元素

    -

    配合$ref食用更佳

    +

    配合$ref食用更佳

    @@ -59,15 +59,15 @@

    Vue 引用 DOM 元素

    } </script>

    做主人

    -

    通过new Hammer(myElement, myOptions)创建的事件监听默认开启了tap, doubletap, press, horizontal panswipe,并且加上了禁用了的pinchrotate

    -

    所以使用new Hammer.Manager(myElement, myOptions)更加舒服。

    -

    理解get

    -

    为什么用Hammer创建的对象可以随便get然后设置enable: false,而Hammer.Manager出来是null呢?

    -

    如上所述,Hammer创建的默认加上了全套事件监听,而Hammer.Manager出来是白板一块,自然不能get没有加入的事件了。

    +

    通过new Hammer(myElement, myOptions)创建的事件监听默认开启了tap, doubletap, press, horizontal panswipe,并且加上了禁用了的pinchrotate

    +

    所以使用new Hammer.Manager(myElement, myOptions)更加舒服。

    +

    理解get

    +

    为什么用Hammer创建的对象可以随便get然后设置enable: false,而Hammer.Manager出来是null呢?

    +

    如上所述,Hammer创建的默认加上了全套事件监听,而Hammer.Manager出来是白板一块,自然不能get没有加入的事件了。

    大坑一个

    -

    文档里的确说了pinchrotate会阻塞元素的事件响应,但没想到有点愚蠢和暴力:启用了pinch之后,scroll就不行了?!一个手指的滑动和两个手指的缩放有关系?

    +

    文档里的确说了pinchrotate会阻塞元素的事件响应,但没想到有点愚蠢和暴力:启用了pinch之后,scroll就不行了?!一个手指的滑动和两个手指的缩放有关系?

    还好找到了解决方案:https://stackoverflow.com/a/27550784/8810271

    -

    由于hammerjs2.x 版本不允许绑定touchstart and touchend,可以直接绑 DOM 元素上啊!

    +

    由于hammerjs2.x 版本不允许绑定touchstart and touchend,可以直接绑 DOM 元素上啊!

    于是解决如下:

    @@ -107,6 +107,6 @@

    大坑一个

    } } </script>
    - + diff --git a/post/vue-iblog/index.html b/post/vue-iblog/index.html index eafaaf23c..9289ab2a6 100644 --- a/post/vue-iblog/index.html +++ b/post/vue-iblog/index.html @@ -72,14 +72,14 @@

    Generate a Personal Access Token

    Let's play with vue-apollo !

    ⚠️ Heads up!

    -

    Do not use vue-cli-plugin-apollo, it sucks: Akryum/vue-cli-plugin-apollo#28

    -

    I want to manage production dependencies myself, not vue-cli-plugin-apollo!

    +

    Do not use vue-cli-plugin-apollo, it sucks: Akryum/vue-cli-plugin-apollo#28

    +

    I want to manage production dependencies myself, not vue-cli-plugin-apollo!

    -

    As GitHub API v4 needs Authorization header, the basic configuration is not enough. Use the full configuration install, and follow this apollo-client issue to setup.

    +

    As GitHub API v4 needs Authorization header, the basic configuration is not enough. Use the full configuration install, and follow this apollo-client issue to setup.

    -

    If you import ApolloClient from 'apollo-boost', ApolloClient will not honor headers in fetchOption

    +

    If you import ApolloClient from 'apollo-boost', ApolloClient will not honor headers in fetchOption

    -

    Basically, src/plugins/vue-apollo.js will look like this:

    +

    Basically, src/plugins/vue-apollo.js will look like this:

    - + diff --git a/post/vue-pwa-1/index.html b/post/vue-pwa-1/index.html index 07973fd05..d7bec9176 100644 --- a/post/vue-pwa-1/index.html +++ b/post/vue-pwa-1/index.html @@ -71,7 +71,7 @@

    如何开始

    npm install -g @vue/cli-init # 使用 init 需要安装
     vue init pwa test

    看看加了什么

    -

    运行vue add pwa后,会输出修改的文件。如果直接新建项目并且 Git 配置正确,Vue 会自动初始化提交。这时再添加 PWA,可使用git status查看。输出略有不同

    +

    运行vue add pwa后,会输出修改的文件。如果直接新建项目并且 Git 配置正确,Vue 会自动初始化提交。这时再添加 PWA,可使用git status查看。输出略有不同

    输出:

    看看加了什么 src/registerServiceWorker.js package-lock.json package.json - src/main.js">
    The following files have been updated / added:
    + src/main.js">
    The following files have been updated / added:
     
      public/img/icons/android-chrome-192x192.png
      public/img/icons/android-chrome-512x512.png
    @@ -103,7 +103,7 @@ 

    看看加了什么

    package.json src/main.js
    -

    git status输出:

    +

    git status输出:

    看看加了什么 public/robots.txt src/registerServiceWorker.js -no changes added to commit (use "git add" and/or "git commit -a")">
    On branch master
    +no changes added to commit (use "git add" and/or "git commit -a")">
    On branch master
     Changes not staged for commit:
       (use "git add <file>..." to update what will be committed)
       (use "git checkout -- <file>..." to discard changes in working directory)
    @@ -140,19 +140,19 @@ 

    看看加了什么

    • -

      更新了package-lock.json, package.json没说的

      +

      更新了package-lock.json, package.json没说的

    • 加了很多图片是为了适应不同端显示。为什么很矫情地用不同的名字呢?因为不同浏览器很矫情地用不同的图标。。

    • -

      robots.txt,为了通过 LightHouse 的检测 vuejs/vue-cli#1715

      +

      robots.txt,为了通过 LightHouse 的检测 vuejs/vue-cli#1715

    • -

      registerServiceWorker.js,注册 Service Worker,并监听生命周期的事件。注意它不是 Service Worker 哦,只是注册者而已。

      +

      registerServiceWorker.js,注册 Service Worker,并监听生命周期的事件。注意它不是 Service Worker 哦,只是注册者而已。

    • -

      main.js,导入了./registerServiceWorker

      +

      main.js,导入了./registerServiceWorker

      ⚠️避免踩坑

      虽然 PWA 里 Service Worker 很重要,但是你完全可以选择自动生成,从而避免涉及对 Service Worker 本身的研 zhe 究 teng

      @@ -164,7 +164,7 @@

      配置 PWA

      😜 TL;DR

      一切默认也可,你可以不用做任何配置。

      -

      在项目根目录下新建vue.config.js进行配置

      +

      在项目根目录下新建vue.config.js进行配置

      配置以文档为准

      • https://cli.vuejs.org/config/#pwa
      • @@ -191,7 +191,7 @@

        配置 PWA

        } }

        名字和颜色涉及添加至桌面的应用名,及桌面进入的启动页面的长相。

        -

        workboxPluginMode: 'GenerateSW'就是自动生成 Service Worker,也是默认操作。具体要求就如workboxOptions。这里根据默认,一股脑 precache 了所有东西,可以达到离线可看的目的。

        +

        workboxPluginMode: 'GenerateSW'就是自动生成 Service Worker,也是默认操作。具体要求就如workboxOptions。这里根据默认,一股脑 precache 了所有东西,可以达到离线可看的目的。

        如果部署环境不在网站根目录,还需加上:

        @@ -221,8 +221,8 @@

        配置 PWA

        本地测试

        ⚠️ 避免踩坑

        -

        使用 devserver 是看不到想要的结果的,Service Worker 也显示不能正常工作。因为 dev 环境下使用的是“傻子” Service Worker,啥都不存(不然开发是时候不得心态崩)。

        -

        但是npm run build再开本地服务器,或使用 GitHub Pages 部署后,Android Chrome 成功跳出安装至桌面的提示。

        +

        使用 devserver 是看不到想要的结果的,Service Worker 也显示不能正常工作。因为 dev 环境下使用的是“傻子” Service Worker,啥都不存(不然开发是时候不得心态崩)。

        +

        但是npm run build再开本地服务器,或使用 GitHub Pages 部署后,Android Chrome 成功跳出安装至桌面的提示。

        @@ -231,9 +231,9 @@

        本地测试

        npm run build

        提示

        -

        如果如上第二种配置了publicPath,此时要设置NODE_ENV 不为production,但是非production不能成功注册 Service Worker。所以 publicPath依赖的其实不是NODE_ENV

        +

        如果如上第二种配置了publicPath,此时要设置NODE_ENV 不为production,但是非production不能成功注册 Service Worker。所以 publicPath依赖的其实不是NODE_ENV

        -

        可以使用browser-sync作为本地服务器。

        +

        可以使用browser-sync作为本地服务器。

        @@ -247,7 +247,7 @@

        本地测试

        logger.mjs:44 View newly precached URLs. registerServiceWorker.js:17 Content has been cached for offline use. registerServiceWorker.js:8 App is being served from cache by a service worker. -For more details, visit https://goo.gl/AFskqB">
        Service worker has been registered.
        +For more details, visit https://goo.gl/AFskqB">
        Service worker has been registered.
         registerServiceWorker.js:20 New content is downloading.
         logger.mjs:44 workbox Precaching 7 files.
         logger.mjs:44 View newly precached URLs.
        @@ -263,7 +263,7 @@ 

        本地测试

        app.23a81c05.js?__WB_REVISION__=1542843adc3336277dad:1 App is being served from cache by a service worker. For more details, visit https://goo.gl/AFskqB logger.mjs:44 workbox Precaching is responding to: /manifest.json -app.23a81c05.js?__WB_REVISION__=1542843adc3336277dad:1 Service worker has been registered.">
        workbox Precaching is responding to: /css/app.b706d8fd.css
        +app.23a81c05.js?__WB_REVISION__=1542843adc3336277dad:1 Service worker has been registered.">
        workbox Precaching is responding to: /css/app.b706d8fd.css
         logger.mjs:44 workbox Precaching is responding to: /css/chunk-vendors.bb30aab5.css
         logger.mjs:44 workbox Precaching is responding to: /js/app.23a81c05.js
         logger.mjs:44 workbox Precaching is responding to: /js/chunk-vendors.57affefc.js
        @@ -274,6 +274,6 @@ 

        本地测试

        然后呢

        恭喜你有了 PWA 的基本配置,像正常一样开发 Vue 应用,初级阶段也没什么问题。

        - + diff --git a/post/vue-pwa-2/index.html b/post/vue-pwa-2/index.html index f7f6f39ea..d612f7ac4 100644 --- a/post/vue-pwa-2/index.html +++ b/post/vue-pwa-2/index.html @@ -56,11 +56,11 @@ } } ] -

        urlPattern

        -

        需要缓存的请求 URL,可以是一个字符串、正则表达式,甚至一个回调函数。对于跨域请求,必须从头到尾匹配正则表达式。如果喜欢暴力,那么就可以使用/.*/来缓存所有请求。

        +

        urlPattern

        +

        需要缓存的请求 URL,可以是一个字符串、正则表达式,甚至一个回调函数。对于跨域请求,必须从头到尾匹配正则表达式。如果喜欢暴力,那么就可以使用/.*/来缓存所有请求。

        不透明响应 (Opaque Response)

        Reference https://developers.google.com/web/tools/workbox/guides/handle-third-party-requests

        -

        如果跨域请求不是 CORS,或者发起的姿势不对(比如没有使用 crossorigin="anonymous" )导致原本支持 CORS 的变成了 no-cors,那么你就会得到 Opaque Response。对于 Opaque Response,你不能以任何方式得到返回的信息,甚至连返回代码都不知道,永远是 0。只能将其作为图片资源等。对于 staleWhileRevalidate 和 networkFirst 策略会默认缓存,但是对于 cacheFirst 策略默认不缓存。所以需要使用

        +

        如果跨域请求不是 CORS,或者发起的姿势不对(比如没有使用 crossorigin="anonymous" )导致原本支持 CORS 的变成了 no-cors,那么你就会得到 Opaque Response。对于 Opaque Response,你不能以任何方式得到返回的信息,甚至连返回代码都不知道,永远是 0。只能将其作为图片资源等。对于 staleWhileRevalidate 和 networkFirst 策略会默认缓存,但是对于 cacheFirst 策略默认不缓存。所以需要使用

        @@ -68,16 +68,16 @@

        不透明响应 (Opaque Response)

        cacheableResponse: {
           statuses: [0, 200]
         }
        -

        来强行缓存 Opaque Response。注意如果请求发生错误,也会被缓存!请谨慎选择缓存策略,比如首次出错的 cacheFirst 将导致以后一直拿不到正确结果。

        +

        来强行缓存 Opaque Response。注意如果请求发生错误,也会被缓存!请谨慎选择缓存策略,比如首次出错的 cacheFirst 将导致以后一直拿不到正确结果。

        Opaque Response 的大小也是不能读取的,所以 Chrome 会虚报大小,比如几 K 的图片会占用几 M 的空间!于是你会更快地达到空间限制。显然 Chrome 不会傻到真的占用了那么多磁盘空间。

        -

        handler 缓存策略

        +

        handler 缓存策略

        -

        expiration

        +

        expiration

        有的时候缓存会变动,有点资源变动后以后都用不着了,得清除出去,所以可以设置过期时间。

        - + diff --git a/post/vue-pwa-3/index.html b/post/vue-pwa-3/index.html index 7f70afb38..e916d6b46 100644 --- a/post/vue-pwa-3/index.html +++ b/post/vue-pwa-3/index.html @@ -46,16 +46,16 @@

        理解 service worker 的“哲学”

        知识补充:precache 机制

        Precache 简单讲就是在 service worker 安装的时候预先下载需要的文件( JS, CSS 之类),保证安装成功的时候这些文件都已经缓存完毕,一旦 service worker 激活即可直接从 cache 中获取。

        -

        cli-plugin-pwa 用到了 workbox-webpack-plugin。之所以成为 webpack-plugin,就是为了自动将打包出来的文件 precache。

        +

        cli-plugin-pwa 用到了 workbox-webpack-plugin。之所以成为 webpack-plugin,就是为了自动将打包出来的文件 precache。

        安装成功不代表可用

        按照第一条,由于第一次打开时是无 service worker 的,所以即使安装成功了,激活了,请求也不会走 service worker。必须刷新页面才行。

        sw-install

        -

        不过可以用选项 clientsClaim 可以让 service worker 激活后立马接管页面,如果当前无人接管的话。需要注意的是,如果请求早于 service worker 安装并激活,显然即使激活后立马接管页面也无能为力了。而且如果 precache 的东西太多的话会严重减慢 service worker 的安装进度,不利于快速接管。

        +

        不过可以用选项 clientsClaim 可以让 service worker 激活后立马接管页面,如果当前无人接管的话。需要注意的是,如果请求早于 service worker 安装并激活,显然即使激活后立马接管页面也无能为力了。而且如果 precache 的东西太多的话会严重减慢 service worker 的安装进度,不利于快速接管。

        Scope

        -

        当然安装成功了但是没法用还有一种可能,作用域不对。如果是注册 /assets/js/sw.js,那默认作用域就是 /assets/js/。解决方法有二:

        +

        当然安装成功了但是没法用还有一种可能,作用域不对。如果是注册 /assets/js/sw.js,那默认作用域就是 /assets/js/。解决方法有二:

          -
        1. sw.js 放到应用根目录下
        2. -
        3. 注册的时候说明 register('/assets/js/sw.js', { scope: '/' }) +
        4. sw.js 放到应用根目录下
        5. +
        6. 注册的时候说明 register('/assets/js/sw.js', { scope: '/' })
          @@ -67,10 +67,10 @@

          Scope

          })
        -

        注意如果你的应用在 /app/ 下,注册了 /app/sw.js,但是某一次以 /app (没有末尾斜杠)访问主页,那么 service worker 是不会起作用的。

        +

        注意如果你的应用在 /app/ 下,注册了 /app/sw.js,但是某一次以 /app (没有末尾斜杠)访问主页,那么 service worker 是不会起作用的。

        什么时候更新

        这个简单情况很简单,只要访问到作用域内的页面即可触发浏览器查看更新。

        -

        不过 pushsync 的事件在一定条件下也可触发,但这是直接写 InjectManifest 的大佬的事情了。

        +

        不过 pushsync 的事件在一定条件下也可触发,但这是直接写 InjectManifest 的大佬的事情了。

        还可以手动更新:

        @@ -100,7 +100,7 @@

        更新完要重开才有效果

        为什么安装是刷新就可以,但是更新是重开?因为在刷新的时候,旧页面和新页面是有交叠的,也就是说旧的 service worker 没法放手,要一直抓着,直到页面关闭。

        sw-update

        所以结合起来,要(正常)更新 service worker(也就是更新应用版本,因为 precache 机制),需要打开页面,稍等一会,等待新的 service worker 安装完成后

        -

        不过(在新的 service worker 上)用 skipWaiting 选项可以让新的 service worker 在安装成功后直接激活。但是也有可能使页面混乱,谨慎使用。

        +

        不过(在新的 service worker 上)用 skipWaiting 选项可以让新的 service worker 在安装成功后直接激活。但是也有可能使页面混乱,谨慎使用。

        更新这么麻烦,开发测试的时候等不起啊

        勾上 Update on reload

        这样每次刷新或访问新的页面都会触发更新,且无论 service worker 是否有变化都会重新安装并立即生效。

        @@ -114,6 +114,6 @@

        强制刷新

        js
        location.reload(true)
        - + diff --git a/post/why-flask/index.html b/post/why-flask/index.html index e84afe840..9ad177c9e 100644 --- a/post/why-flask/index.html +++ b/post/why-flask/index.html @@ -37,9 +37,9 @@

        不错,我又开始后悔了 😳

          -
        • Jinja2, inspired by django
        • -
        • WTForm, looks like django
        • -
        • Flask-SQLAlchemy, even more like django
        • +
        • Jinja2, inspired by django
        • +
        • WTForm, looks like django
        • +
        • Flask-SQLAlchemy, even more like django
        • Tons of extensions but many are not in active development.
        • Lack of documentations, and existing documentation is not friendly towards newcomers.
            @@ -47,13 +47,13 @@
        -

        但是django怎么样呢?(以前用过的感受)

        +

        但是django怎么样呢?(以前用过的感受)

        • 比较死板的项目目录
        • -
        • 迷惑的views.py
        • +
        • 迷惑的views.py
        • 庞大的体积
        • 以及 redirect 后一直没弄懂的数目不定的斜杠
        • -
        • 据说还有一堆super().__init__(self)
        • +
        • 据说还有一堆super().__init__(self)

        既然选择了 Flask,那就要一直做下去。于是,被逼之下,解锁新技能——面向 GitHub 编程

          @@ -75,6 +75,6 @@

          UPDATE

          All in all, Flask or Django is your own choice.

          声明:本文扯上基督教只是因为课上讲到并有感而发,并非黑,而且对基督教思想还停留在比较浅薄的地步,如有错误轻喷

        - + diff --git a/post/win-cmd-cht/index.html b/post/win-cmd-cht/index.html index 999f42927..e9c3e363a 100644 --- a/post/win-cmd-cht/index.html +++ b/post/win-cmd-cht/index.html @@ -34,22 +34,22 @@
        AC Dustbin

        Windows CMD Cheatsheet

        2020-04-18 04:49 2020-08-06 13:16

        Create Soft / Hard link

        -

        Using ln in WSL does not create Windows capable symlinks. To do so, open CMD with administartor priviledge, and use mklink command. Notice that mklink has an odd argument order.

        +

        Using ln in WSL does not create Windows capable symlinks. To do so, open CMD with administartor priviledge, and use mklink command. Notice that mklink has an odd argument order.

        To make soft link for directory:

        batchfile
        mklink /D Link Target
        -

        Target is the directory which already exist.

        -

        Type mklink for more information.

        +

        Target is the directory which already exist.

        +

        Type mklink for more information.

        Checkout battery info

        batchfile
        powerconfig /batteryreport
        -

        Checkout html generated at $HOME/battery_report.html

        - +

        Checkout html generated at $HOME/battery_report.html

        + diff --git a/post/win-cmd-font/index.html b/post/win-cmd-font/index.html index d02dccd37..09ff8d1eb 100644 --- a/post/win-cmd-font/index.html +++ b/post/win-cmd-font/index.html @@ -44,10 +44,10 @@

        Introduction to the Problem

      • CMD recognizes the font only when I right click and choose "Default" (默认值), but not "Properties" (属性)

      Root of the Problem

      -

      That's the Code Page. I have not heard of this before, but I do know cp-936 encoding stuff when configuring gVim, that is short for Code Page 936 (ANSI/OEM - 简体中文 GBK).

      +

      That's the Code Page. I have not heard of this before, but I do know cp-936 encoding stuff when configuring gVim, that is short for Code Page 936 (ANSI/OEM - 简体中文 GBK).

      Check it Out

      When you right click and click "Option" (选项) tab, it will show you the current code page.

      -

      In CMD, you can use chcp 65001 to change Unicode code page. Now right click again and go to "Properties", fonts enabled should be there.

      +

      In CMD, you can use chcp 65001 to change Unicode code page. Now right click again and go to "Properties", fonts enabled should be there.

      Solutions

      Here are two ways to go:

      @@ -65,11 +65,11 @@

      Solutions

      I am sorry that I have to run some legacy Chinese apps which still use non-Unicode encoding.

      -

      So here is another way, choose Beta: Use Unicode UTF-8 for i18n, in the prompt said above, and click to reboot.

      +

      So here is another way, choose Beta: Use Unicode UTF-8 for i18n, in the prompt said above, and click to reboot.

      intl.cpl

      You will see that CMD is using UFT-8 now, and CMD recognizes those fonts! Enjoy programing!

      Still Problem

      -

      PowerShell still changes to cp 936 when executing commands or running programs 🤔...

      - +

      PowerShell still changes to cp 936 when executing commands or running programs 🤔...

      + diff --git a/post/win11-explorer-patcher/index.html b/post/win11-explorer-patcher/index.html index dffc1c720..1798a5695 100644 --- a/post/win11-explorer-patcher/index.html +++ b/post/win11-explorer-patcher/index.html @@ -45,17 +45,17 @@

      Install ExplorerPatcher

      ExplorerPatcher brings old Windows 10 taskbar back, together with some other tweaks. I am on 22000.194.25 and the installation is pretty simple:

        -
      1. Download dxgi.dll here
      2. -
      3. Copy it to C:\Windows (needs administrator permission grant)
      4. -
      5. Restart explorer.exe from task manager
      6. -
      7. Wait explorer.exe downloading ~40M of Microsoft files (can take a while, can be monitored in network section of taskmanager)
      8. +
      9. Download dxgi.dll here
      10. +
      11. Copy it to C:\Windows (needs administrator permission grant)
      12. +
      13. Restart explorer.exe from task manager
      14. +
      15. Wait explorer.exe downloading ~40M of Microsoft files (can take a while, can be monitored in network section of taskmanager)

      Follow README for other customization. You probably need Win + X and "Enable missing system tray icons".

      Then you will get something like ExplorerPatcher's demo:

      ExplorerPatcher's demo

      Moving taskbar to left

      As you can see in the first image, I prefer taskbar docked on the left side.

      -

      Open regedit, go to HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\StuckRects3, and edit Settings. Change the following part to 00:

      +

      Open regedit, go to HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\StuckRects3, and edit Settings. Change the following part to 00:

      image

      And here is the meaning of the numbers:

    @@ -85,14 +85,14 @@

    Moving taskbar to left

    -

    And don't forget to restart explorer.exe to make changes take effect.

    +

    And don't forget to restart explorer.exe to make changes take effect.

    Make smaller icons

    Large icons look ugly on a vertical taskbar →_→ Let's make them smaller.

    -

    Again in regedit, go to HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced, change TaskbarSmallIcons to 1.

    +

    Again in regedit, go to HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced, change TaskbarSmallIcons to 1.

    -

    Again, restart explorer.exe.

    +

    Again, restart explorer.exe.

    Done

    If everything goes well, you will get a old-fashioned taskbar like mine!

    - + diff --git a/post/wsl-cht/index.html b/post/wsl-cht/index.html index 13536df08..1089fdece 100644 --- a/post/wsl-cht/index.html +++ b/post/wsl-cht/index.html @@ -37,10 +37,10 @@

    No Network

    ping: socket: Operation not permitted
    +Temporary failure in name resolution">
    ping: socket: Operation not permitted
     Temporary failure in name resolution
     
    -

    Solution 1: resolv.conf

    +

    Solution 1: resolv.conf

    microsoft/WSL#6404 (comment)

    It seems lounching VSCode daemon messes things up

    @@ -49,34 +49,34 @@

    Solution 1: resolv.conf

  • /etc/wsl.conf
  • [network]
    +generateResolvConf = false">
    [network]
     generateResolvConf = false
     
    1. Shutdown wsl
    -
    wsl --shutdown
    +
    wsl --shutdown
     
    1. Start wsl, delete /etc/resolv.conf symbolic link and create a file instead:
      /etc/resolv.conf
    -
    nameserver 8.8.8.8
    +
    nameserver 8.8.8.8
     

    But this solution ruins mDNS.

    Solution 2: Check Firewall

    -

    If the DNS works after disabling Firewall, try allowing 172.16.0.0/12 inbound in Windows Defender.

    +

    If the DNS works after disabling Firewall, try allowing 172.16.0.0/12 inbound in Windows Defender.

    Run Linux GUI apps on the Windows Subsystem for Linux

    https://docs.microsoft.com/en-us/windows/wsl/tutorials/gui-apps

    Hyper-V & Reserved Ports

    https://hungyi.net/posts/wsl2-reserved-ports/

    -

    Run netsh int ipv4 show excludedportrange protocol=tcp to show reserved ports.

    +

    Run netsh int ipv4 show excludedportrange protocol=tcp to show reserved ports.

      -
    1. Run netsh int ipv4 set dynamic tcp start=51001 num=5000 to reset the dynamic port range.
    2. -
    3. Run reg add HKLM\SYSTEM\CurrentControlSet\Services\hns\State /v EnableExcludedPortRange /d 0 /f to disable the HNS port exclusion behavior.
    4. +
    5. Run netsh int ipv4 set dynamic tcp start=51001 num=5000 to reset the dynamic port range.
    6. +
    7. Run reg add HKLM\SYSTEM\CurrentControlSet\Services\hns\State /v EnableExcludedPortRange /d 0 /f to disable the HNS port exclusion behavior.
    8. Reboot
    - + diff --git a/post/wtf-c/index.html b/post/wtf-c/index.html index 12cb7f23c..b528cc39e 100644 --- a/post/wtf-c/index.html +++ b/post/wtf-c/index.html @@ -60,7 +60,7 @@

    往年题?

    printf("%d\n%d\n",i,j); }
    -

    其中,gcc -E认为的是

    +

    其中,gcc -E认为的是

    @@ -68,7 +68,7 @@

    往年题?

    i=- - -x+n*-x+n+-k;

    注意到 C 的运算符处理和空格有关,故结果为

    -4
    +-1">
    -4
     -1
     

    而 VC 认为的是

    @@ -79,7 +79,7 @@

    往年题?

    i=- --x+n*-x+n+-k;

    注意到第二句时 x 已自减,故结果为

    -1
    +0">
    -1
     0
     

    h(x) != g(x) ?

    @@ -104,7 +104,7 @@

    h(x) != g(x) ?


    An occurrence of a parameter in a function-like macro, unless it is the operand of # or ##, is expanded before substituting it and rescanning the whole for further expansion. Because g's parameter is the operand of #, the argument is not expanded but instead immediately stringified ("f(1,2)"). Because h's parameter is not the operand of # nor ##, the argument is first expanded (12), then substituted (g(12)), then rescanning and further expansion occurs ("12").

    -

    也就是说,碰到###后,宏就不会递归展开

    +

    也就是说,碰到###后,宏就不会递归展开

    No print?

    @@ -125,9 +125,9 @@

    No print?

    return 0; }

    这是非常神奇的, 但是和宏没什么关系。

    -

    首先,sizeof返回的是 unsigned long int, dint 类型,这两个比较的时候会比较迷,现行的是 unsigned preserving 策略,即把 int 变成 unsigned int,再行比较。

    -

    于是TOTAL_ELEMENTS-2还是unsigned long int,而 -1 就变成巨大无比,条件自然就不可能成立了。

    -

    所以呢,把TOTAL_ELEMENTS换成由unsigned int a=7;定义的a,还是一样的。

    +

    首先,sizeof返回的是 unsigned long int, dint 类型,这两个比较的时候会比较迷,现行的是 unsigned preserving 策略,即把 int 变成 unsigned int,再行比较。

    +

    于是TOTAL_ELEMENTS-2还是unsigned long int,而 -1 就变成巨大无比,条件自然就不可能成立了。

    +

    所以呢,把TOTAL_ELEMENTS换成由unsigned int a=7;定义的a,还是一样的。

    还有一点有意思的是:

    https://stackoverflow.com/questions/2084949

    @@ -149,7 +149,7 @@

    No print?

    c
    if (x >= 0 && ((unsigned int)x) == y)
    -

    continue in do...while

    +

    continue in do...while

    @@ -169,12 +169,12 @@

    continue in do...while

    }while(false); return 0; }
    -

    跟在后面的while也是循环控制的一种,所以continue不会跳过while

    -

    拓展知识:how for equals while?

    +

    跟在后面的while也是循环控制的一种,所以continue不会跳过while

    +

    拓展知识:how for equals while?

    -

    Why does it tend to get into an infinite loop if I use continue in a while loop, but works fine in a for loop?
    -The loop-counter increment i++ gets ignored in while loop if I use it after continue, but it works if it is in for loop.

    -

    If continue ignores subsequent statements, then why doesn't it ignore the third statement of the for loop then, which contains the counter increment i++? Isn't the third statement of for loop subsequent to continue as well and should be ignored, given the third statement of for loop is executed after the loop body?

    +

    Why does it tend to get into an infinite loop if I use continue in a while loop, but works fine in a for loop?
    +The loop-counter increment i++ gets ignored in while loop if I use it after continue, but it works if it is in for loop.

    +

    If continue ignores subsequent statements, then why doesn't it ignore the third statement of the for loop then, which contains the counter increment i++? Isn't the third statement of for loop subsequent to continue as well and should be ignored, given the third statement of for loop is executed after the loop body?

    @@ -194,7 +194,7 @@

    continue in do...while

    ... }

    -

    The reason is because the continue statement will short-circuit the statements that follow it in the loop body. Since the way you wrote the while loop has the increment statement following the continue statement, it gets short-circuited. You can solve this by changing your while loop.

    +

    The reason is because the continue statement will short-circuit the statements that follow it in the loop body. Since the way you wrote the while loop has the increment statement following the continue statement, it gets short-circuited. You can solve this by changing your while loop.

    A lot of text books claim that:

    @@ -231,7 +231,7 @@

    continue in do...while

    if (i < 10) do { /*...*/ } while (++i, (i < 10));
    -

    These are more equivalent, since now if the body of the while has a continue, the increment still occurs, just like in a for. The latter alternative only executes the increment after the iteration has completed, just like for (the former executes the increment before the iteration, deferring to save it in i until after the iteration).

    +

    These are more equivalent, since now if the body of the while has a continue, the increment still occurs, just like in a for. The latter alternative only executes the increment after the iteration has completed, just like for (the former executes the increment before the iteration, deferring to save it in i until after the iteration).

    著名的变态题

    @@ -251,7 +251,7 @@

    著名的变态题

    Stackoverflow 如是说

    https://stackoverflow.com/a/2989771/8810271

    -

    Modifying the value of i more than once without a sequence point in between the modifications results in undefined behavior. So, the results of your code are undefined.

    +

    Modifying the value of i more than once without a sequence point in between the modifications results in undefined behavior. So, the results of your code are undefined.

    简单翻译成人话就是,想一口气多次改变变量值的行为是未定义的。所以考试考一个未定义的东西有什么用呢?

    所以“往年题”部分提到的例子也是未定义的。

    @@ -278,10 +278,10 @@

    这只是普通的标签

    } return 0; }
    -

    坑就在deefaultl打成了1😂

    +

    坑就在deefaultl打成了1😂

    居然编译器给过了!?

    -

    原来假的defau1t被处理成了一个标签供goto使用,casedefault也类似于 C 里的标签,共用类似的语法,毕竟case本身就可以直接跳到任何地方,包括if里面。

    -

    case真的可以随便跳吗?

    +

    原来假的defau1t被处理成了一个标签供goto使用,casedefault也类似于 C 里的标签,共用类似的语法,毕竟case本身就可以直接跳到任何地方,包括if里面。

    +

    case真的可以随便跳吗?

    @@ -300,6 +300,6 @@

    case真的可以随便跳吗?

    return 0; }

    跳过了变量初始化就会报错了,编译都过不了

    - + diff --git a/post/wtf-hugo/index.html b/post/wtf-hugo/index.html index de0d6b876..2b0975f42 100644 --- a/post/wtf-hugo/index.html +++ b/post/wtf-hugo/index.html @@ -51,12 +51,12 @@

    从 v0.55 到 v0.59 一直有 Bug 的 Hugo

    This block should be out of <ul>...</ul>
    +And also out of blockquote">
    This block should be out of <ul>...</ul>
     And also out of blockquote
     

    Ref: https://discourse.gohugo.io/t/possible-regression-in-v0-55-5-regarding-lists-containing-code-blocks/18502/4?u=kaushalmodi

    -

    /cc @aignas as you seem to be the last person to work on this Blackfriday plain-list/code block issue :)

    +

    /cc @aignas as you seem to be the last person to work on this Blackfriday plain-list/code block issue :)

    来,你瞅瞅,现在还是这个样子的😭

    v0.60.0 UPDATE: 见“新的引擎”一节

    @@ -65,11 +65,11 @@

    仿佛急冰的 toc 功能

    Hugo 自带的目录功能简直了,因为只能从 h1 开始,否则:

        • Header 3
        • Header 3

    自带的功能,老兄!这种事情都做得出来!NOT PYTHONIC AT ALL!

    -

    最后我找到了https://gist.github.com/skyzyx/a796d66f6a124f057f3374eff0b3f99a @looeee 的代码,基本可以满足需求。

    +

    最后我找到了https://gist.github.com/skyzyx/a796d66f6a124f057f3374eff0b3f99a @looeee 的代码,基本可以满足需求。

    修改找到的 TOC 代码

    -

    如果你要更改标题级别的范围,可以把[2-4]替换成你想要的。

    -

    而且这位老兄的代码就是会把标题里的一些诸如don't的标点干掉,也不会保留加粗的格式。

    -

    解决办法就是去掉planify | htmlEscape, 并在

    +

    如果你要更改标题级别的范围,可以把[2-4]替换成你想要的。

    +

    而且这位老兄的代码就是会把标题里的一些诸如don't的标点干掉,也不会保留加粗的格式。

    +

    解决办法就是去掉planify | htmlEscape, 并在

    @@ -83,8 +83,8 @@

    修改找到的 TOC 代码

    {{- $header := replaceRE "<h[2-4].*?>((.|\n])+?)</h[2-4]>" "$1" $header -}}

    谜之 Template 语法

    看了 Hugo 的之后这才发现 Jinja 原来是真的漂亮简洁

    -

    就问你and (ConditionA) (ConditionB) 是人话吗?没有not是什么?没有括号也叫 Function?怕是 Shell 用多了?

    -

    not的正确打开方式:

    +

    就问你and (ConditionA) (ConditionB) 是人话吗?没有not是什么?没有括号也叫 Function?怕是 Shell 用多了?

    +

    not的正确打开方式:

    @@ -95,14 +95,14 @@

    奇奇怪怪的 Toml

    放着好好的 YAML 和 JSON 不用,突然冒出来一个 TOML。好像 Markdown 前面加 YAML 已经很常用了,然后跟我说用 TOML,不觉得等于号 有点 丑?不觉得 引号 有点 丑?虽然在做配置文件方面可能有独到之处,对不起,Vim 原生不支持,一键秒杀颜值。

    新的引擎

    突然,Hugo 给我换了一个引擎 https://github.com/gohugoio/hugo/releases/tag/v0.60.0

    -

    使用了新的引擎之后之前的问题消失了,但是变成了默认忽略 HTML,需要在config.toml里做修改

    +

    使用了新的引擎之后之前的问题消失了,但是变成了默认忽略 HTML,需要在config.toml里做修改

    toml
    [markup.goldmark.renderer]
         unsafe = true
    -

    或者config.yml

    +

    或者config.yml

    @@ -116,12 +116,12 @@

    headerID

    Goldmark 在设置 headerID 的时候,默认是仅保留字母和数字,但是这对 CJK 及其不友好。“更有甚者”,该项目的作者是日本人,竟然不考虑对本国文字的支持?!还说,仅保留字母和数字是合适的默认行为。Excuse me? 不应该原封不动保留 Unicode 才是好的默认行为吗?

    据说会在 Hugo v0.63.0 解决,那就拭目以待吧。。

    render_link

    -

    本来说好可以支持 link 的 Markdown hook 的,但是呢,只支持[text](url)型,不支持<url>型。。[摊手.jpg]

    +

    本来说好可以支持 link 的 Markdown hook 的,但是呢,只支持[text](url)型,不支持<url>型。。[摊手.jpg]

    而且,说好引用其他 Markdown 文档的 render_link 实现居然还有点复杂。。还好找到了官方实现,不然又是大坑。。

    Hugo theme,注定又是一个面向 GitHub 编程。

    TOC 新回复

    -

    我又针对 toc 写了一篇博客 hugo-toc,然后有去原来的 gist 上评论了,受到了@looeee 的回复。他说。。。他已经弃坑 Hugo 了,使用 js 解决问题。

    +

    我又针对 toc 写了一篇博客 hugo-toc,然后有去原来的 gist 上评论了,受到了@looeee 的回复。他说。。。他已经弃坑 Hugo 了,使用 js 解决问题。

    我。。。

    - + diff --git a/series/vue-pwa/index.html b/series/vue-pwa/index.html index 7d3a13344..7cd25629e 100644 --- a/series/vue-pwa/index.html +++ b/series/vue-pwa/index.html @@ -16,7 +16,7 @@ } -
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    用 Vue 做 PWA (三):理解生命周期
    2020-09-03 02:40 2020-09-08 03:09
    +
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    用 Vue 做 PWA (三):理解生命周期
    2020-09-03 02:40 2020-09-08 03:09

    虽然前两节的内容足以写出能用的 service worker,但是如果深入细节,仍然会有一些“匪夷所思”的现象发生。

    用 Vue 做 PWA(二):Runtime Caching
    2020-04-18 04:49 2021-03-13 00:27

    本系列已经很久没更新了,在此讨论实践过程中遇到的 Runtime Caching 的一些问题。主要注意点是在处理跨域请求上。

    diff --git a/service-worker.js b/service-worker.js index 69a6d0df6..db6a045c5 100644 --- a/service-worker.js +++ b/service-worker.js @@ -1 +1 @@ -if(!self.define){const e=e=>{"require"!==e&&(e+=".js");let s=Promise.resolve();return a[e]||(s=new Promise((async s=>{if("document"in self){const a=document.createElement("script");a.src=e,document.head.appendChild(a),a.onload=s}else importScripts(e),s()}))),s.then((()=>{if(!a[e])throw new Error(`Module ${e} didn’t register its module`);return a[e]}))},s=(s,a)=>{Promise.all(s.map(e)).then((e=>a(1===e.length?e[0]:e)))},a={require:Promise.resolve(s)};self.define=(s,r,i)=>{a[s]||(a[s]=Promise.resolve().then((()=>{let a={};const b={uri:location.origin+s.slice(1)};return Promise.all(r.map((s=>{switch(s){case"exports":return a;case"module":return b;default:return e(s)}}))).then((e=>{const s=i(...e);return a.default||(a.default=s),a}))})))}}define("./service-worker.js",["./workbox-3cd600db"],(function(e){"use strict";e.setCacheNameDetails({prefix:"AC Dustbin"}),self.addEventListener("message",(e=>{e.data&&"SKIP_WAITING"===e.data.type&&self.skipWaiting()})),e.precacheAndRoute([{url:"/blog/assets/css/styles.952165db.css",revision:"06fc2195c8d2c3fbfe26534b1261d739"},{url:"/blog/assets/js/196.75ff2e4b.js",revision:"96e826c495ecb90d2cad41b8a204b725"},{url:"/blog/assets/js/416.2edeb43c.js",revision:"725a88db2d2940aa5b41ee355b2558e3"},{url:"/blog/assets/js/46.c41f61e7.js",revision:"9e64bdd4f76787ea3a822bd1ae0a54be"},{url:"/blog/assets/js/app.5017c2a6.js",revision:"bbc44690bd86c3e3b6df9a264b756c9c"},{url:"/blog/assets/js/axios.3232deb7.js",revision:"2f85f60eea893ff722a8844829919fa5"},{url:"/blog/assets/js/core-js.365248a2.js",revision:"a4925fbb1b63decb21a4dfba91f5ac0c"},{url:"/blog/assets/js/gridsome-vendors.a4d1a31b.js",revision:"589f9c7be58ed1099c666949bf2c4a79"},{url:"/blog/assets/js/page--src--pages--index-vue.40826d72.js",revision:"3cedcfb83803971ebde5c4e7ebab7d5e"},{url:"/blog/assets/js/page--src--pages--labels-vue.30c3d8ab.js",revision:"7a02dbad31ac20eb2ced95aca0b1c5d9"},{url:"/blog/assets/js/page--src--pages--offline-vue.e4806600.js",revision:"96eebaac89fca2eaeb307910c5face07"},{url:"/blog/assets/js/page--src--templates--label-vue.46dcc102.js",revision:"9a98f972be22fafa5b8194339d7baf54"},{url:"/blog/assets/js/page--src--templates--post-vue.64a2bae6.js",revision:"1537034e87d56b694fbfd573e8ff57a2"},{url:"/blog/assets/js/runtime.ba56f071.js",revision:"1efd46be3c79d8feabcbbe335df553ba"},{url:"/blog/assets/js/vue-vendors.4d0a8523.js",revision:"58794d543f2edea0b8fa3e4c49dc5010"},{url:"/blog/offline/index.html",revision:"f9cfefbbaeb541d75bfd9b8bc9030cd2"}],{}),e.registerRoute(new e.NavigationRoute(e.createHandlerBoundToURL("/blog/offline/index.html"),{allowlist:[/\/$/]})),e.registerRoute(/\/img\/.*/,new e.CacheFirst({cacheName:"GithHub",plugins:[new e.ExpirationPlugin({maxEntries:20})]}),"GET"),e.registerRoute(/\/index.json$/,new e.NetworkFirst({cacheName:"Post-Data",networkTimeoutSeconds:3,plugins:[new e.ExpirationPlugin({maxEntries:20})]}),"GET"),e.registerRoute(/https:\/\/(cdn\.jsdelivr\.net|fonts\.(gstatic|googleapis)\.com)\/.*/,new e.CacheFirst({cacheName:"CDN",plugins:[new e.ExpirationPlugin({maxEntries:5})]}),"GET")})); +if(!self.define){const e=e=>{"require"!==e&&(e+=".js");let s=Promise.resolve();return a[e]||(s=new Promise((async s=>{if("document"in self){const a=document.createElement("script");a.src=e,document.head.appendChild(a),a.onload=s}else importScripts(e),s()}))),s.then((()=>{if(!a[e])throw new Error(`Module ${e} didn’t register its module`);return a[e]}))},s=(s,a)=>{Promise.all(s.map(e)).then((e=>a(1===e.length?e[0]:e)))},a={require:Promise.resolve(s)};self.define=(s,r,i)=>{a[s]||(a[s]=Promise.resolve().then((()=>{let a={};const o={uri:location.origin+s.slice(1)};return Promise.all(r.map((s=>{switch(s){case"exports":return a;case"module":return o;default:return e(s)}}))).then((e=>{const s=i(...e);return a.default||(a.default=s),a}))})))}}define("./service-worker.js",["./workbox-3cd600db"],(function(e){"use strict";e.setCacheNameDetails({prefix:"AC Dustbin"}),self.addEventListener("message",(e=>{e.data&&"SKIP_WAITING"===e.data.type&&self.skipWaiting()})),e.precacheAndRoute([{url:"/blog/assets/css/styles.952165db.css",revision:"06fc2195c8d2c3fbfe26534b1261d739"},{url:"/blog/assets/js/196.75ff2e4b.js",revision:"96e826c495ecb90d2cad41b8a204b725"},{url:"/blog/assets/js/416.2edeb43c.js",revision:"725a88db2d2940aa5b41ee355b2558e3"},{url:"/blog/assets/js/46.c41f61e7.js",revision:"9e64bdd4f76787ea3a822bd1ae0a54be"},{url:"/blog/assets/js/app.5017c2a6.js",revision:"bbc44690bd86c3e3b6df9a264b756c9c"},{url:"/blog/assets/js/axios.3232deb7.js",revision:"2f85f60eea893ff722a8844829919fa5"},{url:"/blog/assets/js/core-js.365248a2.js",revision:"a4925fbb1b63decb21a4dfba91f5ac0c"},{url:"/blog/assets/js/gridsome-vendors.a4d1a31b.js",revision:"589f9c7be58ed1099c666949bf2c4a79"},{url:"/blog/assets/js/page--src--pages--index-vue.40826d72.js",revision:"3cedcfb83803971ebde5c4e7ebab7d5e"},{url:"/blog/assets/js/page--src--pages--labels-vue.30c3d8ab.js",revision:"7a02dbad31ac20eb2ced95aca0b1c5d9"},{url:"/blog/assets/js/page--src--pages--offline-vue.e4806600.js",revision:"96eebaac89fca2eaeb307910c5face07"},{url:"/blog/assets/js/page--src--templates--label-vue.46dcc102.js",revision:"9a98f972be22fafa5b8194339d7baf54"},{url:"/blog/assets/js/page--src--templates--post-vue.64a2bae6.js",revision:"1537034e87d56b694fbfd573e8ff57a2"},{url:"/blog/assets/js/runtime.ba56f071.js",revision:"1efd46be3c79d8feabcbbe335df553ba"},{url:"/blog/assets/js/vue-vendors.4d0a8523.js",revision:"58794d543f2edea0b8fa3e4c49dc5010"},{url:"/blog/offline/index.html",revision:"997d4fb6733fa0d5d3ad36b40af3f66a"}],{}),e.registerRoute(new e.NavigationRoute(e.createHandlerBoundToURL("/blog/offline/index.html"),{allowlist:[/\/$/]})),e.registerRoute(/\/img\/.*/,new e.CacheFirst({cacheName:"GithHub",plugins:[new e.ExpirationPlugin({maxEntries:20})]}),"GET"),e.registerRoute(/\/index.json$/,new e.NetworkFirst({cacheName:"Post-Data",networkTimeoutSeconds:3,plugins:[new e.ExpirationPlugin({maxEntries:20})]}),"GET"),e.registerRoute(/https:\/\/(cdn\.jsdelivr\.net|fonts\.(gstatic|googleapis)\.com)\/.*/,new e.CacheFirst({cacheName:"CDN",plugins:[new e.ExpirationPlugin({maxEntries:5})]}),"GET")})); diff --git a/tag/android/index.html b/tag/android/index.html index 3d2bb8e07..4049edff7 100644 --- a/tag/android/index.html +++ b/tag/android/index.html @@ -16,7 +16,7 @@ } -
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    A Simple Way to Use More Addons (e.g. Tampermonkey) on Newest Fenix (FireFox Android)
    2021-03-07 09:34 2022-01-27 12:29
    +
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    A Simple Way to Use More Addons (e.g. Tampermonkey) on Newest Fenix (FireFox Android)
    2021-03-07 09:34 2022-01-27 12:29

    Yes, very simple and... violent. It's about compiling your own copy of Fenix.

    我是如何获得微信内置表情的
    2020-12-28 09:46 2020-12-28 09:58

    授人以鱼,不如授人以渔。虽然百度出来有很多下载资源,但并没有讲怎么获得的(毕竟天朝特色)

    diff --git a/tag/c/index.html b/tag/c/index.html index 1b64fe3c4..3155ed9ec 100644 --- a/tag/c/index.html +++ b/tag/c/index.html @@ -16,7 +16,7 @@ } -
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    数算环境准备
    2020-02-19 08:50 2020-07-01 13:00
    +
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    数算环境准备
    2020-02-19 08:50 2020-07-01 13:00

    g++ & VS Code 党对《数据结构与算法》的环境准备

    有必要解释一下名称的由来:Data Structure and Algorithm

    WTFs in C
    2020-01-01 08:59 2020-06-30 07:43
    diff --git a/tag/docker/index.html b/tag/docker/index.html index aea0e80ab..5f55511f2 100644 --- a/tag/docker/index.html +++ b/tag/docker/index.html @@ -16,11 +16,12 @@ } -
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    Docker Cheatsheet
    2021-07-31 09:58 2021-09-11 13:38
    -

    Docker commands are easily fogotten

    +
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    Docker Cheatsheet
    2021-07-31 09:58 2022-05-01 13:49
    +

    Docker commands are easily forgotten

    +

    Above image from scmagazine.com

    列出 USTC Docker 镜像的标签
    2020-02-28 13:38 2020-06-21 12:56

    不再为网络条件发愁!

    - + diff --git a/tag/flask/index.html b/tag/flask/index.html index 7ef74b3ac..68679ec18 100644 --- a/tag/flask/index.html +++ b/tag/flask/index.html @@ -16,7 +16,7 @@ } -
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    我为什么要选 Flask?
    2020-01-03 13:00 2021-02-22 08:02
    +
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    我为什么要选 Flask?
    2020-01-03 13:00 2021-02-22 08:02

    注意,这里不是说 Flask 有多好,而是。。用 Flask 用到怀疑人生!

    diff --git a/tag/gh-action/index.html b/tag/gh-action/index.html index d04de4a34..8be475ab2 100644 --- a/tag/gh-action/index.html +++ b/tag/gh-action/index.html @@ -16,7 +16,7 @@ } -
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    Setup GitHub Action Cache the Right Way
    2020-06-21 09:16 2020-07-06 10:52
    +
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    Setup GitHub Action Cache the Right Way
    2020-06-21 09:16 2020-07-06 10:52

    What on earth is happening? Why my cache never hits?

    diff --git a/tag/gridsome/index.html b/tag/gridsome/index.html index 30876538b..c868b6969 100644 --- a/tag/gridsome/index.html +++ b/tag/gridsome/index.html @@ -16,7 +16,7 @@ } -
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    Gridsome is not that Ready for PWA
    2020-09-16 03:22 2021-07-03 13:06
    +
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    Gridsome is not that Ready for PWA
    2020-09-16 03:22 2021-07-03 13:06

    How I am tring to build a nice gridsome-generated site with PWA support

    虽然可以勉强并入“用 Vue 做 PWA”系列,但想着能让更多人看到,就还是用国际语言吧

    diff --git a/tag/hugo/index.html b/tag/hugo/index.html index f067e41c7..0ee1d645d 100644 --- a/tag/hugo/index.html +++ b/tag/hugo/index.html @@ -16,11 +16,11 @@ } -
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    谜之 Hugo | AC's Blog
    2020-01-03 13:19 2020-11-02 04:31
    TOC in Hugo
    2019-12-31 15:17 2020-06-30 07:33
    +
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    谜之 Hugo | AC's Blog
    2020-01-03 13:19 2020-11-02 04:31
    TOC in Hugo
    2019-12-31 15:17 2020-06-30 07:33

    What a challenge to build toc in hugo!

    Deploying Hugo with CircleCI
    2019-10-27 12:12 2020-06-30 07:28
    -

    Building blog sites manually is tedious and often leads to mistakes, especially when hosting on master/docs

    +

    Building blog sites manually is tedious and often leads to mistakes, especially when hosting on master/docs

    - + diff --git a/tag/javascript/index.html b/tag/javascript/index.html index 1b2d8b187..6c6c876de 100644 --- a/tag/javascript/index.html +++ b/tag/javascript/index.html @@ -16,7 +16,7 @@ } -
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    聊一聊 React 的 virtual scroll
    2020-07-10 09:58 2020-07-11 01:08
    +
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    聊一聊 React 的 virtual scroll
    2020-07-10 09:58 2020-07-11 01:08

    也没啥高见,重复一下网上现有的资料而已

    JS 无依赖获取阴历
    2020-02-08 05:46 2020-07-03 02:55

    Intl 是个好东西,但是还不是很强大,浏览器支持上也有 Node 和 Android 的小缺憾。

    diff --git a/tag/jupyter/index.html b/tag/jupyter/index.html index bf7431a4b..110eb214c 100644 --- a/tag/jupyter/index.html +++ b/tag/jupyter/index.html @@ -16,7 +16,7 @@ } -
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    Jupyter etc. Cheatsheet
    2020-12-05 01:25 2021-09-20 13:32
    +
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    Jupyter etc. Cheatsheet
    2020-12-05 01:25 2021-09-20 13:32

    主要是一些数据与绘图处理相关

    Jupyter on Raspberry Pi
    2020-02-26 12:20 2020-07-01 12:33
    Install Wolfram With Jupyter
    2020-02-26 09:50 2020-07-01 12:28
    diff --git a/tag/linux/index.html b/tag/linux/index.html index 455b41948..211322573 100644 --- a/tag/linux/index.html +++ b/tag/linux/index.html @@ -16,7 +16,7 @@ } -
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    Manjaro Linux 初体验
    2021-04-19 13:50 2021-04-19 14:31
    +
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    Manjaro Linux 初体验
    2021-04-19 13:50 2021-04-19 14:31

    偏随笔性质的文章,也没什么新东西,就随便谈谈,直接用母语好了

    Two Points to Notice when Upgrading Ubuntu
    2020-04-26 00:00 2020-06-21 12:55

    Ever feel frustated when a new release is available but it keeps saying no release found?

    diff --git a/tag/minecraft/index.html b/tag/minecraft/index.html index 135933d43..9369a2914 100644 --- a/tag/minecraft/index.html +++ b/tag/minecraft/index.html @@ -16,7 +16,7 @@ } -
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    Getting Started with Minecraft Fabric Without Any Java Knowledge
    2020-08-08 03:38 2021-02-22 08:40
    +
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    Getting Started with Minecraft Fabric Without Any Java Knowledge
    2020-08-08 03:38 2021-02-22 08:40

    Thats challenging but possible

    diff --git a/tag/mysql/index.html b/tag/mysql/index.html index c626f22f3..ab1c0fab8 100644 --- a/tag/mysql/index.html +++ b/tag/mysql/index.html @@ -24,14 +24,14 @@ } -
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    数据库查询性能优化手记
    2021-03-09 10:08 2021-08-12 11:59
    +
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    数据库查询性能优化手记
    2021-03-09 10:08 2021-08-12 11:59

    简单的手记,没有什么系统性。相关的变量名是经过简单替换的,以免关注点偏离(bushi

    Mysql in WSL
    2020-02-07 08:59 2020-06-24 05:55

    😜 TL;DR

    -

    查看日志/var/log/mysql/error.log,上互联网搜

    +

    查看日志/var/log/mysql/error.log,上互联网搜

    MySQL 存储 Emoji
    2019-12-23 14:21 2020-09-26 07:41

    最近需要使用 SQLAlchemy 存弹幕的内容,但是遇到了存 emoji 的问题。

    - + diff --git a/tag/network/index.html b/tag/network/index.html index 990d5f73b..9cb3c1ae0 100644 --- a/tag/network/index.html +++ b/tag/network/index.html @@ -16,7 +16,7 @@ } -
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    NPM Registry: What If I Cannot Reach It?
    2021-02-01 08:18 2021-02-01 08:18
    +
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    NPM Registry: What If I Cannot Reach It?
    2021-02-01 08:18 2021-02-01 08:18

    The story of mirrors, ipv6, and yarn 2

    diff --git a/tag/pwa/index.html b/tag/pwa/index.html index e768aaf54..2ea808351 100644 --- a/tag/pwa/index.html +++ b/tag/pwa/index.html @@ -16,8 +16,8 @@ } -
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    `skipWaiting()` with `StaleWhileRevalidate` the right way
    2021-06-28 04:31 2021-06-28 10:11
    -

    It is common to use workbox StaleWhileRevalidate strategy to cache resources which may take some time to fetch. Usually the resource needs to be updated but not immediately. However if the resource request takes too much time to complete, service worker's life cycle and some functionality may be impacted, especially self.skipWaiting().

    +
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    `skipWaiting()` with `StaleWhileRevalidate` the right way
    2021-06-28 04:31 2021-06-28 10:11
    +

    It is common to use workbox StaleWhileRevalidate strategy to cache resources which may take some time to fetch. Usually the resource needs to be updated but not immediately. However if the resource request takes too much time to complete, service worker's life cycle and some functionality may be impacted, especially self.skipWaiting().

    Gridsome is not that Ready for PWA
    2020-09-16 03:22 2021-07-03 13:06

    How I am tring to build a nice gridsome-generated site with PWA support

    虽然可以勉强并入“用 Vue 做 PWA”系列,但想着能让更多人看到,就还是用国际语言吧

    @@ -28,6 +28,6 @@
    用 Vue 做 PWA(一):开始
    2020-01-30 07:59 2020-09-04 23:59

    Vue 党快速上手 PWA

    - + diff --git a/tag/python/index.html b/tag/python/index.html index bb381bfe9..f3d8ea90b 100644 --- a/tag/python/index.html +++ b/tag/python/index.html @@ -16,7 +16,7 @@ } -
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    Use pipenv and poetry in a way that works
    2020-08-23 07:29 2020-09-02 01:59
    +
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    Use pipenv and poetry in a way that works
    2020-08-23 07:29 2020-09-02 01:59

    Strange as the title is, they just don't work conveniently "out of box"

    异步 & 异步获取命令输出
    2020-02-05 10:53 2020-06-30 08:58

    与 JavaScript 的异步对比,初步了解 Python 的异步,并通过异步获取命令输出“实战”

    diff --git a/tag/raspi/index.html b/tag/raspi/index.html index ec7bd54bb..e630d9bfc 100644 --- a/tag/raspi/index.html +++ b/tag/raspi/index.html @@ -16,7 +16,7 @@ } -
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    Jupyter on Raspberry Pi
    2020-02-26 12:20 2020-07-01 12:33
    Install Wolfram With Jupyter
    2020-02-26 09:50 2020-07-01 12:28
    Tricks about Raspberry Pi
    2020-01-21 08:29 2020-06-24 06:04
    +
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    Jupyter on Raspberry Pi
    2020-02-26 12:20 2020-07-01 12:33
    Install Wolfram With Jupyter
    2020-02-26 09:50 2020-07-01 12:28
    Tricks about Raspberry Pi
    2020-01-21 08:29 2020-06-24 06:04

    Handy tricks when playing with raspberry pi. You should know them.

    diff --git a/tag/tmux/index.html b/tag/tmux/index.html index b8bf62209..aae6ca09d 100644 --- a/tag/tmux/index.html +++ b/tag/tmux/index.html @@ -16,7 +16,7 @@ } -
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    Introduce and Setup Tmux
    2020-01-21 13:55 2020-07-10 12:46
    +
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    Introduce and Setup Tmux
    2020-01-21 13:55 2020-07-10 12:46

    Setup and introduce Tmux so that I can use it

    Working with tmux and SSH
    2020-01-21 13:55 2020-07-03 01:54

    Useful tips when SSH in remote device which uses tmux

    diff --git a/tag/vim/index.html b/tag/vim/index.html index 6033bc27f..5a0fc55e9 100644 --- a/tag/vim/index.html +++ b/tag/vim/index.html @@ -16,7 +16,7 @@ } -
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    How I compile and install Vim with +python3
    2020-11-15 06:36 2020-11-15 06:53
    +
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    How I compile and install Vim with +python3
    2020-11-15 06:36 2020-11-15 06:53

    It's known to all that compiling is hard. Just record it.

    Vim Cheatsheet
    2020-04-18 04:48 2020-07-01 13:01
    Setup Vim Airline (in Termux)
    2019-02-09 00:00 2021-02-22 01:59
    diff --git a/tag/vscode/index.html b/tag/vscode/index.html index b475c441d..c158e0aba 100644 --- a/tag/vscode/index.html +++ b/tag/vscode/index.html @@ -16,7 +16,7 @@ } -
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    VSCode 与如何配置 VSCode
    2021-02-14 02:30 2021-02-14 02:45
    +
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    VSCode 与如何配置 VSCode
    2021-02-14 02:30 2021-02-14 02:45

    这是一篇(尽量)新手向的文章,以 VSCode, Python, JavaScript (Vue) 为例,介绍编辑器的功能和配置。

    当然,没说必须选择 VSCode,大可选择 JetBrains 那一套。但是由于笔者平日折腾跨越的语言、领域较多(且前端居多),VSCode 是更好的选择,故只对 VSCode 有少量研究。

    diff --git a/tag/vue/index.html b/tag/vue/index.html index 5dbaa7d11..de52d7ff6 100644 --- a/tag/vue/index.html +++ b/tag/vue/index.html @@ -16,7 +16,7 @@ } -
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    Gridsome is not that Ready for PWA
    2020-09-16 03:22 2021-07-03 13:06
    +
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    Gridsome is not that Ready for PWA
    2020-09-16 03:22 2021-07-03 13:06

    How I am tring to build a nice gridsome-generated site with PWA support

    虽然可以勉强并入“用 Vue 做 PWA”系列,但想着能让更多人看到,就还是用国际语言吧

    用 Vue 做 PWA (三):理解生命周期
    2020-09-03 02:40 2020-09-08 03:09
    @@ -28,8 +28,8 @@
    用 Vue 做 PWA(一):开始
    2020-01-30 07:59 2020-09-04 23:59

    Vue 党快速上手 PWA

    Vue 加 Hammer 处理手势
    2020-01-27 08:33 2020-07-03 02:20
    -

    接近原生 App 体验,手势是少不了的。但是无论是 Vue 还是 Vuetify 对手势的支持并不好,于是需要 Hammer.js 支持。

    +

    接近原生 App 体验,手势是少不了的。但是无论是 Vue 还是 Vuetify 对手势的支持并不好,于是需要 Hammer.js 支持。

    - + diff --git a/tag/wolfram/index.html b/tag/wolfram/index.html index a6703cb28..75d73bcd2 100644 --- a/tag/wolfram/index.html +++ b/tag/wolfram/index.html @@ -16,7 +16,7 @@ } -
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    Install Wolfram With Jupyter
    2020-02-26 09:50 2020-07-01 12:28
    +
    AC Dustbin
    Hit Enter to do fulltext search on GitHub
    Install Wolfram With Jupyter
    2020-02-26 09:50 2020-07-01 12:28