# Technical Documentation: HerbertHe/iptv-sources

> ℹ️ **Provenance:** Hybrid Fusion: `HerbertHe/iptv-sources` + `HerbertHe/HerbertHe.github.io` · [CodeWiki Reference](https://codewiki.google/github.com/HerbertHe/iptv-sources) · Recency: Active (< 180 days)

## 1. Project Overview & Quickstart (HerbertHe/iptv-sources)

# iptv-sources

Autoupdate iptv sources

[](https://hub.docker.com/r/herberthe0229/iptv-sources)
[](https://hub.docker.com/r/herberthe0229/iptv-sources)
[](https://hub.docker.com/r/herberthe0229/iptv-sources)
[](https://hub.docker.com/r/herberthe0229/iptv-sources)
[](https://hub.docker.com/r/herberthe0229/iptv-sources)

Join discord: [](https://discord.gg/betxHcsTqa)

Sources are from:

- <https://epg.pw/test_channel_page.html>
- [iptv.org](https://github.com/iptv-org/iptv)
- [YueChan/Live](https://github.com/YueChan/Live)
- [YanG-1989/m3u](https://github.com/YanG-1989/m3u)
- [fanmingming/live](https://github.com/fanmingming/live)
- [qwerttvv/Beijing-IPTV](https://github.com/qwerttvv/Beijing-IPTV)
- [joevess/IPTV](https://github.com/joevess/IPTV)
- [cymz6/AutoIPTV-Hotel](https://github.com/cymz6/AutoIPTV-Hotel)

EPG Sources are from:

- [fanmingming/live](https://github.com/fanmingming/live)
- [112114.xyz](https://diyp1.112114.xyz)
- [epg.51zmt.top:8000](http://epg.51zmt.top:8000/)

See <https://m3u.ibert.me> to get more.

## Deploy by yourself

- [How to deploy with GitHub Pages](https://github.com/HerbertHe/iptv-sources/discussions/35)
- [How to deploy with docker](https://github.com/HerbertHe/iptv-sources/discussions/36)
- [How to deploy with nodejs](https://github.com/HerbertHe/iptv-sources/discussions/37)

## Supported Environment Variables

```shell
# add custom rollback urls, default is empty
# ROLLBACK_URLS=https://xxxx.xxx.com

# close source proxy, default is false
# CLOSE_SOURCE_PROXY=true

# add custom github raw source proxy url
# The custom proxy service you configured MUST supports the request urls, like `${CUSTOM_GITHUB_RAW_SOURCE_PROXY_URL}/https://raw.githubusercontent.com/xxx/xxx`
# If you want to deploy the ghproxy by yourself, see https://github.com/hunshcn/gh-proxy
# CUSTOM_GITHUB_RAW_SOURCE_PROXY_URL=https://ghp.ci/

# enable iptv checker, default is false
# ENABLE_IPTV_CHECKER=true

# add iptv checker url, default is empty
# IPTV_CHECKER_URL=http://[::1]:8081
```

## Q&A

- [How to close the github raw content proxy](https://github.com/HerbertHe/iptv-sources/discussions/38)
- [How to set the custom github raw source proxy url](https://github.com/HerbertHe/iptv-sources/discussions/39)
- [How to use `iptv-checker` feature](https://github.com/HerbertHe/iptv-sources/discussions/40)
- [How to add your own rollback urls](https://github.com/HerbertHe/iptv-sources/discussions/41)
- [How to create custom sources based on the upstream](https://github.com/HerbertHe/iptv-sources/discussions/68)

## Star History

[](https://star-history.com/#HerbertHe/iptv-sources&Date)

## LICENSE

GPL-3.0 &copy; Herbert He

本项目基于 GPL-3.0 协议开源。

## 2. Official Technical Reference & Guides (HerbertHe/HerbertHe.github.io)

## File: README.md

# Blog

The blog is developed with [VitePress](https://vitepress.dev), and the theme is based on [clark-cui/vitepress-blog-zaun](https://github.com/clark-cui/vitepress-blog-zaun).

I've redeveloped the theme to support some personal features, such as:

- [x] Use Giscus instead of gitalk
- [x] Support `$draft` and `$pin` tags

## LICENSE

The content of this blog is licensed under the [CC BY-NC-SA 4.0](http://creativecommons.org/licenses/by-nc-sa/4.0/?ref=chooser-v1).

The code is licensed under the [MIT License](https://opensource.org/licenses/MIT).

---

## File: archives.md

---
layout: page
---

<Archives/>

---

## File: index.md

---
layout: home
date: 2018-01-01
title: Home
sidebar: false
---

---

## File: package.json

{
  "name": "blog",
  "version": "1.0.0",
  "packageManager": "pnpm@10.32.1",
  "main": "index.js",
  "type": "module",
  "author": "Herbert He<Herbert.He0229@gmail.com>",
  "license": "MIT",
  "devDependencies": {
    "@types/node": "^26.4.0",
    "cross-env": "^10.1.0",
    "esbuild": "^0.28.2",
    "fast-glob": "^3.3.3",
    "feed": "^6.0.0",
    "fs-extra": "^11.4.0",
    "globby": "^16.2.4",
    "gray-matter": "^4.0.3",
    "markdown-it": "^15.0.1",
    "markdown-it-mathjax3": "^5.2.0",
    "vitepress": "^1.6.4"
  },
  "scripts": {
    "dev": "cross-env NODE_ENV=development vitepress dev",
    "build": "vitepress build",
    "serve": "vitepress serve",
    "post:new": "node scripts/new_post.js",
    "bump": "node scripts/bump_dep.js"
  },
  "dependencies": {
    "@giscus/vue": "^3.1.1",
    "dayjs": "^1.11.23",
    "mermaid": "^11.17.2"
  },
  "pnpm": {
    "overrides": {
      "vite": "6.4.3"
    },
    "peerDependencyRules": {
      "allowedVersions": {
        "markdown-it-mathjax3": "^5.2.0"
      }
    }
  }
}

---

## File: pnpm-lock.yaml

lockfileVersion: '9.0'

settings:
  autoInstallPeers: true
  excludeLinksFromLockfile: false

overrides:
  vite: 6.4.3

importers:

  .:
    dependencies:
      '@giscus/vue':
        specifier: ^3.1.1
        version: 3.1.1(vue@3.5.42)
      dayjs:
        specifier: ^1.11.23
        version: 1.11.23
      mermaid:
        specifier: ^11.17.2
        version: 11.17.2
    devDependencies:
      '@types/node':
        specifier: ^26.4.0
        version: 26.4.0
      cross-env:
        specifier: ^10.1.0
        version: 10.1.0
      esbuild:
        specifier: ^0.28.2
        version: 0.28.2
      fast-glob:
        specifier: ^3.3.3
        version: 3.3.3
      feed:
        specifier: ^6.0.0
        version: 6.0.0
      fs-extra:
        specifier: ^11.4.0
        version: 11.4.0
      globby:
        specifier: ^16.2.4
        version: 16.2.4
      gray-matter:
        specifier: ^4.0.3
        version: 4.0.3
      markdown-it:
        specifier: ^15.0.1
        version: 15.0.1
      markdown-it-mathjax3:
        specifier: ^5.2.0
        version: 5.2.0
      vitepress:
        specifier: ^1.6.4
        version: 1.6.4(@algolia/client-search@5.57.0)(@types/node@26.4.0)(markdown-it-mathjax3@5.2.0)(postcss@8.5.26)(search-insights@2.17.3)

packages:

  '@algolia/abtesting@1.23.0':
    resolution: {integrity: sha512-j45MBISstltys9QyQ4xf6quRiN1g7vMuwQL9VM4dx8YuRZvCQ173b9royZAx6iAbRX3IB1VnG1z//NuwyQ8jpQ==}
    engines: {node: '>= 14.0.0'}

  '@algolia/autocomplete-core@1.17.7':
    resolution: {integrity: sha512-BjiPOW6ks90UKl7TwMv7oNQMnzU+t/wk9mgIDi6b1tXpUek7MW0lbNOUHpvam9pe3lVCf4xPFT+lK7s+e+fs7Q==}

  '@algolia/autocomplete-plugin-algolia-insights@1.17.7':
    resolution: {integrity: sha512-Jca5Ude6yUOuyzjnz57og7Et3aXjbwCSDf/8onLHSQgw1qW3ALl9mrMWaXb5FmPVkV3EtkD2F/+NkT6VHyPu9A==}
    peerDependencies:
      search-insights: '>= 1 < 3'

  '@algolia/autocomplete-preset-algolia@1.17.7':
    resolution: {integrity: sha512-ggOQ950+nwbWROq2MOCIL71RE0DdQZsceqrg32UqnhDz8FlO9rL8ONHNsI2R1MH0tkgVIDKI/D0sMiUchsFdWA==}
    peerDependencies:
      '@algolia/client-search': '>= 4.9.1 < 6'
      algoliasearch: '>= 4.9.1 < 6'

  '@algolia/autocomplete-shared@1.17.7':
    resolution: {integrity: sha512-o/1Vurr42U/qskRSuhBH+VKxMvkkUVTLU6WZQr+L5lGZZLYWyhdzWjW0iGXY7EkwRTjBqvN2EsR81yCTGV/kmg==}
    peerDependencies:
      '@algolia/client-search': '>= 4.9.1 < 6'
      algoliasearch: '>= 4.9.1 < 6'

  '@algolia/client-abtesting@5.57.0':
    resolution: {integrity: sha512-JVFFujiZUCguk5tz3LZr4fTQxqpIrj4/Jw3SI7kMljSqtfLxYn/s/TWH0J2s4iNfsDpxPhgFGMotCpmDI4kZ8w==}
    engines: {node: '>= 14.0.0'}

  '@algolia/client-analytics@5.57.0':
    resolution: {integrity: sha512-6KqECK4ED3JJQEoDrQWnGPQzElA828xAD4qK5ceawNNyP/LcSvzAoLHjFkoTPksZ/kxj6VUtCRH+IHZesLltng==}
    engines: {node: '>= 14.0.0'}

  '@algolia/client-common@5.57.0':
    resolution: {integrity: sha512-uqpGF3oXYsoCbQq5d7BzNrNTfIfuvJyGP1CKvSW27T9boUg7KOwyxsAw1AX0a3jSW2HrYEJ/NN+Z4MiGivbpeQ==}
    engines: {node: '>= 14.0.0'}

  '@algolia/client-insights@5.57.0':
    resolution: {integrity: sha512-u5NboJVJXDEFplvNnqqX4CxkXPYysjJRj47hOSh9329H8kG5gFLKJBIiS5utMQ+GZm8xQl3Te7NInDk6elEADQ==}
    engines: {node: '>= 14.0.0'}

  '@algolia/client-personalization@5.57.0':
    resolution: {integrity: sha512-uzc0b2LmHAK9/QID4xeo35OG84AkZl4YewkCqawqAOGLjT2eZpM/OZx45ESygMHG30Ws+ZTSdluPtMJcUnrbWQ==}
    engines: {node: '>= 14.0.0'}

  '@algolia/client-query-suggestions@5.57.0':
    resolution: {integrity: sha512-dIAhnM6ue/ssa5PjgNfu4g8A4yTojl9ZOUzZU3wIaIKRerL2R/3Emuf9n/D6ICXXP167KC6XCeC7nliSw7cuSw==}
    engines: {node: '>= 14.0.0'}

  '@algolia/client-search@5.57.0':
    resolution: {integrity: sha512-2TTPTTKSJmCptvhCm4Xf3bBYMqZni+Pgc2hVdqc4l9wsBpSJNVTVIKpnd10OubUgkGcmppVDj1XQqYaf6EnPSQ==}
    engines: {node: '>= 14.0.0'}

  '@algolia/ingestion@1.57.0':
    resolution: {integrity: sha512-W4JseHKt+pzOxlFV+T3MWEG0h4Z2Se5zjoXUD0ewlw8aOWMG/yjRdopUdLQsXULepB/My2tDuZjkwk2sMfsUrQ==}
    engines: {node: '>= 14.0.0'}

  '@algolia/monitoring@1.57.0':
    resolution: {integrity: sha512-BrxJVE0/eLinEPICCD7BKN/2xnt0nkjge70u8zzE2ISP3fuB3tjLgcwpanUycvlHBFLI4gK0l5ol54p6IYuR/Q==}
    engines: {node: '>= 14.0.0'}

  '@algolia/recommend@5.57.0':
    resolution: {integrity: sha512-Gc29jkeiLKlVfHvyrIgyUHHE+aYTdXEeLfK42rjr5/1TTVsYwUJz0XkvoIBIqfMjcDg6gXeHb1jTUZ0H+SYIlQ==}
    engines: {node: '>= 14.0.0'}

  '@algolia/requester-browser-xhr@5.57.0':
    resolution: {integrity: sha512-PIPnPN7MP3fp2VAi01BVXhCWmD366ZB2Hkq5TlYKtThd4KxUtMmaaNDpFgVCTXtSIqWVZLJntOHRvxg/sIPd8Q==}
    engines: {node: '>= 14.0.0'}

  '@algolia/requester-fetch@5.57.0':
    resolution: {integrity: sha512-AX3RlOudXMdTwtwUqdAf5hAVLvXfOZZH1FZh6ALDdrhVLT0TtAIe48N6nYcWcTnwoTxK/wDIQqZ19IMjK8zJAA==}
    engines: {node: '>= 14.0.0'}

  '@algolia/requester-node-http@5.57.0':
    resolution: {integrity: sha512-cWZc1dKb7wy9/wPpwMtL1y89gK2G7y2A47Coa7zwf1ydtIeJm4+S+XxoQ2b/ZRiQnrC1YHavLjYUPDCdnT7Khg==}
    engines: {node: '>= 14.0.0'}

  '@antfu/install-pkg@1.1.0':
    resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==}

  '@babel/helper-string-parser@7.29.7':
    resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==}
    engines: {node: '>=6.9.0'}

  '@babel/helper-validator-identifier@7.29.7':
    resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==}
    engines: {node: '>=6.9.0'}

  '@babel/parser@7.29.8':
    resolution: {integrity: sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==}
    engines: {node: '>=6.0.0'}
    hasBin: true

  '@babel/types@7.29.8':
    resolution: {integrity: sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==}
    engines: {node: '>=6.9.0'}

  '@braintree/sanitize-url@7.1.2':
    resolution: {integrity: sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==}

  '@chevrotain/types@11.1.2':
    resolution: {integrity: sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw==}

  '@docsearch/css@3.8.2':
    resolution: {integrity: sha512-y05ayQFyUmCXze79+56v/4HpycYF3uFqB78pLPrSV5ZKAlDuIAAJNhaRi8tTdRNXh05yxX/TyNnzD6LwSM89vQ==}

  '@docsearch/js@3.8.2':
    resolution: {integrity: sha512-Q5wY66qHn0SwA7Taa0aDbHiJvaFJLOJyHmooQ7y8hlwwQLQ/5WwCcoX0g7ii04Qi2DJlHsd0XXzJ8Ypw9+9YmQ==}

  '@docsearch/react@3.8.2':
    resolution: {integrity: sha512-xCRrJQlTt8N9GU0DG4ptwHRkfnSnD/YpdeaXe02iKfqs97TkZJv60yE+1eq/tjPcVnTW8dP5qLP7itifFVV5eg==}
    peerDependencies:
      '@types/react': '>= 16.8.0 < 19.0.0'
      react: '>= 16.8.0 < 19.0.0'
      react-dom: '>= 16.8.0 < 19.0.0'
      search-insights: '>= 1 < 3'
    peerDependenciesMeta:
      '@types/react':
        optional: true
      react:
        optional: true
      react-dom:
        optional: true
      search-insights:
        optional: true

  '@epic-web/invariant@1.0.0':
    resolution: {integrity: sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==}

  '@esbuild/aix-ppc64@0.25.12':
    resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==}
    engines: {node: '>=18'}
    cpu: [ppc64]
    os: [aix]

  '@esbuild/aix-ppc64@0.28.2':
    resolution: {integrity: sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==}
    engines: {node: '>=18'}
    cpu: [ppc64]
    os: [aix]

  '@esbuild/android-arm64@0.25.12':
    resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==}
    engines: {node: '>=18'}
    cpu: [arm64]
    os: [android]

  '@esbuild/android-arm64@0.28.2':
    resolution: {integrity: sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==}
    engines: {node: '>=18'}
    cpu: [arm64]
    os: [android]

  '@esbuild/android-arm@0.25.12':
    resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==}
    engines: {node: '>=18'}
    cpu: [arm]
    os: [android]

  '@esbuild/android-arm@0.28.2':
    resolution: {integrity: sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==}
    engines: {node: '>=18'}
    cpu: [arm]
    os: [android]

  '@esbuild/android-x64@0.25.12':
    resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==}
    engines: {node: '>=18'}
    cpu: [x64]
    os: [android]

  '@esbuild/android-x64@0.28.2':
    resolution: {integrity: sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==}
    engines: {node: '>=18'}
    cpu: [x64]
    os: [android]

  '@esbuild/darwin-arm64@0.25.12':
    resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==}
    engines: {node: '>=18'}
    cpu: [arm64]
    os: [darwin]

  '@esbuild/darwin-arm64@0.28.2':
    resolution: {integrity: sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==}
    engines: {node: '>=18'}
    cpu: [arm64]
    os: [darwin]

  '@esbuild/darwin-x64@0.25.12':
    resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==}
    engines: {node: '>=18'}
    cpu: [x64]
    os: [darwin]

  '@esbuild/darwin-x64@0.28.2':
    resolution: {integrity: sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==}
    engines: {node: '>=18'}
    cpu: [x64]
    os: [darwin]

  '@esbuild/freebsd-arm64@0.25.12':
    resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==}
    engines: {node: '>=18'}
    cpu: [arm64]
    os: [freebsd]

  '@esbuild/freebsd-arm64@0.28.2':
    resolution: {integrity: sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==}
    engines: {node: '>=18'}
    cpu: [arm64]
    os: [freebsd]

  '@esbuild/freebsd-x64@0.25.12':
    resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==}
    engines: {node: '>=18'}
    cpu: [x64]
    os: [freebsd]

  '@esbuild/freebsd-x64@0.28.2':
    resolution: {integrity: sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==}
    engines: {node: '>=18'}
    cpu: [x64]
    os: [freebsd]

  '@esbuild/linux-arm64@0.25.12':
    resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==}
    engines: {node: '>=18'}
    cpu: [arm64]
    os: [linux]

  '@esbuild/linux-arm64@0.28.2':
    resolution: {integrity: sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==}
    engines: {node: '>=18'}
    cpu: [arm64]
    os: [linux]

  '@esbuild/linux-arm@0.25.12':
    resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==}
    engines: {node: '>=18'}
    cpu: [arm]
    os: [linux]

  '@esbuild/linux-arm@0.28.2':
    resolution: {integrity: sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==}
    engines: {node: '>=18'}
    cpu: [arm]
    os: [linux]

  '@esbuild/linux-ia32@0.25.12':
    resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==}
    engines: {node: '>=18'}
    cpu: [ia32]
    os: [linux]

  '@esbuild/linux-ia32@0.28.2':
    resolution: {integrity: sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==}
    engines: {node: '>=18'}
    cpu: [ia32]
    os: [linux]

  '@esbuild/linux-loong64@0.25.12':
    resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==}
    engines: {node: '>=18'}
    cpu: [loong64]
    os: [linux]

  '@esbuild/linux-loong64@0.28.2':
    resolution: {integrity: sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==}
    engines: {node: '>=18'}
    cpu: [loong64]
    os: [linux]

  '@esbuild/linux-mips64el@0.25.12':
    resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==}
    engines: {node: '>=18'}
    cpu: [mips64el]
    os: [linux]

  '@esbuild/linux-mips64el@0.28.2':
    resolution: {integrity: sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==}
    engines: {node: '>=18'}
    cpu: [mips64el]
    os: [linux]

  '@esbuild/linux-ppc64@0.25.12':
    resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==}
    engines: {node: '>=18'}
    cpu: [ppc64]
    os: [linux]

  '@esbuild/linux-ppc64@0.28.2':
    resolution: {integrity: sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==}
    engines: {node: '>=18'}
    cpu: [ppc64]
    os: [linux]

  '@esbuild/linux-riscv64@0.25.12':
    resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==}
    engines: {node: '>=18'}
    cpu: [riscv64]
    os: [linux]

  '@esbuild/linux-riscv64@0.28.2':
    resolution: {integrity: sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==}
    engines: {node: '>=18'}
    cpu: [riscv64]
    os: [linux]

  '@esbuild/linux-s390x@0.25.12':
    resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==}
    engines: {node: '>=18'}
    cpu: [s390x]
    os: [linux]

  '@esbuild/linux-s390x@0.28.2':
    resolution: {integrity: sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==}
    engines: {node: '>=18'}
    cpu: [s390x]
    os: [linux]

  '@esbuild/linux-x64@0.25.12':
    resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==}
    engines: {node: '>=18'}
    cpu: [x64]
    os: [linux]

  '@esbuild/linux-x64@0.28.2':
    resolution: {integrity: sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==}
    engines: {node: '>=18'}
    cpu: [x64]
    os: [linux]

  '@esbuild/netbsd-arm64@0.25.12':
    resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==}
    engines: {node: '>=18'}
    cpu: [arm64]
    os: [netbsd]

  '@esbuild/netbsd-arm64@0.28.2':
    resolution: {integrity: sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==}
    engines: {node: '>=18'}
    cpu: [arm64]
    os: [netbsd]

  '@esbuild/netbsd-x64@0.25.12':
    resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==}
    engines: {node: '>=18'}
    cpu: [x64]
    os: [netbsd]

  '@esbuild/netbsd-x64@0.28.2':
    resolution: {integrity: sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==}
    engines: {node: '>=18'}
    cpu: [x64]
    os: [netbsd]

  '@esbuild/openbsd-arm64@0.25.12':
    resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==}
    engines: {node: '>=18'}
    cpu: [arm64]
    os: [openbsd]

  '@esbuild/openbsd-arm64@0.28.2':
    resolution: {integrity: sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==}
    engines: {node: '>=18'}
    cpu: [arm64]
    os: [openbsd]

  '@esbuild/openbsd-x64@0.25.12':
    resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==}
    engines: {node: '>=18'}
    cpu: [x64]
    os: [openbsd]

  '@esbuild/openbsd-x64@0.28.2':
    resolution: {integrity: sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==}
    engines: {node: '>=18'}
    cpu: [x64]
    os: [openbsd]

  '@esbuild/openharmony-arm64@0.25.12':
    resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==}
    engines: {node: '>=18'}
    cpu: [arm64]
    os: [openharmony]

  '@esbuild/openharmony-arm64@0.28.2':
    resolution: {integrity: sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==}
    engines: {node: '>=18'}
    cpu: [arm64]
    os: [openharmony]

  '@esbuild/sunos-x64@0.25.12':
    resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==}
    engines: {node: '>=18'}
    cpu: [x64]
    os: [sunos]

  '@esbuild/sunos-x64@0.28.2':
    resolution: {integrity: sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==}
    engines: {node: '>=18'}
    cpu: [x64]
    os: [sunos]

  '@esbuild/win32-arm64@0.25.12':
    resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==}
    engines: {node: '>=18'}
    cpu: [arm64]
    os: [win32]

  '@esbuild/win32-arm64@0.28.2':
    resolution: {integrity: sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==}
    engines: {node: '>=18'}
    cpu: [arm64]
    os: [win32]

  '@esbuild/win32-ia32@0.25.12':
    resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==}
    engines: {node: '>=18'}
    cpu: [ia32]
    os: [win32]

  '@esbuild/win32-ia32@0.28.2':
    resolution: {integrity: sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==}
    engines: {node: '>=18'}
    cpu: [ia32]
    os: [win32]

  '@esbuild/win32-x64@0.25.12':
    resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==}
    engines: {node: '>=18'}
    cpu: [x64]
    os: [win32]

  '@esbuild/win32-x64@0.28.2':
    resolution: {integrity: sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==}
    engines: {node: '>=18'}
    cpu: [x64]
    os: [win32]

  '@giscus/vue@3.1.1':
    resolution: {integrity: sha512-xgsc93MibRQ0d1glBxN1BrEREHTIf3BPYs/3R+UZHNqawFFoWBV6iJ7uVCXMqoEKZdE2c78B3aKt5gfGqo8I5A==}
    peerDependencies:
      vue: '>=3.2.0'

  '@iconify-json/simple-icons@1.2.93':
    resolution: {integrity: sha512-/XhANjfGYOuqvSR3TmUnkQkINvQ4GVjVuukvymRbxtVFBvIq/yiXJqCDycKcQPT401OYT9H2vIY6ihAlz1QIAw==}

  '@iconify/types@2.0.0':
    resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==}

  '@iconify/utils@3.1.4':
    resolution: {integrity: sha512-b1S7B1k9ohZ+iNTi2ATxbRYG9fTrJmUT0rc46bvVnNxqNRGW7dyo/vRREwyniI5IRN2RSJHDcm+s3BjWrSAjHw==}

  '@jridgewell/sourcemap-codec@1.6.0':
    resolution: {integrity: sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==}

  '@lit-labs/ssr-dom-shim@1.6.0':
    resolution: {integrity: sha512-VHb0ALPMTlgKjM6yIxxoQNnpKyUKLD04VzeQdsiXkMqkvYlAHxq9glGLmgbb889/1GsohSOAjvQYoiBppXFqrQ==}

  '@lit/reactive-element@2.1.2':
    resolution: {integrity: sha512-pbCDiVMnne1lYUIaYNN5wrwQXDtHaYtg7YEFPeW+hws6U47WeFvISGUWekPGKWOP1ygrs0ef0o1VJMk1exos5A==}

  '@mermaid-js/parser@1.2.1':
    resolution: {integrity: sha512-n12NohV3mrUyUL2o93IgG/ifeW9FTyeJn3zDxkhwa8MJ9Fxg3HQMlA3RiGmD/3UnJvheztkjjQAjA2T4LmUcpw==}

  '@napi-rs/lzma-linux-x64-gnu@1.5.1':
    resolution: {integrity: sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==}
    engines: {node: ^22.20 || ^24.12 || >=25}
    cpu: [x64]
    os: [linux]
    libc: [glibc]

  '@nodelib/fs.scandir@2.1.5':
    resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
    engines: {node: '>= 8'}

  '@nodelib/fs.stat@2.0.5':
    resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==}
    engines: {node: '>= 8'}

  '@nodelib/fs.walk@1.2.8':
    resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
    engines: {node: '>= 8'}

  '@rollup/rollup-android-arm-eabi@4.63.0':
    resolution: {integrity: sha512-70TeIFezKKy65LgAVyQh+w94/gjWhvPWaLaGGeMEgVrPkQhuj/M5bAYYZzIFUj9Y69oHyTm5Um/R6gcLh4A8JA==}
    cpu: [arm]
    os: [android]

  '@rollup/rollup-android-arm64@4.63.0':
    resolution: {integrity: sha512-YC86tYIHK6M1IV+wbzO+Bxk8RCBr6ZyWYgWxUCzaZD8mc8rrFoIJDNzDrkHBYRc/wKdrsIXmm6/F7NzrAO+OrA==}
    cpu: [arm64]
    os: [android]

  '@rollup/rollup-darwin-arm64@4.63.0':
    resolution: {integrity: sha512-oI+ECtUcli0y0fi4xpW82GdPIXdTkI8G8DSjG2LRuw09fPAGykaWYH/hXxiKuTxiAjiPSTIIuYUqof5Z2hShWw==}
    cpu: [arm64]
    os: [darwin]

  '@rollup/rollup-darwin-x64@4.63.0':
    resolution: {integrity: sha512-NwV+1s7TiKrMe4owHyKB/dTLD7ZJD0YEBEhIz+hvav1Cu1GReJjF+rsdNwjzENQeIAbE/CoNiaAc5Vz2h5DPAA==}
    cpu: [x64]
    os: [darwin]

  '@rollup/rollup-freebsd-arm64@4.63.0':
    resolution: {integrity: sha512-tWtHBTu5gOPK4u4Urtk4qAHW3zZ9rQAmbssO8gp7ELvGTGI3aCiq6NqyTQ0PCIg7KbHJF2UkGDDs77YZGxfjCA==}
    cpu: [arm64]
    os: [freebsd]

  '@rollup/rollup-freebsd-x64@4.63.0':
    resolution: {integrity: sha512-2qPoJiwTvtHQ27NnYvTnsgk8laXWYuVmNESG8WFZBcEPKLfZ3I27qBJarjVRQtwGeYyRfq5ZowHXih9lm2BItw==}
    cpu: [x64]
    os: [freebsd]

  '@rollup/rollup-linux-arm-gnueabihf@4.63.0':
    resolution: {integrity: sha512-FQwsTRvLNuHoTdICABJQfbPUSEueISGmnpT06tXTMpfprf5NiKLSXKA0A+w45wJnCmZAnzgqBwbt6ARFuyOi5w==}
    cpu: [arm]
    os: [linux]
    libc: [glibc]

  '@rollup/rollup-linux-arm-musleabihf@4.63.0':
    resolution: {integrity: sha512-BBVTXziw8mY1a4ZbWME9tZyfzqXCDPqaC7Z3heQ29p5dkvXzwL0NwelO8zLa8c3RBKvl3YTuSnBgsBhYBtwjIw==}
    cpu: [arm]
    os: [linux]
    libc: [musl]

  '@rollup/rollup-linux-arm64-gnu@4.63.0':
    resolution: {integrity: sha512-w2Iyy9+RqKwx3d9qWMKsJg0FfRBsY0/pXNv0mCQ3ueRvJI6+QAScfD4nrMlzFLs2HNVW6Ew+mtZfDl9b7Ew5/Q==}
    cpu: [arm64]
    os: [linux]
    libc: [glibc]

  '@rollup/rollup-linux-arm64-musl@4.63.0':
    resolution: {integrity: sha512-YK++KtrFRHYE0P6/RtYEAy9t8F37znP+K03RrIuLPYOL6SVlObRumf/0OE4V/h63xL9DwkWbNssZfmA9hawuDA==}
    cpu: [arm64]
    os: [linux]
    libc: [musl]

  '@rollup/rollup-linux-loong64-gnu@4.63.0':
    resolution: {integrity: sha512-aBfOG6fP7YkkPmTqPwufRJeFyz7WPpECv9XNbnsk9+vg7rxdih0lbtEel7jcRng4LZrrmU3FfitCFyEj4BWDWg==}
    cpu: [loong64]
    os: [linux]
    libc: [glibc]

  '@rollup/rollup-linux-loong64-musl@4.63.0':
    resolution: {integrity: sha512-LGaHEOeHNAag9VuS1Crs5DFg4RrU9MPi2nVnNJk9DTePx/B6RRYKVmrIXt2h7YOJlwjaFJ6lwtFDliZxScTLrQ==}
    cpu: [loong64]
    os: [linux]
    libc: [musl]

  '@rollup/rollup-linux-ppc64-gnu@4.63.0':
    resolution: {integrity: sha512-jClvk+J0FC3b7Udvegiw5/4hErbHtmsNsQgENnKXDWtNCJXsJYZH5WURvu7imDOO38xYml24eeh5x3A04ppwCw==}
    cpu: [ppc64]
    os: [linux]
    libc: [glibc]

  '@rollup/rollup-linux-ppc64-musl@4.63.0':
    resolution: {integrity: sha512-0OJlaGK+8+B777Ql5okIpD7ua5Ro9+VB9Ve0OKa28OQJZ1RbuUBVNHK/e3pr4BROqsyPl1JrPO1ZxJseCNffcA==}
    cpu: [ppc64]
    os: [linux]
    libc: [musl]

  '@rollup/rollup-linux-riscv64-gnu@4.63.0':
    resolution: {integrity: sha512-Ygsx+HoNH7afwi1bTIXbnTvVnsO+zurPLSYxybV1hHFVU72OWOCl6v05ql/z0hkpAPx+DK7Kn9Bi7MayCcjLTA==}
    cpu: [riscv64]
    os: [linux]
    libc: [glibc]

  '@rollup/rollup-linux-riscv64-musl@4.63.0':
    resolution: {integrity: sha512-pDQxtMGb+OvG3fLwR2OkZlSd47hW+kWg4BYMG/++sR6RqorQccwPTDsxda5hPwiIeIErAnCF9ma3SAU06bdQtQ==}
    cpu: [riscv64]
    os: [linux]
    libc: [musl]

  '@rollup/rollup-linux-s390x-gnu@4.63.0':
    resolution: {integrity: sha512-0BnUG9mS8I4SSHr3XsxVhuCMEiu+rX61xxZF5vujso4LaiAGFZFxvDjg6Xn6tLPNTUAfuCvQYas4LMQMVsKRSQ==}
    cpu: [s390x]
    os: [linux]
    libc: [glibc]

  '@rollup/rollup-linux-x64-gnu@4.63.0':
    resolution: {integrity: sha512-Adu/VttB1dpPNW+FEacrZ+xVm9tFty84+RrFzsqlFaPxoJB+9XXyDGtp5dCOoBwGBIEVH0To7lExFXEx0BIF4A==}
    cpu: [x64]
    os: [linux]
    libc: [glibc]

  '@rollup/rollup-linux-x64-musl@4.63.0':
    resolution: {integrity: sha512-NQ3bDvjUbFKmP23671xUlXtKmqVsUBd6M4PQCvbmNtOy06hnQIdKHy8oG/6S3R/S6He1JgPk6A5VT+prAJMYEw==}
    cpu: [x64]
    os: [linux]
    libc: [musl]

  '@rollup/rollup-openbsd-x64@4.63.0':
    resolution: {integrity: sha512-u2eDAl4+0aFvA13GxlGBtTI3SS3sdgwgtV0HyjZ0QaQVCgNE+jqNGey+GtxWiq+wxr/UycAx/OnfJzApCFamvA==}
    cpu: [x64]
    os: [openbsd]

  '@rollup/rollup-openharmony-arm64@4.63.0':
    resolution: {integrity: sha512-XvRb5vfW3wAZQ+ZUG21AnHHDKtNcw99eigzEhjr//NZ3u7SoBaPP0seSc7FgP7p1epAEdAoZckMW9WY/+4w70w==}
    cpu: [arm64]
    os: [openharmony]

  '@rollup/rollup-win32-arm64-msvc@4.63.0':
    resolution: {integrity: sha512-iZPmniy4kNBf5yo2RezbkYNNK5HPbXE9+g+twnbqSng7dtLEJy1SKoxiE/ni4FDacjyuZpEeb9U054N4EoKHYw==}
    cpu: [arm64]
    os: [win32]

  '@rollup/rollup-win32-ia32-msvc@4.63.0':
    resolution: {integrity: sha512-mFBBd+LF37fnE8JnYUOH+imj0aPFPK30vpar4ehJkgnLj9sZn8ZxiRENmLtgIwxK7TC8klF6N57fxdNBwQoqOA==}
    cpu: [ia32]
    os: [win32]

  '@rollup/rollup-win32-x64-gnu@4.63.0':
    resolution: {integrity: sha512-ujeqEY3B+zbGn3Z4Q03cUBG/LGWnBJncVT36WER31LcOsQk9+1dmINKKtvmmfChUvRbK1G0R8OhMWFgHgaZtAw==}
    cpu: [x64]
    os: [win32]

  '@rollup/rollup-win32-x64-msvc@4.63.0':
    resolution: {integrity: sha512-hncn90N4sOky0L2LKE5oESKLbxCPeVo4eLA2LSMoDzM+879ml4WSr+Rr4DWknNIVVvS1Hirkc9hx02W6YxS8rQ==}
    cpu: [x64]
    os: [win32]

  '@se-oss/deasync-darwin-arm64@1.0.1':
    resolution: {integrity: sha512-0YWmIDEGQfW3GGopmZHhfA6mamsG0HFKZhmBzHVyFiMKkJts8kpQwGbGrWlK8eOAoPCihOsG6tCotYR3p7HZaQ==}
    engines: {node: '>= 10'}
    cpu: [arm64]
    os: [darwin]

  '@se-oss/deasync-darwin-x64@1.0.1':
    resolution: {integrity: sha512-r3FRTLIXqGqOb1DjTLW3YhO/Dd1vA2qRLP0Ym3Wmk3yMv6c/nm15zg6UVoXbgBu8cjbvcsI/OfbHPdErmjMWsw==}
    engines: {node: '>= 10'}
    cpu: [x64]
    os: [darwin]

  '@se-oss/deasync-linux-arm64-gnu@1.0.1':
    resolution: {integrity: sha512-657uRew7fZAx663Li03ilLV2lN09Dqb/NxawlDu8kKmboK1BLitHJRS+taiT5oFZqyIDrU45tlQKfCrW0p0sYA==}
    engines: {node: '>= 10'}
    cpu: [arm64]
    os: [linux]
    libc: [glibc]

  '@se-oss/deasync-linux-arm64-musl@1.0.1':
    resolution: {integrity: sha512-IE3fIQPIJtko4lx9sRam+Zz0P4xbpAPJgDCHaz6k9cP1yUvVI179B4IZRnFx0GyjyQpm0KhHoIGHJc4KUmA81Q==}
    engines: {node: '>= 10'}
    cpu: [arm64]
    os: [linux]
    libc: [musl]

  '@se-oss/deasync-linux-x64-gnu@1.0.1':
    resolution: {integrity: sha512-XQl7etZESGIjIraCyxfAey8ZTIJUB4dUFU3rPR/xLVn9bKpZGlJLIms0z3hoHX9mipO+Cqo53vK4IVm6A7U/ww==}
    engines: {node: '>= 10'}
    cpu: [x64]
    os: [linux]
    libc: [glibc]

  '@se-oss/deasync-linux-x64-musl@1.0.1':
    resolution: {integrity: sha512-vWgFAZlqImqMV6jhCWV7C9wcCS1eb1ajhlKduBRPfyUxxkoObe+EqTG2BKJAuafxp3/KS1aUsIMJma9mhwFvow==}
    engines: {node: '>= 10'}
    cpu: [x64]
    os: [linux]
    libc: [musl]

  '@se-oss/deasync-win32-arm64-msvc@1.0.1':
    resolution: {integrity: sha512-yk7lEE7Zd8GX7o6CuUbg3HnnmUhBx4tgfn5ff3eoq05CgBO6Z3ZtL4l+utAe1cxcFaXPhyvcgnHYyA4OF544tg==}
    engines: {node: '>= 10'}
    cpu: [arm64]
    os: [win32]

  '@se-oss/deasync-win32-x64-msvc@1.0.1':
    resolution: {integrity: sha512-ixizmuLGRPGyAesWUNWVzVOsvuunNb/qMqU8SmjfLR/vVgzdQEkSHFf+fkX9GXPN6FDv+DAz5uskTzhjUyCXFA==}
    engines: {node: '>= 10'}
    cpu: [x64]
    os: [win32]

  '@se-oss/deasync@1.0.1':
    resolution: {integrity: sha512-Ha7P/xCNxOuH72BNdLRWs4TT8rsMMrERnHtfKWBeTWu+UFW9OBTrRgfZJOlbAAQFR0l4Q30cpAn8CuR7PXWcPg==}
    engines: {node: '>= 10'}

  '@shikijs/core@2.5.0':
    resolution: {integrity: sha512-uu/8RExTKtavlpH7XqnVYBrfBkUc20ngXiX9NSrBhOVZYv/7XQRKUyhtkeflY5QsxC0GbJThCerruZfsUaSldg==}

  '@shikijs/engine-javascript@2.5.0':
    resolution: {integrity: sha512-VjnOpnQf8WuCEZtNUdjjwGUbtAVKuZkVQ/5cHy/tojVVRIRtlWMYVjyWhxOmIq05AlSOv72z7hRNRGVBgQOl0w==}

  '@shikijs/engine-oniguruma@2.5.0':
    resolution: {integrity: sha512-pGd1wRATzbo/uatrCIILlAdFVKdxImWJGQ5rFiB5VZi2ve5xj3Ax9jny8QvkaV93btQEwR/rSz5ERFpC5mKNIw==}

  '@shikijs/langs@2.5.0':
    resolution: {integrity: sha512-Qfrrt5OsNH5R+5tJ/3uYBBZv3SuGmnRPejV9IlIbFH3HTGLDlkqgHymAlzklVmKBjAaVmkPkyikAV/sQ1wSL+w==}

  '@shikijs/themes@2.5.0':
    resolution: {integrity: sha512-wGrk+R8tJnO0VMzmUExHR+QdSaPUl/NKs+a4cQQRWyoc3YFbUzuLEi/KWK1hj+8BfHRKm2jNhhJck1dfstJpiw==}

  '@shikijs/transformers@2.5.0':
    resolution: {integrity: sha512-SI494W5X60CaUwgi8u4q4m4s3YAFSxln3tzNjOSYqq54wlVgz0/NbbXEb3mdLbqMBztcmS7bVTaEd2w0qMmfeg==}

  '@shikijs/types@2.5.0':
    resolution: {integrity: sha512-ygl5yhxki9ZLNuNpPitBWvcy9fsSKKaRuO4BAlMyagszQidxcpLAr0qiW/q43DtSIDxO6hEbtYLiFZNXO/hdGw==}

  '@shikijs/vscode-textmate@10.0.2':
    resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==}

  '@sindresorhus/merge-streams@4.0.0':
    resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==}
    engines: {node: '>=18'}

  '@types/d3-array@3.2.2':
    resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==}

  '@types/d3-axis@3.0.6':
    resolution: {integrity: sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==}

  '@types/d3-brush@3.0.6':
    resolution: {integrity: sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==}

  '@types/d3-chord@3.0.6':
    resolution: {integrity: sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==}

  '@types/d3-color@3.1.3':
    resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==}

  '@types/d3-contour@3.0.6':
    resolution: {integrity: sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==}

  '@types/d3-delaunay@6.0.4':
    resolution: {integrity: sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==}

  '@types/d3-dispatch@3.0.7':
    resolution: {integrity: sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==}

  '@types/d3-drag@3.0.7':
    resolution: {integrity: sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==}

  '@types/d3-dsv@3.0.7':
    resolution: {integrity: sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==}

  '@types/d3-ease@3.0.2':
    resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==}

  '@types/d3-fetch@3.0.7':
    resolution: {integrity: sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==}

  '@types/d3-force@3.0.10':
    resolution: {integrity: sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==}

  '@types/d3-format@3.0.4':
    resolution: {integrity: sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==}

  '@types/d3-geo@3.1.1':
    resolution: {integrity: sha512-65Emv9fQiQQqphLlRkuQ5ypPsOmWPhtBGCMv61JDPEPMvsx+gzhGf74yw1a78xFKPj6zw4AgQICJoQv0vK9M2w==}

  '@types/d3-hierarchy@3.1.7':
    resolution: {integrity: sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==}

  '@types/d3-interpolate@3.0.4':
    resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==}

  '@types/d3-path@3.1.1':
    resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==}

  '@types/d3-polygon@3.0.2':
    resolution: {integrity: sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==}

  '@types/d3-quadtree@3.0.6':
    resolution: {integrity: sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==}

  '@types/d3-random@3.0.4':
    resolution: {integrity: sha512-UHYId5WTCx4L4YNel7NU00XUXXgvgpgZOvp10PuvsQENjMDXhh2RyFc0KBjO7B45ne4Ha1yVH7ii0vnzKkuzWA==}

  '@types/d3-scale-chromatic@3.1.0':
    resolution: {integrity: sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==}

  '@types/d3-scale@4.0.9':
    resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==}

  '@types/d3-selection@3.0.11':
    resolution: {integrity: sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==}

  '@types/d3-shape@3.2.0':
    resolution: {integrity: sha512-kVd74ta9eof3eJOvbNd1vGKS/XERRyQbT26Og63hIsvDO84cjD5gEOhsXf26w3FSoNlPVz84DOFcKv/oou+fMw==}

  '@types/d3-time-format@4.0.3':
    resolution: {integrity: sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==}

  '@types/d3-time@3.0.4':
    resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==}

  '@types/d3-timer@3.0.2':
    resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==}

  '@types/d3-transition@3.0.9':
    resolution: {integrity: sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==}

  '@types/d3-zoom@3.0.8':
    resolution: {integrity: sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==}

  '@types/d3@7.4.3':
    resolution: {integrity: sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==}

  '@types/estree@1.0.9':
    resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==}

  '@types/geojson@7946.0.16':
    resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==}

  '@types/hast@3.0.5':
    resolution: {integrity: sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==}

  '@types/linkify-it@5.0.0':
    resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==}

  '@types/markdown-it@14.2.0':
    resolution: {integrity: sha512-NoQ2yGlLWj4wpxMs+TYmRKk3thDrQ97agr7sFqfLsAlvoS8SNQuTrlObhFqG9iugdTtgOE9jpJ6FNM4ZGsa5xQ==}

  '@types/mdast@4.0.4':
    resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==}

  '@types/mdurl@2.0.0':
    resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==}

  '@types/node@26.4.0':
    resolution: {integrity: sha512-faiGnoIrLH/V8cibOMEAZ8pMw6oXqSukl29ra4mN8GdaB2ZewzeaLj+INpV5N+Z1eKWzY+IzaIZH2EIR6YZRNQ==}

  '@types/trusted-types@2.0.7':
    resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==}

  '@types/unist@3.0.3':
    resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==}

  '@types/web-bluetooth@0.0.21':
    resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==}

  '@ungap/structured-clone@1.4.0':
    resolution: {integrity: sha512-1mEZtMKPM09vDmQt5y7YvmN2+DFTP7Tg0EWXdic8/C6VRnpb33e4ghisCIE3WZjsE2N8mf+QV1Zqh7ZFYLWInQ==}

  '@upsetjs/venn.js@2.0.0':
    resolution: {integrity: sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw==}

  '@vitejs/plugin-vue@5.2.4':
    resolution: {integrity: sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==}
    engines: {node: ^18.0.0 || >=20.0.0}
    peerDependencies:
      vite: 6.4.3
      vue: ^3.2.25

  '@vue/compiler-core@3.5.42':
    resolution: {integrity: sha512-2Ye1ilMtKXxl8qZUrQ5j0CdgenFp/HFQmta6rfRyfEsTG69L6Wk+tWuNoHYHMx9E8tF2Slvdg1FuwDvAXdy1LQ==}

  '@vue/compiler-dom@3.5.42':
    resolution: {integrity: sha512-qbhQZEFmycr+ni/qyuccS4sucNN7VAbDfbkvNxWOX2VfgFm90MNs3/UhRNKoPMEIVn0F8gdlYjLPvqxHwHeQOA==}

  '@vue/compiler-sfc@3.5.42':
    resolution: {integrity: sha512-fkCAFB4okcAANGMThboWnScp/gzWjU0ZSkVnjTIiplmMDq2uq0tIB3j+xVu4rhv5rvOgBySCysudmbMd6xRRqw==}

  '@vue/compiler-ssr@3.5.42':
    resolution: {integrity: sha512-xmLk3wLkbizPAiLyomjgFFosf2ys9b5Ghb+oh/k2tnvipNz8OFrQOiTcWCzyK7MpBp9KkyGtfvgfLUivbmuGYA==}

  '@vue/devtools-api@7.7.10':
    resolution: {integrity: sha512-KxtEpUOOpFz/qOGRrAwA36QF7DqIA+FXgCYit9mk9wjbaZt0sXOFz81ElOZtKA4HbWHUdwNjZHBFsFFyp5BZiA==}

  '@vue/devtools-kit@7.7.10':
    resolution: {integrity: sha512-3WNi2Kq4tbpVbmhml7RiphmAt0279oh3fKNeWMQIrltfX8Q91b4i5PL8DtyNKdwmcsGrV4fg+erwWOmD05CLIw==}

  '@vue/devtools-shared@7.7.10':
    resolution: {integrity: sha512-wOPslzB8vTvpxwdaOcR2qAbwmuSP0L+rhpoC6Cf56V3Jip+HWb7PQQXOUPgBNQARpXsbQX/+mvi8kKucmBGRwQ==}

  '@vue/reactivity@3.5.42':
    resolution: {integrity: sha512-TzNNfKpb7hDxbQltwAut8VDQA5YP+BuRlxntHUuRjyKwlMvmAPbs3unhCvieijifY6vFfVBwsS7wG/C7uq+bEQ==}

  '@vue/runtime-core@3.5.42':
    resolution: {integrity: sha512-9uACtuHs7vJGkm5Bp3xu4xRDLFTIYy5DgxpToVjqGIAhAEKwQfsaLvKINhM6nFVp6bZPRFGdDqd1g52MqKsotA==}

  '@vue/runtime-dom@3.5.42':
    resolution: {integrity: sha512-rsCmhiWLaRxGltLwhlCWyYkFn7WAbKRh0q17eZ1A6Dq6eqc2ACQ61IIryxz0LrsvCzHSilLA9JHovVwM8CNE2g==}

  '@vue/server-renderer@3.5.42':
    resolution: {integrity: sha512-2++5dUyYS4gvo7xQXSECUDhB7TS0aOl5SeVfC5qSq1Jgfhjvegw1zqhwTIR3imZ+QYPJQw9gfcFvXGAjGZ7ajQ==}

  '@vue/shared@3.5.42':
    resolution: {integrity: sha512-2rPxex1jQf4jvl9MOHl6YaXCPcrNqz/FstMOEh3QWY+/OME9nQTvl9WYeCwhW7AFjaR0SnngZGlp/wkR6rkI6g==}

  '@vueuse/core@12.8.2':
    resolution: {integrity: sha512-HbvCmZdzAu3VGi/pWYm5Ut+Kd9mn1ZHnn4L5G8kOQTPs/IwIAmJoBrmYk2ckLArgMXZj0AW3n5CAejLUO+PhdQ==}

  '@vueuse/integrations@12.8.2':
    resolution: {integrity: sha512-fbGYivgK5uBTRt7p5F3zy6VrETlV9RtZjBqd1/HxGdjdckBgBM4ugP8LHpjolqTj14TXTxSK1ZfgPbHYyGuH7g==}
    peerDependencies:
      async-validator: ^4
      axios: ^1
      change-case: ^5
      drauu: ^0.4
      focus-trap: ^7
      fuse.js: ^7
      idb-keyval: ^6
      jwt-decode: ^4
      nprogress: ^0.2
      qrcode: ^1.5
      sortablejs: ^1
      universal-cookie: ^7
    peerDependenciesMeta:
      async-validator:
        optional: true
      axios:
        optional: true
      change-case:
        optional: true
      drauu:
        optional: true
      focus-trap:
        optional: true
      fuse.js:
        optional: true
      idb-keyval:
        optional: true
      jwt-decode:
        optional: true
      nprogress:
        optional: true
      qrcode:
        optional: true
      sortablejs:
        optional: true
      universal-cookie:
        optional: true

  '@vueuse/metadata@12.8.2':
    resolution: {integrity: sha512-rAyLGEuoBJ/Il5AmFHiziCPdQzRt88VxR+Y/A/QhJ1EWtWqPBBAxTAFaSkviwEuOEZNtW8pvkPgoCZQ+HxqW1A==}

  '@vueuse/shared@12.8.2':
    resolution: {integrity: sha512-dznP38YzxZoNloI0qpEfpkms8knDtaoQ6Y/sfS0L7Yki4zh40LFHEhur0odJC6xTHG5dxWVPiUWBXn+wCG2s5w==}

  algoliasearch@5.57.0:
    resolution: {integrity: sha512-HpND7MBGctOAkd1GoQoDZCGoCpqNTS5NG1LuhElFet3RdLJkwnyTYZXZhXwtpAQPrI36fqQ3eT6KQrdKDTKu3A==}
    engines: {node: '>= 14.0.0'}

  argparse@1.0.10:
    resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==}

  argparse@3.0.1:
    resolution: {integrity: sha512-nM4mHF/KM1v59ZNKX7zfusQz5wUAxR511YG8Vo6TyiV4aqhu++rbJW4v04xsWhpSsHFj66flT8P7znVpyO20xQ==}

  birpc@2.9.0:
    resolution: {integrity: sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==}

  braces@3.0.3:
    resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
    engines: {node: '>=8'}

  ccount@2.0.1:
    resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==}

  character-entities-html4@2.1.0:
    resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==}

  character-entities-legacy@3.0.0:
    resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==}

  comma-separated-tokens@2.0.3:
    resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==}

  commander@7.2.0:
    resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==}
    engines: {node: '>= 10'}

  commander@8.3.0:
    resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==}
    engines: {node: '>= 12'}

  copy-anything@4.1.0:
    resolution: {integrity: sha512-ufbM3smX/Jbnpk5wcQjzd1MgBpzmqfNETUAyZNrGwU9foRlyHoGzMMBBCRzEhQLBjZfFDE1W2ufPXX2vdWkV8Q==}
    engines: {node: '>=18'}

  cose-base@1.0.3:
    resolution: {integrity: sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==}

  cose-base@2.2.0:
    resolution: {integrity: sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==}

  cross-env@10.1.0:
    resolution: {integrity: sha512-GsYosgnACZTADcmEyJctkJIoqAhHjttw7RsFrVoJNXbsWWqaq6Ym+7kZjq6mS45O0jij6vtiReppKQEtqWy6Dw==}
    engines: {node: '>=20'}
    hasBin: true

  cross-spawn@7.0.6:
    resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
    engines: {node: '>= 8'}

  csstype@3.2.3:
    resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}

  cytoscape-cose-bilkent@4.1.0:
    resolution: {integrity: sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==}
    peerDependencies:
      cytoscape: ^3.2.0

  cytoscape-fcose@2.2.0:
    resolution: {integrity: sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==}
    peerDependencies:
      cytoscape: ^3.2.0

  cytoscape@3.34.2:
    resolution: {integrity: sha512-Cm2jaj1X/PBNlzV9yH8zcfGOxO7U+CJ/+mxSBVPSchLaugdp4jtlGx5qaHtPRZ6tgiZ5P+o1XoRfJA+ba6KM3g==}
    engines: {node: '>=0.10'}

  d3-array@2.12.1:
    resolution: {integrity: sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==}

  d3-array@3.2.4:
    resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==}
    engines: {node: '>=12'}

  d3-axis@3.0.0:
    resolution: {integrity: sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==}
    engines: {node: '>=12'}

  d3-brush@3.0.0:
    resolution: {integrity: sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==}
    engines: {node: '>=12'}

  d3-chord@3.0.1:
    resolution: {integrity: sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==}
    engines: {node: '>=12'}

  d3-color@3.1.0:
    resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==}
    engines: {node: '>=12'}

  d3-contour@4.0.2:
    resolution: {integrity: sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==}
    engines: {node: '>=12'}

  d3-delaunay@6.0.4:
    resolution: {integrity: sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==}
    engines: {node: '>=12'}

  d3-dispatch@3.0.1:
    resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==}
    engines: {node: '>=12'}

  d3-drag@3.0.0:
    resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==}
    engines: {node: '>=12'}

  d3-dsv@3.0.1:
    resolution: {integrity: sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==}
    engines: {node: '>=12'}
    hasBin: true

  d3-ease@3.0.1:
    resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==}
    engines: {node: '>=12'}

  d3-fetch@3.0.1:
    resolution: {integrity: sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==}
    engines: {node: '>=12'}

  d3-force@3.0.0:
    resolution: {integrity: sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==}
    engines: {node: '>=12'}

  d3-format@3.1.2:
    resolution: {integrity: sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==}
    engines: {node: '>=12'}

  d3-geo@3.1.1:
    resolution: {integrity: sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==}
    engines: {node: '>=12'}

  d3-hierarchy@3.1.2:
    resolution: {integrity: sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==}
    engines: {node: '>=12'}

  d3-interpolate@3.0.1:
    resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==}
    engines: {node: '>=12'}

  d3-path@1.0.9:
    resolution: {integrity: sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==}

  d3-path@3.1.0:
    resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==}
    engines: {node: '>=12'}

  d3-polygon@3.0.1:
    resolution: {integrity: sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==}
    engines: {node: '>=12'}

  d3-quadtree@3.0.1:
    resolution: {integrity: sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==}
    engines: {node: '>=12'}

  d3-random@3.0.1:
    resolution: {integrity: sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==}
    engines: {node: '>=12'}

  d3-sankey@0.12.3:
    resolution: {integrity: sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==}

  d3-scale-chromatic@3.1.0:
    resolution: {integrity: sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==}
    engines: {node: '>=12'}

  d3-scale@4.0.2:
    resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==}
    engines: {node: '>=12'}

  d3-selection@3.0.0:
    resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==}
    engines: {node: '>=12'}

  d3-shape@1.3.7:
    resolution: {integrity: sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==}

  d3-shape@3.2.0:
    resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==}
    engines: {node: '>=12'}

  d3-time-format@4.1.0:
    resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==}
    engines: {node: '>=12'}

  d3-time@3.1.0:
    resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==}
    engines: {node: '>=12'}

  d3-timer@3.0.1:
    resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==}
    engines: {node: '>=12'}

  d3-transition@3.0.1:
    resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==}
    engines: {node: '>=12'}
    peerDependencies:
      d3-selection: 2 - 3

  d3-zoom@3.0.0:
    resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==}
    engines: {node: '>=12'}

  d3@7.9.0:
    resolution: {integrity: sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==}
    engines: {node: '>=12'}

  dagre-d3-es@7.0.14:
    resolution: {integrity: sha512-P4rFMVq9ESWqmOgK+dlXvOtLwYg0i7u0HBGJER0LZDJT2VHIPAMZ/riPxqJceWMStH5+E61QxFra9kIS3AqdMg==}

  dayjs@1.11.23:
    resolution: {integrity: sha512-QDTCU0M0MxR3hQfnlDJfwekQiaanm1ubOD231u73WBckQ/fsamwRLiE2GBz6D3a/xF1NgfiDLJjXBa1hYOYTtQ==}

  delaunator@5.1.0:
    resolution: {integrity: sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==}

  dequal@2.0.3:
    resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
    engines: {node: '>=6'}

  devlop@1.1.0:
    resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==}

  dompurify@3.4.14:
    resolution: {integrity: sha512-dVoH9z+MY+C9IilgGCk3YfFqjLi3fChm2OiKJMzh6axrJ5qwxqWaZamgmHrpv22CN/KdbZJuGEGgfQoL00LTdg==}

  emoji-regex-xs@1.0.0:
    resolution: {integrity: sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==}

  entities@7.0.1:
    resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==}
    engines: {node: '>=0.12'}

  entities@8.0.0:
    resolution: {integrity: sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==}
    engines: {node: '>=20.19.0'}

  es-toolkit@1.51.0:
    resolution: {integrity: sha512-zC2lQGkM7QX+Gm6iM3+WIdZJzthsEd14LvRNJneSO2hzyz/zNBENR8+YXWo1cKxgPBtV6ksPYHELbcwBRzmdCw==}

  esbuild@0.25.12:
    resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==}
    engines: {node: '>=18'}
    hasBin: true

  esbuild@0.28.2:
    resolution: {integrity: sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==}
    engines: {node: '>=18'}
    hasBin: true

  esprima@4.0.1:
    resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==}
    engines: {node: '>=4'}
    hasBin: true

  estree-walker@2.0.2:
    resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}

  extend-shallow@2.0.1:
    resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==}
    engines: {node: '>=0.10.0'}

  fast-glob@3.3.3:
    resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==}
    engines: {node: '>=8.6.0'}

  fastdom@1.0.12:
    resolution: {integrity: sha512-LB+xjSTEbjHE1cWsxu+tN2Xqr1kpi+V9aADI7sVM5ZMaXyYGPHULQMzpJMYqOTULK/73pUkWVzzObFRBkPr+hg==}

  fastq@1.20.2:
    resolution: {integrity: sha512-UpGiiODyCGprM8EPP6JodP6jC9Rws6TCuiDOD+nn0CJhR8guI3g/ozo4ugL0vJ+Yz1UtJuuRPqvQuybVOF1VQA==}

  fdir@6.5.0:
    resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
    engines: {node: '>=12.0.0'}
    peerDependencies:
      picomatch: ^3 || ^4
    peerDependenciesMeta:
      picomatch:
        optional: true

  feed@6.0.0:
    resolution: {integrity: sha512-rvFOF1POce0ijAKy3tZN5T8PuprgIit/VbMyXJWcoYwcMOTSIJ7NqZb8DTLPJr77yiEO27kidm/fw0kcSIV+ww==}
    engines: {node: '>=20', pnpm: '>=10'}

  fill-range@7.1.1:
    resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
    engines: {node: '>=8'}

  focus-trap@7.8.0:
    resolution: {integrity: sha512-/yNdlIkpWbM0ptxno3ONTuf+2g318kh2ez3KSeZN5dZ8YC6AAmgeWz+GasYYiBJPFaYcSAPeu4GfhUaChzIJXA==}

  fs-extra@11.4.0:
    resolution: {integrity: sha512-EQsFzMUJkCKGr1ePqlYADkIUmHW1s3ZXr5Yqy6wbGrfUCphpl2maM/kyOIRA2HpP3AaFQTZXD4ldjek+nccddA==}
    engines: {node: '>=14.14'}

  fsevents@2.3.3:
    resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
    engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
    os: [darwin]

  giscus@1.6.0:
    resolution: {integrity: sha512-Zrsi8r4t1LVW950keaWcsURuZUQwUaMKjvJgTCY125vkW6OiEBkatE7ScJDbpqKHdZwb///7FVC21SE3iFK3PQ==}

  glob-parent@5.1.2:
    resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
    engines: {node: '>= 6'}

  globby@16.2.4:
    resolution: {integrity: sha512-c8B/VNLmxRcmqqenRA9t+9IyOjf9+V6lTxPaUJLqOCONdQkWZ0ETYgX0qbtJqPsgCNusT9MZ5Jeidw8Eb9tn2g==}
    engines: {node: '>=20'}

  graceful-fs@4.2.11:
    resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}

  gray-matter@4.0.3:
    resolution: {integrity: sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==}
    engines: {node: '>=6.0'}

  hachure-fill@0.5.2:
    resolution: {integrity: sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==}

  hast-util-to-html@9.0.5:
    resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==}

  hast-util-whitespace@3.0.0:
    resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==}

  hookable@5.5.3:
    resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==}

  html-void-elements@3.0.0:
    resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==}

  iconv-lite@0.6.3:
    resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
    engines: {node: '>=0.10.0'}

  ignore@7.0.6:
    resolution: {integrity: sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==}
    engines: {node: '>= 4'}

  import-meta-resolve@4.2.0:
    resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==}

  internmap@1.0.1:
    resolution: {integrity: sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==}

  internmap@2.0.3:
    resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==}
    engines: {node: '>=12'}

  is-extendable@0.1.1:
    resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==}
    engines: {node: '>=0.10.0'}

  is-extglob@2.1.1:
    resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
    engines: {node: '>=0.10.0'}

  is-glob@4.0.3:
    resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
    engines: {node: '>=0.10.0'}

  is-number@7.0.0:
    resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
    engines: {node: '>=0.12.0'}

  is-path-inside@4.0.0:
    resolution: {integrity: sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==}
    engines: {node: '>=12'}

  isexe@2.0.0:
    resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}

  js-yaml@3.15.2:
    resolution: {integrity: sha512-6EuL879VkRA+1Cz578mKMiKvjPNEuk6+r1JaFzoSWejZmtf7xWbIyw1e3KkxlkzTIt9Taw6JBhEppG7utc1P+w==}
    hasBin: true

  jsonfile@6.2.1:
    resolution: {integrity: sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==}

  katex@0.16.47:
    resolution: {integrity: sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg==}
    hasBin: true

  khroma@2.1.0:
    resolution: {integrity: sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==}

  kind-of@6.0.3:
    resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==}
    engines: {node: '>=0.10.0'}

  layout-base@1.0.2:
    resolution: {integrity: sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==}

  layout-base@2.0.1:
    resolution: {integrity: sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==}

  linkify-it@6.1.0:
    resolution: {integrity: sha512-wJ/TwpSDTLepCrQoYWYIExIKg5Zchex2Nn5yk2mFnB+6PtdkHtyLx742md9csRjjOnGkKIS/RrbY7l8D6gT9Vw==}

  lit-element@4.2.2:
    resolution: {integrity: sha512-aFKhNToWxoyhkNDmWZwEva2SlQia+jfG0fjIWV//YeTaWrVnOxD89dPKfigCUspXFmjzOEUQpOkejH5Ly6sG0w==}

  lit-html@3.3.3:
    resolution: {integrity: sha512-el8M6jK2o3RXBnrSHX3ZKrsN8zEV63pSExTO1wYJz7QndGYZ8353e2a5PPX+qHe2aGayfnchQmkAojaWAREOIA==}

  lit@3.3.3:
    resolution: {integrity: sha512-fycuvZg/hkpozL00lm1pEJH5nN/lr9ZXd6mJI2HSN4+Bzc+LDNdEApJ6HFbPkdFNHLvOplIIuJvxkS4XUxqirw==}

  lodash-es@4.18.1:
    resolution: {integrity: sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==}

  magic-string@0.30.21:
    resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}

  mark.js@8.11.1:
    resolution: {integrity: sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==}

  markdown-it-mathjax3@5.2.0:
    resolution: {integrity: sha512-R+XAy5/7vSGuhG9Z0/cJm6zKxOzStcScfSKVwoarh4nBra+v1KClvbALr/xFTEe9iQhwfQM4SJnO68LXL+btMA==}

  markdown-it@15.0.1:
    resolution: {integrity: sha512-9/7gE95FNPkfUWrjJIoHZza2iLmuJlPD0UNMxPi7bxUrbCR525YZY0r+zyfes0dZI5ZZ/uNIXUJca0pJvtw41g==}
    hasBin: true

  marked@16.4.2:
    resolution: {integrity: sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==}
    engines: {node: '>= 20'}
    hasBin: true

  mathxyjax3@0.8.3:
    resolution: {integrity: sha512-eXjFaiyQsTdVOeTFoFaFJ/r1FITpB1f9c5MW4FETfcoVV/+xa5SD9pS05AwugzL/gNuDtWXrTOSmoD2e0Du+UA==}

  mdast-util-to-hast@13.2.1:
    resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==}

  mdurl@2.1.0:
    resolution: {integrity: sha512-1+HBaOx0zi/dQWht8rNv9MYf9qqpqL/kxI0hXImU6Y547zM6Sni8BQibt7ifgMcYtQg41ao3Ivd6cnSM86inpg==}

  merge2@1.4.1:
    resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
    engines: {node: '>= 8'}

  mermaid@11.17.2:
    resolution: {integrity: sha512-V6K3C8EBdEsPFZXSKMJe6ppQOENxuHARr9GvHX4hh47lAbhMRD9qf4oEK7LoaRQxULMa80/qt5gHO73aCleBBg==}

  micromark-util-character@2.1.1:
    resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==}

  micromark-util-encode@2.0.1:
    resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==}

  micromark-util-sanitize-uri@2.0.1:
    resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==}

  micromark-util-symbol@2.0.1:
    resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==}

  micromark-util-types@2.0.2:
    resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==}

  micromatch@4.0.8:
    resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
    engines: {node: '>=8.6'}

  minisearch@7.2.0:
    resolution: {integrity: sha512-dqT2XBYUOZOiC5t2HRnwADjhNS2cecp9u+TJRiJ1Qp/f5qjkeT5APcGPjHw+bz89Ms8Jp+cG4AlE+QZ/QnDglg==}

  mitt@3.0.1:
    resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==}

  nanoid@3.3.18:
    resolution: {integrity: sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==}
    engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
    hasBin: true

  oniguruma-to-es@3.1.1:
    resolution: {integrity: sha512-bUH8SDvPkH3ho3dvwJwfonjlQ4R80vjyvrU8YpxuROddv55vAEJrTuCuCVUhhsHbtlD9tGGbaNApGQckXhS8iQ==}

  package-manager-detector@1.8.0:
    resolution: {integrity: sha512-yQA4H19AmPEoMUeavPMDIe1higySl/gH/yaQrkT/s07Qp+7pp2hYz30N3z2l5BkjVkF9Ow6o0wjJamm2y7Sn0A==}

  path-data-parser@0.1.0:
    resolution: {integrity: sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==}

  path-key@3.1.1:
    resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
    engines: {node: '>=8'}

  perfect-debounce@1.0.0:
    resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==}

  picocolors@1.1.1:
    resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}

  picomatch@2.3.2:
    resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==}
    engines: {node: '>=8.6'}

  picomatch@4.0.7:
    resolution: {integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==}
    engines: {node: '>=12'}

  points-on-curve@0.2.0:
    resolution: {integrity: sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==}

  points-on-path@0.2.1:
    resolution: {integrity: sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==}

  postcss@8.5.26:
    resolution: {integrity: sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==}
    engines: {node: ^10 || ^12 || >=14}

  preact@10.29.8:
    resolution: {integrity: sha512-ej2aVZ+vZ8WO7tvlQWRM9N63A0KzF9q4mWJfDUHgYaIofWY9hu74QdnQrjoPMmZi2/nZ5gN0bJCQF49xQqx09Q==}
    peerDependencies:
      preact-render-to-string: '>=5'
    peerDependenciesMeta:
      preact-render-to-string:
        optional: true

  property-information@7.2.0:
    resolution: {integrity: sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==}

  punycode.js@2.3.1:
    resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==}
    engines: {node: '>=6'}

  queue-microtask@1.2.3:
    resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}

  regex-recursion@6.0.2:
    resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==}

  regex-utilities@2.3.0:
    resolution: {integrity: sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==}

  regex@6.1.0:
    resolution: {integrity: sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==}

  reusify@1.1.0:
    resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
    engines: {iojs: '>=1.0.0', node: '>=0.10.0'}

  rfdc@1.4.1:
    resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==}

  robust-predicates@3.0.3:
    resolution: {integrity: sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==}

  rollup@4.63.0:
    resolution: {integrity: sha512-T5vnZ2y4QqC3/4P+w2+JO+Q/OVdnPsv4XcSYJYMEn0R9/jjl5AgLwO9LAZMzP2lN71O6pypn91rB7lDstUkfrQ==}
    engines: {node: '>=18.0.0', npm: '>=8.0.0'}
    hasBin: true

  roughjs@4.6.6:
    resolution: {integrity: sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==}

  run-parallel@1.2.0:
    resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}

  rw@1.3.3:
    resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==}

  safer-buffer@2.1.2:
    resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}

  sax@1.6.1:
    resolution: {integrity: sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==}
    engines: {node: '>=11.0.0'}

  search-insights@2.17.3:
    resolution: {integrity: sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==}

  section-matter@1.0.0:
    resolution: {integrity: sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==}
    engines: {node: '>=4'}

  shebang-command@2.0.0:
    resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
    engines: {node: '>=8'}

  shebang-regex@3.0.0:
    resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
    engines: {node: '>=8'}

  shiki@2.5.0:
    resolution: {integrity: sha512-mI//trrsaiCIPsja5CNfsyNOqgAZUb6VpJA+340toL42UpzQlXpwRV9nch69X6gaUxrr9kaOOa6e3y3uAkGFxQ==}

  slash@5.1.0:
    resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==}
    engines: {node: '>=14.16'}

  source-map-js@1.2.1:
    resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
    engines: {node: '>=0.10.0'}

  space-separated-tokens@2.0.2:
    resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==}

  speakingurl@14.0.1:
    resolution: {integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==}
    engines: {node: '>=0.10.0'}

  sprintf-js@1.0.3:
    resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==}

  strictdom@1.0.1:
    resolution: {integrity: sha512-cEmp9QeXXRmjj/rVp9oyiqcvyocWab/HaoN4+bwFeZ7QzykJD6L3yD4v12K1x0tHpqRqVpJevN3gW7kyM39Bqg==}

  stringify-entities@4.0.4:
    resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==}

  strip-bom-string@1.0.0:
    resolution: {integrity: sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==}
    engines: {node: '>=0.10.0'}

  stylis@4.4.0:
    resolution: {integrity: sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==}

  superjson@2.2.6:
    resolution: {integrity: sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==}
    engines: {node: '>=16'}

  tabbable@6.5.0:
    resolution: {integrity: sha512-wieBHXygIm7OyQOu5hQlkk62/WyCFYGlWg7L6/ZCUZwx0o398Zkn4pVmMyfYhfMG8kGrj/Krt8eIk6UKC6VzwA==}

  tinyexec@1.3.0:
    resolution: {integrity: sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==}
    engines: {node: '>=18'}

  tinyglobby@0.2.17:
    resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==}
    engines: {node: '>=12.0.0'}

  to-regex-range@5.0.1:
    resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
    engines: {node: '>=8.0'}

  trim-lines@3.0.1:
    resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==}

  ts-dedent@2.3.0:
    resolution: {integrity: sha512-JfJeIHke7y2egdGGgRAvpCwYFUsHlM2gPcrVOxFkznt/4uzQ7HFmvE63iFHVLBJNDuyDOQgijDK/tXH/f6Msjg==}
    engines: {node: '>=6.10'}

  type-fest@4.41.0:
    resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==}
    engines: {node: '>=16'}

  uc.micro@3.0.0:
    resolution: {integrity: sha512-U3PppEkleoTnIfi8BozMx3yju3qc/L6SwqWo2Sw+54PX+PX0q9I+r1Um5HCmqD7n9VDX5/v3vQH/AjA6deDdtw==}

  undici-types@8.3.0:
    resolution: {integrity: sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==}

  unicorn-magic@0.4.0:
    resolution: {integrity: sha512-wH590V9VNgYH9g3lH9wWjTrUoKsjLF6sGLjhR4sH1LWpLmCOH0Zf7PukhDA8BiS7KHe4oPNkcTHqYkj7SOGUOw==}
    engines: {node: '>=20'}

  unist-util-is@6.0.1:
    resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==}

  unist-util-position@5.0.0:
    resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==}

  unist-util-stringify-position@4.0.0:
    resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==}

  unist-util-visit-parents@6.0.2:
    resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==}

  unist-util-visit@5.1.0:
    resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==}

  universalify@2.0.1:
    resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
    engines: {node: '>= 10.0.0'}

  uuid@14.0.2:
    resolution: {integrity: sha512-xZe/16rV4aa+HGSOCiY2YeLT1OybRLrrkL/Rqaq7p7GMVXjFh+6wN4oMYgjFmnSnhY8t6Xpdl2l9qmnHYuMHwQ==}
    hasBin: true

  vfile-message@4.0.3:
    resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==}

  vfile@6.0.3:
    resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==}

  vite@6.4.3:
    resolution: {integrity: sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==}
    engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
    hasBin: true
    peerDependencies:
      '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0
      jiti: '>=1.21.0'
      less: '*'
      lightningcss: ^1.21.0
      sass: '*'
      sass-embedded: '*'
      stylus: '*'
      sugarss: '*'
      terser: ^5.16.0
      tsx: ^4.8.1
      yaml: ^2.4.2
    peerDependenciesMeta:
      '@types/node':
        optional: true
      jiti:
        optional: true
      less:
        optional: true
      lightningcss:
        optional: true
      sass:
        optional: true
      sass-embedded:
        optional: true
      stylus:
        optional: true
      sugarss:
        optional: true
      terser:
        optional: true
      tsx:
        optional: true
      yaml:
        optional: true

  vitepress@1.6.4:
    resolution: {integrity: sha512-+2ym1/+0VVrbhNyRoFFesVvBvHAVMZMK0rw60E3X/5349M1GuVdKeazuksqopEdvkKwKGs21Q729jX81/bkBJg==}
    hasBin: true
    peerDependencies:
      markdown-it-mathjax3: ^4
      postcss: ^8
    peerDependenciesMeta:
      markdown-it-mathjax3:
        optional: true
      postcss:
        optional: true

  vue@3.5.42:
    resolution: {integrity: sha512-4RyHQTbQvOPs3MfvUO1Sg0YRrKNnA0mAVtvpd12Tg1fKDN7OHBUl1IqSn8zGJjK9nI3NkNp8cgTpVrSZC5TTcA==}
    peerDependencies:
      typescript: '*'
    peerDependenciesMeta:
      typescript:
        optional: true

  which@2.0.2:
    resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
    engines: {node: '>= 8'}
    hasBin: true

  xml-js@1.6.11:
    resolution: {integrity: sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==}
    hasBin: true

  zwitch@2.0.4:
    resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}

snapshots:

  '@algolia/abtesting@1.23.0':
    dependencies:
      '@algolia/client-common': 5.57.0
      '@algolia/requester-browser-xhr': 5.57.0
      '@algolia/requester-fetch': 5.57.0
      '@algolia/requester-node-http': 5.57.0

  '@algolia/autocomplete-core@1.17.7(@algolia/client-search@5.57.0)(algoliasearch@5.57.0)(search-insights@2.17.3)':
    dependencies:
      '@algolia/autocomplete-plugin-algolia-insights': 1.17.7(@algolia/client-search@5.57.0)(algoliasearch@5.57.0)(search-insights@2.17.3)
      '@algolia/autocomplete-shared': 1.17.7(@algolia/client-search@5.57.0)(algoliasearch@5.57.0)
    transitivePeerDependencies:
      - '@algolia/client-search'
      - algoliasearch
      - search-insights

  '@algolia/autocomplete-plugin-algolia-insights@1.17.7(@algolia/client-search@5.57.0)(algoliasearch@5.57.0)(search-insights@2.17.3)':
    dependencies:
      '@algolia/autocomplete-shared': 1.17.7(@algolia/client-search@5.57.0)(algoliasearch@5.57.0)
      search-insights: 2.17.3
    transitivePeerDependencies:
      - '@algolia/client-search'
      - algoliasearch

  '@algolia/autocomplete-preset-algolia@1.17.7(@algolia/client-search@5.57.0)(algoliasearch@5.57.0)':
    dependencies:
      '@algolia/autocomplete-shared': 1.17.7(@algolia/client-search@5.57.0)(algoliasearch@5.57.0)
      '@algolia/client-search': 5.57.0
      algoliasearch: 5.57.0

  '@algolia/autocomplete-shared@1.17.7(@algolia/client-search@5.57.0)(algoliasearch@5.57.0)':
    dependencies:
      '@algolia/client-search': 5.57.0
      algoliasearch: 5.57.0

  '@algolia/client-abtesting@5.57.0':
    dependencies:
      '@algolia/client-common': 5.57.0
      '@algolia/requester-browser-xhr': 5.57.0
      '@algolia/requester-fetch': 5.57.0
      '@algolia/requester-node-http': 5.57.0

  '@algolia/client-analytics@5.57.0':
    dependencies:
      '@algolia/client-common': 5.57.0
      '@algolia/requester-browser-xhr': 5.57.0
      '@algolia/requester-fetch': 5.57.0
      '@algolia/requester-node-http': 5.57.0

  '@algolia/client-common@5.57.0': {}

  '@algolia/client-insights@5.57.0':
    dependencies:
      '@algolia/client-common': 5.57.0
      '@algolia/requester-browser-xhr': 5.57.0
      '@algolia/requester-fetch': 5.57.0
      '@algolia/requester-node-http': 5.57.0

  '@algolia/client-personalization@5.57.0':
    dependencies:
      '@algolia/client-common': 5.57.0
      '@algolia/requester-browser-xhr': 5.57.0
      '@algolia/requester-fetch': 5.57.0
      '@algolia/requester-node-http': 5.57.0

  '@algolia/client-query-suggestions@5.57.0':
    dependencies:
      '@algolia/client-common': 5.57.0
      '@algolia/requester-browser-xhr': 5.57.0
      '@algolia/requester-fetch': 5.57.0
      '@algolia/requester-node-http': 5.57.0

  '@algolia/client-search@5.57.0':
    dependencies:
      '@algolia/client-common': 5.57.0
      '@algolia/requester-browser-xhr': 5.57.0
      '@algolia/requester-fetch': 5.57.0
      '@algolia/requester-node-http': 5.57.0

  '@algolia/ingestion@1.57.0':
    dependencies:
      '@algolia/client-common': 5.57.0
      '@algolia/requester-browser-xhr': 5.57.0
      '@algolia/requester-fetch': 5.57.0
      '@algolia/requester-node-http': 5.57.0

  '@algolia/monitoring@1.57.0':
    dependencies:
      '@algolia/client-common': 5.57.0
      '@algolia/requester-browser-xhr': 5.57.0
      '@algolia/requester-fetch': 5.57.0
      '@algolia/requester-node-http': 5.57.0

  '@algolia/recommend@5.57.0':
    dependencies:
      '@algolia/client-common': 5.57.0
      '@algolia/requester-browser-xhr': 5.57.0
      '@algolia/requester-fetch': 5.57.0
      '@algolia/requester-node-http': 5.57.0

  '@algolia/requester-browser-xhr@5.57.0':
    dependencies:
      '@algolia/client-common': 5.57.0

  '@algolia/requester-fetch@5.57.0':
    dependencies:
      '@algolia/client-common': 5.57.0

  '@algolia/requester-node-http@5.57.0':
    dependencies:
      '@algolia/client-common': 5.57.0

  '@antfu/install-pkg@1.1.0':
    dependencies:
      package-manager-detector: 1.8.0
      tinyexec: 1.3.0

  '@babel/helper-string-parser@7.29.7': {}

  '@babel/helper-validator-identifier@7.29.7': {}

  '@babel/parser@7.29.8':
    dependencies:
      '@babel/types': 7.29.8

  '@babel/types@7.29.8':
    dependencies:
      '@babel/helper-string-parser': 7.29.7
      '@babel/helper-validator-identifier': 7.29.7

  '@braintree/sanitize-url@7.1.2': {}

  '@chevrotain/types@11.1.2': {}

  '@docsearch/css@3.8.2': {}

  '@docsearch/js@3.8.2(@algolia/client-search@5.57.0)(search-insights@2.17.3)':
    dependencies:
      '@docsearch/react': 3.8.2(@algolia/client-search@5.57.0)(search-insights@2.17.3)
      preact: 10.29.8
    transitivePeerDependencies:
      - '@algolia/client-search'
      - '@types/react'
      - preact-render-to-string
      - react
      - react-dom
      - search-insights

  '@docsearch/react@3.8.2(@algolia/client-search@5.57.0)(search-insights@2.17.3)':
    dependencies:
      '@algolia/autocomplete-core': 1.17.7(@algolia/client-search@5.57.0)(algoliasearch@5.57.0)(search-insights@2.17.3)
      '@algolia/autocomplete-preset-algolia': 1.17.7(@algolia/client-search@5.57.0)(algoliasearch@5.57.0)
      '@docsearch/css': 3.8.2
      algoliasearch: 5.57.0
    optionalDependencies:
      search-insights: 2.17.3
    transitivePeerDependencies:
      - '@algolia/client-search'

  '@epic-web/invariant@1.0.0': {}

  '@esbuild/aix-ppc64@0.25.12':
    optional: true

  '@esbuild/aix-ppc64@0.28.2':
    optional: true

  '@esbuild/android-arm64@0.25.12':
    optional: true

  '@esbuild/android-arm64@0.28.2':
    optional: true

  '@esbuild/android-arm@0.25.12':
    optional: true

  '@esbuild/android-arm@0.28.2':
    optional: true

  '@esbuild/android-x64@0.25.12':
    optional: true

  '@esbuild/android-x64@0.28.2':
    optional: true

  '@esbuild/darwin-arm64@0.25.12':
    optional: true

  '@esbuild/darwin-arm64@0.28.2':
    optional: true

  '@esbuild/darwin-x64@0.25.12':
    optional: true

  '@esbuild/darwin-x64@0.28.2':
    optional: true

  '@esbuild/freebsd-arm64@0.25.12':
    optional: true

  '@esbuild/freebsd-arm64@0.28.2':
    optional: true

  '@esbuild/freebsd-x64@0.25.12':
    optional: true

  '@esbuild/freebsd-x64@0.28.2':
    optional: true

  '@esbuild/linux-arm64@0.25.12':
    optional: true

  '@esbuild/linux-arm64@0.28.2':
    optional: true

  '@esbuild/linux-arm@0.25.12':
    optional: true

  '@esbuild/linux-arm@0.28.2':
    optional: true

  '@esbuild/linux-ia32@0.25.12':
    optional: true

  '@esbuild/linux-ia32@0.28.2':
    optional: true

  '@esbuild/linux-loong64@0.25.12':
    optional: true

  '@esbuild/linux-loong64@0.28.2':
    optional: true

  '@esbuild/linux-mips64el@0.25.12':
    optional: true

  '@esbuild/linux-mips64el@0.28.2':
    optional: true

  '@esbuild/linux-ppc64@0.25.12':
    optional: true

  '@esbuild/linux-ppc64@0.28.2':
    optional: true

  '@esbuild/linux-riscv64@0.25.12':
    optional: true

  '@esbuild/linux-riscv64@0.28.2':
    optional: true

  '@esbuild/linux-s390x@0.25.12':
    optional: true

  '@esbuild/linux-s390x@0.28.2':
    optional: true

  '@esbuild/linux-x64@0.25.12':
    optional: true

  '@esbuild/linux-x64@0.28.2':
    optional: true

  '@esbuild/netbsd-arm64@0.25.12':
    optional: true

  '@esbuild/netbsd-arm64@0.28.2':
    optional: true

  '@esbuild/netbsd-x64@0.25.12':
    optional: true

  '@esbuild/netbsd-x64@0.28.2':
    optional: true

  '@esbuild/openbsd-arm64@0.25.12':
    optional: true

  '@esbuild/openbsd-arm64@0.28.2':
    optional: true

  '@esbuild/openbsd-x64@0.25.12':
    optional: true

  '@esbuild/openbsd-x64@0.28.2':
    optional: true

  '@esbuild/openharmony-arm64@0.25.12':
    optional: true

  '@esbuild/openharmony-arm64@0.28.2':
    optional: true

  '@esbuild/sunos-x64@0.25.12':
    optional: true

  '@esbuild/sunos-x64@0.28.2':
    optional: true

  '@esbuild/win32-arm64@0.25.12':
    optional: true

  '@esbuild/win32-arm64@0.28.2':
    optional: true

  '@esbuild/win32-ia32@0.25.12':
    optional: true

  '@esbuild/win32-ia32@0.28.2':
    optional: true

  '@esbuild/win32-x64@0.25.12':
    optional: true

  '@esbuild/win32-x64@0.28.2':
    optional: true

  '@giscus/vue@3.1.1(vue@3.5.42)':
    dependencies:
      giscus: 1.6.0
      vue: 3.5.42

  '@iconify-json/simple-icons@1.2.93':
    dependencies:
      '@iconify/types': 2.0.0

  '@iconify/types@2.0.0': {}

  '@iconify/utils@3.1.4':
    dependencies:
      '@antfu/install-pkg': 1.1.0
      '@iconify/types': 2.0.0
      import-meta-resolve: 4.2.0

  '@jridgewell/sourcemap-codec@1.6.0': {}

  '@lit-labs/ssr-dom-shim@1.6.0': {}

  '@lit/reactive-element@2.1.2':
    dependencies:
      '@lit-labs/ssr-dom-shim': 1.6.0

  '@mermaid-js/parser@1.2.1':
    dependencies:
      '@chevrotain/types': 11.1.2

  '@napi-rs/lzma-linux-x64-gnu@1.5.1':
    optional: true

  '@nodelib/fs.scandir@2.1.5':
    dependencies:
      '@nodelib/fs.stat': 2.0.5
      run-parallel: 1.2.0

  '@nodelib/fs.stat@2.0.5': {}

  '@nodelib/fs.walk@1.2.8':
    dependencies:
      '@nodelib/fs.scandir': 2.1.5
      fastq: 1.20.2

  '@rollup/rollup-android-arm-eabi@4.63.0':
    optional: true

  '@rollup/rollup-android-arm64@4.63.0':
    optional: true

  '@rollup/rollup-darwin-arm64@4.63.0':
    optional: true

  '@rollup/rollup-darwin-x64@4.63.0':
    optional: true

  '@rollup/rollup-freebsd-arm64@4.63.0':
    optional: true

  '@rollup/rollup-freebsd-x64@4.63.0':
    optional: true

  '@rollup/rollup-linux-arm-gnueabihf@4.63.0':
    optional: true

  '@rollup/rollup-linux-arm-musleabihf@4.63.0':
    optional: true

  '@rollup/rollup-linux-arm64-gnu@4.63.0':
    optional: true

  '@rollup/rollup-linux-arm64-musl@4.63.0':
    optional: true

  '@rollup/rollup-linux-loong64-gnu@4.63.0':
    optional: true

  '@rollup/rollup-linux-loong64-musl@4.63.0':
    optional: true

  '@rollup/rollup-linux-ppc64-gnu@4.63.0':
    optional: true

  '@rollup/rollup-linux-ppc64-musl@4.63.0':
    optional: true

  '@rollup/rollup-linux-riscv64-gnu@4.63.0':
    optional: true

  '@rollup/rollup-linux-riscv64-musl@4.63.0':
    optional: true

  '@rollup/rollup-linux-s390x-gnu@4.63.0':
    optional: true

  '@rollup/rollup-linux-x64-gnu@4.63.0':
    optional: true

  '@rollup/rollup-linux-x64-musl@4.63.0':
    optional: true

  '@rollup/rollup-openbsd-x64@4.63.0':
    optional: true

  '@rollup/rollup-openharmony-arm64@4.63.0':
    optional: true

  '@rollup/rollup-win32-arm64-msvc@4.63.0':
    optional: true

  '@rollup/rollup-win32-ia32-msvc@4.63.0':
    optional: true

  '@rollup/rollup-win32-x64-gnu@4.63.0':
    optional: true

  '@rollup/rollup-win32-x64-msvc@4.63.0':
    optional: true

  '@se-oss/deasync-darwin-arm64@1.0.1':
    optional: true

  '@se-oss/deasync-darwin-x64@1.0.1':
    optional: true

  '@se-oss/deasync-linux-arm64-gnu@1.0.1':
    optional: true

  '@se-oss/deasync-linux-arm64-musl@1.0.1':
    optional: true

  '@se-oss/deasync-linux-x64-gnu@1.0.1':
    optional: true

  '@se-oss/deasync-linux-x64-musl@1.0.1':
    optional: true

  '@se-oss/deasync-win32-arm64-msvc@1.0.1':
    optional: true

  '@se-oss/deasync-win32-x64-msvc@1.0.1':
    optional: true

  '@se-oss/deasync@1.0.1':
    dependencies:
      type-fest: 4.41.0
    optionalDependencies:
      '@se-oss/deasync-darwin-arm64': 1.0.1
      '@se-oss/deasync-darwin-x64': 1.0.1
      '@se-oss/deasync-linux-arm64-gnu': 1.0.1
      '@se-oss/deasync-linux-arm64-musl': 1.0.1
      '@se-oss/deasync-linux-x64-gnu': 1.0.1
      '@se-oss/deasync-linux-x64-musl': 1.0.1
      '@se-oss/deasync-win32-arm64-msvc': 1.0.1
      '@se-oss/deasync-win32-x64-msvc': 1.0.1

  '@shikijs/core@2.5.0':
    dependencies:
      '@shikijs/engine-javascript': 2.5.0
      '@shikijs/engine-oniguruma': 2.5.0
      '@shikijs/types': 2.5.0
      '@shikijs/vscode-textmate': 10.0.2
      '@types/hast': 3.0.5
      hast-util-to-html: 9.0.5

  '@shikijs/engine-javascript@2.5.0':
    dependencies:
      '@shikijs/types': 2.5.0
      '@shikijs/vscode-textmate': 10.0.2
      oniguruma-to-es: 3.1.1

  '@shikijs/engine-oniguruma@2.5.0':
    dependencies:
      '@shikijs/types': 2.5.0
      '@shikijs/vscode-textmate': 10.0.2

  '@shikijs/langs@2.5.0':
    dependencies:
      '@shikijs/types': 2.5.0

  '@shikijs/themes@2.5.0':
    dependencies:
      '@shikijs/types': 2.5.0

  '@shikijs/transformers@2.5.0':
    dependencies:
      '@shikijs/core': 2.5.0
      '@shikijs/types': 2.5.0

  '@shikijs/types@2.5.0':
    dependencies:
      '@shikijs/vscode-textmate': 10.0.2
      '@types/hast': 3.0.5

  '@shikijs/vscode-textmate@10.0.2': {}

  '@sindresorhus/merge-streams@4.0.0': {}

  '@types/d3-array@3.2.2': {}

  '@types/d3-axis@3.0.6':
    dependencies:
      '@types/d3-selection': 3.0.11

  '@types/d3-brush@3.0.6':
    dependencies:
      '@types/d3-selection': 3.0.11

  '@types/d3-chord@3.0.6': {}

  '@types/d3-color@3.1.3': {}

  '@types/d3-contour@3.0.6':
    dependencies:
      '@types/d3-array': 3.2.2
      '@types/geojson': 7946.0.16

  '@types/d3-delaunay@6.0.4': {}

  '@types/d3-dispatch@3.0.7': {}

  '@types/d3-drag@3.0.7':
    dependencies:
      '@types/d3-selection': 3.0.11

  '@types/d3-dsv@3.0.7': {}

  '@types/d3-ease@3.0.2': {}

  '@types/d3-fetch@3.0.7':
    dependencies:
      '@types/d3-dsv': 3.0.7

  '@types/d3-force@3.0.10': {}

  '@types/d3-format@3.0.4': {}

  '@types/d3-geo@3.1.1':
    dependencies:
      '@types/geojson': 7946.0.16

  '@types/d3-hierarchy@3.1.7': {}

  '@types/d3-interpolate@3.0.4':
    dependencies:
      '@types/d3-color': 3.1.3

  '@types/d3-path@3.1.1': {}

  '@types/d3-polygon@3.0.2': {}

  '@types/d3-quadtree@3.0.6': {}

  '@types/d3-random@3.0.4': {}

  '@types/d3-scale-chromatic@3.1.0': {}

  '@types/d3-scale@4.0.9':
    dependencies:
      '@types/d3-time': 3.0.4

  '@types/d3-selection@3.0.11': {}

  '@types/d3-shape@3.2.0':
    dependencies:
      '@types/d3-path': 3.1.1

  '@types/d3-time-format@4.0.3': {}

  '@types/d3-time@3.0.4': {}

  '@types/d3-timer@3.0.2': {}

  '@types/d3-transition@3.0.9':
    dependencies:
      '@types/d3-selection': 3.0.11

  '@types/d3-zoom@3.0.8':
    dependencies:
      '@types/d3-interpolate': 3.0.4
      '@types/d3-selection': 3.0.11

  '@types/d3@7.4.3':
    dependencies:
      '@types/d3-array': 3.2.2
      '@types/d3-axis': 3.0.6
      '@types/d3-brush': 3.0.6
      '@types/d3-chord': 3.0.6
      '@types/d3-color': 3.1.3
      '@types/d3-contour': 3.0.6
      '@types/d3-delaunay': 6.0.4
      '@types/d3-dispatch': 3.0.7
      '@types/d3-drag': 3.0.7
      '@types/d3-dsv': 3.0.7
      '@types/d3-ease': 3.0.2
      '@types/d3-fetch': 3.0.7
      '@types/d3-force': 3.0.10
      '@types/d3-format': 3.0.4
      '@types/d3-geo': 3.1.1
      '@types/d3-hierarchy': 3.1.7
      '@types/d3-interpolate': 3.0.4
      '@types/d3-path': 3.1.1
      '@types/d3-polygon': 3.0.2
      '@types/d3-quadtree': 3.0.6
      '@types/d3-random': 3.0.4
      '@types/d3-scale': 4.0.9
      '@types/d3-scale-chromatic': 3.1.0
      '@types/d3-selection': 3.0.11
      '@types/d3-shape': 3.2.0
      '@types/d3-time': 3.0.4
      '@types/d3-time-format': 4.0.3
      '@types/d3-timer': 3.0.2
      '@types/d3-transition': 3.0.9
      '@types/d3-zoom': 3.0.8

  '@types/estree@1.0.9': {}

  '@types/geojson@7946.0.16': {}

  '@types/hast@3.0.5':
    dependencies:
      '@types/unist': 3.0.3

  '@types/linkify-it@5.0.0': {}

  '@types/markdown-it@14.2.0':
    dependencies:
      '@types/linkify-it': 5.0.0
      '@types/mdurl': 2.0.0

  '@types/mdast@4.0.4':
    dependencies:
      '@types/unist': 3.0.3

  '@types/mdurl@2.0.0': {}

  '@types/node@26.4.0':
    dependencies:
      undici-types: 8.3.0

  '@types/trusted-types@2.0.7': {}

  '@types/unist@3.0.3': {}

  '@types/web-bluetooth@0.0.21': {}

  '@ungap/structured-clone@1.4.0': {}

  '@upsetjs/venn.js@2.0.0':
    optionalDependencies:
      d3-selection: 3.0.0
      d3-transition: 3.0.1(d3-selection@3.0.0)

  '@vitejs/plugin-vue@5.2.4(vite@6.4.3(@types/node@26.4.0))(vue@3.5.42)':
    dependencies:
      vite: 6.4.3(@types/node@26.4.0)
      vue: 3.5.42

  '@vue/compiler-core@3.5.42':
    dependencies:
      '@babel/parser': 7.29.8
      '@vue/shared': 3.5.42
      entities: 7.0.1
      estree-walker: 2.0.2
      source-map-js: 1.2.1

  '@vue/compiler-dom@3.5.42':
    dependencies:
      '@vue/compiler-core': 3.5.42
      '@vue/shared': 3.5.42

  '@vue/compiler-sfc@3.5.42':
    dependencies:
      '@babel/parser': 7.29.8
      '@vue/compiler-core': 3.5.42
      '@vue/compiler-dom': 3.5.42
      '@vue/compiler-ssr': 3.5.42
      '@vue/shared': 3.5.42
      estree-walker: 2.0.2
      magic-string: 0.30.21
      postcss: 8.5.26
      source-map-js: 1.2.1

  '@vue/compiler-ssr@3.5.42':
    dependencies:
      '@vue/compiler-dom': 3.5.42
      '@vue/shared': 3.5.42

  '@vue/devtools-api@7.7.10':
    dependencies:
      '@vue/devtools-kit': 7.7.10

  '@vue/devtools-kit@7.7.10':
    dependencies:
      '@vue/devtools-shared': 7.7.10
      birpc: 2.9.0
      hookable: 5.5.3
      mitt: 3.0.1
      perfect-debounce: 1.0.0
      speakingurl: 14.0.1
      superjson: 2.2.6

  '@vue/devtools-shared@7.7.10':
    dependencies:
      rfdc: 1.4.1

  '@vue/reactivity@3.5.42':
    dependencies:
      '@vue/shared': 3.5.42

  '@vue/runtime-core@3.5.42':
    dependencies:
      '@vue/reactivity': 3.5.42
      '@vue/shared': 3.5.42

  '@vue/runtime-dom@3.5.42':
    dependencies:
      '@vue/reactivity': 3.5.42
      '@vue/runtime-core': 3.5.42
      '@vue/shared': 3.5.42
      csstype: 3.2.3

  '@vue/server-renderer@3.5.42':
    dependencies:
      '@vue/compiler-ssr': 3.5.42
      '@vue/runtime-dom': 3.5.42
      '@vue/shared': 3.5.42

  '@vue/shared@3.5.42': {}

  '@vueuse/core@12.8.2':
    dependencies:
      '@types/web-bluetooth': 0.0.21
      '@vueuse/metadata': 12.8.2
      '@vueuse/shared': 12.8.2
      vue: 3.5.42
    transitivePeerDependencies:
      - typescript

  '@vueuse/integrations@12.8.2(focus-trap@7.8.0)':
    dependencies:
      '@vueuse/core': 12.8.2
      '@vueuse/shared': 12.8.2
      vue: 3.5.42
    optionalDependencies:
      focus-trap: 7.8.0
    transitivePeerDependencies:
      - typescript

  '@vueuse/metadata@12.8.2': {}

  '@vueuse/shared@12.8.2':
    dependencies:
      vue: 3.5.42
    transitivePeerDependencies:
      - typescript

  algoliasearch@5.57.0:
    dependencies:
      '@algolia/abtesting': 1.23.0
      '@algolia/client-abtesting': 5.57.0
      '@algolia/client-analytics': 5.57.0
      '@algolia/client-common': 5.57.0
      '@algolia/client-insights': 5.57.0
      '@algolia/client-personalization': 5.57.0
      '@algolia/client-query-suggestions': 5.57.0
      '@algolia/client-search': 5.57.0
      '@algolia/ingestion': 1.57.0
      '@algolia/monitoring': 1.57.0
      '@algolia/recommend': 5.57.0
      '@algolia/requester-browser-xhr': 5.57.0
      '@algolia/requester-fetch': 5.57.0
      '@algolia/requester-node-http': 5.57.0

  argparse@1.0.10:
    dependencies:
      sprintf-js: 1.0.3

  argparse@3.0.1: {}

  birpc@2.9.0: {}

  braces@3.0.3:
    dependencies:
      fill-range: 7.1.1

  ccount@2.0.1: {}

  character-entities-html4@2.1.0: {}

  character-entities-legacy@3.0.0: {}

  comma-separated-tokens@2.0.3: {}

  commander@7.2.0: {}

  commander@8.3.0: {}

  copy-anything@4.1.0: {}

  cose-base@1.0.3:
    dependencies:
      layout-base: 1.0.2

  cose-base@2.2.0:
    dependencies:
      layout-base: 2.0.1

  cross-env@10.1.0:
    dependencies:
      '@epic-web/invariant': 1.0.0
      cross-spawn: 7.0.6

  cross-spawn@7.0.6:
    dependencies:
      path-key: 3.1.1
      shebang-command: 2.0.0
      which: 2.0.2

  csstype@3.2.3: {}

  cytoscape-cose-bilkent@4.1.0(cytoscape@3.34.2):
    dependencies:
      cose-base: 1.0.3
      cytoscape: 3.34.2

  cytoscape-fcose@2.2.0(cytoscape@3.34.2):
    dependencies:
      cose-base: 2.2.0
      cytoscape: 3.34.2

  cytoscape@3.34.2: {}

  d3-array@2.12.1:
    dependencies:
      internmap: 1.0.1

  d3-array@3.2.4:
    dependencies:
      internmap: 2.0.3

  d3-axis@3.0.0: {}

  d3-brush@3.0.0:
    dependencies:
      d3-dispatch: 3.0.1
      d3-drag: 3.0.0
      d3-interpolate: 3.0.1
      d3-selection: 3.0.0
      d3-transition: 3.0.1(d3-selection@3.0.0)

  d3-chord@3.0.1:
    dependencies:
      d3-path: 3.1.0

  d3-color@3.1.0: {}

  d3-contour@4.0.2:
    dependencies:
      d3-array: 3.2.4

  d3-delaunay@6.0.4:
    dependencies:
      delaunator: 5.1.0

  d3-dispatch@3.0.1: {}

  d3-drag@3.0.0:
    dependencies:
      d3-dispatch: 3.0.1
      d3-selection: 3.0.0

  d3-dsv@3.0.1:
    dependencies:
      commander: 7.2.0
      iconv-lite: 0.6.3
      rw: 1.3.3

  d3-ease@3.0.1: {}

  d3-fetch@3.0.1:
    dependencies:
      d3-dsv: 3.0.1

  d3-force@3.0.0:
    dependencies:
      d3-dispatch: 3.0.1
      d3-quadtree: 3.0.1
      d3-timer: 3.0.1

  d3-format@3.1.2: {}

  d3-geo@3.1.1:
    dependencies:
      d3-array: 3.2.4

  d3-hierarchy@3.1.2: {}

  d3-interpolate@3.0.1:
    dependencies:
      d3-color: 3.1.0

  d3-path@1.0.9: {}

  d3-path@3.1.0: {}

  d3-polygon@3.0.1: {}

  d3-quadtree@3.0.1: {}

  d3-random@3.0.1: {}

  d3-sankey@0.12.3:
    dependencies:
      d3-array: 2.12.1
      d3-shape: 1.3.7

  d3-scale-chromatic@3.1.0:
    dependencies:
      d3-color: 3.1.0
      d3-interpolate: 3.0.1

  d3-scale@4.0.2:
    dependencies:
      d3-array: 3.2.4
      d3-format: 3.1.2
      d3-interpolate: 3.0.1
      d3-time: 3.1.0
      d3-time-format: 4.1.0

  d3-selection@3.0.0: {}

  d3-shape@1.3.7:
    dependencies:
      d3-path: 1.0.9

  d3-shape@3.2.0:
    dependencies:
      d3-path: 3.1.0

  d3-time-format@4.1.0:
    dependencies:
      d3-time: 3.1.0

  d3-time@3.1.0:
    dependencies:
      d3-array: 3.2.4

  d3-timer@3.0.1: {}

  d3-transition@3.0.1(d3-selection@3.0.0):
    dependencies:
      d3-color: 3.1.0
      d3-dispatch: 3.0.1
      d3-ease: 3.0.1
      d3-interpolate: 3.0.1
      d3-selection: 3.0.0
      d3-timer: 3.0.1

  d3-zoom@3.0.0:
    dependencies:
      d3-dispatch: 3.0.1
      d3-drag: 3.0.0
      d3-interpolate: 3.0.1
      d3-selection: 3.0.0
      d3-transition: 3.0.1(d3-selection@3.0.0)

  d3@7.9.0:
    dependencies:
      d3-array: 3.2.4
      d3-axis: 3.0.0
      d3-brush: 3.0.0
      d3-chord: 3.0.1
      d3-color: 3.1.0
      d3-contour: 4.0.2
      d3-delaunay: 6.0.4
      d3-dispatch: 3.0.1
      d3-drag: 3.0.0
      d3-dsv: 3.0.1
      d3-ease: 3.0.1
      d3-fetch: 3.0.1
      d3-force: 3.0.0
      d3-format: 3.1.2
      d3-geo: 3.1.1
      d3-hierarchy: 3.1.2
      d3-interpolate: 3.0.1
      d3-path: 3.1.0
      d3-polygon: 3.0.1
      d3-quadtree: 3.0.1
      d3-random: 3.0.1
      d3-scale: 4.0.2
      d3-scale-chromatic: 3.1.0
      d3-selection: 3.0.0
      d3-shape: 3.2.0
      d3-time: 3.1.0
      d3-time-format: 4.1.0
      d3-timer: 3.0.1
      d3-transition: 3.0.1(d3-selection@3.0.0)
      d3-zoom: 3.0.0

  dagre-d3-es@7.0.14:
    dependencies:
      d3: 7.9.0
      lodash-es: 4.18.1

  dayjs@1.11.23: {}

  delaunator@5.1.0:
    dependencies:
      robust-predicates: 3.0.3

  dequal@2.0.3: {}

  devlop@1.1.0:
    dependencies:
      dequal: 2.0.3

  dompurify@3.4.14:
    optionalDependencies:
      '@types/trusted-types': 2.0.7

  emoji-regex-xs@1.0.0: {}

  entities@7.0.1: {}

  entities@8.0.0: {}

  es-toolkit@1.51.0: {}

  esbuild@0.25.12:
    optionalDependencies:
      '@esbuild/aix-ppc64': 0.25.12
      '@esbuild/android-arm': 0.25.12
      '@esbuild/android-arm64': 0.25.12
      '@esbuild/android-x64': 0.25.12
      '@esbuild/darwin-arm64': 0.25.12
      '@esbuild/darwin-x64': 0.25.12
      '@esbuild/freebsd-arm64': 0.25.12
      '@esbuild/freebsd-x64': 0.25.12
      '@esbuild/linux-arm': 0.25.12
      '@esbuild/linux-arm64': 0.25.12
      '@esbuild/linux-ia32': 0.25.12
      '@esbuild/linux-loong64': 0.25.12
      '@esbuild/linux-mips64el': 0.25.12
      '@esbuild/linux-ppc64': 0.25.12
      '@esbuild/linux-riscv64': 0.25.12
      '@esbuild/linux-s390x': 0.25.12
      '@esbuild/linux-x64': 0.25.12
      '@esbuild/netbsd-arm64': 0.25.12
      '@esbuild/netbsd-x64': 0.25.12
      '@esbuild/openbsd-arm64': 0.25.12
      '@esbuild/openbsd-x64': 0.25.12
      '@esbuild/openharmony-arm64': 0.25.12
      '@esbuild/sunos-x64': 0.25.12
      '@esbuild/win32-arm64': 0.25.12
      '@esbuild/win32-ia32': 0.25.12
      '@esbuild/win32-x64': 0.25.12

  esbuild@0.28.2:
    optionalDependencies:
      '@esbuild/aix-ppc64': 0.28.2
      '@esbuild/android-arm': 0.28.2
      '@esbuild/android-arm64': 0.28.2
      '@esbuild/android-x64': 0.28.2
      '@esbuild/darwin-arm64': 0.28.2
      '@esbuild/darwin-x64': 0.28.2
      '@esbuild/freebsd-arm64': 0.28.2
      '@esbuild/freebsd-x64': 0.28.2
      '@esbuild/linux-arm': 0.28.2
      '@esbuild/linux-arm64': 0.28.2
      '@esbuild/linux-ia32': 0.28.2
      '@esbuild/linux-loong64': 0.28.2
      '@esbuild/linux-mips64el': 0.28.2
      '@esbuild/linux-ppc64': 0.28.2
      '@esbuild/linux-riscv64': 0.28.2
      '@esbuild/linux-s390x': 0.28.2
      '@esbuild/linux-x64': 0.28.2
      '@esbuild/netbsd-arm64': 0.28.2
      '@esbuild/netbsd-x64': 0.28.2
      '@esbuild/openbsd-arm64': 0.28.2
      '@esbuild/openbsd-x64': 0.28.2
      '@esbuild/openharmony-arm64': 0.28.2
      '@esbuild/sunos-x64': 0.28.2
      '@esbuild/win32-arm64': 0.28.2
      '@esbuild/win32-ia32': 0.28.2
      '@esbuild/win32-x64': 0.28.2

  esprima@4.0.1: {}

  estree-walker@2.0.2: {}

  extend-shallow@2.0.1:
    dependencies:
      is-extendable: 0.1.1

  fast-glob@3.3.3:
    dependencies:
      '@nodelib/fs.stat': 2.0.5
      '@nodelib/fs.walk': 1.2.8
      glob-parent: 5.1.2
      merge2: 1.4.1
      micromatch: 4.0.8

  fastdom@1.0.12:
    dependencies:
      strictdom: 1.0.1

  fastq@1.20.2:
    dependencies:
      reusify: 1.1.0

  fdir@6.5.0(picomatch@4.0.7):
    optionalDependencies:
      picomatch: 4.0.7

  feed@6.0.0:
    dependencies:
      xml-js: 1.6.11

  fill-range@7.1.1:
    dependencies:
      to-regex-range: 5.0.1

  focus-trap@7.8.0:
    dependencies:
      tabbable: 6.5.0

  fs-extra@11.4.0:
    dependencies:
      graceful-fs: 4.2.11
      jsonfile: 6.2.1
      universalify: 2.0.1

  fsevents@2.3.3:
    optional: true

  giscus@1.6.0:
    dependencies:
      lit: 3.3.3

  glob-parent@5.1.2:
    dependencies:
      is-glob: 4.0.3

  globby@16.2.4:
    dependencies:
      '@sindresorhus/merge-streams': 4.0.0
      fast-glob: 3.3.3
      ignore: 7.0.6
      is-path-inside: 4.0.0
      micromatch: 4.0.8
      slash: 5.1.0
      unicorn-magic: 0.4.0

  graceful-fs@4.2.11: {}

  gray-matter@4.0.3:
    dependencies:
      js-yaml: 3.15.2
      kind-of: 6.0.3
      section-matter: 1.0.0
      strip-bom-string: 1.0.0

  hachure-fill@0.5.2: {}

  hast-util-to-html@9.0.5:
    dependencies:
      '@types/hast': 3.0.5
      '@types/unist': 3.0.3
      ccount: 2.0.1
      comma-separated-tokens: 2.0.3
      hast-util-whitespace: 3.0.0
      html-void-elements: 3.0.0
      mdast-util-to-hast: 13.2.1
      property-information: 7.2.0
      space-separated-tokens: 2.0.2
      stringify-entities: 4.0.4
      zwitch: 2.0.4

  hast-util-whitespace@3.0.0:
    dependencies:
      '@types/hast': 3.0.5

  hookable@5.5.3: {}

  html-void-elements@3.0.0: {}

  iconv-lite@0.6.3:
    dependencies:
      safer-buffer: 2.1.2

  ignore@7.0.6: {}

  import-meta-resolve@4.2.0: {}

  internmap@1.0.1: {}

  internmap@2.0.3: {}

  is-extendable@0.1.1: {}

  is-extglob@2.1.1: {}

  is-glob@4.0.3:
    dependencies:
      is-extglob: 2.1.1

  is-number@7.0.0: {}

  is-path-inside@4.0.0: {}

  isexe@2.0.0: {}

  js-yaml@3.15.2:
    dependencies:
      argparse: 1.0.10
      esprima: 4.0.1

  jsonfile@6.2.1:
    dependencies:
      universalify: 2.0.1
    optionalDependencies:
      graceful-fs: 4.2.11

  katex@0.16.47:
    dependencies:
      commander: 8.3.0

  khroma@2.1.0: {}

  kind-of@6.0.3: {}

  layout-base@1.0.2: {}

  layout-base@2.0.1: {}

  linkify-it@6.1.0:
    dependencies:
      uc.micro: 3.0.0

  lit-element@4.2.2:
    dependencies:
      '@lit-labs/ssr-dom-shim': 1.6.0
      '@lit/reactive-element': 2.1.2
      lit-html: 3.3.3

  lit-html@3.3.3:
    dependencies:
      '@types/trusted-types': 2.0.7

  lit@3.3.3:
    dependencies:
      '@lit/reactive-element': 2.1.2
      lit-element: 4.2.2
      lit-html: 3.3.3

  lodash-es@4.18.1: {}

  magic-string@0.30.21:
    dependencies:
      '@jridgewell/sourcemap-codec': 1.6.0

  mark.js@8.11.1: {}

  markdown-it-mathjax3@5.2.0:
    dependencies:
      '@se-oss/deasync': 1.0.1
      mathxyjax3: 0.8.3

  markdown-it@15.0.1:
    dependencies:
      argparse: 3.0.1
      entities: 8.0.0
      linkify-it: 6.1.0
      mdurl: 2.1.0
      punycode.js: 2.3.1
      uc.micro: 3.0.0

  marked@16.4.2: {}

  mathxyjax3@0.8.3: {}

  mdast-util-to-hast@13.2.1:
    dependencies:
      '@types/hast': 3.0.5
      '@types/mdast': 4.0.4
      '@ungap/structured-clone': 1.4.0
      devlop: 1.1.0
      micromark-util-sanitize-uri: 2.0.1
      trim-lines: 3.0.1
      unist-util-position: 5.0.0
      unist-util-visit: 5.1.0
      vfile: 6.0.3

  mdurl@2.1.0: {}

  merge2@1.4.1: {}

  mermaid@11.17.2:
    dependencies:
      '@braintree/sanitize-url': 7.1.2
      '@iconify/utils': 3.1.4
      '@mermaid-js/parser': 1.2.1
      '@types/d3': 7.4.3
      '@upsetjs/venn.js': 2.0.0
      cytoscape: 3.34.2
      cytoscape-cose-bilkent: 4.1.0(cytoscape@3.34.2)
      cytoscape-fcose: 2.2.0(cytoscape@3.34.2)
      d3: 7.9.0
      d3-sankey: 0.12.3
      dagre-d3-es: 7.0.14
      dayjs: 1.11.23
      dompurify: 3.4.14
      es-toolkit: 1.51.0
      fastdom: 1.0.12
      katex: 0.16.47
      khroma: 2.1.0
      marked: 16.4.2
      roughjs: 4.6.6
      stylis: 4.4.0
      ts-dedent: 2.3.0
      uuid: 14.0.2

  micromark-util-character@2.1.1:
    dependencies:
      micromark-util-symbol: 2.0.1
      micromark-util-types: 2.0.2

  micromark-util-encode@2.0.1: {}

  micromark-util-sanitize-uri@2.0.1:
    dependencies:
      micromark-util-character: 2.1.1
      micromark-util-encode: 2.0.1
      micromark-util-symbol: 2.0.1

  micromark-util-symbol@2.0.1: {}

  micromark-util-types@2.0.2: {}

  micromatch@4.0.8:
    dependencies:
      braces: 3.0.3
      picomatch: 2.3.2

  minisearch@7.2.0: {}

  mitt@3.0.1: {}

  nanoid@3.3.18: {}

  oniguruma-to-es@3.1.1:
    dependencies:
      emoji-regex-xs: 1.0.0
      regex: 6.1.0
      regex-recursion: 6.0.2

  package-manager-detector@1.8.0: {}

  path-data-parser@0.1.0: {}

  path-key@3.1.1: {}

  perfect-debounce@1.0.0: {}

  picocolors@1.1.1: {}

  picomatch@2.3.2: {}

  picomatch@4.0.7: {}

  points-on-curve@0.2.0: {}

  points-on-path@0.2.1:
    dependencies:
      path-data-parser: 0.1.0
      points-on-curve: 0.2.0

  postcss@8.5.26:
    dependencies:
      nanoid: 3.3.18
      picocolors: 1.1.1
      source-map-js: 1.2.1

  preact@10.29.8: {}

  property-information@7.2.0: {}

  punycode.js@2.3.1: {}

  queue-microtask@1.2.3: {}

  regex-recursion@6.0.2:
    dependencies:
      regex-utilities: 2.3.0

  regex-utilities@2.3.0: {}

  regex@6.1.0:
    dependencies:
      regex-utilities: 2.3.0

  reusify@1.1.0: {}

  rfdc@1.4.1: {}

  robust-predicates@3.0.3: {}

  rollup@4.63.0:
    dependencies:
      '@types/estree': 1.0.9
    optionalDependencies:
      '@napi-rs/lzma-linux-x64-gnu': 1.5.1
      '@rollup/rollup-android-arm-eabi': 4.63.0
      '@rollup/rollup-android-arm64': 4.63.0
      '@rollup/rollup-darwin-arm64': 4.63.0
      '@rollup/rollup-darwin-x64': 4.63.0
      '@rollup/rollup-freebsd-arm64': 4.63.0
      '@rollup/rollup-freebsd-x64': 4.63.0
      '@rollup/rollup-linux-arm-gnueabihf': 4.63.0
      '@rollup/rollup-linux-arm-musleabihf': 4.63.0
      '@rollup/rollup-linux-arm64-gnu': 4.63.0
      '@rollup/rollup-linux-arm64-musl': 4.63.0
      '@rollup/rollup-linux-loong64-gnu': 4.63.0
      '@rollup/rollup-linux-loong64-musl': 4.63.0
      '@rollup/rollup-linux-ppc64-gnu': 4.63.0
      '@rollup/rollup-linux-ppc64-musl': 4.63.0
      '@rollup/rollup-linux-riscv64-gnu': 4.63.0
      '@rollup/rollup-linux-riscv64-musl': 4.63.0
      '@rollup/rollup-linux-s390x-gnu': 4.63.0
      '@rollup/rollup-linux-x64-gnu': 4.63.0
      '@rollup/rollup-linux-x64-musl': 4.63.0
      '@rollup/rollup-openbsd-x64': 4.63.0
      '@rollup/rollup-openharmony-arm64': 4.63.0
      '@rollup/rollup-win32-arm64-msvc': 4.63.0
      '@rollup/rollup-win32-ia32-msvc': 4.63.0
      '@rollup/rollup-win32-x64-gnu': 4.63.0
      '@rollup/rollup-win32-x64-msvc': 4.63.0
      fsevents: 2.3.3

  roughjs@4.6.6:
    dependencies:
      hachure-fill: 0.5.2
      path-data-parser: 0.1.0
      points-on-curve: 0.2.0
      points-on-path: 0.2.1

  run-parallel@1.2.0:
    dependencies:
      queue-microtask: 1.2.3

  rw@1.3.3: {}

  safer-buffer@2.1.2: {}

  sax@1.6.1: {}

  search-insights@2.17.3: {}

  section-matter@1.0.0:
    dependencies:
      extend-shallow: 2.0.1
      kind-of: 6.0.3

  shebang-command@2.0.0:
    dependencies:
      shebang-regex: 3.0.0

  shebang-regex@3.0.0: {}

  shiki@2.5.0:
    dependencies:
      '@shikijs/core': 2.5.0
      '@shikijs/engine-javascript': 2.5.0
      '@shikijs/engine-oniguruma': 2.5.0
      '@shikijs/langs': 2.5.0
      '@shikijs/themes': 2.5.0
      '@shikijs/types': 2.5.0
      '@shikijs/vscode-textmate': 10.0.2
      '@types/hast': 3.0.5

  slash@5.1.0: {}

  source-map-js@1.2.1: {}

  space-separated-tokens@2.0.2: {}

  speakingurl@14.0.1: {}

  sprintf-js@1.0.3: {}

  strictdom@1.0.1: {}

  stringify-entities@4.0.4:
    dependencies:
      character-entities-html4: 2.1.0
      character-entities-legacy: 3.0.0

  strip-bom-string@1.0.0: {}

  stylis@4.4.0: {}

  superjson@2.2.6:
    dependencies:
      copy-anything: 4.1.0

  tabbable@6.5.0: {}

  tinyexec@1.3.0: {}

  tinyglobby@0.2.17:
    dependencies:
      fdir: 6.5.0(picomatch@4.0.7)
      picomatch: 4.0.7

  to-regex-range@5.0.1:
    dependencies:
      is-number: 7.0.0

  trim-lines@3.0.1: {}

  ts-dedent@2.3.0: {}

  type-fest@4.41.0: {}

  uc.micro@3.0.0: {}

  undici-types@8.3.0: {}

  unicorn-magic@0.4.0: {}

  unist-util-is@6.0.1:
    dependencies:
      '@types/unist': 3.0.3

  unist-util-position@5.0.0:
    dependencies:
      '@types/unist': 3.0.3

  unist-util-stringify-position@4.0.0:
    dependencies:
      '@types/unist': 3.0.3

  unist-util-visit-parents@6.0.2:
    dependencies:
      '@types/unist': 3.0.3
      unist-util-is: 6.0.1

  unist-util-visit@5.1.0:
    dependencies:
      '@types/unist': 3.0.3
      unist-util-is: 6.0.1
      unist-util-visit-parents: 6.0.2

  universalify@2.0.1: {}

  uuid@14.0.2: {}

  vfile-message@4.0.3:
    dependencies:
      '@types/unist': 3.0.3
      unist-util-stringify-position: 4.0.0

  vfile@6.0.3:
    dependencies:
      '@types/unist': 3.0.3
      vfile-message: 4.0.3

  vite@6.4.3(@types/node@26.4.0):
    dependencies:
      esbuild: 0.25.12
      fdir: 6.5.0(picomatch@4.0.7)
      picomatch: 4.0.7
      postcss: 8.5.26
      rollup: 4.63.0
      tinyglobby: 0.2.17
    optionalDependencies:
      '@types/node': 26.4.0
      fsevents: 2.3.3

  vitepress@1.6.4(@algolia/client-search@5.57.0)(@types/node@26.4.0)(markdown-it-mathjax3@5.2.0)(postcss@8.5.26)(search-insights@2.17.3):
    dependencies:
      '@docsearch/css': 3.8.2
      '@docsearch/js': 3.8.2(@algolia/client-search@5.57.0)(search-insights@2.17.3)
      '@iconify-json/simple-icons': 1.2.93
      '@shikijs/core': 2.5.0
      '@shikijs/transformers': 2.5.0
      '@shikijs/types': 2.5.0
      '@types/markdown-it': 14.2.0
      '@vitejs/plugin-vue': 5.2.4(vite@6.4.3(@types/node@26.4.0))(vue@3.5.42)
      '@vue/devtools-api': 7.7.10
      '@vue/shared': 3.5.42
      '@vueuse/core': 12.8.2
      '@vueuse/integrations': 12.8.2(focus-trap@7.8.0)
      focus-trap: 7.8.0
      mark.js: 8.11.1
      minisearch: 7.2.0
      shiki: 2.5.0
      vite: 6.4.3(@types/node@26.4.0)
      vue: 3.5.42
    optionalDependencies:
      markdown-it-mathjax3: 5.2.0
      postcss: 8.5.26
    transitivePeerDependencies:
      - '@algolia/client-search'
      - '@types/node'
      - '@types/react'
      - async-validator
      - axios
      - change-case
      - drauu
      - fuse.js
      - idb-keyval
      - jiti
      - jwt-decode
      - less
      - lightningcss
      - nprogress
      - preact-render-to-string
      - qrcode
      - react
      - react-dom
      - sass
      - sass-embedded
      - search-insights
      - sortablejs
      - stylus
      - sugarss
      - terser
      - tsx
      - typescript
      - universal-cookie
      - yaml

  vue@3.5.42:
    dependencies:
      '@vue/compiler-dom': 3.5.42
      '@vue/compiler-sfc': 3.5.42
      '@vue/runtime-dom': 3.5.42
      '@vue/server-renderer': 3.5.42
      '@vue/shared': 3.5.42

  which@2.0.2:
    dependencies:
      isexe: 2.0.0

  xml-js@1.6.11:
    dependencies:
      sax: 1.6.1

  zwitch@2.0.4: {}

---

## File: tags.md

---
layout: page
---

<Tags/>

---

## File: tools.md

---
layout: page
---

<Tools/>

---

## File: tsconfig.json

{
  "compilerOptions": {
    "target": "esnext" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */,
    "module": "esnext" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */,
    "moduleResolution": "node",
    "outDir": "./dist" /* Redirect output structure to the directory. */,
    "rootDir": "./" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */,
    "strict": true /* Enable all strict type-checking options. */,
    "noImplicitAny": true /* Raise error on expressions and declarations with an implied 'any' type. */,
    "strictNullChecks": true /* Enable strict null checks. */,
    "esModuleInterop": true,
    "skipLibCheck": true /* Skip type checking of declaration files. */,
    "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
  }
}

---

## File: docs/.vitepress/cache/deps/_metadata.json

{
  "hash": "0405d454",
  "browserHash": "d1020abb",
  "optimized": {
    "vue": {
      "src": "../../../../node_modules/.pnpm/vue@3.3.7/node_modules/vue/dist/vue.runtime.esm-bundler.js",
      "file": "vue.js",
      "fileHash": "e206c70d",
      "needsInterop": false
    },
    "vitepress > @vue/devtools-api": {
      "src": "../../../../node_modules/.pnpm/@vue+devtools-api@6.5.1/node_modules/@vue/devtools-api/lib/esm/index.js",
      "file": "vitepress___@vue_devtools-api.js",
      "fileHash": "fe2d95b1",
      "needsInterop": false
    },
    "@theme/index": {
      "src": "../../../../node_modules/.pnpm/vitepress@1.0.0-rc.24_@algolia+client-search@4.20.0_markdown-it-mathjax3@4.3.2_react-dom@15.7_h7x6bzpmfyn2o7n6gmjj7ipb5u/node_modules/vitepress/dist/client/theme-default/index.js",
      "file": "@theme_index.js",
      "fileHash": "364b43b6",
      "needsInterop": false
    }
  },
  "chunks": {
    "chunk-DO6W2M3F": {
      "file": "chunk-DO6W2M3F.js"
    }
  }
}

---

## File: docs/.vitepress/cache/deps/package.json

{
  "type": "module"
}

---

## File: posts/zh/$draft 从堆栈理解rust语言设计精妙.md

[Binary file]

---

## File: posts/zh/$draft 前端富文本编辑器开发最佳实践.md

[Binary file]

---

## File: posts/zh/$pin markdown自定义的思考.md

[Binary file]

---

## File: posts/zh/$pin 为HomeAssistant创建一个自定义组件.md

---
title: 为HomeAssistant创建一个自定义组件
date: 2024-03-24 13:56:09
toc: true
tags: [ homeassistant, ha, 自定义组件, 教程 ]
---

> 更新于 2024-03-27

## 前言

关于 Home Assistant 的顶级玩法，可以去看看我的b站视频合集 [Home Assistant顶级玩法](https://space.bilibili.com/137683614/channel/collectiondetail?sid=2050382)

> 本教程为 Home Assistant 社区的文章翻译，原文链接为：<https://community.home-assistant.io/t/tutorial-for-creating-a-custom-component/204793>，作者：Aaron Godfrey。

下面是原作者的帖子内容：

> 我开始了一个如何为 HomeAssistant 创建自定义组件的教程。<https://aarongodfrey.dev/home%20automation/building_a_home_assistant_custom_component_part_1/>
> 想要获得一些反馈，看看其中的一些内容是否有用，或者我是否忽略了一些显而易见的东西。

- [第一部分 如何创建一个基础集成(integration)](https://aarongodfrey.dev/home%20automation/building_a_home_assistant_custom_component_part_1/)
- [第二部分 讨论单元测试和持续集成](https://aarongodfrey.dev/home%20automation/building_a_home_assistant_custom_component_part_2/)
- [第三部分 重点添加一个配置流(Config Flow)](https://aarongodfrey.dev/home%20automation/building_a_home_assistant_custom_component_part_3/)
- [第四部分 重点添加一个选项流(Options Flow)](https://aarongodfrey.dev/home%20automation/building_a_home_assistant_custom_component_part_4/)
- [第五部分 讨论使用 devcontainer 进行调试](https://aarongodfrey.dev/home%20automation/building_a_home_assistant_custom_component_part_5/)

## 构建一个 Home Assistant 自定义组件

## 第一部分：项目结构和基础

### 摘要

这个系列的文章为一个教程，旨在教会我们为 Home Assistant 创建一个自定义组件。我们将从一个组件骨架开始，然后在每篇教程里添加内容。在本系列教程文章的最后，你将会拥有一个完整的功能组件，至少可以在 [集成质量衡量(Integration Quality Scale)](https://developers.home-assistant.io/docs/integration_quality_scale_index/) 中拿到 Sliver 级别的分数。

在这个项目中，我们将使用 [GitHub API](https://developer.github.com/v3/) 来为我们的组件提供数据。这已经有一个存在的 [GitHub 集成](https://www.home-assistant.io/integrations/github/)，但我们将自己去实现并尝试去通过单元测试去提升这个已有的组件质量。可以通过 UI 来进行配置，并添加一些实用的功能。

每篇文章都是 [GitHub 仓库](https://github.com/boralyl/github-custom-component-tutorial) 的不同分支。所以你可以在自己的编辑器中跟随学习，或者浏览适当分支中的代码。此部分的更改都可以在 [feature/part1 分支](https://github.com/boralyl/github-custom-component-tutorial/compare/feature/bare-repo...feature/part1?expand=1) 中查阅。

我建议去查阅官方的 [开发者文档](https://developers.home-assistant.io/)，从 Home Assistant 架构中的概念获得一些灵感。

### 项目结构

一开始，我们需要为自定义组件生成基础的文件。幸运的是，通过 [cookiecutter project template](https://github.com/boralyl/cookiecutter-homeassistant-component) 项目做这件事很容易。

让我们安装 [cokkiecutter](https://github.com/cookiecutter/cookiecutter)，并且通过问答的方式创建我们的工程。

```txt
$ pip install cookiecutter
$ cookiecutter https://github.com/boralyl/cookiecutter-homeassistant-component
domain [my_component]: github_custom
name [My Component]: Github Custom
docs_url [https://github.com/user/my_component/]: https://github.com/boralyl/github-custom-component-tutorial
owner [@user]: @boralyl
Select config_flow:
1 - yes
2 - no
Choose from 1, 2 [1]: 2
Select iot_class:
1 - Assumed State
2 - Cloud Polling
3 - Cloud Push
4 - Local Polling
5 - Local Push
Choose from 1, 2, 3, 4, 5 [1]: 2
```

> 注意：一开始我们跳过了使用配置流(Config Flow)。我们将在教程的后续文章中添加这部分功能。



现在我们忽略根目录文件夹下的文件和测试文件夹。让我们关注 `custom_components` 和`github_custom` 文件夹。[manifest.json](https://developers.home-assistant.io/docs/creating_integration_manifest) 包含了一些我们这个组件的基础信息，Home Assistant 在加载的时候会用到。`const.py` 只包含了我们的常量，在这个案例中，我们只有组件的 [DOMAIN](https://developers.home-assistant.io/docs/creating_integration_manifest#domain)。`__init__.py` 包含了 `async_setup` 方法，用于 Home Assistant 加载我们的组件。

此时自定义组件是合法的，如果你放在自己的 Home Assistant `config` 目录下，Home Assistant 会正常加载，并不会实际创建任何实体(entities)。

### 实现组件

现在，是时候开始编写我们的组件了。有四个基本部分去实现。

1. 在 `manifest.json` 添加我们的 requrements。如果我们需要添加额外的 python 依赖，就需要在这添加。
2. 添加我们自己的配置规则。将用于定义我们希望用户添加进他们的 `configuration.yaml` 文件的值。
3. 在 Home Assistant 中注册我们所有的传感器。将在 `async_setuo_platform` 函数中进行实现。
4. 创建一个实体，用于代表我们希望手收集关于 GitHub 仓库的状态和数据。这个实体也应该实现 `async_update` 方法，从 GitHub 来更新数据。

这四处的最终实现，可以看这个 [diff](https://github.com/boralyl/github-custom-component-tutorial/compare/feature/bare-repo...feature/part1?expand=1)。在这个 diff 中需要注意的是，我从 `__init__.py` 中移除了 `async_setup` 函数。因为我们集成使用了 platform，所以我们可以移除这部分的代码。这些 platforms 允许你在集成中拥有多个实例，而不是仅一个。如果你正在同时监控公共仓库和一些使用 GitHub Enterprise 服务的私有仓库，是非常有用的。

### 向 manifest.json 添加 requirements

我们将使用 [gidgethub](https://github.com/brettcannon/gidgethub) 库来与 GitHub API 进行交互。它支持开箱即用的异步通信，可以非常简单直接使用。

```diff
  "documentation": "https://github.com/boralyl/github-custom-component-tutorial",
  "domain": "github_custom",
  "name": "Github Custom",
-  "requirements": []
+  "requirements": ["gidgethub[aiohttp]==4.1.1"]
}
```

我们向 `requirements` 数组添加了指定版本的依赖。值得注意的是，指定一个异步库是可选的。为了保证这些依赖被正确安装，我们需要额外在 requirement 中额外指定 `aiohttp`。

### platform 配置方案

对于我们的 platform 配置方案来说，我们需要根据官方的 [GitHub 集成](https://www.home-assistant.io/integrations/github/) 来实现。下面是一个基础的示例：

```yaml
# Example configuration.yaml entry
sensor:
  - platform: github_custom
    access_token: !secret github_access_token
    repositories:
      - path: "home-assistant/core"
        name: "Home Assistant Core"
      - path: "boralyl/steam-wishlist"

  - platform: github_custom
    url: https://my.enterprisegithubserver.com
    access_token: !secret github_access_token
    repositories:
      - path: "company/some-repo"
```

我们不需要去修改任何内容，所以配置方案与官方集成的完全一致：

```txt
REPO_SCHEMA = vol.Schema(
    {vol.Required(CONF_PATH): cv.string, vol.Optional(CONF_NAME): cv.string}
)

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
    {
        vol.Required(CONF_ACCESS_TOKEN): cv.string,
        vol.Required(CONF_REPOS): vol.All(cv.ensure_list, [REPO_SCHEMA]),
        vol.Optional(CONF_URL): cv.url,
    }
)
```

上面的内容非常直观。我们需要一个 access token和一个仓库列表。每个仓库必须拥有 `path` 键，并且可以可选有一个 `name` 键。我们也可选地允许 `url` 键，用于指定 GitHub Enterprise 服务器的 URL。

上面的代码本质上扩展了 Home Assistant platform 配置方案，添加了我们自己的域名 `github_custom`。它将为我们处理认证和展示适当的错误。

### 注册传感器(sensors)

下一步注册我们所有的传感器。将为在 platform 配置中的每一个仓库创建一个传感器。习惯上，Home Assistant 会查看你 `sensor.py` 文件中的 `setup_platform` 或者 `async_setup_platform` 函数。如果你的数据通过库来更新，使用 [asyncio](https://docs.python.org/3/library/asyncio.html) 来异步获取数据，那就需要使用 `async_setup_platform` 函数，否则应该创建 `setup_platform` 函数。自我们使用 [gidgethub](https://github.com/brettcannon/gidgethub/) 库，他就支持异步，所以我们使用 `async_setup_platform` 函数。

```python
async def async_setup_platform(
    hass: HomeAssistantType,
    config: ConfigType,
    async_add_entities: Callable,
    discovery_info: Optional[DiscoveryInfoType] = None,
) -> None:
    """Set up the sensor platform."""
    session = async_get_clientsession(hass)
    github = GitHubAPI(session, "requester", oauth_token=config[CONF_ACCESS_TOKEN])
    sensors = [GitHubRepoSensor(github, repo) for repo in config[CONF_REPOS]]
    async_add_entities(sensors, update_before_add=True)
```

在这个函数中，我们首先恢复了一个 aiohttp 客户端会话。这个帮助函数关注于为我们恢复和关闭会话（少了一件需要考虑的事情）。我们初始化了 GitHub API 客户端，并为在我们的 `configuration.yaml` 中指定的每个仓库创建了一个 `GitHubRepoSensor`。

`async_add_entities` 函数将会处理在 Home Assistant 中添加和注册这些传感器。第二个参数也值得注意。将它设置为 `True` 的时候，是告诉 Home Assistant 应该在集成完成加载之后进行数据更新。没有这项配置（或者将它设置为 `False`），它将等待直到 `SCAN_INTERVAL` 的时候再从 GitHub 获取数据。这个常量被设置为 10 分钟，意味着在 Home Assistant 重启之后的十分钟，我们的传感器是没有数据的。（或者，它将在恢复重新启动之前的上一次更新的数据）

### GitHub 仓库传感器实体和 async_update

上一部分我们定义了组件的实体和指定了一个更新方法。简而言之，并没有包含完整的类（的代码），但你可以在 [GitHub](https://github.com/boralyl/github-custom-component-tutorial/blob/3ee4d35f4bb08ec92098e7932fd4ae29ef59591f/custom_components/github_custom/sensor.py#L98-L212) 中查看。

最主要的部分是它继承了 `homeassistant.helpers.entity.Entity`，这个类为你实现了必要的逻辑。我们定义自己的返回传感器的 `状态(state)` 属性。这个传感器，我们将使用 commit sha 的前7个字符。我们也定义了一个返回和状态相关的 `device_state_attributes` 的属性，可以被自动化(automations) 和 lovelace UI 所使用。

我们的自定义传感器类必须指定一个 `update` 或者 `async_update` 方法，为了从 GitHub 获取数据用于更新 state 和 device_state_attributes。再者，我们的组件使用了一个支持异步的库，包含了 `async_update` 方法，用于从 GitHub 获取需要的数据。Home Assistant 将基于我们定义的间隔调用这个方法，习惯上去寻找一个叫做 `SCAN_INTERVAL` 的常量。如果你在自己的文件中定义了它，它的值是一个 `datetime.timedelta` 实例。我们为自己的组件使用10分钟作为更新的间隔。

```python
SCAN_INTERVAL = timedelta(minutes=10)
```

### 结语

我们现在有了一个完整功能的异步自定义组件，是官方集成的提升版。

在后面的文章中，我们将简单的讨论如何去为帮助提升自定义组件，来添加单元测试和捕获 bugs。我们也会使用 [GitHub Actions](https://github.com/features/actions) 来为自定义组件添加持续集成。

## 第二部分：单元测试和持续集成

> 原文链接：<https://aarongodfrey.dev/home%20automation/building_a_home_assistant_custom_component_part_2/>

### 摘要

这篇教程我们将讨论如何对自定义组件进行单元测试，并和 GitHub 的持续集成一起使用。我们仍然使用一样的示例项目 [github-custom-component](https://github.com/boralyl/github-custom-component-tutorial)，我已经为此添加了单元测试和一些持续集成的配置。你可以在 [feature/part2 分支](https://github.com/boralyl/github-custom-component-tutorial/compare/feature/part1...feature/part2?expand=1) 寻找这篇文章带来的差异。

### 单元测试

通常来说测试 Home Assistant 自定义组件并没有什么特殊之处，与其他的 python 项目的流程非常相似。然而，在某些情况下，访问一些 Home Assistant 的特定功能会使编写测试变得容易得多。

Home Assistant 有一堆测试工具集和 [pytest fixturew](https://docs.pytest.org/en/latest/fixture.html)，可以帮组我们对 [core repo](https://github.com/home-assistant/core) 写单元测试更加简单（比如获取 `hass` 实例），但是它们并没有被对外暴露，你可以完全不需要复制/粘贴代码直接进行使用。为了使这些对自定义组件的复用性，你可以下载 [pytest 插件](https://github.com/MatthewFlamm/pytest-homeassistant-custom-component) 可以提供这类特性。

如果你使用 [cookiecutter project template](https://github.com/boralyl/cookiecutter-homeassistant-component) 项目来为 Home Assistant 创建自定义组件，在你的 `requirements.test.txt` 中早已包含了这个依赖。如果需要在已经存在的组件中添加单元测试，你可以通过 pip 安装这个依赖。

```bash
pip install pytest-homeassistant-custom-component
```

你不必去做任何额外的事情，`pytest-homeassistant-custom-component` 插件早已提供了 pytest fixtures 的权限。pytest 会自动获取知道这些，你可以直接在测试中进行使用。其中最有用的当属 `hass`，为我们提供了一个 `hass` 实例，可以用于测试环境的启动。当测试配置流的时候，这也非常有用。看看下面的例子：

```python
async def test_flow_user_step_no_input(hass):
    """Test appropriate error when no input is provided."""
    _result = await hass.config_entries.flow.async_init(
        config_flow.DOMAIN, context={"source": "user"}
    )
    result = await hass.config_entries.flow.async_configure(
        _result["flow_id"], user_input={}
    )
    assert {"base": "missing"} == result["errors"]
```

当 pytest 看到你测试函数的参数之时，它将基于名称和所有被注册的插件进行检查。`pytest-homeassistant-custom-component` 对此进行注册，当测试函数被调用的时候将在合适的时候进行初始化。现在我们可以通过配置流的不同的值来运行不同的步骤，并且断言返回的数据。在特定的 case，如果在配置流程中，用户没有配置组件提供任何输入，我们将测试并展示适当的错误。

另外来自 Home Assistant 非常有用的是，`pytest-homeassistant-custom-component` 提供了 `AsyncMock` 用于模拟从异步函数中返回值。在这个例子中，我们对 `github.getitem` 异步函数进行模拟，扔出一个异常。

```python
from pytest_homeassistant_custom_component.async_mock import AsyncMock, MagicMock

from custom_components.github_custom.sensor import GitHubRepoSensor

async def test_async_update_failed():
    """Tests a failed async_update."""
    github = MagicMock()
    github.getitem = AsyncMock(side_effect=GitHubException)

    sensor = GitHubRepoSensor(github, {"path": "homeassistant/core"})
    await sensor.async_update()

    assert sensor.available is False
    assert {"path": "homeassistant/core"} == sensor.attrs
```

在这个测试中，验证了当我们设置传感器的可用性为 `False`，`async_update` 函数是否抛出了异常。

我建议在 Home Assistant Core 中阅读一些 [platinum 质量等级](https://github.com/home-assistant/core/search?q=platinum&unscoped_q=platinum) 的组件，以获取关于应该测试什么、如何去测试的灵感。写这篇文章时的推荐：

- [Brother Printer](https://github.com/home-assistant/core/tree/dev/tests/components/brother)
- [Daikin AC](https://github.com/home-assistant/core/tree/dev/tests/components/daikin)
- [deCONZ](https://github.com/home-assistant/core/tree/dev/tests/components/deconz)
- [Elgato Key Light](https://github.com/home-assistant/core/tree/dev/tests/components/elgato)
- [Global Disaster Alert and Coordination System (GDACS)](https://github.com/home-assistant/core/tree/dev/tests/components/gdacs)
- [GeoNet NZ Quakes](https://github.com/home-assistant/core/tree/dev/tests/components/geonetnz_quakes)
- [HomematicIP Cloud](https://github.com/home-assistant/core/tree/dev/tests/components/homematicip_cloud)
- [Philips Hue](https://github.com/home-assistant/core/tree/dev/tests/components/hue)
- [Internet Printing Protocol (IPP)](https://github.com/home-assistant/core/tree/dev/tests/components/ipp)
- [National Weather Service (NWS)](https://github.com/home-assistant/core/tree/dev/tests/components/nws)
- [Spain electricity hourly pricing (PVPC)](https://github.com/home-assistant/core/tree/dev/tests/components/pvpc_hourly_pricing)
- [Ubiquiti UniFi](https://github.com/home-assistant/core/tree/dev/tests/components/unifi)
- [VIZIO SmartCast](https://github.com/home-assistant/core/tree/dev/tests/components/vizio)
- [WLED](https://github.com/home-assistant/core/tree/dev/tests/components/wled)

你也可以检查我如何在自己的自定义组件中实现的测试：

- [github-custom-component-tutorial](https://github.com/boralyl/github-custom-component-tutorial/tree/master/tests/)
- [nintendo-wishlist](https://github.com/custom-components/sensor.nintendo_wishlist/tree/master/tests)
- [steam-wishlist](https://github.com/boralyl/steam-wishlist/tree/master/tests)

### 持续集成

[持续集成](https://en.wikipedia.org/wiki/Continuous_integration) 允许你每次向仓库提交的时候进行各种检查（在其他任务中）。在这篇文章中，我将特别介绍 [GitHub Actions](https://github.com/features/actions)，但是你可以使用类似于 [Travis CI](https://travis-ci.org/) 这样的其他服务实现相同的效果。

你的 GitHub actions 将会被放置在仓库根目录结构的 `.github/workflows/` 下。每个工作流都是不同的运行任务，通常是在向 GitHub 推送代码的时候触发。

### Hassfest

[@ludeeus](https://www.github.com/ludeeus) 创建了一个 GitHub action 为你的组件进行验证。（也是超棒的 [Home Assistant Store](https://hacs.xyz/) 的背后实现）。看看在 [Home Assistant Developers Blog](https://developers.home-assistant.io/blog/2020/04/16/hassfest/) 上的文章了解更多信息。

下面是 `.github/workflows/hassfest.yaml` 的内容：

```yaml
name: Validate with hassfest

on:
  push:
  pull_request:
  schedule:
    - cron: "0 0 * * *"

jobs:
  validate:
    runs-on: "ubuntu-latest"
    steps:
      - uses: "actions/checkout@v2"
      - uses: home-assistant/actions/hassfest@master
```

这个本质上是对你自定义组件的合法配置项进行检查。

### Python 构建

在我们 python 构建之中，我们想要在每次推送代码运行所有的单元测试。下面是 `.github/workflows/pythonpackage.yaml` 的内容：

```yaml
name: Python package

on: [push]

jobs:
  build:
    runs-on: ubuntu-latest
    strategy:
      max-parallel: 4
      matrix:
        python-version: [3.7]

    steps:
      - uses: actions/checkout@v1
      - name: Set up Python ${{ matrix.python-version }}
        uses: actions/setup-python@v1
        with:
          python-version: ${{ matrix.python-version }}

      - name: Set PY env
        run: echo "::set-env name=PY::$(python -VV | sha256sum | cut -d' ' -f1)"

      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -r requirements.test.txt
      - name: Run pytest
        run: |
          pytest
```

在每次推送代码时，它将默认会把 python 的版本环境设置为 `3.7`。然后将下载我们所有的测试依赖，最终运行 `pytest` 执行我们的测试。如果测试没问题，构建将会成功完成，并且在你的 GitHub 提交信息之后获得一个绿色的成功检查标记。（这也代表了 `hassfest` 检查也成功了）

### 预提交(Pre-Commit)

[预提交(Pre-Commit)](https://pre-commit.com/) 在提交之前，提供了一个任意数量检查代码的方式。这将帮助去检查一些常见的问题，根据仓库和其他检查对代码进行规范。

如果你使用 cookie-cutter template，在你的生成代码中早已包含了[`.pre-commit-config.yaml`](https://github.com/boralyl/cookiecutter-homeassistant-component/blob/master/%7B%7B%20cookiecutter.domain%20%7D%7D/.pre-commit-config.yaml)，其中包含了大部分 Home Assistant 核心使用的检查。在现有的组件中，可以简单的添加到你自己的项目之中，选用你想要去对代码进行的检查。

由 cookie-cutter template 生成的文件会帮助你的代码和 Home Assistant 标准更加兼容。当你向将组件合并到核心仓库的时候，省去很多麻烦。

开始如果没有的话，在你的仓库根目录添加 `.pre-commit-config.yaml`。然后安装 pre-commit。

```bash
pip install pre-commit
pre-commit install
```

现在，下次你进行提交的时候，它将运行所有的检查来检查你的试图提交，任一检查失败就将失败。一个成功的提交如下面的输出：

```txt
$ git commit -a
pyupgrade..........................Passed
black..............................Passed
codespell..........................Passed
flake8.............................Passed
bandit.............................Passed
isort..............................Passed
Check JSON.........................Passed
mypy...............................Passed
```

失败的检查可能像这样：

```txt
$ git commit -a
pyupgrade........................(no files to check)Skipped
black............................(no files to check)Skipped
codespell........................Failed
- hook id: codespell
- exit code: 1

README.md:21: recommend  ==> recommend

flake8...........................(no files to check)Skipped
bandit...........................(no files to check)Skipped
isort............................(no files to check)Skipped
Check JSON.......................(no files to check)Skipped
mypy.............................(no files to check)Skipped
```

任何检查的失败都会打断你的提交，你需要对问题进行修复然后再次提交。如果一些原因你想进行提交并跳过检查，可以在提交的时候添加 `--no-verify` 标记。

```bash
git commit -a --no-verify
```

### 结语

在这篇文章中，我们接触了如何在自定义组件中开始单元测试，并将此与 GitHub 工作流的持续集成或者第三方解决方案连接起来。使用这些将会使你的自定义组件不仅更加健壮和无 bug，并且与 Home Assistant 核心代码的标准保持一致。

在下篇文章中，我们将关注于如何在 [github-custom-component-tutorial](https://github.com/boralyl/github-custom-component-tutorial/) 项目中添加 [配置流Config Flow](https://developers.home-assistant.io/docs/config_entries_config_flow_handler)。

## 第三部分：配置流

> 原文链接：<https://aarongodfrey.dev/home%20automation/building_a_home_assistant_custom_component_part_3/>

### 摘要

在这篇文章中，我们将更新自定义组件，使之可以通过 UI 进行配置，通过添加 [配置流](https://developers.home-assistant.io/docs/config_entries_config_flow_handler) 进行实现。我们仍然使用相同的示例项目 [github-component-tutorial](https://github.com/boralyl/github-custom-component-tutorial)。你可以在 [feature/part3](https://github.com/boralyl/github-custom-component-tutorial/compare/feature/part2...feature/part3) 分支查看本篇文章带来的不同。

### 更新 manifest.json

首先，我们需要更新 `manifest.json` 文件。将 `config_flow` 的值设为 `true`，这一步是让 Home Assistant 知道这个组件可以通过配置 UI 添加。

```diff
{
   "codeowners": ["@boralyl"],
-  "config_flow": false,
+  "config_flow": true,
   "dependencies": [],
   "documentation": "https://github.com/boralyl/github-custom-component-tutorial",
   "domain": "github_custom",
```

### 添加配置流

接下来，我们将创建自己的 [config_flow.py](https://github.com/boralyl/github-custom-component-tutorial/blob/master/custom_components/github_custom/config_flow.py) 文件。在这这个文件中，我们将继承 `ConfigFlow` 类，并且定义当用户首次配置组件时所在 UI 展示的不同步骤。

截止本文撰写，通过配置流，拥有需要未知大小的配置值列表的组件并不是最简单的事情。为了绕过这个限制，我决定设计配置流有两个步骤。第一步是问询用户的 GitHub access token 和可选的 enterprise server URL。提交该信息后，用户将进入第二步，该步骤允许他们为其输入存储库和可选名称。为了允许用户添加额外的存储库，我添加了一个复选框，如果选中该复选框将重复第二步。用户可以重复多次去添加仓库，直到他们添加所有为创建传感器的仓库。

### 用户步骤

当用户点击添加按钮和选择 GitHub Custom 集成的时候，我们配置流类的 [async_step_user](https://github.com/boralyl/github-custom-component-tutorial/blob/master/custom_components/github_custom/config_flow.py#L55) 方法将被调用。



让我们看看那这个方法做了什么。

```python
async def async_step_user(self, user_input: Optional[Dict[str, Any]] = None):
    """Invoked when a user initiates a flow via the user interface."""
    errors: Dict[str, str] = {}
    if user_input is not None:
        try:
            await validate_auth(user_input[CONF_ACCESS_TOKEN], self.hass)
        except ValueError:
            errors["base"] = "auth"
        if not errors:
            # Input is valid, set data.
            self.data = user_input
            self.data[CONF_REPOS] = []
            # Return the form of the next step.
            return await self.async_step_repo()

    return self.async_show_form(
        step_id="user", data_schema=AUTH_SCHEMA, errors=errors
    )
```

在这一步首次被调用的时候，变量 `user_input` 的默认值为 `None`。当用户点击提交按钮的时候，变量的值将被填入一个包含他们输入数据的字典。Home Assistant 将基于你定义的数据结构代表你去做一些基本的验证检查。我添加了一些额外的验证方法，确保 access token 是合法的。如果失败了，我们将为 `auth` 设置基本错误。这个值与 [strings.json](https://github.com/boralyl/github-custom-component-tutorial/blob/master/custom_components/github_custom/strings.json#L4) 中的 errors 对象相关，将展示在此定义的描述。

如果没错误，数据将会被存储在类的 `self.data` 属性。除了存储输入的数据，我也为下一步添加的仓库初始化了一个空列表。最后，我们调用下一步的 `async_step_repo` 方法去推进用户进入第二步的表单，以输入他们所有想监控的仓库。

### 仓库步骤

在用户成功完成初始化步骤之后，[async_step_repo](https://github.com/boralyl/github-custom-component-tutorial/blob/master/custom_components/github_custom/config_flow.py#L74) 方法被调用。这一步是响应式展示输入仓库信息的表单。如果用户选择了 `Add another repo`，我们将在提交之后保存输入的数据并重置表单。



这个方法的逻辑和第一步的非常相似。

```python
async def async_step_repo(self, user_input: Optional[Dict[str, Any]] = None):
    """Second step in config flow to add a repo to watch."""
    errors: Dict[str, str] = {}
    if user_input is not None:
        # Validate the path.
        try:
            validate_path(user_input[CONF_PATH])
        except ValueError:
            errors["base"] = "invalid_path"

        if not errors:
            # Input is valid, set data.
            self.data[CONF_REPOS].append(
                {
                    "path": user_input[CONF_PATH],
                    "name": user_input.get(CONF_NAME, user_input[CONF_PATH]),
                }
            )
            # If user ticked the box show this form again so they can add an
            # additional repo.
            if user_input.get("add_another", False):
                return await self.async_step_repo()

            # User is done adding repos, create the config entry.
            return self.async_create_entry(title="GitHub Custom", data=self.data)

    return self.async_show_form(
        step_id="repo", data_schema=REPO_SCHEMA, errors=errors
    )
```

一个关键的不同点是，如果 `add_another` 复选框被选中，我们将返回当前的步骤。当用户完成最后一步时，调用 `async_create_entry` 方法创建我们的 config entry 并在 Home Assistant 中注册。

### 设置 Config Entry

下一件需要做的事情是，通过 config entry 设置我们创建的传感器。在 `__init__.py` 文件，我们定义了 `async_setup_entry` 函数将转发任务到 sensor platform。更多关于这如何工作的细节，我建议你去看非常棒的 [专题文档](https://developers.home-assistant.io/docs/config_entries_index/)。

```python
async def async_setup_entry(
    hass: core.HomeAssistant, entry: config_entries.ConfigEntry
) -> bool:
    """Set up platform from a ConfigEntry."""
    hass.data.setdefault(DOMAIN, {})
    hass.data[DOMAIN][entry.entry_id] = entry.data

    # Forward the setup to the sensor platform.
    hass.async_create_task(
        hass.config_entries.async_forward_entry_setup(entry, "sensor")
    )
    return True
```

在上面的方法中，我们在 hass 自己的 `DOMAIN` 字段下存储了 config entry 的数据。这样允许我们存储用户多次配置集成的 config entries。可能他们拥有一个 enterprise server 账户为了工作和一个常规的个人账户。他们可以设置两个不同的输入，连链接那些不同的情况。

我们将转发这些设置到 `sensor` platform。在 [sensor.py](https://github.com/boralyl/github-custom-component-tutorial/blob/master/custom_components/github_custom/sensor.py) 中，我们添加了一个接收 config entry 实例，并为组件创建传感器的 `async_setup_entry` 函数。你会注意到这个函数近似等同于下面的 `async_setup_platform` 函数，这个函数被用于从 `configuration.yaml` 设置传感器。唯一的区别是我们从 config entry 实例中获取配置数据。

```python
async def async_setup_entry(
    hass: core.HomeAssistant,
    config_entry: config_entries.ConfigEntry,
    async_add_entities,
):
    """Setup sensors from a config entry created in the integrations UI."""
    config = hass.data[DOMAIN][config_entry.entry_id]
    session = async_get_clientsession(hass)
    github = GitHubAPI(session, "requester", oauth_token=config[CONF_ACCESS_TOKEN])
    sensors = [GitHubRepoSensor(github, repo) for repo in config[CONF_REPOS]]
    async_add_entities(sensors, update_before_add=True)
```

### 翻译

我简要创建了 [strings.json](https://github.com/boralyl/github-custom-component-tutorial/blob/master/custom_components/github_custom/strings.json) 用于解释错误是如何被定义的。这个文件包含了在配置流进程中被使用的字符串。我将 `strings.json` 文件复制到了 `translations` 文件夹下，并将其改为 `en.json` 表示因为翻译。你可以如你所想添加doge翻译文件，可以使用两个字符的 ISO
639-2 语言代码来命名。所有的字段必须和 `strings.json` 保持一致，其值为翻译后的字符串。举个例子，在我的组件里有另外的挪威语翻译：[nb.json](https://github.com/boralyl/steam-wishlist/blob/master/custom_components/steam_wishlist/translations/nb.json)。

获取更多在自定义组件中的翻译信息，查阅 [官方文档](https://developers.home-assistant.io/docs/internationalization/custom_integration/#translation-strings)。

### 单元测试

我想简要说一下如何对配置流进行单元测试。如果你安装和使用了 [pytest-homeassistant-custom-component](https://github.com/MatthewFlamm/pytest-homeassistant-custom-component)，你可以使用一些 pytest fixtures 来简化测试。

让我们看一看如果 GitHub access token 是非法的，如何进行验证并展示错误。

```python
@patch("custom_components.github_custom.config_flow.validate_auth")
async def test_flow_user_init_invalid_auth_token(m_validate_auth, hass):
    """Test errors populated when auth token is invalid."""
    m_validate_auth.side_effect = ValueError
    _result = await hass.config_entries.flow.async_init(
        config_flow.DOMAIN, context={"source": "user"}
    )
    result = await hass.config_entries.flow.async_configure(
        _result["flow_id"], user_input={CONF_ACCESS_TOKEN: "bad"}
    )
    assert {"base": "auth"} == result["errors"]
```

在这个测试中，我们模拟了 `validate_auth` 函数，并使之扔出 `ValueError` 错误。从 `pytest-homeassistant-custom-component` 的 pytest fixture 来的 `hass` 参数，传给我们的测试函数。首先在 `user` 中，我们通过指定 domain 和步骤初始化了一个流。我们接着在流中运行那个步骤并传递用户输入。结果中包含了一个 `errors` 字段，我们断言匹配异常。

### 下一步

通过这部分的代码，现在我们可以通过 UI 来设置和添加仓库，替代 `configuration.yaml` 文件。当你正在开发一个新的工作流，请确保在修改文件和重启 Home Assistant 之时，在浏览器中强制刷新。我注意到浏览器可以缓存信息，导致你看见过期的数据。

在我们的实现中，有一个明显的问题是，不通过初始化流的方式创建一个新的 config entry 的话，没法去移除或者添加新的仓库。虽然可以工作，但是非常不理想的是每次都得重新输入 GitHub access token。在下一篇文章中，我们将使用 [OptionsFlowHandler](https://developers.home-assistant.io/docs/config_entries_options_flow_handler) 去跳出这个限制。

---

## File: posts/zh/CSS中的position.md

[Binary file]

---

## File: posts/zh/Dart之旅.md

[Binary file]

---

## File: posts/zh/EBNF.md

---
title: EBNF
date: 2021-10-27 00:30:59
tags: [ 编译原理, EBNF ]
toc: true
categories: [ 语言 ]
---

## 扩展巴科斯范式

> 参考来源 [Wikipedia](https://zh.wikipedia.org/wiki/%E6%89%A9%E5%B1%95%E5%B7%B4%E7%A7%91%E6%96%AF%E8%8C%83%E5%BC%8F)

一种表达形式语言文法的代码, EBNF 定义了把各符号序列分别指派非终结符的**产生规则**

```ebnf
digit excluding zero = "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9";
digit = "0" | digit excluding zero;
```

定义左端非终结符 *digit*。竖杠表示可供选择, 终结符被 `"` 包围, 最后跟着 `;` 作为终止字符。

产生规则还可以包括由逗号分隔的一序列 *终结符* 或 *非终结符*

```ebnf
twelve = "1", "2";
two hundred one = "2", "0", "1";
three hundred twelve = "3", twelve;
twelve thousand two hundred one = twelve, two hundred one;
```

可以 **省略** 或 **重复** 的表达式可以通过花括号 `{...}` 表示: (大概相当于正则表达式的 `*`)

```ebnf
natural number = digit excluding zero, { digit };
```

在这种情况下, 字符串 1, 2, ..., 10, 12345, ... 都是正确的表达式。表示这种情况, 于花括号内设立的所有东西 *可以重复任意次*, 包括根本不出现。

可选项可以通过方括号 `[...]` 表示: (大概相当于正则表达式的 `?`)

```ebnf
integer = "0" | ["-"], natural number;
```

integer 是一个 *零(0)* 或可能 *前导可选的负号* 的一个自然数。

EBNF 还包括描述 **指定次数的重复**, 和 **排除产生的某部分** 或向 EBNF 文法 **插入注释** 的语法。

## 符号表

| 用途 | 符号表示 |
| :--- | :---: |
| 定义 | `=` |
| 串接 | `,` |
| 终止 | `;` |
| 分隔 | `\|` |
| 可选 | `[...]` |
| 重复 | `{...}` |
| 分组 | `(...)` |
| 双引号 | `"..."` |
| 单引号 | `'...'` |
| 注释 | `(*...*)` |
| 特殊序列 | `?...?` |
| 除外 | `-` |

## 约定

1. 使用如下约定:
   - EBNF 的每个元标识符否被写为 *连字号* 连接起来的一个或多个字；
   - 结束于 "-symbol" 的元标识符是 EBNF 的终结符的名字。
2. 表示 EBNF 的每个操作符的正常字符和它所蕴涵的 ***优先级***(顶部为**最高**优先级)为:

   ```txt
   * repetition-symbol
   - except-symbol
   , concatenate-symbol
   | definiton-separator-symbol
   = defining-symbol
   ; terminator-symbol
   ```

3. 下列 *括号对* **超越** 正常优先级:

    ```txt
    ‘first-quote-symbol  first-quote-symbol’
    "second-quote-symbol  second-quote-symbol"
    (*start-comment-symbol  end-comment-symbol*)
    (start-group-symbol  end-group-symbol)
    [start-option-symbol  end-option-symbol]
    {start-repeat-symbol  end-repeat-symbol}
    ?special-sequence-symbol  special-sequence-symbol?
    ```

举例:

```ebnf
aa = "A";
bb = 3 * aa, "B";
cc = 3 * [aa], "C";
dd = {aa}, "D";
ee = aa, {aa}, "E";
ff = 3 * aa, 3 * [aa], "F";
gg = {3 * aa}, "D";
```

定义的终结字符串:

```txt
aa: A
bb: AAAB
cc: C AC AAC AAAC
dd: D AD AAD AAAD ...
ee: AE AAE AAAE AAAAE ...
ff: AAAF AAAAF AAAAAF AAAAAAF
gg: D AAAD AAAAAAD ...
```

## EBNF & BNF

> BNF 有 **可选项** 和 **重复** 不能直接表达的问题。

可选项:

```ebnf
signed number = [sign, ] number;
```

BNF 风格:

```bnf
signed number = sign, number | number;
```

or

```ebnf
signed number = optional sign, number;
optional sign, = ε | sign, ; (*使用ε来清晰的指示空产生式*)
```

重复:

```ebnf
number = { digit } digit;
```

BNF风格:

```bnf
number = digit | number digit;
```

## EBNF 较 BNF 的优点

BNF 的缺陷:

- BNF 自身使用了符号(`<`, `>`, `|`, `::=`), 定义会产生误解。
- BNF 语法在一行只能表示一个规则。

EBNF 解决了这些问题:

- 终结符被严格包围在引号 (`"..."` 或 `'...'`) 中。给非终结符的尖括号 (`"<...>"`) 可以省略。
- 通常使用终止字符 **分号** 结束一个规则。

进一步提供了定义 **重复次数**, **排除法选择** 和 **注释** 等增强机制。

原理上, EBNF 任何文法都可以用 BNF 表达。

EBNF 已经被 ISO 以 *ISO/IEC 14977:1996(E)* 标准化。

在某些场合任何扩展的 BNF 都被称为 EBNF。例如 W3C 使用 [one EBNF](https://web.archive.org/web/20031203082847/http://www.w3c.org/TR/REC-xml#sec-notation) 来规定 XML。

## 扩展

根据 ISO 14977 标准, 提供了两个设施来扩展 EBNF。其一, 是指 EBNF 文法部分的特殊序列, 它是问号内包围内的任意文本, **其解释超出了 EBNF 标准的范围**。

```ebnf
space = ?US-ASCII character 32?
```

其二, **圆括号** 在 EBNF 中不能放置到紧跟标识符之后。

```ebnf
something = foo (bar); (*无效*)
```

EBNF 的扩展可以使用这种表示法。

---

## File: posts/zh/ECMAScript 中的 Symbol 类型.md

[Binary file]

---

## File: posts/zh/EMQ试用Log.md

---
title: EMQ试用Log
date: 2019-07-09 00:01:27
toc: true
tags: [ EMQ, emqx, 物联网, mqtt ]
---

## 什么是EMQ

EMQ是一款部署在服务端的物联网框架，支持主流的物联网协议。更多的介绍可以参考 [EMQ官网](https://www.emqx.io/cn/) 的介绍。



## 为什么要选择EMQ

> 各个框架的对比可以参考 [GitHub的一篇比较全面的对比文章](https://github.com/mqtt/mqtt.github.io/wiki/server-support)，其中的emqttd就是这里的EMQ。

EMQ国人开发的，因此在中文文档支持上会好很多，更容易去使用。EMQ提供了可视的数据监控台、大量的API和集成了一些插件，对于快速建立和部署服务端很友好。

## EMQ的安装

作者用的是Win10的系统，在开发上来说个人不是很推荐，但是做硬件和软件客户端的测试和学习还是很不错的！EMQ也提供了Win10的版本！！

官方的简介和安装可以直接参考文档 [开始使用](https://developer.emqx.io/docs/broker/v3/cn/getstarted.html#) 和 [程序安装](https://developer.emqx.io/docs/broker/v3/cn/install.html)

> Win10直接下载zip到非系统盘解压就完事了

## 程序的启动（Win10）

1. 进入文件夹的bin下
2. 打开Git Bash 或者 Windows PowerShell
3. ./emqx.cmd start 启动服务
4. ./emqx_ctl.cmd status 查看状态
5. ./emqx.cmd stop 停止服务

## 在线监控

> 浏览器打开localhost:18083即可进入DashBoard页面，默认用户名是admin，密码是public

---

## File: posts/zh/Hexo基础搭建使用.md

---
title: Hexo基础搭建使用
date: 2019-07-02 22:25:57
toc: true
tags: [ Hexo, nginx ]
---

## Hexo安装环境

* [Node.js](http://nodejs.org/) \([nvm](https://github.com/creationix/nvm)\)
* [Git](http://git-scm.com/)

> 推荐使用nvm，在实践之后发现nvm真的很好用，当然Windows用户可以直接下载安装

```shell
# 下载node.js
 nvm install stable
```



## 安装Hexo

> Linux和Mac用户直接使用下面的shell就完事了，Windows用户可以使用在相应文件夹下使用Window Powershell或者git bash然后shell，国内推荐用npm下载cnpm，代替npm来提高依赖的下载速度和稳定性！！（cnpm用的是淘宝的镜像）

```shell
# 下载cnpm
 npm install -g cnpm --registry=https://registry.npm.taobao.org
```

```shell
# 安装Hexo
 cnpm install -g hexo-cli
```

## 文章迁移插件的安装（没有需要的可以跳过）

Hexo官方支持的迁移来源

* [RSS](https://hexo.io/zh-cn/docs/migration#RSS)
* [Jekyll](https://hexo.io/zh-cn/docs/migration#Jekyll)
* [Octopress](https://hexo.io/zh-cn/docs/migration#Octopress)
* [WordPress](https://hexo.io/zh-cn/docs/migration#WordPress)
* [Joomla](https://hexo.io/zh-cn/docs/migration#Joomla)

## 初始化一个Hexo项目

```shell
# 初始化博客项目
 hexo init <项目名称>
 cd <项目名称>      # 打开项目文件夹

# 下载Hexo server
 cnpm install hexo-server --save

# 启动本地预览
 hexo server       # 默认http://localhost:4000
```

> 关于更改预览设置的请参考 [Hexo Server](https://hexo.io/zh-cn/docs/server)

## 修改Hexo的主题

这里以yilia为例（请保证网络环境）

```sh
# 确保在上面项目文件夹下
git clone https://github.com/litten/hexo-theme-yilia.git themes/yilia
```

> themes/yilia 也就是下载到项目文件夹下的themes/yilia目录下

进入themes/yilia下，修改_config.yml文件

```shell
# Linux的进入修改
 cd themes/yilia
 nano _config.yml
```

> Windows建议使用vscode打开编辑

```yml
# 修改内容
themes: yilia        # (上面下载位置文件夹名)
```

## 生成保存更改

> Hexo每次修改或者添加等操作都需要重新生成文件，生成的文件在工程根目录的public文件夹下

```shell
# 清除之前的文件
 hexo clean

# 生成新的文件
 hexo generate
# 或者
 hexo g

# 预览文件
 hexo server
# 或者
 hexo start
# 或者
 hexo s

```

> 在生成操作时可以使用--watch来比较文件的SHA1 checksum仅生成不同的文件

```shell
# 监视文件变动
 hexo generate --watch
```

一条命令也可以这样写

```shell
# 清除保存预览
 hexo clean && hexo g && hexo s
```

## Hexo的目录树

目录树在此就不赘述了，请参考Hexo官方介绍 [资源文件夹](https://hexo.io/zh-cn/docs/asset-folders) 和 [数据文件夹](https://hexo.io/zh-cn/docs/data-files)

> 作为仅markdown用户个人没有采取官方的引用方式而是使用了\! \[ \] \( \)

## Hexo新建文章

Hexo的文章放置在根目录的/sources/_post下

```shell
# 生成一篇md文章
 hexo new <文章名>
# 或者
 hexo n <文件名>
```

> 相关关于写作的具体操作参考 [写作](https://hexo.io/zh-cn/docs/writing)

## Hexo的部署

作为一个Git用户表示也习惯了git的使用，本文采取Gitpage来演示（其他参考 [官方教程](https://hexo.io/zh-cn/docs/deployment)）

### 首先得下载依赖（根目录下）

```shell
# 下载hexo-deployer-git
 cnpm install hexo-deployer-git --save
```

### 修改配置文件

修改根目录下的_config.yml

```yml
deploy:
    type: git
    repo: <Gitpage仓库地址>
    branch: master            # 默认的分支就是master
```

### 重新生成更新文件（同上）

### 发到部署仓库

```shell
# 部署命令
 hexo deploy
# 这里采取非监视的一条shell
 hexo clean && hexo g && hexo d
```

> 私有库要求输入用户名和密码，首次安装git请参考git的设置

## 私人云服务器Git仓库的搭建、nginx配置及部署

### 云Git仓库的建立

```shell
# 设置新的用户
 useradd git           # 添加git用户
 passwd git            # 设置git密码

# 修改权限文件
 chown 740 /etc/sudoers
 nano /etc/sudoers

# 添加内容
# 找到 root ALL=(ALL:ALL) ALL
# 下面添加
git ALL=(ALL:ALL) ALL

# 保存退出
# 修改权限
 chown 440 /etc/sudoers

# 关闭git用户shell权限
 nano /etc/passwd

# 将git:x:1001:1001:,,,:/home/git:/bin/bash改为
git:x:1001:1001:,,,:/home/git:/usr/bin/git-shell
```

在网上教程中会要求su git切换用户！！这里有一个坑，会报错的！！因为上面禁掉了git的shell权限！！可以不切换的

```shell
# 创建git目录
 cd /home/git                # 没有的进入home然后mkdir git
 mkdir blog.git              # 这里以blog.git为例建立git仓库
 git init --bare             # 创建空仓库

# 创建网站目录
 cd /var/www
 mkdir blog                  # 这里/var/www/blog作为网站源码的存放位置
```

网上教程在这里证书配置ssh免密码认证，个人没有采用这种方式（老老实实输密码）

用户组的管理

```shell
 ll /home/git/
 ll /var/www/
# 确保blog.git、blog目录的用户组权限为git:git
```

> 如果不是

```shell
# 执行
 chown git:git -R /var/www/blog
 chown git:git -R /home/git/blog.git
```

### 配置nginx

这里就不赘述nginx的参数意义和配置了，仅仅修改源码放置的配置！！！

```nginx
# 进入/etc/nginx/sites-available下
 nano default

# 修改配置文件的location下的root
# 举个例子

root /var/www/blog;
```

保存退出！然后重新加载nginx

```shell
# 重新加载nginx
 service nginx reload
```

### 配置Git Hooks

```shell
# 创建post-receive文件并添加内容
 cd /home/git/blog.git/hooks           # 切换目录

 nano post-receive
```

添加内容

```txt
#!/bin/bash
GIT_REPO=/home/git/blog.git
TMP_GIT_CLONE=/tmp/blog
PUBLIC_WWW=/var/www/blog
rm -rf ${TMP_GIT_CLONE}
git clone $GIT_REPO $TMP_GIT_CLONE
rm -rf ${PUBLIC_WWW}/*
cp -rf ${TMP_GIT_CLONE}/* ${PUBLIC_WWW}
```

> 上面内容可以按需要修改

保存退出之后，chmod +x post-receive赋予可执行权限

### 配置本地工程部署

上面是基于Gitpage配置的Hexo，在此只需要修改_config.yml的deploy下的repo

```yml
repo: git@你的服务器IP:blog.git               # git访问对应服务器的blog.git仓库
```

然后就是上面的部署操作就完成了！！

## yilia主题下文章预览设置more的操作

```md
<!-- 在你要中断文章结尾的位置添加下面的内容 -->

<!-- more -->
```

## 参考文档

* [官网文档](https://hexo.io/zh-cn/docs/)
* [简书博文](https://www.jianshu.com/p/b926ecf1c6f6)

---

## File: posts/zh/JavaScript中的ArrayBuffer.md

---
title: JavaScript中的ArrayBuffer
toc: true
tags: [ ArrayBuffer, JavaScript, ES6 ]
categories: []
date: 2021-11-17T01:16:10+08:00
---

## 写在前面的

在尝试用国密算法实现 jwt 的时候, 发现可以使用的库很少, 便根据 jwt 的生成规则和国密 SM3 算法自行参考实现。SM3 是国密算法中的哈希散列算法, 生成单向指纹校验数据有着重要的意义。在生成 jwt 的指纹部分, 尝试使用 HMACSM3 替代掉 HMACSHA256算法。

HMACSM3 可用的 JavaScript 库确实没发现, 参考了 Java 版本进行自行实现。其中涉及到了二进制数据的位运算, 并且 [byte-fe/gm-crypto](https://github.com/byte-fe/gm-crypto) 中的 SM3 的返回值类型为 `ArrayBuffer`, 因此觉得研究此 ES6 的新内置对象很有意义。

## 关于 ArrayBuffer

在 MDN 中, 对于 `ArrayBuffer` 对象表述为表示通用的、固定长度的原市二进制数据缓冲区, **在其他语言中被称为"byte array(字节数组)"**

在 golang 中, byte array 和 string 的类型转化很常见。byte array在不同语言中均有用处, 可以提高程序计算的性能。

JavaScript 的 `ArrayBuffer` 设计目的也同样如此, 需要注意的这又是一个 *array-like* object, 使用 TypedArray or DataView 对象进行操作。

## 语法

> new ArrayBuffer(length)

- 参数

| 参数     | 描述                                      |
| -------- | ----------------------------------------- |
| `length` | 要创建的 `ArrayBuffer` 的大小, 单位为字节 |

- 返回值

一个指定大小的 `ArrayBuffer` 对象, 其内容被初始化为 0

- 异常

如果 `length` ＞ [Number.MAX_SAFE_INTEGER](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER) (≥2**53) 或为 负数, 会抛 RangeError 异常。

## 属性

- `ArrayBuffer,length`

ArrayBuffer 构造函数的 length 属性, 其值为 1

- `ArrayBuffer.prototype.byteLength`

只读, 表示 `ArrayBuffer` 的 byte 大小, 在 ArrayBuffer 构造完成时生成, 不可更改。

## 方法

- `ArrayBuffer.isView(arg)`

如果参数是 ArrayBuffer 的视图实例则返回 `true`, 例如 TypedArray or DataView 对象；否则返回 `false`

- `ArrayBuffer.transfer(oldBuffer [, newByteLength])` ***实验性***

返回一个新的 ArrayBuffer 对象, 其内容取自 `oldBuffer` 中的数据, 并根据 `newByteLength` 的大小对数据进行截取或补 0

## 兼容性

就让 IE 消失于世间吧~

---

## File: posts/zh/JavaScript的import语法.md

[Binary file]

---

## File: posts/zh/Leetcode-160-相交链表.md

---
title: Leetcode-160-相交链表
date: 2021-03-25 18:51:59
toc: true
tags: [ 算法, 面试 ]
categories: [ Leetcode ]
---

## 相交链表

题目: [相交链表](https://leetcode-cn.com/problems/intersection-of-two-linked-lists/)

## 破题

一开始面对这题, 第一反应就是暴力去解题了。看了一眼题解, 双指针的解法特别的优雅。

如果两个单链表有相同节点的话, 那两个链表的指针必然 **headA --> headB** 和 **headB --> headA** 过程中有相遇, 相遇的时候节点就是交叉的节点。

举个栗子

```js
const headA = [4, 1, 8, 4, 5]

const headB = [5, 0, 1, 8, 4, 5]

// pA: 4 -> 1 -> 8 -> 4 -> 5 -> null -> 5 -> 0 -> 1 -> 8 -> 4 -> 5 -> null
// pB: 5 -> 0 -> 1 -> 8 -> 4 -> 5 -> null -> 4 -> 1 -> 8 -> 4 -> 5 -> null
```

## 题解

```js
/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */

/**
 * @param {ListNode} headA
 * @param {ListNode} headB
 * @return {ListNode}
 */
var getIntersectionNode = function(headA, headB) {
    if (headA === null || headB === null) return null
    let pA = headA, pB = headB
    while(pA !== pB) {
        pA = pA === null ? headB : pA.next
        pB = pB === null ? headA : pB.next
        console.log("pA", pA)
        console.log("pB", pB)
    }
    return pA
};

// 控制台输出:
// pA [1,8,4,5]
// pB [6,1,8,4,5]
// pA [8,4,5]
// pB [1,8,4,5]
// pA [4,5]
// pB [8,4,5]
// pA [5]
// pB [4,5]
// pA null
// pB [5]
// pA [5,6,1,8,4,5]
// pB null
// pA [6,1,8,4,5]
// pB [4,1,8,4,5]
// pA [1,8,4,5]
// pB [1,8,4,5]
// pA [8,4,5]
// pB [8,4,5]
```

---

## File: posts/zh/Leetcode-206-反转链表.md

---
title: Leetcode-206-反转链表
date: 2021-03-23 15:31:27
toc: true
tags: [ 算法, 面试 ]
categories: [ Leetcode ]
---

## 反转链表

题见: [反转链表](https://leetcode-cn.com/problems/reverse-linked-list/)

## 破题

```txt
输入: 1 -> 2 -> 3 -> 4 -> 5 -> NULL
输出: 5 -> 4 -> 3 -> 2 -> 1 -> NULL
```



注释给的定义为

```js
/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
```

题解中给的遍历很好理解, 即把`next`指向上一个, 而当前的节点是下一个的上一个节点, 需要定义一个变量`prev`储存为下一个节点使用的上一个节点(也就是当前节点), 最后的返回值必定为最后保存的`prev`(此时迭代已经越界)

## 题解

```js
/**
 * @param {ListNode} head
 * @return {ListNode}
 */
var reverseList = function(head) {
    let prev = null
    let curr = head  // 初始化当前节点
    while(curr) {
        const next = curr.next  // 储存下一个节点, 需要反转
        curr.next = prev  // 反转链表
        prev = curr  // 保存反转之后的链表
        curr = next  // 继续迭代
    }
    // 返回最后的最前
    return prev
}
```

---

## File: posts/zh/Leetcode-215-数组中的第K个最大元素.md

---
title: Leetcode-215-数组中的第K个最大元素
date: 2021-03-23 15:44:51
toc: true
tags: [ 算法, 面试 ]
categories: [ Leetcode ]
---

## 数组中的第K个最大元素

题见: [数组中的第K个最大元素](https://leetcode-cn.com/problems/kth-largest-element-in-an-array/)

## 破题

题目意思就是排序之后, 然后找到第几大的数字(不去重)



## 题解

其实整个题就两部分, 一个是排序, 另一个是取值

```js
/**
 * @param {number[]} nums
 * @param {number} k
 * @return {number}
 */
var findKthLargest = function(nums, k) {
    const afterSorted = nums.sort((a, b) => b - a)  // 先从大到小排序(不去重)
    return afterSorted[k - 1]  // 再返回值
}
```

> 需要注意的是, js的`sort()`函数排序是首字符的ASCII码, 如果数字超过个位数那排的结果是错的

从大到小排

```js
sort((a, b) => b - a)
```

从小到大排

```js
sort((a, b) => a - b)
```

---

## File: posts/zh/Leetcode-3-无重复字符的最长子串.md

[Binary file]

---

## File: posts/zh/M系列芯片MacOS初始化esp-idf环境指北.md

[Binary file]

---

## File: posts/zh/Nodejs安装若干问题避坑.md

---
title: Nodejs安装若干问题避坑
date: 2020-01-26 21:43:54
toc: true
tags: [ Nodejs , Frontend , 前端开发 , 环境搭建 , nvm ]
---

## nodejs的windows下传统安装

[nodejs官网](https://nodejs.org/en/)下载：LTS为长期支持版，Current是最新版

> 根据官网给的安装包一步步来即可安装

## nodejs的不同版本安装思考

由于平时可能涉及到的node版本不同导致所用的代码会出现各种各样的问题，node的版本管理工具切换node版本显得比较重要！在`Linux/Mac`端`nvm`很好的解决了版本管理的问题，在Windows端不可使用`nvm`进行管理！

Github项目[nvm-windows](https://github.com/coreybutler/nvm-windows)利用go语言开发解决了Windows环境下的nodejs管理问题！

> 下面对 `nodejs` 和 `node`, `nvm` 和 `nvm-windows` 不加以区分



## 配置安装nvm-windows

1. 首先建议`卸载`本地环境下的nodejs，以便更好地管理node版本

2. [releases](https://github.com/coreybutler/nvm-windows/releases)中下载最新的安装包

    > 建议使用: `nvm-setup.zip` 傻瓜式安装, `nvm-noinstall.zip` 为绿色版需要自行配置

3. 安装配置`nvm-windows`

    * `nvm` 安装路径可以自定义
    * symlink为超链接的目标目录，即用`nvm use`切换node版本时，本质上是切换链接的指向！symlink配置之后安装工具会把链接的地址加入系统环境变量之中，下面将会详述`nvm`的命令和具体使用！这个目录就当成node的安装目录就好了。

## nvm命令详解

* `nvm version`: 查询当前的nvm版本
* `nvm`: 列出所有的命令

    ```shell
    Running version 1.1.7.

    Usage:

    nvm arch                     : 显示node运行在32位还是在64位系统上
    nvm install <version> [arch] : 利用nvm下载node, 可以指定版本下载或者"latest"下载最新稳定版, 可选指定下载32/64位的版本, 默认与系统架构相同。
                                    arch参数为"all"则32/64位版本都下载, 命令后面添加--insecure可以跳过SSL检查下载
    nvm list [available]         : 列出所有的node下载, "nvm list available" 命令可以查看所有可下载的node版本, 别名ls
    nvm on                       : 允许进行node版本管理
    nvm off                      : 不允许进行node版本管理
    nvm proxy [url]              : 为下载配置代理, url参数为空打印当前代理, 参数为"none"删除代理
    nvm node_mirror [url]        : 设置node镜像, 默认是 https://nodejs.org/dist/ （国内可用: https://npm.taobao.org/mirrors/node/）
    nvm npm_mirror [url]         : 设置npm镜像, 默认是 https://github.com/npm/cli/archive/ （国内可用淘宝镜像: https://npm.taobao.org/mirrors/npm/）
    nvm uninstall <version>      : 卸载指定版本node
    nvm use [version] [arch]     : 切换指定使用的node版本, 可选指定32或64位架构, "arch"参数可选
    nvm root [path]              : 设置nvm管理的不同版本node的储存位置, 如果路径没有设置就打印当前nvm安装的目录（ps: 这个其实可以不用设置的）
    nvm version                  : 显示当前Windows环境下nvm的版本, 别名 v
    ```

## Win10一般配置nvm

```shell
# 设置64位架构
nvm arch 64

# 设置node镜像
nvm node_mirror https://npm.taobao.org/mirrors/node/

# 设置npm镜像
nvm npm_mirror https://npm.taobao.org/mirrors/npm/

# 列出所有可以下载的node
nvm list available

# 以12.14.1版本为例下载node
nvm install 12.14.1

# 显示所有已下载的node
nvm list

# 切换使用12.14.1
nvm use 12.14.1
```

## 需要注意的问题

1. `nvm`管理node时，利用npm/yarn全局下载的包只能是下载到当前使用版本的node的node_modules\node_global\node_modules下，如果切换node版本，依赖包是要重新下载的！

2. 由于yarn和npm全局下载的包在node_modules中的结构是不同的，yarn全局下载的包使用PowerShell跑的时候找不到命令，所以强烈建议全局下载例如Taro、Vue、React等的脚手架工具时使用npm做node的依赖包管理，而非yarn
3. 因为yarn可以多线程下载，下载包的速度比npm要快还能多，所以建议在项目中使用yarn进行包管理
4. 安装过程中出现的异常通常可以通过重启或者使用`管理员身份打开`解决

## npm/yarn包镜像源的管理

> 强烈建议使用`yrm`进行镜像源管理

```shell
# 下载yrm
npm install yrm -g

# 列出所有可以使用的镜像源
yrm ls

# 示例使用taobao镜像源
yrm use taobao
```

## yrm命令详解

> 命令行使用`yrm`即可显示所有的yrm命令

```shell
Usage: cli [options] [command]

Options:
  -V, --version                输出版本
  -h, --help                   输出使用信息

Commands:
  ls                           列出所有的仓库
  current                      展示当前的仓库名
  use <仓库名>                 使用指定的仓库
  add <仓库名> <url> [home]    添加一个自定义仓库
  del <仓库名>                 删除一个仓库
  home <仓库名> [browser]      用可选浏览器打开仓库首页
  test [仓库名]                展示指定仓库或者所有仓库的响应时间
  help                         打印帮助
```

---

## File: posts/zh/NumPy的一些点.md

[Binary file]

---

## File: posts/zh/Promises A+ 规范.md

[Binary file]

---

## File: posts/zh/React的WorkInProgressHook.md

---
title: React 的 WorkInProgressHook
date: 2024-5-4 14:43:57
toc: true
tags: [ react, hook ]
---

## 前言

React 在数年前全面转向函数式编程，并引入了 hook 的概念。卡颂的 [React 技术揭秘](https://react.iamkasong.com/) 是很多的前端开发者进一步了解 react 的入门资料，但随着 react 的迭代更新，其底层实现可能发生了一些变化。这个本篇文章即是在讨论 `useCallback` 和 `useMemo` 的差异上，去阅读 react 底层实现源码上发现的一些问题。React 的源码很长，将分片去研究 react 的底层实现逻辑。

> 本篇所使用的源码为 2024年5月4日的 react 主分支的实现。

## Hook 类型定义

```typescript
export type Hook = {
  memoizedState: any,
  baseState: any,
  baseQueue: Update<any, any> | null,
  queue: any,
  next: Hook | null,
};
```

## 相关变量声明

```typescript
// The work-in-progress fiber. I've named it differently to distinguish it from
// the work-in-progress hook.

// work-in-progress fiber，区分于work-in-progress hook。
let currentlyRenderingFiber: Fiber = (null: any);


// Hooks are stored as a linked list on the fiber's memoizedState field. The
// current hook list is the list that belongs to the current fiber. The
// work-in-progress hook list is a new list that will be added to the
// work-in-progress fiber.

// Hooks 被存储为 fiber 的 memoizedState 字段中的一个链表。
// current hook 列表是属于 current fiber 的列表。
// work-in-progress hook 列表是一个新的列表，将被添加到 work-in-progress fiber 中。
let currentHook: Hook | null = null;
let workInProgressHook: Hook | null = null;
```

## mountWorkInProgressHook

```typescript
function mountWorkInProgressHook(): Hook {
  const hook: Hook = {
    memoizedState: null,

    baseState: null,
    baseQueue: null,
    queue: null,

    next: null,
  };

  if (workInProgressHook === null) {
    // This is the first hook in the list
    // 列表中的第一个 hook
    currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
  } else {
    // Append to the end of the list
    // 追加到列表的末尾
    workInProgressHook = workInProgressHook.next = hook;
  }
  return workInProgressHook;
}
```

## updateWorkInProgressHook

```typescript
function updateWorkInProgressHook(): Hook {
  // This function is used both for updates and for re-renders triggered by a
  // render phase update. It assumes there is either a current hook we can
  // clone, or a work-in-progress hook from a previous render pass that we can
  // use as a base.
  // 这个函数既用于更新，也用于由渲染阶段更新触发的重新渲染。
  // 它假设有一个可以 clone 的 current hook，或者一个之前渲染的 work-in-progress hook，可以作为基础。
  let nextCurrentHook: null | Hook;
  if (currentHook === null) {
    const current = currentlyRenderingFiber.alternate; // WIP Fiber 的 alternate 属性指向 current Fiber
    if (current !== null) {
      nextCurrentHook = current.memoizedState;
    } else {
      nextCurrentHook = null;
    }
  } else {
    nextCurrentHook = currentHook.next;
  }

  let nextWorkInProgressHook: null | Hook;
  if (workInProgressHook === null) {
    nextWorkInProgressHook = currentlyRenderingFiber.memoizedState;  // WIP Fiber 的 memoizedState 属性指向 work-in-progress hook
  } else {
    nextWorkInProgressHook = workInProgressHook.next;
  }

  if (nextWorkInProgressHook !== null) {
    // There's already a work-in-progress. Reuse it.
    // 有已经存在的 work-in-progress。复用它。
    workInProgressHook = nextWorkInProgressHook;
    nextWorkInProgressHook = workInProgressHook.next;

    currentHook = nextCurrentHook;
  } else {
    // Clone from the current hook.

    if (nextCurrentHook === null) {
      const currentFiber = currentlyRenderingFiber.alternate;
      if (currentFiber === null) {
        // This is the initial render. This branch is reached when the component
        // suspends, resumes, then renders an additional hook.
        // Should never be reached because we should switch to the mount dispatcher first.
        // 这是初始渲染。这个分支在组件挂起、恢复后，再渲染一个额外的 hook 时被触发。
        // 理论上永远不会被触发，因为我们应该先切换到 mount dispatcher。
        throw new Error(
          'Update hook called on initial render. This is likely a bug in React. Please file an issue.',
        );
      } else {
        // This is an update. We should always have a current hook.
        // 这是更新。我们应该总是有 current hook。
        throw new Error('Rendered more hooks than during the previous render.');
      }
    }

    currentHook = nextCurrentHook;

    const newHook: Hook = {
      memoizedState: currentHook.memoizedState,

      baseState: currentHook.baseState,
      baseQueue: currentHook.baseQueue,
      queue: currentHook.queue,

      next: null,
    };

    if (workInProgressHook === null) {
      // This is the first hook in the list.
      // 这是列表中的第一个 hook。
      currentlyRenderingFiber.memoizedState = workInProgressHook = newHook;
    } else {
      // Append to the end of the list.
      // 追加到列表的末尾。
      workInProgressHook = workInProgressHook.next = newHook;
    }
  }
  return workInProgressHook;
}
```

---

## File: posts/zh/TheTEXbook知识点总结.md

[Binary file]

---

## File: posts/zh/Ubuntu环境安装docker记录.md

---
title: Ubuntu环境安装docker记录
date: 2020-01-28 13:26:34
toc: true
tags: [ docker, Ubuntu19, Linux, 记录 ]
---

## 本地环境

* Ubuntu系统版本: Ubuntu19.10
* 软件源: 清华大学Tuna
* 虚拟机环境: VMware 15

## 新装Ubuntu基本配置

```shell
# 配置软件源
sudo nano /etc/apt/sources.list
```

```txt
# 首先注释掉所有的默认软件源
# 添加清华源 (演示系统为Ubuntu19.10, 软件代号为eoan! )

# 默认注释了源码镜像以提高 apt update 速度，如有需要可自行取消注释
deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ eoan main restricted universe multiverse
# deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ eoan main restricted universe multiverse
deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ eoan-updates main restricted universe multiverse
# deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ eoan-updates main restricted universe multiverse
deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ eoan-backports main restricted universe multiverse
# deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ eoan-backports main restricted universe multiverse
deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ eoan-security main restricted universe multiverse
# deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ eoan-security main restricted universe multiverse

# 预发布软件源，不建议启用
# deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ eoan-proposed main restricted universe multiverse
# deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ eoan-proposed main restricted universe multiverse
```

> Ubuntu各版本代号简介请参考: [传送门](https://blog.csdn.net/zhengmx100/article/details/78352773), 不同版本对应的软件清华源: [传送门](https://mirrors.tuna.tsinghua.edu.cn/help/ubuntu/)

```shell
sudo apt update
sudo apt upgrade
```

## 安装docker社区版

安装docker可以参考docker官网的[Get Docker Engine - Community for Ubuntu](https://docs.docker.com/install/linux/docker-ce/ubuntu/), 也可以使用清华源镜像, 下面是参考*docker官网*和*清华源*对于安装过程的总结

docker支持的Ubuntu版本为:

* Disco 19.04
* Cosmic 18.10
* Bionic 18.04 (LTS)
* Xenial 16.04 (LTS)

> 社区版支持的架构为: `x86_64(amd64)`, `armhf`, `arm64`, `s390x`, `ppc64le`!**Eoan 19.10软件源并不受支持!因此官网和清华源的命令在Ubuntu19.10下并不会成功!**报错请参考: [分享Ubuntu19无法安装docker源问题](https://www.jb51.net/article/173316.htm)

### 卸载旧版本的docker

```shell
sudo apt-get remove docker docker-engine docker.io containerd runc
```

> docker社区版新版本名称为: docker-ce

### 更新软件源

```shell
sudo apt update
```

### 下载依赖

```shell
sudo apt-get install \
    apt-transport-https \
    ca-certificates \
    curl \
    gnupg-agent \
    software-properties-common
```

### 添加docker官方的GPG key

```shell
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
```

### 验证指纹信息

```shell
sudo apt-key fingerprint 0EBFCD88

# 输出
pub   rsa4096 2017-02-22 [SCEA]
      9DC8 5822 9FC7 DD38 854A  E2D8 8D81 803C 0EBF CD88
uid           [ unknown] Docker Release (CE deb) <docker@docker.com>
sub   rsa4096 2017-02-22 [S]
```

### 设置软件源

> 这一步很关键！

* 官网命令

```shell
sudo add-apt-repository \
   "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
   $(lsb_release -cs) \
   stable"
```

* 清华源命令

```shell
sudo add-apt-repository \
   "deb [arch=amd64] https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/ubuntu \
   $(lsb_release -cs) \
   stable"
```

> 上面的命令会在`/etc/apt/sources.list`文件中添加下面的内容(以清华源为例), 执行之后会报上面提到的错误

```txt
deb [arch=amd64] https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/ubuntu eoan stable
# deb-src [arch=amd64] https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/ubuntu eoan stable
```

> 正确的做法是手动更改添加`Disco 19.04`的docker软件源！

```shell
sudo nano /etc/apt/sources.list
```

```txt
deb [arch=amd64] https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/ubuntu disco stable
# deb-src [arch=amd64] https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/ubuntu disco stable
```

保存之后执行更新和下载

```shell
sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io
```

> 指定版本下载，列出软件源所有可下载的版本可以参考官网的[To install a specific version of Docker Engine - Community](https://docs.docker.com/install/linux/docker-ce/ubuntu/#install-docker-engine---community-1), 用包下载安装可以参考[Install from a package](https://docs.docker.com/install/linux/docker-ce/ubuntu/#install-from-a-package)

### 验证docker安装

通过跑测试镜像`hello-world`验证

```shell
sudo docker run hello-world
```

> 可能会因为网络的原因报错, 参考这篇帖子: [docker pull error pulling image configuration](https://blog.csdn.net/anjie5595/article/details/101501786)

报错解决

```shell
# 停止docker服务
systemctl stop docker
# 修改daemon配置文件添加加速源
sudo nano /etc/docker/daemon.json
```

```json
{
　　"registry-mirrors": ["https://registry.docker-cn.com","http://hub-mirror.c.163.com"]
}
```

```shell
# 重载配置
systemctl daemon-reload
# 重启docker服务
systemctl restart docker
```

> 测试成功信息

```shell
Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (amd64)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 \$ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
 https://hub.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/get-started/

```

### docker更新

直接使用`sudo apt upgrade`即可

---

## File: posts/zh/unist.md

---
title: unist
date: 2024-5-13 00:30:37
toc: true
tags: [ unist, unifiedjs, ast, mdast ]
---

## 写在前面的

本篇文章是对 [unist](https://github.com/syntax-tree/unist) 仓库的官方内容翻译，原文地址: <https://github.com/syntax-tree/unist>。

![unist][logo]

unist, 即: **Uni**versal **S**yntax **T**ree.

**unist** 是一个语法树规范。在 JavaScript 处理语法树上，拥有一个强大的 [工具集生态系统][list-of-utilities]。它的实现基于一些其他的规范。

这篇文档可能不会被发布。参考 [releases][] 页面获取已发布的文档。最新发布版本为 [`3.0.0`][release]。

## 介绍

这篇文章定义了一个通用的语法树格式。unist 的开发开始于 2015 年 7 月。本篇规范使用类 [Web IDL][webidl] 的语法进行书写。

### 语法树

语法树是源代码或自然语言的表示。这些树是抽象的，使得它可以分析、转换和生成代码。

语法树有两种 [形式][abstract-vs-concrete-trees]：

* **具象语法树**：表示每个细节（例如，空格不敏感语言中的空格）的结构。
* **抽象语法树**：只表示代码的语法结构相关的细节（例如，忽略语言是否都支持使用双引号或单引号，如 JavaScript）。

本规范可以同时表示抽象和具象语法树。

### 本规范的适用场景

unist 并不趋向于自给自足。

相反，期望其他规范实现 unist 并扩展它以表示语言特定节点。

举个例子，请参阅如 **[hast][]** (HTML)、**[nlcst][]** (自然语言)、**[mdast][]** (Markdown) 和 **[xast][]** (XML) 等项目。

unist 与 [JSON][] 有关，因为兼容语法树可以完全被 JSON 表示。
然而，unist 并不受限于 JSON，可以表示其他数据格式，如 [XML][]。

unist 与 [JavaScript][] 有关，因为它拥有丰富的 [工具集生态系统][list-of-utilities]，可通过 JavaScript 处理兼容语法树。
五个被使用最多的工具集加起来每个月会被下载三千万次。
然而，unist 并不受限于 JavaScript，可以用于其他编程语言。

unist 与 [unified][]、[remark][]、[rehype][]、[retext][]、[vfile][] 有关，因为它们都在其生态系统中使用 unist 语法树。

unist 与 [vfile][] 有关，因为它接收 unist 节点作为消息存储，并且 vfile 可以作为语法树的源 *[文件][term-file]*。

## 类型

如果你正在使用 TypeScript，你可以通过 npm 安装 unist 类型：

```sh
npm install @types/unist
```

## 节点(Nodes)

unist 语法树中的句法单元叫做节点(nodes)，它实现了 **[Node][dfn-node]** 接口。

### `Node`

```idl
interface Node {
  type: string
  data: Data?
  position: Position?
}
```

`type` 字段是一个非空字符串，表示节点的变体。
这个字段可以被用作确定 *[类型定义][term-type]* 一个节点实现。

`data` 字段表示来源于生态的信息。
`data` 字段的值实现 **[Data][dfn-data]** 接口。

`position` 字段表示节点在源码文件中的位置。
`position` 字段的值实现 **[Position][dfn-position]** 接口。
如果一个节点是被 *[生成][term-generated]* 的，则 `position` 字段一定不存在。

规范实现 unist 时，被鼓励去定义更多的字段。
生态可以在 **[Data][dfn-data]** 接口上定义字段。

unist 的任何值 **必须** 被表示为 JSON 值：`string`，`number`，`object`，`array`，`true`，`false`，或 `null`。
这意味着语法树应该能够被转换为和生成 JSON，并产生相同的树。
例如，在 JavaScript 中，一个树可以通过 `JSON.parse(JSON.stringify(tree))` 转换为 JSON 并产生相同的树。

#### `Position`

```idl
interface Position {
  start: Point
  end: Point
}
```

**Position** 表示一个节点在源码 *[文件][term-file]* 中的位置。

**Position** 中的 `start` 字段表示解析后的源码区域的第一个字符的位置。
`start` 字段的值实现 **[Point][dfn-point]** 接口。

在源码 *[文件][term-file]* 被解析的时候，如果由节点表示的句法单元不存在，则节点被称为 *[被生成][term-generated]*，并且它必须不应该有位置信息。

举个例子，如果下面的值被表示为 unist:

```markdown
alpha
bravo
```

第一个单词 (`alpha`) 开始于第 `1` 行，第 `1` 列，第 `0` 个偏移，并结束于第 `1` 行，第 `6` 列，第 `5` 个偏移。
换行则开始于第 `1` 行，第 `6` 列，第 `5` 个偏移，结束于第 `2` 行，第 `1` 列，第 `6` 个偏移。
最后一个单词 (`bravo`) 开始于第 `2` 行，第 `1` 列，第 `6` 个偏移，并结束于第 `2` 行，第 `6` 列，第 `11` 个偏移。

#### `Point`

```idl
interface Point {
  line: number >= 1
  column: number >= 1
  offset: number >= 0?
}
```

**Point** 代表 *[源文件][term-file]* 中的一个位置。

`line` 字段（1开始索引）代表源文件中的一行。
`column` 字段（1开始索引）代表源文件中的一列。
`offset` 字段（0开始索引）代表源文件中的一个字符。

术语字符表示在 [Web IDL][webidl] 中的一个 (UTF-16) 编码单元。

#### `Data`

```idl
interface Data { }
```

**Data** 表示与生态系统相关的信息。

此空间永远不会被 unist 或者实现 unist 的规范指定。

### `Parent`

```idl
interface Parent <: Node {
  children: [Node]
}
```

节点包含其他节点（被称为 *[子节点][term-child]*）继承抽象接口 **Parent**（**[Node][dfn-node]**）。

`children` 字段表示一个节点的子节点列表。

### `Literal`

```idl
interface Literal <: Node {
  value: any
}
```

节点包含一个值，继承抽象接口 **Literal** (**[Node][dfn-node]**)。

`value` 字段可以包含任何值。

## 术语

### 树

**树** 是一个包含它的所有 *[后代][term-descendant]* 的节点。

### 子节点

如果 Y 的 `children` 包含 X，则 X 是 Y 的 **子节点**。

### 父节点

如果 Y 是 X 的一个 *[子节点][term-child]*，则 Y 是 X 的 **父节点**。

### 索引

子节点的 **索引** 是它 *[兄弟节点][term-sibling]* 之前的数字，如果没有的话则为 `0`。

### 兄弟节点

如果 X 和 Y 有相同的 *[父节点][term-parent]*，则 X 和 Y 是 **兄弟节点**。

*[子节点][term-child]* 的 **前兄弟节点** 是它的 **兄弟节点** *[索引][term-index]* 减 1 的那个。

*[子节点][term-child]* 的 **后兄弟节点** 是它的 **兄弟节点** *[索引][term-index]* 加 1 的那个。

### 根节点

如果没有 *[父节点][term-parent]* 或它的 *[父节点][term-parent]* 的 **根节点**，则 **根节点** 为它自身。

*[树][term-tree]* 的 **根节点** 是 *[树][term-tree]* 没有 *[父节点][term-parent]* 的那个节点。

### 后代

如果 X 是 Y 的一个 *[子节点][term-child]*，那么节点 X 是 Y 的 **后代**。
或者如果 X 是 Z 的一个 *[子节点][term-child]*，且 Z 是 Y 的一个 **后代**，则 X 是 Y 的 **后代**。

一个 **包含后代** 是一个节点，或者是它的 **后代** 之一。

### 祖先

如果 Y 是 X 的一个 *[后代][term-descendant]*，则节点 X 是节点 Y 的 **祖先**。

一个 **包含祖先** 是一个节点，或者是它的 **祖先** 之一。

### 头节点

节点的 **头节点** 是它的第一个 *[子节点][term-child]*。

### 尾节点

节点的 **尾节点** 是它的最后一个 *[子节点][term-child]*。

### 叶节点

**叶节点** 是没有 *[子节点][term-child]* 的节点。

### 分支节点

**分支节点** 是有至少一个 *[子节点][term-child]* 的节点。

### 被生成节点

**被生成节点** 是没有 *[位置信息][term-positional-info]* 的节点。

### 类型

节点的 **类型** 是它的 `type` 字段的值。

### 位置信息

节点的 **位置信息** 是它的 `position` 字段的值。

### 文件

**文件** 是一个源文档，代表被解析生成语法树的原始文件。
*[位置信息][term-positional-info]* 表示节点在这个文件中的位置。
文件由主机环境提供，并不由 unist 定义。

举个例子，参考如 **[vfile][]** 项目。

### 前序遍历

在 [深度优先][traversal-depth] [树遍历][traversal] 中，**前序遍历** 为每个节点 *N* 实现以下步骤：

1. **N**: 访问 *N* 自身
2. **L**: 访问 *[头节点][term-head]* (然后是 *下一个兄弟节点*，递归向前移动直到 *尾节点*)
3. **R**: 遍历 *[尾节点][term-tail]*

### 后序遍历

在 [深度优先][traversal-depth] [树遍历][traversal] 中，**后序遍历** 为每个节点 *N* 实现以下步骤：

1. **L**: 遍历 *[头节点][term-head]* (然后是 *下一个兄弟节点*，递归向前移动直到 *尾节点*)
2. **R**: 遍历 *[尾节点][term-tail]*
3. **N**: 访问 *N* 自身

### 进入

**进入** 就是在当 **[遍历][traversal]** 树时，在对给定节点 *N* 执行其他步骤之前，进行的步骤。

举个例子，当执行 *前序* 遍历时，**进入** 是在访问 *N* 自身之前进行的第一步。

### 离开

**离开** 就是在当 **[遍历][traversal]** 树时，在对给定节点 *N* 执行其他步骤之后，进行的步骤。

举个例子，当执行 *前序* 遍历时，**离开** 是在访问 *N* 的 *[尾节点][term-tail]* 之后进行的最后一步。

## 遍历树

当处理 *[树][term-tree]* 的查询时，**树的遍历** 是一项常规任务。

树的遍历可以分为两种：*广度优先遍历* 和 *深度优先遍历*。

在下面的例子中，我们将使用下面的树：

```mermaid
graph TD
    A-->B-->C
        B-->D
        B-->E
    A-->F-->G
```

### 广度优先遍历

**广度优先遍历** 是在遍历 *[子节点][term-child]* 之前，访问一个节点并扩大搜索范围到他在同一层级的所有 *[兄弟节点][term-sibling]*。

在图中定义的语法树，广度优先遍历首先搜索树的根节点（**A**），然后是它的子节点（**B** 和 **F**），然后是它们的子节点（**C**、**D**、**E** 和 **G**）。

### 深度优先遍历

取而代之的，更加常用的是 **深度优先遍历**。
搜索是首先是通过遍历 *[子节点][term-child]* 进行深入，然后再遍历它的 *[兄弟节点][term-sibling]*。

在图中定义的语法树，深度优先遍历首先搜索树的根节点（**A**），然后是它的子节点其中之一（**B** 或 **F**），然后是它们的子节点（**C**、**D**、**E** 或 **G**）。

在给定的有 *[子组件][term-child]* 的节点 *N*，一个 **深度优先遍历** 的实现有三步，简化到二叉树（每个节点都有 *[头节点][term-head]* 和 *[尾节点][term-tail]*，但没有其他子节点）：

* **N**: 访问 *N* 本身
* **L**: 遍历 *[头节点][term-head]*
* **R**: 遍历 *[尾节点][term-tail]*

这些步骤可以按任何顺序执行，但对于非二叉树，**L** 和 **R** 同时发生。
如果 **L** 先于 **R** 执行，则遍历被称为 *左到右* 遍历，否则为 *右到左* 遍历。

对于非二叉树，*头节点* 和 *尾节点* 之间的其他子节点也会按相同顺序处理，因此对于 *左到右* 遍历，首先遍历 *头节点* (**L**)，然后遍历它的 *下一个兄弟节点*，依此类推，最后遍历 *尾节点* (**R**)。

因为对于非二叉树来说，**L** 和 **R** 同时发生，所以我们可以产生四种类型的遍历顺序：NLR、NRL、LRN、RLN。

NLR 和 LRN（两种 *左到右* 遍历选项）是最常用的，分别称为 *[前序遍历][term-preorder]* 和 *[后序遍历][term-postorder]*。

对于图中定义的语法树而言，*前序* 和 *后序* 遍历首先寻找树的根节点（**A**），然后是它的头节点（**B**），然后是它从左到右顺序的子节点（**C**、**D**、**E**）。
在所有 **B** 的 *[后代][term-descendant]* 被遍历之后，它的下一个兄弟节点（**F**）被遍历，然后是它的唯一子节点（**G**）。

## 工具集

**工具集** 是作用节点的函数。

他们是处理 unist 规范实现节点的一些项目：

* [hast 工具集](https://github.com/syntax-tree/hast#list-of-utilities)
* [mdast 工具集](https://github.com/syntax-tree/mdast#list-of-utilities)
* [nlcst 工具集](https://github.com/syntax-tree/nlcst#list-of-utilities)
* [xast 工具集](https://github.com/syntax-tree/xast#list-of-utilities)

### 工具集列表

* [`unist-util-ancestor`](https://github.com/gorango/unist-util-ancestor)
    — 获取一个或多个节点的共同祖先
* [`unist-util-assert`](https://github.com/syntax-tree/unist-util-assert)
    — 断言节点
* [`unist-util-filter`](https://github.com/syntax-tree/unist-util-filter)
    — 使用所有的节点创建一个新的树，并传递一个给定的函数
* [`unist-util-find`](https://github.com/blahah/unist-util-find)
    — 通过条件寻找节点
* [`unist-util-find-after`](https://github.com/syntax-tree/unist-util-find-after)
    — 寻找另一个节点之后的节点
* [`unist-util-find-all-after`](https://github.com/syntax-tree/unist-util-find-all-after)
    — 寻找另一个节点或坐标之后的所有节点
* [`unist-util-find-all-before`](https://github.com/syntax-tree/unist-util-find-all-before)
    — 寻找另一个节点或坐标之前的所有节点
* [`unist-util-find-all-between`](https://github.com/mrzmmr/unist-util-find-all-between)
    — 寻找两个节点或坐标之间的所有节点
* [`unist-util-find-before`](https://github.com/syntax-tree/unist-util-find-before)
    — 寻找另一个节点之前的节点
* [`unist-util-flat-filter`](https://github.com/unicorn-utterances/unist-util-flat-filter)
    — `unist-util-filter` 的扁平化映射版本
* [`unist-util-flatmap`](https://gitlab.com/staltz/unist-util-flatmap)
    — 通过向一个节点拓展的方式创建一个新的树
* [`unist-util-generated`](https://github.com/syntax-tree/unist-util-generated)
    — 检查一个节点是否是被生成的
* [`unist-util-index`](https://github.com/syntax-tree/unist-util-index)
    — 通过属性或者计算的键来索引节点
* [`unist-util-inspect`](https://github.com/syntax-tree/unist-util-inspect)
    — 节点监测工具
* [`unist-util-is`](https://github.com/syntax-tree/unist-util-is)
    — 检查一个节点是否通过测试
* [`unist-util-map`](https://github.com/syntax-tree/unist-util-map)
    — 通过映射节点创建一棵树
* [`unist-util-modify-children`](https://github.com/syntax-tree/unist-util-modify-children)
    — 修改父节点中的直接子节点
* [`unist-util-parents`](https://github.com/syntax-tree/unist-util-parents)
    — 节点的 `父节点` 引用
* [`unist-util-position`](https://github.com/syntax-tree/unist-util-position)
    — 获取节点的坐标信息
* [`unist-util-reduce`](https://github.com/GenerousLabs/unist-util-reduce)
    — 递归 reduce 一棵树
* [`unist-util-remove`](https://github.com/syntax-tree/unist-util-remove)
    — 从树中移除节点
* [`unist-util-remove-position`](https://github.com/syntax-tree/unist-util-remove-position)
    — 移除树中的坐标信息
* [`unist-util-replace-all-between`](https://github.com/unicorn-utterances/unist-util-replace-all-between)
    — 在两个节点或者坐标之间替换节点
* [`unist-util-select`](https://github.com/syntax-tree/unist-util-select)
    — 通过类 CSS 选择器的方式选择节点
* [`unist-util-size`](https://github.com/syntax-tree/unist-util-size)
    — 计算一颗树的节点数量
* [`unist-util-source`](https://github.com/syntax-tree/unist-util-source)
    — 获取文件中值的原始数据（节点或者坐标）
* [`unist-util-stringify-position`](https://github.com/syntax-tree/unist-util-stringify-position)
    — 字符串化一个节点、位置或点
* [`unist-util-visit`](https://github.com/syntax-tree/unist-util-visit)
    — 递归遍历所有节点
* [`unist-util-visit-parents`](https://github.com/syntax-tree/unist-util-visit-parents)
    — 通过一堆父节点递归访问节点
* [`unist-util-visit-children`](https://github.com/syntax-tree/unist-util-visit-children)
    — 访问父节点的直接子节点
* [`unist-util-visit-all-after`](https://github.com/mrzmmr/unist-util-visit-all-after)
    — 访问另一个节点之后的节点
* [`unist-builder`](https://github.com/syntax-tree/unist-builder)
    — 创建树的辅助工具

## 参考材料

* **JavaScript**:
    [ECMAScript Language Specification][javascript].
    Ecma International.
* **JSON**:
    [The JavaScript Object Notation (JSON) Data Interchange Format][json],
    T. Bray.
    IETF.
* **XML**:
    [Extensible Markup Language][xml],
    T. Bray, J. Paoli, C. Sperberg-McQueen, E. Maler, F. Yergeau.
    W3C.
* **Web IDL**:
    [Web IDL][webidl],
    C. McCormack.
    W3C.

## 致谢

最初发行版的作者是：**[@wooorm](https://github.com/wooorm)**。

特别感谢 **[@eush77](https://github.com/eush77)** 的工作，想法和令人难以置信的有价值的反馈！
感谢 **[@anandthakker](https://github.com/anandthakker)**,
**[@anko](https://github.com/anko)**,
**[@arobase-che](https://github.com/arobase-che)**,
**[@azu](https://github.com/azu)**,
**[@BarryThePenguin](https://github.com/BarryThePenguin)**,
**[@ben-eb](https://github.com/ben-eb)**,
**[@blahah](https://github.com/blahah)**,
**[@blakeembrey](https://github.com/blakeembrey)**,
**[@brainkim](https://github.com/brainkim)**,
**[@ChristianMurphy](https://github.com/ChristianMurphy)**,
**[@davidtheclark](https://github.com/davidtheclark)**,
**[@denysdovhan](https://github.com/denysdovhan)**,
**[@derhuerst](https://github.com/derhuerst)**,
**[@dozoisch](https://github.com/dozoisch)**,
**[@fazouane-marouane](https://github.com/fazouane-marouane)**,
**[@gibson042](https://github.com/gibson042)**,
**[@hrajchert](https://github.com/hrajchert)**,
**[@ikatyang](https://github.com/ikatyang)**,
**[@inklesspen](https://github.com/inklesspen)**,
**[@izumin5210](https://github.com/izumin5210)**,
**[@jasonLaster](https://github.com/jasonLaster)**,
**[@JDvorak](https://github.com/JDvorak)**,
**[@jlevy](https://github.com/jlevy)**,
**[@justjake](https://github.com/justjake)**,
**[@kmck](https://github.com/kmck)**,
**[@kt3k](https://github.com/kt3k)**,
**[@KyleAMathews](https://github.com/KyleAMathews)**,
**[@luca3m](https://github.com/luca3m)**,
**[@mattdesl](https://github.com/mattdesl)**,
**[@muraken720](https://github.com/muraken720)**,
**[@mrzmmr](https://github.com/mrzmmr)**,
**[@nwtn](https://github.com/nwtn)**,
**[@rhysd](https://github.com/rhysd)**,
**[@Rokt33r](https://github.com/Rokt33r)**,
**[@Sarah-Seo](https://github.com/Sarah-Seo)**,
**[@sethvincent](https://github.com/sethvincent)**,
**[@shawnbot](https://github.com/shawnbot)**,
**[@simov](https://github.com/simov)**,
**[@staltz](https://github.com/staltz)**,
**[@TitanSnow](https://github.com/TitanSnow)**,
**[@tmcw](https://github.com/tmcw)**,
和
**[@vhf](https://github.com/vhf)**,
为 unist 和相关项目作出的贡献！

## 许可证

[CC-BY-4.0][license] &copy; [Titus Wormer][author]



[logo]: https://raw.githubusercontent.com/syntax-tree/unist/367da2e/logo.svg?sanitize=true

[releases]: https://github.com/syntax-tree/unist/releases

[license]: https://creativecommons.org/licenses/by/4.0/

[author]: https://wooorm.com

[release]: https://github.com/syntax-tree/unist/releases/tag/3.0.0

[abstract-vs-concrete-trees]: https://eli.thegreenplace.net/2009/02/16/abstract-vs-concrete-syntax-trees/

[dfn-node]: #node

[dfn-position]: #position

[dfn-point]: #point

[dfn-data]: #data

[term-tree]: #树

[term-preorder]: #前序遍历

[term-postorder]: #后序遍历

[term-child]: #子节点

[term-parent]: #父节点

[term-index]: #索引

[term-sibling]: #兄弟节点

[term-descendant]: #后代

[term-head]: #头节点

[term-tail]: #尾节点

[term-generated]: #被生成节点

[term-type]: #类型

[term-positional-info]: #位置信息

[term-file]: #文件

[traversal]: #遍历树

[traversal-depth]: #深度优先遍历

[list-of-utilities]: #工具集列表

[webidl]: https://webidl.spec.whatwg.org

[json]: https://datatracker.ietf.org/doc/html/rfc7159

[xml]: https://www.w3.org/TR/xml/

[javascript]: https://262.ecma-international.org/9.0/

[hast]: https://github.com/syntax-tree/hast

[xast]: https://github.com/syntax-tree/xast

[nlcst]: https://github.com/syntax-tree/nlcst

[mdast]: https://github.com/syntax-tree/mdast

[unified]: https://github.com/unifiedjs/unified

[remark]: https://github.com/remarkjs/remark

[rehype]: https://github.com/rehypejs/rehype

[retext]: https://github.com/retextjs/retext

[vfile]: https://github.com/vfile/vfile

---

## File: posts/zh/Verilog-HDL基础语法（上）.md

[Binary file]

---

## File: posts/zh/Verilog-HDL基础语法（下）.md

---
title: Verilog-HDL基础语法（下）
date: 2019-07-19 21:50:01
toc: true
tags: [ Verilog, HDL, 硬件开发, 数字电子技术, FPGA ]
---

## 过程块

* initial块，只执行一次
* always块，循环执行

过程块中的部件：

* 过程赋值语句
* 高级结构（循环，条件语句）
* 时序控制



## 过程赋值

过程赋值的对象必须是寄存器类型，过程赋值语句给wire赋值会产生错误！！

## 过程时序控制

* 简单延时(\#delay)：延时指定时间之后执行
* 边沿敏感的时序控制：\@(\<signal\>)，上升沿(posedge)，下降沿(negedge)，用关键字or指定多个参数
* 电平敏感的时序控制：wait(\<expr\>)，expr为真时执行

！！！综合工具不支持 **wait语句** ！！！

内容参考 [百度文库](https://wenku.baidu.com/view/b4a43aefaeaad1f346933f7d.html)，不支持综合的部分内容，略。

## 块语句

* 顺序块：语句置于begin和end之间，块中语句顺序执行
* 并行块：语句置于fork和join之间，语句并行执行（不可综合）

## 延时赋值语句（可以用于模拟寄存器交换和移位）

```verilog
LHS = <timing_control> RHS;

// 举例
begin
    temp = b;
    @(posedge clk) a = temp;
end

// 等价于

a = @(posedge clk) b;
```

## 阻塞赋值与非阻塞赋值

* 阻塞赋值：语句结束立即完成赋值操作，前面的赋值语句完成之前，后面的语句不能被执行，使用 "="
* 非阻塞赋值：整个过程块结束时才完成赋值操作，使用 "<="

对比举例

```verilog
// 非阻塞赋值
module non_block(c, b, a, clk);
    output c, b;
    input a, clk;
    reg c, b;
    always @(posedge clk)
        begin
        b <= a;
        c <= b;
        end
endmodule

// 阻塞赋值
module block(c, b, a, clk);
    output c, b;
    input a, clk;
    reg c, b;
    always @(posedge clk)
        begin
        b = a;
        c = b;
        end
endmodule

// 非阻塞赋值中c的结果为上一个时刻的b的值（旧值），阻塞赋值中c和b的值相等
```

---

## File: posts/zh/Win10体验优化策略.md

---
title: Win10体验优化策略
date: 2021-02-02 15:47:22
toc: true
tags: [ Win10, Windows, C盘清理 ]
---

## 写在前面的

随着代码越写越多，越来越发现自己有很严重的精神洁癖。尤其在对于自己日常使用的软件体验上，无论是手机还是PC。这篇帖子着重讲述，这么多年来对于Win10系统使用的体验优化，还有对于C盘清理的经验。

希望Win10体验不会随着时间而变得更差，首先需要养成一个良好的使用习惯。下面的每一节将从不同的纬度提升PC的使用体验，无论是基础软件、辅助功能还是高级玩法。



## 使用PC您需要做的事情（含基础软件）

明确一个观念，PC是你的个人财产，不要被任何第三方厂商所绑架。所以，为了您的PC更好报于您更好的体验，请在每一次安装软件之前谨慎！

> 这里所有的优化体验都基于您的电脑是一个素机的情况下（参考*素人*的释义）。如果您是小白，但凡您的电脑出现了2345、360系、百度系、腾讯系等的任何软件，请优先考虑**重装**，而不是尝试去做清理！

### 对于刚拆封的电脑

- 检查您的C盘，看容量是否少于100G（用于开发等应不少于120G）；如果是，建议您在下面检查软件之后，直接重装调整分区；如果您只有一个盘，建议分给C盘不少于120G的空间，其他空间给D盘

- 实现检查您的**程序与功能**，`控制面板 ---> 卸载程序`



> 因为某些厂商本来就不是什么好东西，自己会自带某些推广的软件，实际点名**联想**。如果您的电脑不是联想的游戏本系列，没有自带电竞版的电脑管家，建议包含您所不必要的软件和联想电脑管家一起干掉。它存在的唯一用处对于非游戏本来说，几乎只有更新驱动，驱动的更新频率极低。如果你对此并不是必要，就直接卸掉，反正用的时候官网可以继续下载。

- 安装**必要**的软件

对于国内绝大部分人来说，日常使用的软件根本没有多少。下面会列举最常用的建议

1. 使用正版的`Microsoft Office`，而不是广告里找功能的`WPS`
2. 使用默认的`Win10 自带输入法`，而不是第三方输入法（第三方输入法会导致直接广告和个人信息被收集）
3. 使用默认的`Windows Defender`也要好于任何国内的杀毒软件，您可以外加`火绒安全软件`来做**防御**。如果自带了送的`迈克菲McAFee`，白嫖完之后可以卸掉了
4. 如果您需要使用`腾讯QQ`，请使用`Tim`而不是`QQ`
5. 浏览器在最新的Win10中已经内置了Chromium版的Edge，如果不是，请[官网](https://www.microsoft.com/zh-cn/edge)升级您的Edge。除此之外，您还可以使用Chrome和Firefox，而非包括但不限于 “2345极速浏览器”、“360安全浏览器”、“360极速浏览器”、“QQ浏览器”等，如果您希望您的电脑广告满天飞的话
6. 有些电脑可能会不带压缩软件，或者带了Winrar。如果自带了Winrar，请卸掉，可以使用**无广告版本的**bandzip（bandzip后面的版本出现了广告）和PeaZip

### 对于使用了一段时间

- 打开您的**程序与功能**，`控制面板 ---> 卸载程序`



- 卸掉2345系的任何软件

包括但不限于2345网址导航、2345看图王、2345加速浏览器、2345安全卫士、2345王牌浏览器、2345王牌输入法等，您随便去搜一下就知道这个东西有多恶心，希望这家公司早日倒闭。您的电脑如果在电脑城购买或线下实体店购买，极有可能携带着这些“病毒”，2345是有推广费的

> 如果说360是流氓的话，毫不客气的说2345就是强奸犯，但凡您的电脑里出现弹窗、捆绑下载等，仔细注意这个玩意

- 卸掉所有除**360杀毒**（如果您迫切需要的话）的360系软件

360安全团队在杀毒的领域很厉害，但请记住“免费的永远是最贵的”，**360杀毒**属于调教之后还可用的，其他的碰都不要碰。包括但不限于360安全卫士、鲁大师、360极速浏览器、360安全浏览器等，自带的Windows Defender已经足够满足个人用户的需求

- 卸掉几乎所有的腾讯系和百度系的软件

腾讯系所必要使用的受社交的绑架，对于普通用户来说只有QQ和微信，QQ极其臃肿而且充满了大量的不必要营销、广告、捆绑下载等。如果你在电脑上没有这些需求，那建议一个都别装，腾讯系所有的数据缓存都在C盘里

现在除了百度搜索之外，我实在想不到有什么不可以替代百度的使用场景，百度搜索也可以用油猴的脚本直接去掉广告。

- 卸掉所有给您带来不好体验的软件，但凡它们是可替代的

## 必要的设置和良好的习惯

### 必要的设置

- 修改`Microsoft Store`默认的数据位置，`设置 ---> 系统 ---> 储存 ---> 更改新内容的保存位置`





- 无论是不是新机，请检查浏览器的主页锁定情况，国内流氓尤其喜欢篡改浏览器主页



- 开启弹窗拦截，以免存在漏网之鱼



- 检查启动项，删掉不必要自启动的软件，会显著提高开机速度



- 检查右键管理，以免过多的右键菜单栏占用



- 对于Adobe系的软件(eg. PhotoShop)

请修改首先首选项，再去下载软件

### 良好的习惯

1. 不要下载来路不明的软件
2. 不要下载不必要的国内大厂软件
3. 仔细使用搜索引擎和知乎等，查看软件评价
4. 去官网下载，而不是所谓的“应用市场”或“软件管家”
5. 使用自定义路径的方式来安装软件，而非默认安装！系统盘和软件安装盘请分离！
6. 使用可替代的开源软件可能会是一个更好的选择

## 辅助功能

为了提高PC的体验，可能需要一些辅助的工具，来达到更好的用户体验，下面列举了一些不错的软件

> 所有的软件都在 [NucoTech推荐的项目](https://nucotech.github.io/RecommendProjects/#/tools/?id=windows%e5%b9%b3%e5%8f%b0) 有所推荐

- QuickLook 快速预览文件
- Snipaste  极强的截屏软件
- PotPlayer  最强的播放器
- Wallpaper Engine  保守好评的壁纸软件
- Internet Download Manager / XDM  多线程下载工具
- Auto Dark Mode  自动切换夜间模式
- flu.x  根据时间自动调节色温
- utools  Alt+Space快速解锁技能，支持自己开发插件

> 浏览器优化可以参考 [浏览器插件](https://nucotech.github.io/RecommendProjects/#/tools/?id=%e6%b5%8f%e8%a7%88%e5%99%a8%e6%8f%92%e4%bb%b6)

## 高级玩法

> 本人强烈不建议小白乱更改电脑设置，此部分及之后的，您需要知晓您的每一步所可能导致的后果

### 使用更专业的杀毒软件

火绒主要在于防御，杀毒推荐卡巴斯基和小红伞，自己实际使用卡巴斯基感觉有点卡，并且对于网页浏览的侵入特别强

### 更改到公共DNS

前段时间看到火绒安全通报了联通官网被挂马之后，可笑死我了，这些厂商经常污染DNS导致了很多操蛋的事情。我在 [提升国内GitHub访问体验](./提升国内GitHub访问体验.md) 中也提到过

还是建议使用AliDNS，其他的`8.8.8.8`和`114.114.114.114`也行

### 使用SwitchHosts!更改Hosts

直接修改Windows系统的hosts文件感觉特别难受，可以使用 [SwitchHosts!](https://github.com/oldj/SwitchHosts)，可以订阅自动更新Hosts

## 清理C盘

Windows的目录结构设计让我感到极其难受，我更喜欢类Unix系统的挂载。Windows作为系统盘的C盘，经常会因为使用时间的而变得越来越空间不够，有些方式可能不太适合小白，请谨慎操作

### 清理缓存等基础垃圾

使用垃圾清理可以清理缓存、日志等最基础的垃圾



### 工具清除深层垃圾

可以使用 [Dism++](http://www.chuyu.me/zh-Hans/) 来做更深层次的清理，如果您不知道您对某一项的勾选会导致什么后果，那就使用默认的配置就好了

### 手动清理可能并不算是垃圾

> 本部分可能会导致不可逆的后果，请谨慎对待

- 优化Windows系统导致的占用

参考知乎专栏 [移除pagefile.sys和hiberfil.sys获取C盘空间](https://zhuanlan.zhihu.com/p/87565681)

> 需要注意的是如果要删除hiberfil.sys文件，其命令是`powercfg -h off`

- 手动分析和清理某些数据

这里使用的工具是WinDirStat，每个人的文件内容都不完全相同，酌情清理



> Program Files(x86)、Program Files、ProgramData、User目录下面的部分文件按大小和用途，可以部分清理

---

## File: posts/zh/Windows下Vim的安装与插件管理.md

[Binary file]

---

## File: posts/zh/从React视角学习Svelte(一).md

[Binary file]

---

## File: posts/zh/免费证书实现https(保姆级教程).md

---
title: 免费证书实现https(保姆级教程)
date: 2019-08-01 11:38:13
toc: true
tags: [ domain, https, Ubuntu18, nginx, ssl ]
categories: [ Linux, 环境搭建 ]
---

## 使用Certbot

Certbot使用的是[Let's Encrypt](https://letsencrypt.org)的证书，该组织也强烈建议使用[Certbot](https://certbot.eff.org/)获取证书。

## Certbot配置

使用这个的目的是获取通配符证书，也就是支持二级域的证书验证，避免https错误的预警。

* 主机商: 阿里云
* 系统版本: Ubuntu18.04
* Web服务: nginx
* 使用环境: python3



### 在Certbot

进入[Certbot](https://certbot.eff.org/)选择Web服务和系统



上图我选的是在Ubuntu18.04上面跑的nginx。下面的提示就是你使用证书实现https之前需要有一个正常运行的网站，并且得有sudo的权限(直接云服务器实例，轻量应用服务器之类的就可以了)



可以选择 **默认(Default)** 和 **通配符(wildcard)**

#### 默认的就是一张单域名的证书 (我用的是通配符的，直接通配符的可以跳过这一节)

1. SSH连接

2. 添加Certbot PPA到库(阿里云root下不需要sudo就直接可以的，下同)

    ```shell
    apt update
    apt install software-properties-common
    add-apt-repository universe
    add-apt-repository ppa:certbot/certbot
    apt-get update
    ```

3. 下载Certbot

    ```shell
    apt install certbot python-certbot-nginx
    ```

4. 选择使用一种方式(推荐使用手动！！)

   自动：使用Certbot下载证书并且更新你的nginx设置

   ```shell
   certbot --nginx
   ```

   手动：只下载证书，手动配置/重启/重载nginx(手动配置nginx需要对nginx有一定的了解，下面的参考文章里有阿里云ssl配置的参考)

   ```shell
   certbot certonly --nginx
   # 主要区别就在于certonly这个参数，通配符咱们也会遇到
   ```

    > 证书成功生成之后会有信息显示证书所在的位置的，或者运行下面的命令查看证书，然后配置nginx的ssl_certificate和ssl_certificate_key这两个参数就好了。其中privkey.pem对应的就是ssl_certificate_key的目标文件。

    ```shell
    certbot certificates
    ```

5. 测试自动更新

    Certbot支持自动更新证书，然后自动添加了定时任务，就不需要手动更新证书了。官网说在/etc/crontab、/etc/cron.\*/\*或者systemctl list-timers里面使用了，不放心的可以手动添加定时任务并且设置重启nginx的。

    ```shell
    certbot renew --dry-run
    # --dry-run这个参数用于测试的
    ```

6. 查看的网站的状态

   这个要看具体的配置，80端口开启ssl直接就好了，443端口https访问！

#### 通配符证书

1. 检查DNS服务商是否被支持（国内的就不用看了）

2. SSH连接

3. 添加Certbot PPA到库(阿里云root下不需要sudo就直接可以的，下同)

    ```shell
    apt update
    apt install software-properties-common
    add-apt-repository universe
    add-apt-repository ppa:certbot/certbot
    apt-get update
    ```

4. 下载Certbot

    ```shell
    apt install certbot python-certbot-nginx
    ```

5. 下载正确的插件(国内的，从这一步开始咱们就可以下一章了！)

## 通配符插件国内主机如何解决之使用certbot-letencrypt-wildcardcertificates-alydns-au

Certbot官方有给出插件的编写的方法，也有提供第三方的插件(有兴趣自行去了解)。国内有大佬自己写了插件，在此使用的是[certbot-letencrypt-wildcardcertificates-alydns-au](https://github.com/ywdblog/certbot-letencrypt-wildcardcertificates-alydns-au)，文档比较友好，并且issues的回复也很快。

> 特别提醒： **下面官方的使用参考文档中的所有./certbot-auto命令不再支持！请使用直接使用certbot！** 下面是基于aliyun+python+nginx的演示，具体流程和参数说明请直接参考官方文档！！

### 下载到/var下

```shell
cd /var
git clone https://github.com/ywdblog/certbot-letencrypt-wildcardcertificates-alydns-au
cd certbot-letencrypt-wildcardcertificates-alydns-au
chmod 0777 au.sh
```

### 配置domain.ini

```shell
cat domain.ini
# 如果有自己域名后缀的话就不用执行下面修改了
nano domain.ini
```

### 获取阿里云的accesskey(腾讯云请参考官方文档)

参考 [阿里云API Key和Secret的申请](https://help.aliyun.com/knowledge_detail/38738.html)

### 修改au.sh参数

```shell
nano au.sh
```

```bash
# 填写下面两个参数
ALY_KEY=""
ALY_TOKEN=""
# 命令行路径可以修改，/usr/bin/python可以改为/usr/bin/python3，不过作者已经对python2、3都进行了适配
```

### 申请证书(nginx+python)

测试配置

```shell
certbot certonly  -d *.example.com --manual --preferred-challenges dns --dry-run  --manual-auth-hook "/var/certbot-letencrypt-wildcardcertificates-alydns-au/au.sh python aly add" --manual-cleanup-hook "/var/certbot-letencrypt-wildcardcertificates-alydns-au/au.sh python aly clean"
```

```shell
# 一个顶级域名获取通配符证书直接修改*.example.com为自己的就好了，上述命令去掉--dry-run参数
# 以baidu.com举例(python)

certbot certonly  -d *.baidu.com --manual --preferred-challenges dns --manual-auth-hook "/var/certbot-letencrypt-wildcardcertificates-alydns-au/au.sh python aly add" --manual-cleanup-hook "/var/certbot-letencrypt-wildcardcertificates-alydns-au/au.sh python aly clean"
```

SAN通配符证书(直接添加-d 和域名就好了)

```shell
# 基于官方示例(python)
certbot certonly  -d *.example.com -d *.example.org -d www.example.cn --manual --preferred-challenges dns --manual-auth-hook "/var/certbot-letencrypt-wildcardcertificates-alydns-au/au.sh python aly add" --manual-cleanup-hook "/var/certbot-letencrypt-wildcardcertificates-alydns-au/au.sh python aly clean"
```

### 配置nginx

上面包括下面的所有命令都带certonly，因此所有的操作都是只做认证，nginx需要自行修改配置！！

> 证书成功生成之后会有信息显示证书所在的位置的，或者运行下面的命令查看证书，然后配置nginx的ssl_certificate和ssl_certificate_key这两个参数就好了。其中privkey.pem对应的就是ssl_certificate_key的目标文件。

如果不知道证书的位置，可以使用下面的命令查看

```shell
certbot certificates
```

配置完成之后

```shell
service nginx restart
```

### 证书的续期

请直接参考[官方文档](https://github.com/ywdblog/certbot-letencrypt-wildcardcertificates-alydns-au/blob/master/README.md)，下面贴出来的只是基于官方文档的 **阿里云+python版** shell，官方演示shell使用的都是php和阿里云！！

```shell
# 所有证书renew
certbot renew  --manual --preferred-challenges dns --manual-auth-hook "/var/certbot-letencrypt-wildcardcertificates-alydns-au/au.sh python aly add" --manual-cleanup-hook "/var/certbot-letencrypt-wildcardcertificates-alydns-au/au.sh python aly clean"

# 某一张证书续期
# 查看证书
certbot certificates

# 记住证书名，比如simplehttps.com
certbot renew --cert-name simplehttps.com  --manual-auth-hook "/var/certbot-letencrypt-wildcardcertificates-alydns-au/au.sh python aly add" --manual-cleanup-hook "/var/certbot-letencrypt-wildcardcertificates-alydns-au/au.sh python aly clean"
```

### 加入crontab

因为我使用了nginx，所以添加crontab就直接renew成功之后重启nginx了。这里强烈建议service nginx restart重启！nginx -s reload重载nginx.conf配置时并不一定会报错而带来影响debug的问题。

```shell
nano /etc/crontab
```

添加内容

```txt
1 1 */1 * * root certbot-auto renew --manual --preferred-challenges dns --deploy-hook  "service nginx restart" --manual-auth-hook "/var/certbot-letencrypt-wildcardcertificates-alydns-au/au.sh python aly add" --manual-cleanup-hook "/var/certbot-letencrypt-wildcardcertificates-alydns-au/au.sh python aly clean"
```

然后，就结束了！！

## 参考文章

* [linux的crontab定时配置全过程](https://blog.csdn.net/u010102390/article/details/80505451)
* [插件certbot-letencrypt-wildcardcertificates-alydns-au的使用](https://github.com/ywdblog/certbot-letencrypt-wildcardcertificates-alydns-au/blob/master/README.md)
* [Nginx/Tengine服务器安装SSL证书](https://help.aliyun.com/knowledge_detail/95491.html?spm=5176.2020520163.cas.52.5cd656a7iFDuEI)

---

## File: posts/zh/关于for-editor的扩展开发及markedjs语法扩展的心得.md

---
title: 关于for-editor的扩展开发及markedjs语法扩展的心得
date: 2020-04-14 17:24:14
toc: true
tags: [ JavaScript, TypeScript, React, marked, markdown, npm ]
---

## 关于`for-editor`

开始接触`for-editor`是因为想自己写一个基于Git的支持markdown的笔记本PC应用，常用开发框架是React、在PC端的开发工具的选择上选择了吃内存狂魔electron，就这样抱着能不造轮子就不造轮子的原则开始使用了`for-editor`（虽然之后没有打算继续开发）。`for-editor`如果对于语法的支持没有太高的要求的话，是一个非常优秀、简洁的编辑器组件。支持Tex渲染插入、mermaid流程图的支持、高级markdown或者扩展的markdown语法，随着需求的提升就准备了开始自己扩展语法之路，当然其中发现了很多需要优化和修改的地方。

## 开发之路

### 通读源码

源码可以从[for-editor](https://github.com/kkfor/for-editor)中查看。

首先，阅读`package.json`。`package.json`是所有React组件开发必不可少的环节，在此之前还是需要足够了解工程的`README.md`的。在`package.json`里，源工程对于markdown解析所用的引擎为`marked.js`和`highlight.js`。在之前了解`markdown` --> `HTML`的渲染学习的时候所用的是`marked.js`，好处是足够简洁，坏处是语法很少并且扩展要求并不低。

其次，对于源码结构的解读。`dist`为生成产物，`doc`为相关文档，`example`为演示文档，`src`为源码目录，`webpack`为配置项。在`src`下有`components`、`lib`和`index.tsx`，`components`为工具栏的组件，`lib`是开发的依赖和功能源码，`index.tsx`为整体的页面结构。

### 发现的问题

> 这里提到的问题其实有一些是还没有完全修复的，有些问题真的存在了很多年了，至今我也没能想到比较好的解决办法。

#### 响应式布局

在现代web开发中，响应式布局对于用户体验来说是非常好的，但是在访问速度上相比加载速度会稍微慢一些。针对于高标准的用户体验，我选择了牺牲掉一些访问速度，当然对于纯的通过CSS实现响应式布局在某些时候根本达不到好的效果，是需要JavaScript来加buff的，这也为之后的开发其实还挖了一个坑，对此我不得不做出妥协，完全的响应式目前看来是做不到的。在下面，我也会阐述具体的坑到底是什么。

响应式布局方面我做出的优化是针对`900px`这个标准进行的分割，随着工具栏功能的拓展，原本工具栏的布局会溢出。因此对于没有二级菜单的工具栏button，我选择写在了more里，并且另开了`for-mobile`，`for-pc`，使用了`flex`布局来处理宽度不够换行处理的优化措施。在下一个坑没有遇到之前，这个方案我觉得解决的还算不错。（当然开了新得分支，还是被提了issue，主要是他遇到的浏览器尺寸在我测试的时候真的没有发现任何问题，很迷惑）

#### `mermaid`的引入

这是我几个月都没有解决的问题，可能是打开方式不太对吧。。。参考了`mermaidAPI`和与`mermaid`开发者交流提了`issue`但是始终没有解决。难道mermaid真的是只能用已存在DOM节点来做渲染的吗？希望能有大佬带我深入了解一下。mermaid的渲染要写在主组件的生命周期里面，但是就我刚刚说的，如果已知存在，在什么时候插进去来触发渲染？最后我选择了原样插入，然后再触发渲染的方式，当我满心欢喜觉得一切都能如愿的时候。我发现真的能不能渲染出来都是薛定谔的猫:(然后不得不放弃，想想也真的可能跟后面那个深坑有关系，总有办法能解决这个问题，然后参考过CSDN的HTML代码，也就是我后来的考虑的深坑。对此，我移除了`mermaid`的渲染支持。

#### 修复js对于二级菜单的控制

感谢[@ivanandonov](https://github.com/ivanandonov)的issue，之前我并不太觉得点击关掉二级菜单很重要，其实也就是添加个关掉二级菜单的事件，问题不大。

#### 拓展`marked.js`的语法

这是本部分最核心的内容了。如何去扩展`marked.js`的语法？？？我看到网上有很多小伙伴尝试去扩展语法，但是也有不少选择了放弃。

如果你想尝试去扩展语法，熟读`marked.js`的[使用指南](https://marked.js.org/#/README.md#README.md)、`marked.js`的[源码](https://github.com/markedjs/marked)和实现逻辑，当然还需要写正则表达式:)

`marked.js`最牛逼的部分就在于正则表达式，就不重复匹配的问题，如何去用正则表达式去表示？因此重写renderer的时候我参考了很多源码部分的内容，这里就不针对`marked.js`详细展开介绍了，我只写一写我到底做了哪些事。

* 抽离`highlight.js`。在原项目发现的问题之一就是——我如何去让使用者自行决定高亮的代码类型？我总不能把所有的语言都注册一遍吧？！不但增加了代码量，还需求不是很大，所以我选择了把`highlight.js`依赖给移除掉。让使用者传入`Hljs.highlightAuto`这个函数，其他的自己引入`highlight.js`然后自己注册就完事了。
* 引入`emoji`、`Tex`、`diff`语法、`mark`做行内高亮。这就涉及了不少的正则表达式，尤其是在`mark`这个渲染上，你总不能把每一句都拿正则循环跑吧，根本不实际。所以得先把高亮块抽离出来然后再排回去，并且不能涉及需要使用的非特殊字符。感兴趣的小伙伴可以参考[marked.ts](https://github.com/HerbertHe/for-editor-herb/blob/master/src/lib/helpers/marked.ts)这个部分的源码。对于类似\`\`\`这种就是对于code块的解析，如果是行内嵌入或者自定义渲染块呢就在`paragraph`的部分进行重写renderer就好了，但是记得一定要看源码对应的`html`的标签。

#### 扩展渲染的锚点和大纲

渲染的锚点就是重写`heading`部分的HTML，不赘述了就是添加一个a标签就能解决的事情。大纲、目录、TOC，一个东西需要用到`marked.js`提供的解析器`lexer`，提取heading部分和深度，然后来写样式部分。

#### 深坑——如何去调整`textarea`的高度

这一切的问题还是来源于，当我使用分栏的功能。我发现记行号并不正确，并且`textarea`的部分并不能很好的解决高度问题，因为高度不足以显示全部的内容。为什么`overflow: hidden`其实我在不断优化这个问题的时候也能体会到，因为外部的滚动条需要与行号对齐。

* 修改一：换掉计算行号的方式。源代码的计算方式是通过计算`\n`来实现的，貌似`vscode`的markdown也是这么实现的，但是就优化而言`textarea`对于行号样式能有`vscode`这样的调整我是没有发现能有什么办法可以做到的。因此我也考虑了很多办法去优化这个问题，甚至重构编辑器。在目前`2.x.x`的版本中，我已经换成了根据textarea的高度来计算行号了。
* 修改二：自适应调整textarea的高度。认真地说，这个坑都坑了多少年了，不知道坑过多少人，知乎还有很多的讨论。其实改起来也不算难，就是把`height: auto`，然后动态调整高度等于`scrollHeight`。然后我选择了把计算行号在重新计算高度的函数中进行了调用。看起来，`everything is ok`了是吧？：）惊喜的是当分栏激活的时候`scrollHeight`并不是会增大，是会减小的，没想到吧。然后我只能选择目前来说我能提供的最优的解决方案，仅当分栏关掉的时候计算高度，然后通过修改值就可以做到精确计算。原作者还提供了fontSize这个可选项，为此我去了解了`line-height`和`font-size`之间的关系，具体的源码请参考`index.tsx`的`reHeight`部分的源码。其实我还考虑过，根据上一时刻的面积除当前时刻的宽度来计算高度的，听起来是真的很美好。实际情况是，当你切换过快的时候，读取到的面积并不准确，因此这个方法就是`理想很丰满，现实很骨感`的问题。
* 为什么不考虑去用可编辑div重写？下次一定，我是准备在`3.x.x`的版本重构的，但是我目前没有这么多时间去考虑这个问题了，是真的很麻烦。

## 心得感悟

小小的富文本编辑器竟然如此复杂，我突然发现我是想慢慢尝试去实现word的最基础的功能。在拓展`for-editor`的过程中我学到了很多根据教程根本是不可能学到的实际开发问题，为此我付出了很长的事件去研究源码，当然也去开始适应TypeScript来做开发。使用开源项目不是减少工作量，很大程度上其实增加了不少学习成本。

如果喜欢[for-editor-herb](https://github.com/HerbertHe/for-editor-herb)我这个分支呢，请给[原项目](https://github.com/kkfor/for-editor)一个star。

---

## File: posts/zh/关于我和物理.md

[Binary file]

---

## File: posts/zh/写给程序员的MATLAB快速上手教程.md

---
title: 写给程序员的MATLAB快速上手教程
date: 2021-03-21 21:37:42
toc: true
tags: [ MATLAB, 快速上手, 程序员 ]
---

## 写在前面的

MATLAB更多的是一门面向非程序员的计算机编程语言, 因此其教程往往对于程序员同学来说并不算是很"友好"。因为自己的本科毕业论文是利用MATLAB来研究光的波动性, 之前因为教程的问题一直感觉MATLAB这个语言奇奇怪怪的, 故作为一个程序员的视角自己来写一份教程。本教程会对比其它语言或者编程语言通用的概念来书写, 所以需要至少有一门语言的编程经验。

## 基本架构图

- 参考书籍: 北京航空航天大学出版的由胡章芳老师编写的《matlab仿真及其在光学课程中的应用》（第二版）

使用软件:

- MindLine
- Matlab
- GNU Octave
- Anoc Octave Pro Editor





## 变量

### 标识符

MATLAB的标识符的规则符合下面的正则表达式:

> \^[A-Za-z]{1}[A-Za-z0-9_]*

- 标识符只能以 **英文字母** 开头
- 标识符只能由 **大小写字母、数字、下划线（\_）** 组成
- 标识符不能与 **[关键字](#关键字) 和 预定义量** 重复
- 长度不超过 **namelengthmax** （不同的MATLAB版本长度也不一样）

> 那本书把预定义量叫做const, 这个跟我们常规编程语言理解的const（常量）意义不太一样, 因此我翻译为了 **预定义量**

#### 关键字

> 执行`iskeyword`输出关键字

```matlab
iskeyword

% ans =

%   20×1 cell 数组

%     {'break'     }
%     {'case'      }
%     {'catch'     }
%     {'classdef'  }
%     {'continue'  }
%     {'else'      }
%     {'elseif'    }
%     {'end'       }
%     {'for'       }
%     {'function'  }
%     {'global'    }
%     {'if'        }
%     {'otherwise' }
%     {'parfor'    }
%     {'persistent'}
%     {'return'    }
%     {'spmd'      }
%     {'switch'    }
%     {'try'       }
%     {'while'     }
```

### 默认变量

MATLAB是一门默认支持REPL的语言而且与计算有关, 因此MATLAB定义了一个默认变量 `ans` 作为未赋值的计算结果的赋值, eg.

```matlab
2 * 3

% 输出: ans = 6
```

未完待续...

---

## File: posts/zh/创建一个unified插件.md

[Binary file]

---

## File: posts/zh/前端样式优先级的问题.md

---
title: 前端样式优先级的问题
date: 2020-07-23 09:50:02
toc: true
tags: [ 前端, CSS ]
---

## 选择器类型

- ID选择器
- 类选择器
- 属性选择器
- 伪类选择器
- 类型选择器
- 伪元素选择器
- !important

## 参考

- [W3 Specificity](https://www.w3.org/TR/selectors/#specificity)

---

## File: posts/zh/前端隐藏元素的区别.md

---
title: 前端隐藏元素的区别
date: 2020-07-14 10:45:13
toc: true
tags: [ 前端, CSS ]
---

## 隐藏元素的方法

前端如果设置元素不可见，可以使用`display: none` `visibility:hidden` `opacity: 0`



## 空间占据

- `display: none`隐藏是不占用空间的，会产生回流和重绘
- `visibility: hidden`和`opacity: 0`隐藏了元素，但是仍然占据着空间，只会引起页面的重绘

> 回流(Layout): 根据生成的渲染树进行回流，得到节点的几何信息；
> 重绘(Painting): 根据渲染树得到的几何信息，得到节点的绝对像素

## 子元素继承

- `display: none`这个父元素都不存在了
- `visibility: hidden`会被继承，通过对子元素设置`visibility: visible`
- `opacity: 0`会被继承，子元素不能设置`opacity: 1`显示，因为`opacity`这个本就是设置透明度的！

## 事件触发

- `display: none`元素不存在，无法触发绑定事件
- `visibility: hidden`绑定事件无法触发
- `opacity: 0`绑定事件是可以触发的

## 过渡动画

`transition`只对可数值化的属性有效，因此只有`opacity`有效

## 一个需求

建立一个搜索框，当`hover`搜索框的时候，`搜索提示元素`通过过渡动画展示；当仅`focus`搜索框时，`搜索提示元素`不展示，但搜索框长度通过过渡动画使宽度拉长；要求点击搜索提示框的内容时，可以跳转其他页面。

需求分析:

- 定位问题：输入框和搜索提示的节点拥有父节点，并且父节点的`position`为`relative`，这样就可以设置`搜索提示框`的绝对定位来设置位置
- 事件处理问题：使用父元素的`hover`伪类控制子元素`搜索提示框`的`opacity`和`高度`；使用输入框的`focus`伪类撑起父元素的宽度；这样当整体被`hover`时，`搜索提示框`可以被展示，并且当点击`搜索提示框`里面的内容那一瞬间，输入框焦点失去，但事件可以被触发，也不用担心会被误触的情况

> 示例代码 [NavBar](https://github.com/NUCOSC/Nucers/blob/dev/site/components/common/NavBar.tsx#L173)和[样式](https://github.com/NUCOSC/Nucers/blob/dev/site/styles/components/common/NavBar.css#L32)

## 引用

- [你真的了解回流和重绘吗](https://segmentfault.com/a/1190000017329980)
- [~之间的区别](https://blog.csdn.net/github_39673115/article/details/77926351)

---

## File: posts/zh/基于AC+AP打造安全的家庭WiFi网络.md

[Binary file]

---

## File: posts/zh/基于canvas-editor的RTL实现的挑战.md

---
title: 基于 canvas-editor RTL 实现的挑战
date: 2024-03-12 18:00
toc: true
tags: [ canvas-editor, canvas, RTL ]
---

## 前言

众所周知，在前端领域实现富文本编辑器是一个深坑，是非常复杂的问题，markdown 编辑器在一定程度上也是富文本编辑器。简要介绍一下，富文本编辑器的实现方式上，一般有以下的实现方案：

- 纯文本：textarea 标签
- 富文本：[contenteditable](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Global_attributes/contenteditable)
- 富文本：利用 canvas、svg 进行渲染

本篇不讨论这种方案的实现差异，只讨论基于 canvas 的 canvas-editor 的 RTL 实现挑战，内容主要与 canvas 的实操有关。

## RTL 实现的挑战

### 预置概念

由于 canvas 是依赖 `[x, y]` 坐标来绘制图形或文字，但在 `ctx.direction='rtl'` 的模式下，canvas 对于图形和文字的处理并不一致，所以为了简化绘制流程的原理解释，这里定义一些预置概念：

| 概念名称 | 释义                                                                                                                                        |
| -------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| 基点坐标 | 指的是绘制 canvas 内容的绝对原点坐标，此坐标决定了 canvas 结构内容绘制的 x, y 轴的基线                                                      |
| 偏移坐标 | 指的是相对于基点的偏移量，根据基点坐标和偏移情况，即可计算出图形和文字的实际坐标                                                            |
| 对称坐标 | 指的是在 RTL 情况下，`ctx.direction='rtl'` 相对的是从基点反方向绘制文字，因此需要更改基点位置，从而实现从行初到行尾的坐标转换 ，其关于基点 y 轴与偏移坐标轴对称             |
| 镜像坐标 | 指的是在 RTL 情况下，`ctx.direction='rtl'` 并不会反方向绘制图形，因此相对于对称坐标需要进行镜像处理，即 **在对称坐标的基础上减去绘制元素的宽度** |

### canvas-editor 的绘制原理

canvas-editor 的绘制原理，总的来说，即:

- 计算画布大小
- 计算减去内边距得到可绘制区域大小
- 计算每行承载元素和元素坐标
- 根据元素坐标逐个绘制文字
- 根据 underline、strikeout 等特殊属性绘制图形

按照现阶段的 canvas-editor 绘制来说，除了文本做了合并优化绘制处理之外，完全可以视为逐个元素绘制

### RTL 的挑战

为 canvas-editor 支持 RTL，是因为目前完全没见过做前端富文本编辑器的可以完美支持 RTL（尤其是基于 canvas 的）。

富文本编辑器无疑是开发里的深坑，即使是相当成熟的商业软件对于 RTL 的支持也很差。在适配上，html dom 可以通过 attribute 的 `dir` 进行适配实现，但是在 canvas 中，无异于是需要自己实现一遍浏览器的 reflow、repaint 机制。

关于 RTL 实现碰到的各种问题，可以参考 [编辑器支持rtl渲染 #451](https://github.com/Hufe921/canvas-editor/issues/451)

目前在 forked 仓库的 [feature/rtl](https://github.com/HerbertHe/canvas-editor/tree/feature/rtl) 分支进行实现。

下面是发现和解决的一些问题：

- 文字样式改变渲染的绘制逻辑造成不正确的排列

> [Date 渲染存在缺陷 #457](https://github.com/Hufe921/canvas-editor/issues/457)

在 Date 渲染和 hyperlink 的渲染中都存在这个问题。源码在 `dateParticle.render` 和 `hyperlinkParticle.render` 的实现中，文本的绘制没有交给 `textParticle.render` 进行完成，而是直接使用 `ctx.fillText` 进行绘制。导致在 RTL 情况下，文本的绘制并没有 LTR 字符连续绘制，而是单个文字绘制；也同样会导致 RTL 和 LTR 混排字符出现问题。

具体的示例和原理，可以参考我的这个PR [refactor: date renderer](https://github.com/Hufe921/canvas-editor/pull/460)

涉及到文本的渲染，在原则上应该全部交给 `textParticle.render` 进行处理，才可以保证对文本内容绘制顺序的可控性。

但这对于实现 Date、hyperlink 等格式文本的合并绘制是远远不够的，这涉及到了 RTL 的绘制位置排序问题。

- RTL 默认绘制的排序

在 `ctx.direction='rtl'` 的情况下，canvas 的绘制顺序是：**总体保持 RTL 顺序，LTR 连续绘制，而标点符号的绘制受下一个字符的类型影响，绘制起始坐标为新的基点坐标。**

但上面的合并文本绘制策略，并不能解决合并后的文本字符串的排序问题。目前 `ctx.fillText` 的样式会因为文本样式的不同而打断文本绘制的进程，不同样式格式的文本是分步绘制的，需要依赖正确的坐标顺序。

因此，在借助 `ctx.direction='rtl'` 进行文本绘制之前，还需要对文本进行重新排序。实现原理为：**将文本按照格式打断分割完成之后，再对绘制的起始点坐标进行重新计算交换，以实现正确的绘制坐标点，从而混合 canvas 的自身绘制顺序进行绘制。**

- RTL 的坐标计算

正如之前所说的，因为 `ctx.direction='rtl'` 根本无法影响图形的绘制，所以文本和非文本的绘制坐标计算是不一致的。**文本的实际坐标**，即之前所说的偏移坐标，通过更改基点坐标的位置实现重置起始点。**但绘制坐标为对称坐标**，为 canvas 自行处理之后的实际绘制位置，是关于基点坐标的 y 轴对称的。

非文本的坐标为 `文本对称坐标 - 文本实际宽度`， canvas 的图形绘制只能通过 LT (Left Top) 点进行绘制。其绘制坐标的位置与文本的位置在 LTR 时存在实际的偏移，需要被纠正计算。

highlight、underline、strikeout 的计算，都是通过这个来进行实现的。

因此也导致了新的问题产生：mousedown 和 command 等的默认操作存在偏差。

- mousedown 等事件的处理

canvas-editor 默认的 mousedown 等鼠标监听事件是通过偏移坐标实现的，但在 RTL 下带来了新的问题。当判断鼠标与文本元素的碰撞检测之时，用的应该是文本的对称坐标，而不是偏移坐标。文本是反向进行绘制的，计算 RTL 下的实际坐标才能精确定位光标的位置，从而进行操作。

- range 的修正

由于在 RTL 下需要对文本进行重新排队绘制，因此原本的 range 记录的方式不再适用。canvas-editor 的 range 通过元素的索引进行实现，在 LTR 的情况下不存在问题；但 RTL 下进行重新排队之后，range 的记录应该为实际的元素及坐标，而非索引，通过索引进行计算会导致错误的元素选区问题。

而 range 的错误选区，会导致后面一系列的绘制问题，依赖 range 的二次操作非常多。

- 上下角标的绘制问题

上下角标应该是强制 LTR 的，需要在文本排序的时候，强制实现 LTR 的绘制渲染。

---

## File: posts/zh/基于Kodi打造家庭iptv.md

---
title: 基于 Kodi 打造家庭 IPTV
date: 2023-11-26 17:00
toc: true
tags: [ 
    Kodi, iptv, 小米电视
]
---

## 写在前面的

随着智能电视的不断迭代，运行 Android 系统的电视设备越来越变的不纯粹，更像是一个默认为影音app入口的超大号显示设备。面对着无数的广告和数不尽的会员，更纯粹的看电视变得奢求，因此开始了基于 Kodi 的家庭 iptv 改造计划。

如果你还不知道什么是 Kodi，请参阅 <https://kodi.tv/> 了解这个项目

## 开始改造

改造计划分为下面的步骤，网络上有大量的教程，这里只对于关注点提供必要的流程。

本次改造计划使用小米电视，其他的 Android 电视设备大同小异。

### 安装 Kodi 至小米电视

- 打开小米电视 **“开发者模式”**
- 打开小米电视 **“允许安装未知应用”** 和 **“允许 adb 调试”**
- 手机安装 **甲壳虫ADB助手**
- 手机下载 **[Kodi for Android](https://kodi.tv/download/android/) APK**（根据架构选择 **v20 ARMV7A** 或者 **v20 ARMV8A**，这取决于你的电视指令集架构，在系统设置中进行查看）
- 打开 **甲壳虫ADB助手** 填写电视 **IP地址** 进行连接（记得电视用遥控器确认允许）
- 通过 **甲壳虫ADB助手** 向电视安装 **Kodi for Android** APK 应用

至此，在电视上安装 Kodi 就完成了。

> 需要解释的是：小米虽然在系统上限制了第三方APP的安装，但我们使用了 **甲壳虫ADB助手** 对电视进行操作，因为 `adb` 本身是 Android 调试桥，便于 Android 开发者对设备应用进行调试，权限比较高，小米在系统层面不对其进行限制。

### 对 Kodi 进行配置

好看的主题提升用户使用体验，所以我们对 Kodi 进行一些简单的配置。

> 需要注意的是：**在稳定使用之前，🙅不要将语言切换到中文！否则，会造成很大的麻烦！** 教程步骤为中文翻译，请注意对应的英文选项

- 更改下载源

Kodi 的插件托管在 GitHub 上，国内能不能连上纯粹是个玄学问题。因此我们需要对 Kodi 的插件仓库软件源进行替换，虽然 清华TUNA 和 中科大USTC 源镜像站都提供了参考文档。但是 Nexus（20）版本，无法找到对应的文件 `addons/repository.xbmc.org/addon.xml`。在查阅官方仓库源代码后发现，需要首先安装 `repository.xbmc.org` 仓库插件，这是我之前完全没想到的.

你可以通过 <https://dl.jieec.cn/s/k5h6> 下载 `repository.xbmc.org` 插件，已经是修改完成了的。

下载完成之后，通过 **ADB助手** 上传文件至电视文件根目录，通过 `插件 -> 从 zip 文件安装插件 -> 找到对应的 zip 文件安装`

电视遥控器按左键，对插件仓库进行更新。

- 更改 Kodi 主题

在试错很多次之后，我选择了 Arctic Horizon 2 作为 Kodi 的主题，原因无他，好看罢了。

首先，需要下载安装 [repository.jurialmunkey](https://jurialmunkey.github.io/repository.jurialmunkey/) 仓库差价，安装步骤与上面 `repository.xbmc.org` 插件完全一致。

其次，需要预先按顺序安装 [script.module.jurialmunkey](https://dl.ghpig.top/https://github.com/jurialmunkey/script.module.jurialmunkey/archive/refs/tags/v0.1.17.zip)（下载安装）、`script.skinvariables` 和 `plugin.video.themoviedb.helper`（电视上的 jurialmunkey 插件仓库搜索安装）插件，不然在下载主题的时候会报错。

然后在 `插件 -> 从库里安装 -> jurialmunkey 插件库 -> look and feel -> 皮肤 -> Arctic Horizon 2` 安装即可，花费的时间不短，耐心等待。

- 设置中文页面

切换字体 `右上角小齿轮 -> 系统设置 -> UI -> skin -> font -> CJK - Chinese/Japanese/Korean`

> 默认 CJK 是不被支持的，因此先切换字体才不会导致乱码

切换区域 `regional -> language -> chinese(simple)`

> 设置简体中文

至此 Kodi 的基础安装全部完成！

### 支持 IPTV

光有 Kodi 是不行的，还需要配置 IPTV 才能收看 Kodi 所说的 **直播电视**。

分为两部分：

- Kodi 安装客户端插件 `IPTV Simple Client`
- 添加 `IPTV Simple Client` 配置文件

安装客户端插件的流程与上面一致，通过 `插件 -> 从库里安装 -> Kodi 官方库 -> PVR 客户端 -> IPTV Simple Client` 安装即可。

配置 `IPTV Simple Client`：`插件的设置 -> 添加附加设置` 分别填写 **名称** 、 **位置选择：远程路径（互联网地址）** 和 **M3U播放列表URL**

你可以使用 <https://epg.pw/test_channel_page.html> 提供的 m3u 服务，或者使用我维护的 <https://m3u.ibert.me> 服务

## 关于 `HerbertHe/iptv-sources`

[HerbertHe/iptv-sources](https://github.com/HerbertHe/iptv-sources/tree/gh-pages) 是 <https://m3u.ibert.me> 服务的 GitHub 开源项目，对 <https://epg.pw/test_channel_page.html> 提供的 m3u 服务进行了过滤去重。考虑到了主要面向中国大陆用户，所以没有对所有的服务进行处理，如果有其他需要请 fork 进行二次开发。

本次改造如使用 <https://m3u.ibert.me> 服务，请关闭本地缓存，打开自动更新，并且调整更新频率为 120 分钟。

支持的 m3u 源，请参考 <https://m3u.ibert.me> 进行查看使用，每 3 个小时自动更新一次。

获取最及时的消息，请加入 Discord <https://discord.gg/EawDmkpd>，这是最及时获取更新消息的地方。

---

## File: posts/zh/如何使用TypeScript操作语法树.md

---
title: 如何使用TypeScript操作语法树
date: 2024-3-26 17:16:44
toc: true
tags: [ unified, 语法树, TypeScript, unist ]
---

[unist]: https://github.com/syntax-tree/unist
[unified]: https://unifiedjs.com/explore/package/unified/
[mdast]: https://github.com/syntax-tree/mdast
[hast]: https://github.com/syntax-tree/hast
[xast]: https://github.com/syntax-tree/xast#readme

> 原文链接：<https://unifiedjs.com/learn/guide/syntax-trees-typescript/>

本教程将向你介绍 TypeScript 使用 [unist] 和 [unified]。

## 基础

所有的 [unified] 语法树都是基于 [unist](**uni**versal **s**yntax **t**ree)。可用的核心类型包只包含类型：[`@types/unist`](https://www.npmjs.com/package/@types/unist)。主要的类型是 `节点(Node)`，其他的都继承于此。`字面量(Literal)` 和 `父节点(Parent)` 这些更具体的类型也是继承于 `节点(Node)`。

由 [unist] 提供的类型为抽象接口(Abstract Interfaces)，通常情况下，你需要根据所使用的语言来选择更加实用的接口。[unified] 所支持的每种语言，例如：markdown、HTML 和 XML 都有他们自己继承于 `unist` 的语法树标准。

让我们一起来看看。

## unist

### `节点(Node)`

`节点(Node)` 是语法树的句法单位。每个节点都继承于 `节点(Node)`（有时候是 `字面量(Literal)` 或者 `父节点(Parent)`）。并且设置 `type` 为一个 [字符串字面量(string literal)](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types)。这个类型字段用于告诉我们节点内容的类型是什么。这个字段唯一标识一种内容，在 TypeScript 中，被称为 [可辨识联合/可区分联合(discriminated union)](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#discriminated-unions)。举个在 ，markdown([mdast]) 中的例子：`Node` 被继承用于一些节点 `Heading` 或 `Link`，以取代使用 `type` 字段设置为 `heading` 和 `link`。

一个节点可以可选地包含一个 `Data` 接口于 `data` 字段。这是一个对象用于存储额外的原始数据，并不是节点的标准，但在生态中被定义（工具类(utilities)和插件(plugins)）。

当从文件中解析语法树时，会包含坐标信息：一个 `Position` 接口于 `position` 字段，这用于描述节点在源文件中的位置。

```typescript
/**
 * Syntactic units in unist syntax trees are called nodes.
 */
interface Node {
  /**
   * The variant of a node.
   */
  type: string

  /**
   * Information from the ecosystem.
   */
  data?: Data | undefined

  /**
   * Location of a node in a source document.
   * Must not be present if a node is generated.
   */
  position?: Position | undefined
}

/**
 * Information associated by the ecosystem with the node.
 * Space is guaranteed to never be specified by unist or specifications
 * implementing unist.
 */
export interface Data {
  [key: string]: unknown
}

/**
 * Location of a node in a source file.
 */
export interface Position {
  /**
   * Place of the first character of the parsed source region.
   */
  start: Point

  /**
   * Place of the first character after the parsed source region.
   */
  end: Point

  /**
   * Start column at each index (plus start line) in the source region,
   * for elements that span multiple lines.
   */
  indent?: number[] | undefined
}

```

### `字面量(Literal)`

`字面量(Literal)` 继承于 `节点(Node)`，并添加了一个 `value` 属性。举个例子，一个 markdown `Code` 节点继承 `Literal`，并设置 `value` 为 `string`。

```typescript
/**
 * Nodes containing a value.
 */
export interface Literal extends Node {
  value: unknown
}
```

### `父节点(Parent)`

`父节点(Parent)` 继承于 `节点(Node)`，并添加了一个 `子节点(children)` 属性。子节点代表其他内容在 `父节点(Parent)` 内部，或者就是这个节点的一部分。

```typescript
/**
 * Nodes containing other nodes.
 */
export interface Parent extends Node {
  /**
   * List representing the children of a node.
   */
  children: Node[];
}
```

### 在项目中拉取 unist

安装：

```bash
npm install --save-dev @types/unist
```

在一个 TypeScript 文件中导入类型，如下：

```typescript
import type { Node, Literal, Parent } from 'unist'
```

在 [JSDoc TypeScript](https://www.typescriptlang.org/docs/handbook/intro-to-js-ts.html) 导入类型，如下：

```typescript
/**
 * @typedef {import('unist').Node} Node
 * @typedef {import('unist').Literal} Literal
 * @typedef {import('unist').Parent} Parent
 */
```

## mdast(markdown)

[mdast] (**m**arkdown **a**bstract **s**yntax **t**ree) 继承于 [unist]，并且为 markdown 具像化类型，例如 `Heading` `Code` `Link` 等。这些具像化的类型包含了一个完整的节点列表。这些类型可以通过一个类型包进行使用。[`@types/mdast`](https://www.npmjs.com/package/@types/mdast)。

安装：

```bash
npm install --save-dev @types/mdast
```

在一个 TypeScript 文件中导入类型，如下：

```typescript
import type { Heading, Code, Link } from 'mdast'
```

在 [JSDoc TypeScript](https://www.typescriptlang.org/docs/handbook/intro-to-js-ts.html) 导入类型，如下：

```typescript
/**
 * @typedef {import('mdast').Heading} Heading
 * @typedef {import('mdast').Code} Code
 * @typedef {import('mdast').Link} Link
 */
```

## hast(HTML)

[hast] (**h**ypertext **a**bstract **s**yntax **t**ree) 继承于 [unist]，并且为 HTML 具像化类型，例如 `Element` `Comment` `DocType` 等。这些具像化的类型包含了一个完整的节点列表。这些类型可以通过一个类型包进行使用。[`@types/hast`](https://www.npmjs.com/package/@types/hast)。

安装：

```bash
npm install --save-dev @types/hast
```

在一个 TypeScript 文件中导入类型，如下：

```typescript
import type { Element, Comment, DocType } from 'hast'
```

在 [JSDoc TypeScript](https://www.typescriptlang.org/docs/handbook/intro-to-js-ts.html) 导入类型，如下：

```typescript
/**
 * @typedef {import('hast').Element} Element
 * @typedef {import('hast').Comment} Comment
 * @typedef {import('hast').DocType} DocType
 */
```

## xast(XML)

[xast] (e**x**tensible **a**bstract **s**yntax **t**ree) 继承于 [unist]，并且为 XML 具像化类型，例如 `Element` `CData` `Instruction` 等。这些具像化的类型包含了一个完整的节点列表。这些类型可以通过一个类型包进行使用。[`@types/xast`](https://www.npmjs.com/package/@types/xast)。

安装：

```bash
npm install --save-dev @types/xast
```

在一个 TypeScript 文件中导入类型，如下：

```typescript
import type { Element, CData, Instruction } from 'xast'
```

在 [JSDoc TypeScript](https://www.typescriptlang.org/docs/handbook/intro-to-js-ts.html) 导入类型，如下：

```typescript
/**
 * @typedef {import('xast').Element} Element
 * @typedef {import('xast').CData} CData
 * @typedef {import('xast').Instruction} Instruction
 */
```

## 总结

- [unified] 提供了不同语言的语法树类型
- 这些类型可以被 TypeScript 和 JSDoc 项目导入

## 进一步

- [学习使用 TypeScript 遍历语法树](https://unifiedjs.com/learn/recipe/tree-traversal-typescript/)
- [学习压缩节点](https://unifiedjs.com/learn/recipe/narrow-node-typescript/)
- [学习构建语法树](https://unifiedjs.com/learn/recipe/build-a-syntax-tree/)

---

## File: posts/zh/如何构建一颗语法树.md

---
title: 如何构建一颗语法树
date: 2024-03-26 22:03:41
toc: true
tags: [ unified, 语法树, mdast, json, hast, xast ]
---

[mdast]: https://github.com/syntax-tree/mdast
[hast]: https://github.com/syntax-tree/hast
[xast]: https://github.com/syntax-tree/xast

> 原文链接：<https://unifiedjs.com/learn/recipe/build-a-syntax-tree/>

当添加或者替换内容时，构建一颗新的语法树往往非常实用。使用纯对象和数组字面量(JSON) 或者用一个小工具编程来创建树，是可行的方案。甚至可以使用 JSX 来构建树。

## JSON

创建树最基本的方法是使用纯对象和数组。为了避免类型问题，可以使用给定语法树语言的类型进行检查，在 [mdast] 的案例：

```typescript
import type { Root } from 'mdast'

// Note the `: Root` is a TypeScript annotation. Remove it (and the import) for plain JavaScript.
const mdast: Root = {
  type: 'root',
  children: [
    {
      type: 'paragraph',
      children: [
        {
          type: 'text',
          value: 'example'
        }
      ]
    }
  ]
}
```

## `unist-builder`

也可以使用 [`unist-builder`](https://unifiedjs.com/explore/package/unist-builder/) 来构建树。它会使之更加简洁，类 "hyperscript" 的语法(也同样与 `React.createElement` 类似)：

```typescript
import { u } from 'unist-builder'

const mdast = u('root', [
  u('paragraph', [
    u('text', 'example')
  ])
])
```

## `hastscript`

当使用 [hast] (HTML) 时，可以使用 [`hastscript`](https://unifiedjs.com/explore/package/hastscript/)。

```typescript
import { h, s } from 'hastscript'

console.log(
  h('div#some-id.foo', [
    h('span', 'some text'),
    h('input', {type: 'text', value: 'foo'}),
    h('a.alpha.bravo.charlie', {download: true}, 'delta')
  ])
)

// SVG:
console.log(
  s('svg', {xmlns: 'http://www.w3.org/2000/svg', viewbox: '0 0 500 500'}, [
    s('title', 'SVG `<circle>` element'),
    s('circle', {cx: 120, cy: 120, r: 100})
  ])
)
```

`hastscript` 也可以用作 JSX 配置注释：

```jsx
/** @jsx h @jsxFrag null */
import { h } from 'hastscript'

console.log(
  <form method="POST">
    <input type="text" name="foo" />
    <input type="text" name="bar" />
    <input type="submit" name="send" />
  </form>
)
```

## `xastscript`

当使用 [xast] (XML) 时，可以使用 [`xastscript`](https://unifiedjs.com/explore/package/xastscript/)。

```typescript
import { x } from 'xastscript'

console.log(
  x('album', {id: 123}, [
    x('name', 'Exile in Guyville'),
    x('artist', 'Liz Phair'),
    x('releasedate', '1993-06-22')
  ])
)
```

`xastscript` 也可以用作 JSX 配置注释：

```jsx
/** @jsx x @jsxFrag null */
import { x } from 'xastscript'

console.log(
  <album id={123}>
    <name>Born in the U.S.A.</name>
    <artist>Bruce Springsteen</artist>
    <releasedate>1984-04-06</releasedate>
  </album>
)
```

---

## File: posts/zh/实用书籍和项目参考.md

---
title: 实用书籍和项目参考
date: 2023-11-17 15:57
toc: true
tags: [ 书籍, 项目, 参考 ]
categories: [ 实用 ]
---

## 书籍

- [Bash 脚本](https://wangdoc.com/bash)
- [Rust 语言圣经](https://course.rs/)
- [Rust 语言实战](https://zh.practice.rs/)

---

## File: posts/zh/对已过去三年来大学生活的总结与建议.md

[Binary file]

---

## File: posts/zh/快速用electron生成一个PC端应用.md

---
title: 快速用electron生成一个PC端应用
date: 2020-03-17 18:13:18
toc: true
tags: [ React, Electron, PC端开发, 框架 ]
---

## 写在前面的

在使用Electron开发应用的过程中遇到了很多小的bug，这些bug有的解决方案尚且有用，有的bug已经过时了。网上的帖子有很多，质量层次不齐，如何去引入解决问题云云，很多的bug是框架自身的问题甚至还活在issues里。为了便于开发electron应用，我整理了三个模板仓库便于直接应用于开发，因为本地没有OSX的环境，所以都是在Win10完成的开发，这也是解决起来最麻烦的。

仓库地址: [Gitee](https://gitee.com/HerbertHe), [GitHub](https://github.com/HerbertHe)

## 为什么是React

针对于Vue的帖子有很多，也很多人使用，我也曾用过。因为自己学过和用过很多开发语言，而React更适用于我的编程逻辑和习惯，Vue面向设计师我感觉是很合适的(～﹃～)~zZ。

## 关于`Eraac`、`Eraasc`和`Eraatc`

`Eraac`全名为`electron-react-antd-antv-cli`，另外的两个`s`和`t`分别代表`sqlite3`的版本和`typescript`的版本。`Eraac`和`Eraasc`的类型检查都是用的`prop-types`，根据react官方的建议和发展趋势`ts`版本会越来越流行，`js`版本降低了开发难度和考虑因素同时存在类型安全的问题，`ts`版本更适合大型应用的开发。

> `sqlite3`的版本本质就是`Eraac`引入了`sqlites3`，但是由于`sqlite3`并不能直接使用。构建同样是一个大坑，因此整理为了一个独立的仓库。**同时需要说明的是：框架限制了electron的版本更新，因为electron版本更新也存在不兼容的问题，如果有需要请自行更新！模板的master分支会尽快跟着electron进行更新的兼容性测试，新项目无需担心。**

### 为什么引入了antd和antv

antd是阿里蚂蚁提供的可视化开源组件，跟饿了么提供的`element-ui`面向vue一样都是组件。antv是蚂蚁提供的数据可视化组件，在一个应用开发中往往数据可视化也能带来更好的用户体验。antv使用的是`BizCharts`，为阿里面向react的商业级数据可视化组件，能够保证足够的开发质量。

## 如何快速生成一个electron应用

第一次使用我建议使用`Eraac`模板，因为足够便捷。根据`README.md`的使用指南，很快就会启动成功一个electron应用，根据自己的需要可以清掉所有的demo和test文件直接构建。

### 基本使用

主进程位于: `/public/electron.js`

渲染进程可以位于任何组件中，只需要使用`window.electron`即可使用`electron`提供的所有API。

### 进阶使用

参考`REAME.md`引用的文档，配置好`package.json`和`.yml`文件，遵循使用说明，即可实现直接构建带有自动升级的PC端应用！

## 使用demo: `EdeverClient`

`EdeverClient`仓库位于[GitHub](https://github.com/HerbertHe/EdeverClient)和[Gitee](https://gitee.com/HerbertHe/EdeverClient)，它是基于`Eraac`和`Gitee OpenAPI`的便于了解系列所有框架实时动态的PC端。目前只提供了win x64的版本，因为没有Mac，如果能参与构建就很感谢了！

下面是使用界面:



自带了自动更新，无需再考虑版本更新问题，请在 [GitHub](https://github.com/HerbertHe/EdeverClient) 的release中获取（因为Gitee限制了附件大小，实在没有办法）

这同时是一个demo基于`React Hooks`开发，仓库里的代码开源，哪里不会看哪里比文档更直观。

## 写在后面的

作者大三，同时也是考研狗，不是科班出身，更新框架可能有时候会慢一点，欢迎使用，欢迎pr，issues，如果能给个star那就是最好的鼓励了！有任何问题都可以issues。

---

## File: posts/zh/提升国内GitHub访问体验.md

[Binary file]

---

## File: posts/zh/有趣的JavaScript正则表达式.md

---
title: 有趣的JavaScript正则表达式
date: 2021-04-11T16:32:35+08:00
toc: true
tags: [ JavaScript ]
---

## 正则表达式

正则表达式被广泛用于文本的提取和测试, 相比于编译原理来说它足够的简单; 而相较于字符匹配来说, 又足够的功能强大。著名的markdown解析器 `marked.js` 即是使用来进行解析的, 然后生成markdown语法树进行渲染。而且正则表达式被绝大多数的高级编程语言支持, 除了实现的程度和实现的方法不太一致之外, 均可以得到很好的通用。

正则表达式上手较为容易而且写法各异, 但想精通比较困难, 如何快速上手和提高正则的执行效率就不赘述了。

## 有趣的JavaScript正则表达式

与匹配相关的JavaScript相关函数主要是 `String.prototype.match()`、`RegExp.prototype.test()` 和 `RegExp.prototype.exec()`

相关MDN文档如下:

- [String.prototype.match()](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/match)
- [RegExp.prototype.test()](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test)
- [RegExp.prototype.exec()](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/RegExp/exec)

## 一个有趣的"BUG"

下述代码节选自 [OLEX](https://github.com/HerbertHe/olex/blob/main/src/core/lex/lexer.ts#L14)

```ts
const tex = `\\documnetclass{article}\n\\usepackage{sdfsfsdf}\n\\usepackage{9ubb}\n\\usepackage{example}`

const usepackageRegex = /\\usepackage\s*(\[([a-zA-Z0-9 ]+)\])?\s*\{([a-zA-Z0-9\-]+)\}/g

export const PackageChecker = (
    tex: string,
    packages: PackagesType
): PackageCheckerType => {
    if (!usepackageRegex.test(tex)) {
        // 没使用额外的包
        return [true, "", tex]
    }

    // 注意上面会改变lastIndex的位置, 需要进行重置

    // ---- 注意这里 Attention Here ----
    // usepackageRegex.lastIndex = 0

    const packsSet = new Set([...packages.keys()])

    let pack

    while ((pack = usepackageRegex.exec(tex)) !== null) {
        console.log(pack)  // 注意这里的打印值
        if (!packsSet.has(pack[3])) {
            return [false, pack[3], tex]
        }
    }

    return [true, "", tex.replace(usepackageRegex, "")]
}
```

> 上述的注意打印值的部分输出的结果会是什么呢？第一次全局匹配的结果是 `\\usepackage{sdfsfsdf}` 还是 `\\usepackage{9ubb}`？这是个问题。

如果把注意部分注释掉的代码, 取消注释结果又是什么呢？

因此就引出了一个非常值得注意的正则表达式属性 `lastIndex`, 而 `test()` 和 `exec()` 均会改变 `lastIndex`的值。这个属性可以理解为正则表达式匹配的指针, 下一次的匹配是从 `lastIndex` 的值开始的。在上述的代码中, 使用了 `test()` 方法逻辑对函数执行效率进行提高, 因此下面的匹配使用 `exec()` 的结果中, **匹配从 `\\usepackage{9ubb}` 开始!**

为了得到正确的结果, 则必须对 `lastIndex` 的值进行重置

## 拓展 —— 全局匹配结果的问题

JavaScript中为了匹配特定字符串的特定匹配分组的值, 只能采取循环 `exec()` 的方式, 因为匹配不到之时返回的结果为 **null**

因此我们可以通过下面的方法, 来全局匹配文本的特定部分

```ts
const tex = `\\documnetclass{article}\n\\usepackage{sdfsfsdf}\n\\usepackage{9ubb}\n\\usepackage{example}`

const usepackageRegex = /\\usepackage\s*(\[([a-zA-Z0-9 ]+)\])?\s*\{([a-zA-Z0-9\-]+)\}/g

let pack

while ((pack = usepackageRegex.exec(tex)) !== null) {
    console.log(pack)  // 注意这里的打印值
}
```

---

## File: posts/zh/环境部署参考.md

---
title: 环境部署参考
date: 2023-11-17 15:56
toc: true
tags: [ 环境部署, git, docker ]
---

## Docker

- [Website](https://www.docker.com/)
- [Install Docker Engine on Ubuntu](https://docs.docker.com/engine/install/ubuntu/)

## Drone

- [GitHub](https://github.com/drone)
- [Chinese Documentation](https://drone.cool/)
- [Overview](https://docs.drone.io/server/overview/)

## Gogs

- [GitHub](https://github.com/gogs/gogs)
- [Website](https://gogs.io/)
- [Docker For Gogs](https://github.com/gogs/gogs/tree/main/docker)

## Nginxconfig

- [GitHub](https://github.com/digitalocean/nginxconfig.io)
- [Website](https://www.digitalocean.com/community/tools/nginx?global.app.lang=zhCN)

---

## File: posts/zh/自制操作系统log.md

---
title: 自制操作系统log
date: 2019-09-15 21:42:42
toc: true
tags: [ OS, 操作系统, 自制, 记录 ]
---

## 参考书

* 《三十天自制操作系统（川合秀实）》
* 《ORANGE'S：一个操作系统的实现》

## 写在前面的

本记录不展示原版书籍的内容，仅作为自己历程的记录和总结。两本书各有所长，根据自己的喜好选择和实践。书籍中的内容根据自己的喜好、习惯，有一定的自己理解和改变！本记录默认对二进制、十六进制和基础硬件等都有一定的了解！



## 制作启动区(512字节)

从开始我对两本书都是懵逼的，结合两本书涉及的启动盘代码总结一点东西

```asm
ORG 0x7c00                  ; 程序加载0x7c00

; FAT12格式软盘代码位
; (--省略--)

JMP entry
DB 0x90

entry:
        MOV AX, 0           ; 初始化寄存器
        MOV SS, AX
        MOV SP, 0x7c00
        MOV DS, AX
        MOV ES, AX

        MOV SI, msg

putloop:
        MOV AL, [SI]        ; []表示内存中
        ADD SI, 1           ; SI加一
        CMP AL, 0

        JE fin              ; jump if equal
        MOV AH, 0x0e        ; 显示一个文字
        MOV BX, 15          ; 指定字符颜色
        INT 0x10            ; 调用显卡BIOS，INT软件中断指令
        JMP putloop

fin:
        HLT                 ; 让CPU停止等待指令
        JMP fin             ; 无限循环

msg:
        DB 0x0a, 0x0a       ; 换行
        DB "hello, world"
        DB 0x0a
        DB 0

        RESB 0x7dfe-$       ; 填写0x00直到0x001fe
        DB 0x55, 0xaa       ; 结束
```

> 注意：30天那个书制作启动盘的部分完全可以看看就行。。。代码就是上面的，就是helloos.nas节选那个末尾加上了两行，写满512字节和结束标志！

代码直接看[这个库](https://github.com/yourtion/30dayMakeOS)

---

## File: posts/zh/记一次JS线程问题.md

---
title: 记一次JS线程问题
date: 2020-01-30 16:55:42
toc: true
tags: [ JavaScript, Promise, JS线程, 异步编程 ]
---

## 遇到的错误



正如你所见的，报了一个`value below was evaluated just now`的信息，意味着我拿到的是原来的数据而不是最新的，下面伴随一些demo来看看这个问题。

## 问题的引出

问题最早来源于我想拿到`Promise`里面返回的数据，所使用的是`React`并使用`Axios`来访问请求，`Axios`为http的异步请求。这里使用的是`GitHub`和`Gitee`的接口，请求得到当前的指定用户的关注、关注者、Star的仓库和自己仓库的数据量。具体的示例可以参考PC版的 [Jieec Server](https://server.jieec.cn)（目前没有在移动端上适配个人信息相关的，用media隐藏掉了）**目前这个问题并未完全解决**

> 全站代码都已经开源，可以在[HerbertHe/JieecServerPage](https://github.com/HerbertHe/JieecServerPage)获取

因为每次API请求的数据获取有限制，然后就使用了`分页`的操作，并且使用了循环的操作。

## 第一个问题：如何降低`React`中异步更新状态的次数

最开始，我是直接在`Axios`的`.then`直接使用了`this.setState()`的方法，这样直接在获取数据之后直接渲染DOM，这样会导致页面的重复更新。**更重要**的问题是`this.setState()`这个方法也是**异步**的！

> demo如下：

```js
import Axios from 'axios'

// .....省略组件定义.....

constructor(props){
    super(props)
    this.state = {
        result: 0
    }
}

for (let i = 1; i < 4; i++) {
    Axios.get(`https://api.github.com/users/HerbertHe/followers?page=${i}`).then(res => {
            if (res.data.length !== 0) {
                this.setState({
                    result: this.state.result + res.data.length
                })
            }
        }
    )
}
```

因为`this.setState()`的操作是异步的，那么问题来了，我根本拿不到最新的`this.state.result`的值。就意味着`setState()`设置的值根本不能确定是不是少算了，并且受执行的时刻影响，就真的是"薛定谔的猫了"

## 第二个问题：如何抽离请求的代码

参考这个commit[d28a5ebf3d47cd4e090208773f3bade28a499945](https://github.com/HerbertHe/JieecServerPage/commit/d28a5ebf3d47cd4e090208773f3bade28a499945#r37037127)，太多的`Axios`请求导致代码越来越冗余，由于项目是小项目并没有使用`Redux`进行状态管理，而是单独抽取了一个文件[githubAndGiteeData.js](https://github.com/HerbertHe/JieecServerPage/blob/master/src/Components/githubAndGiteeData.js)，数据使用了`_data`这样一个全局变量做
统一管理，然后`export default GetReq()`这个函数供外部使用。参考[commit](https://github.com/HerbertHe/JieecServerPage/commit/01805298a39a90a3feae5d7770d6d3ab8c25d78e#r37037265)

## 第三个问题：获取Promise的返回值

`Axios`是一个Promise，取值需要在`.then`之中，因为作用域的问题，对于更新值使用了`setter`这样的方法，在`Axios`返回之后做一个记录，从而异步操作数据和重复的DOM渲染问题。参考[githubAndGiteeData.js](https://github.com/HerbertHe/JieecServerPage/blob/master/src/Components/githubAndGiteeData.js)。

## 第四个问题：上面引言的错误如何解决

最开始的代码是直接调用`GetReq`的方法并且`console.log()`打印了值，也就出现了上面图的报错，我打印的并不是最新的值。后来我才意识到虽然`js`是单线程的，但是它有协程的存在，函数的执行时机是很有影响的。

> 可以参考这篇文章[彻底明白 JS 线程](https://www.jianshu.com/p/8821c6432fe1)

然后参考CSDN做了一个简单而粗暴的解决办法，可以参考commit[01805298a39a90a3feae5d7770d6d3ab8c25d78e](https://github.com/HerbertHe/JieecServerPage/commit/01805298a39a90a3feae5d7770d6d3ab8c25d78e#r37037489)，通过设置`setTimeout()`的方式强行延时处理设置状态渲染DOM

## 更多的问题和预想解决方案

上面设置定时器处理很依赖API的响应时间，如果API响应很慢的话，会导致渲染数据不准确的情况（已经发现，主要是GitHub API真的不好确认时间）；如果设的时间过长，用户体验会大打折扣。

> 目前预想的解决方案是利用`React`提供的下一个生命周期函数来获取数据，具体需要更多的尝试。

---

## File: posts/zh/迭代器辅助方法.md

---
title: 迭代器辅助方法
date: 2024-4-27 18:01:57
toc: true
tags: [ ECMAScript, V8, iterator ]
---

> 本篇文章来源于 v8.dev, 原文链接: <https://v8.dev/features/iterator-helpers>, 作者: Rezvan Mahdavi Hezaveh

*迭代器辅助方法(Iterator helpers)* 是迭代器原型上的新方法集合，旨在帮助迭代器的通用使用。自迭代器原型上存在这些辅助方法开始，任何在其原型链上有 `Iterator.prototype` 的对象（如：数组迭代器）都会获得这些方法。
在下面的子章节，我们将解释这些迭代器辅助方法。所有提供的示例都在博客存档页面中工作，该页面包含博客文章列表，说明了迭代器辅助方法如何有助于查找和处理帖子。你可以在 [V8 博客页面](https://v8.dev/blog) 中进行尝试。

## .map(mapperFn)

`map` 接收一个 mapper 函数作为参数。此辅助方法返回值的迭代器，其中 mapper 函数作用于原始迭代器值。

```javascript
// Select the list of blog posts from a blog archive page.
const posts = document.querySelectorAll('li:not(header li)');

// Get the list of posts, return a list of their text content (titles) and log them.
for (const post of posts.values().map((x) => x.textContent)) {
  console.log(post);
}
```

## .filter(filterFn)

`filter` 接收一个 filter 函数作为参数。此辅助方法返回值的迭代器，其中 filter 函数返回判断为真的原始迭代器值。

```javascript
// Select the list of blog posts from a blog archive page.
const posts = document.querySelectorAll('li:not(header li)');

// Filter blog posts that includes `V8` in their text content (titles) and log them.
for (const post of posts.values().filter((x) => x.textContent.includes('V8'))) {
  console.log(post);
}
```

## .take(limit)

`take` 接收一个整型作为参数。此辅助方法从原始迭代器返回一个值的迭代器，最多为 `limit` 个。

```javascript
// Select the list of blog posts from a blog archive page.
// Select the list of blog posts from a blog archive page.
const posts = document.querySelectorAll('li:not(header li)');

// Filter blog posts that includes `V8` in their text content (titles) and log them.
for (const post of posts.values().filter((x) => x.textContent.includes('V8'))) {
  console.log(post);
}
```

## .drop(limit)

 `drop` 接收一个整型作为参数。此辅助方法从原始迭代器返回一个值的迭代器，跳过 `limit` 个值。

```javascript
// Select the list of blog posts from a blog archive page.
const posts = document.querySelectorAll('li:not(header li)');

// Drop 10 recent blog posts and log the rest of them.
for (const post of posts.values().drop(10)) {
  console.log(post);
}
```

## .flatMap(mapperFn)

`flatMap` 接收一个 mapper 函数作为参数。此辅助方法返回一个通过有原始迭代器值调用 mapper 函数返回值的迭代器。如此，通过这个辅助方法返回一个由 mapper 函数展开的迭代器。

```javascript
// Select the list of blog posts from a blog archive page.
const posts = document.querySelectorAll('li:not(header li)');

// Get list of tags of the blog posts and log them. Each post can have more than
// one tag.
for (const tag of posts.values().flatMap((x) => x.querySelectorAll('.tag').values())) {
    console.log(tag.textContent);
}
```

## .reduce(reducer[, initialValue])

`reduce` 接收一个 reducer 函数和一个可选的初始值。此辅助方法返回一个值作为迭代器每个值调用 reducer 函数的结果，同时追踪 reducer 函数的最后一个结果。初始值被用于 reducer 函数的起点，当它进行处理的时候，其为迭代器的第一个值。

```javascript
// Select the list of blog posts from a blog archive page.
const posts = document.querySelectorAll('li:not(header li)');

// Get list of tags for all posts.
const tagLists = posts.values().flatMap((x) => x.querySelectorAll('.tag').values());

// Get text context for each tag in the list.
const tags = tagLists.map((x) => x.textContent);

// Counts posts with security tag.
const count = tags.reduce((sum , value) => sum + (value === 'security' ? 1 : 0), 0);
console.log(count);
```

## .toArray()

`toArray` 从迭代器返回一个数组。

```javascript
// Select the list of blog posts from a blog archive page.
const posts = document.querySelectorAll('li:not(header li)');

// Create an array from the list of 10 recent blog posts.
const arr = posts.values().take(10).toArray();
```

## .forEach(fn)

`forEach` 接收一个函数作为参数，并在迭代器每个元素上进行调用。这个辅助方法作为它的副作用进行调用并且返回 `undefined`。

```javascript
// Select the list of blog posts from a blog archive page.
const posts = document.querySelectorAll('li:not(header li)');

// Get the dates that at least one blog post is published and log them.
const dates = new Set();
const forEach = posts.values().forEach((x) => dates.add(x.querySelector('time')));
console.log(dates);
```

## .some(fn)

`some` 接收一个判断函数(predicate function) 作为参数。这个辅助方法当某些迭代器元素调用判断函数返回为真的时候，返回 `true`。这个迭代器在 `some` 被调用之后被消费。

```javascript
// Select the list of blog posts from a blog archive page.
const posts = document.querySelectorAll('li:not(header li)');

// Find out if text content (title) of any blog post includes the `Iterators`
// keyword.
posts.values().some((x) => x.textContent.includes('Iterators'));
```

## .every(fn)

`every` 接收一个判断函数作为参数。这个辅助方法当所有迭代器元素调用判断函数返回为真的时候，返回 `true`。这个迭代器在 `every` 被调用之后被消费。

```javascript
// Select the list of blog posts from a blog archive page.
const posts = document.querySelectorAll('li:not(header li)');

// Find out if text content (title) of all blog post includes the `V8` keyword.
posts.values().every((x) => x.textContent.includes('V8'));
```

## .find(fn)

`find` 接收一个判断函数(predicate function) 作为参数。此辅助方法返回满足判断函数的迭代器的第一个值，或者返回 `undefined`（如果迭代器没有值）。

```javascript
// Select the list of blog posts from a blog archive page.
const posts = document.querySelectorAll('li:not(header li)');

// Log the text content (title) of the recent blog post includes `V8` keyword.
console.log(posts.values().find((x) => x.textContent.includes('V8')).textContent);
```

## Iterator.from(object)

`from` 是一个静态方法，并接收一个对象作为参数。如果 `object` 已经是一个迭代器的实例，被这个辅助方法直接返回。如果 `object` 有 `Symbol.iterator`，也就意味着是一个可迭代的对象，它的 `Symbol.iterator` 方法将会被此辅助函数调用并返回。此外，一个新的 `Iterator` 对象（继承于 `Iterator.prototype`，并且有 `next()` 和 `return()` 方法）被创建包裹为 `object`，被此辅助方法返回。

```javascript
// Select the list of blog posts from a blog archive page.
const posts = document.querySelectorAll('li:not(header li)');

// First create an iterator from the posts. Then, log the text content (title) of 
// the recent blog post that includes the `V8` keyword.
console.log(Iterator.from(posts).find((x) => x.textContent.includes('V8')).textContent);
```

## 可用性

迭代器辅助方法在 V8 v12.2 版本推出。

## 支持情况

- [Chrome 122+](https://chromestatus.com/feature/5102502917177344)
- ~~[Firefox](https://bugzilla.mozilla.org/show_bug.cgi?id=1568906)~~
- ~~[Safari](https://bugs.webkit.org/show_bug.cgi?id=248650)~~
- ~~Node.js~~
- [Babel](https://github.com/zloirock/core-js#iterator-helpers)

---

## File: posts/zh/配置Goland主题与工程环境.md

---
title: 配置Goland主题与工程环境
date: 2020-03-09 13:58:20
toc: true
tags: [ golang, goland, go ]
---

## 关于Goland与vscode的比较

Goland为JetBrains出品的针对于Go编程的IDE，与IDEA、Webstorm、Pycharm等耳熟能详的IDE的风格基本相似。针对于不同的语言，略有差异。vscode通过安装插件也可以搭建优雅的Go开发环境，但是在参考了很多网上的帖子和走过很多坑之后还是放弃了，继续使用了Goland。

### Goland的优越性

- 代码智能提示
- 完备的项目开发流程

### Goland的缺点

- 纯英文的开发环境，很容易导致功能了解不完全和误操作
- 自不知道哪一版的更新之后，Goland的中文显示就崩掉了，需要自己配置
- 从非Goland创建的工程移植，本地库的导入需要手动操作
- 需要自行做一些IDE关于proxy的配置

## 配置Goland主题和字体

在这里我使用的是`Material Theme UI`这个插件，`File`->`Settings`->`Plugins`->`Marketplace`搜索就好了，选择一个自己喜欢的主题。



字体的配置在`File`->`Settings`->`Editor`的`Font`、`Color Scheme`的`Color Scheme Font`和`Console Font`下



字体推荐使用的是`Fira Code`看起来很舒服，但是在显示的时候如果习惯普通的符号的话，对于`===`、`!=`、`!==`等这些符号看起来可能会比较陌生。字体链接: [GitHub](https://github.com/tonsky/FiraCode)

> 一定要配置`Fallback font`这一项不然汉字可能出现渲染问题！

## 关于IDE配置和项目配置

默认情况下，只要你根据golang的配置要求配置了`GOROOT`和`GOPATH`，IDE都会默认找到对应的本地库，可以通过下图的位置来检查。







## 从别的IDE迁到Goland

根据go语言的特性，默认go的源代码必须位于`GOPATH`的`src`下，go在更新后提供了`go module`的包管理工具，强烈建议在新的项目中使用`go mod init xxx.xxx`的方式使用`go module`来初始化项目的包管理，以此来抽离项目于默认的文件夹。从别的IDE迁移至`Goland`请仔细检查上图中的配置，否则可能会导致本地包智能提示无法识别的问题。

在上图`Go Modules`的proxy配置中，强烈建议改为上图配置以解决国内网络环境对于包下载的不友好，配置 `https://goproxy.cn,direct`

同样建议使用`go env`检查`GOPROXY`，`go env set GOPROXY=https://goproxy.cn,direct`

---

## File: .github/workflows/deploy.yml

name: Deploy Blog

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [24.x]
        # See supported Node.js release schedule at https://nodejs.org/en/about/releases/

    steps:
      - uses: actions/checkout@v6
      - name: Use Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v6
        with:
          node-version: ${{ matrix.node-version }}
      - name: Enable Corepack
        run: corepack enable
      - name: Install dependencies
        run: pnpm install --frozen-lockfile
      - name: Build
        run: pnpm run build

      - name: Deploy
        uses: peaceiris/actions-gh-pages@v4
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          publish_dir: ./.vitepress/dist
          cname: ibert.me

      - name: Discord
        uses: HerbertHe/discord-action@v1
        with:
          webhook: ${{ secrets.DISCORD_WEBHOOK }}
          message: Herbert He's Blog has been successfullly updated, see https://ibert.me to get more. And you can also subscribe RSS Feed at https://ibert.me/feed.xml

--- METRICS ---
- Files Extracted: 63
- Estimated Token Budget: ~112774 tokens
- Recency Window: Active (< 180 days)
- Canonical Reference: https://codewiki.google/github.com/HerbertHe/iptv-sources
